mirror of
https://github.com/touchHLE/touchHLE.git
synced 2026-01-31 01:25:24 +01:00
Support for iPad device family.
Device family is deduced from the app bundle, but user can also override it with --device-family=... option. For now, device family affects rendered window size and bounds of UIScreen. The iPhone device family is kept to be default. We explicitly fallback to it in the following cases: - App picker case - App doesn't define any device family - App define both (universal app) Change-Id: I104889df0942d63823ca77d5c7ca847087149bf0
This commit is contained in:
@@ -30,6 +30,17 @@ View options:
|
||||
|
||||
This is a natural number that is at least 1.
|
||||
|
||||
Device options:
|
||||
--device-family=...
|
||||
Specifies which device family should be emulated: iPhone or iPad.
|
||||
This only work if running app does support selected family
|
||||
(or the option will be ignored).
|
||||
|
||||
If omitted, suitable device family would be deduced from application
|
||||
bundle itself, falling back to iPhone if needed.
|
||||
|
||||
Accepted values are "iphone" and "ipad" respectively.
|
||||
|
||||
Game controller options:
|
||||
--deadzone=...
|
||||
Configures the size of the \"dead zone\" for analog stick inputs.
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
use crate::fs::{BundleData, Fs, GuestPath, GuestPathBuf};
|
||||
use crate::image::Image;
|
||||
use crate::window::DeviceFamily;
|
||||
use plist::dictionary::Dictionary;
|
||||
use plist::Value;
|
||||
use std::io::Cursor;
|
||||
@@ -201,4 +202,17 @@ impl Bundle {
|
||||
.map_or("UIInterfaceOrientationPortrait", |o| o.as_string().unwrap())]
|
||||
})
|
||||
}
|
||||
|
||||
pub fn device_family_array(&self) -> Vec<DeviceFamily> {
|
||||
self.plist
|
||||
.get("UIDeviceFamily")
|
||||
.map(|v| {
|
||||
v.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|o| DeviceFamily::try_from(o.as_unsigned_integer().unwrap()).unwrap())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| vec![DeviceFamily::iPhone])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::net::TcpListener;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::libc::pthread::cond::pthread_cond_t;
|
||||
use crate::window::DeviceFamily;
|
||||
pub use mutex::{MutexId, MutexType, PTHREAD_MUTEX_DEFAULT};
|
||||
|
||||
/// Index into the [Vec] of threads. Thread 0 is always the main thread.
|
||||
@@ -274,6 +275,34 @@ impl Environment {
|
||||
}
|
||||
}
|
||||
|
||||
let device_family_override = options.device_family;
|
||||
let device_family_array = bundle.device_family_array();
|
||||
let device_family = match device_family_array.len() {
|
||||
// iPhone only or iPad only
|
||||
1 => {
|
||||
let only_supported = device_family_array[0];
|
||||
if let Some(dfo) = device_family_override {
|
||||
if dfo != only_supported {
|
||||
log!("Warning: User-defined {:?} device family override is not supported by the app! ignoring", dfo);
|
||||
}
|
||||
}
|
||||
only_supported
|
||||
}
|
||||
// iPhone and iPad
|
||||
2 => {
|
||||
if let Some(dfo) = device_family_override {
|
||||
assert!(device_family_array.contains(&dfo));
|
||||
dfo
|
||||
} else {
|
||||
assert!(device_family_array.contains(&DeviceFamily::iPhone));
|
||||
DeviceFamily::iPhone
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
log!("{:?} device family is chosen.", device_family);
|
||||
options.device_family = Some(device_family);
|
||||
|
||||
let window = if options.headless {
|
||||
None
|
||||
} else {
|
||||
|
||||
@@ -44,9 +44,10 @@ pub const CLASSES: ClassExports = objc_classes! {
|
||||
// While Apple's documentation says this changes with the interface
|
||||
// orientation, https://useyourloaf.com/blog/uiscreen-bounds-in-ios-8/ says
|
||||
// ths wasn't the case prior to iOS 8.
|
||||
let (width, height) = env.window().device_family().portrait_size();
|
||||
CGRect {
|
||||
origin: CGPoint { x: 0.0, y: 0.0 },
|
||||
size: CGSize { width: 320.0, height: 480.0 },
|
||||
size: CGSize { width: width as f32, height: height as f32 },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! Parsing and management of user-configurable options, e.g. for input methods.
|
||||
|
||||
use crate::gles::GLESImplementation;
|
||||
use crate::window::DeviceOrientation;
|
||||
use crate::window::{DeviceFamily, DeviceOrientation};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader, Read};
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
@@ -35,6 +35,7 @@ pub enum Button {
|
||||
#[derive(Clone)]
|
||||
pub struct Options {
|
||||
pub fullscreen: bool,
|
||||
pub device_family: Option<DeviceFamily>,
|
||||
pub initial_orientation: DeviceOrientation,
|
||||
pub scale_hack: NonZeroU32,
|
||||
pub deadzone: f32,
|
||||
@@ -66,6 +67,7 @@ impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
fullscreen: false,
|
||||
device_family: None,
|
||||
initial_orientation: DeviceOrientation::Portrait,
|
||||
scale_hack: NonZeroU32::new(1).unwrap(),
|
||||
analog_stick_tilt_controls: true,
|
||||
@@ -116,6 +118,10 @@ impl Options {
|
||||
self.initial_orientation = DeviceOrientation::LandscapeLeft;
|
||||
} else if arg == "--landscape-right" {
|
||||
self.initial_orientation = DeviceOrientation::LandscapeRight;
|
||||
} else if let Some(value) = arg.strip_prefix("--device-family=") {
|
||||
let parsed =
|
||||
DeviceFamily::try_from(value).map_err(|_| "Invalid device family".to_string())?;
|
||||
self.device_family = Some(parsed);
|
||||
} else if let Some(value) = arg.strip_prefix("--scale-hack=") {
|
||||
self.scale_hack = value
|
||||
.parse()
|
||||
|
||||
@@ -28,18 +28,58 @@ use std::num::NonZeroU32;
|
||||
use std::ptr::null_mut;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum DeviceFamily {
|
||||
iPhone,
|
||||
iPad,
|
||||
}
|
||||
impl DeviceFamily {
|
||||
pub fn portrait_size(&self) -> (u32, u32) {
|
||||
match self {
|
||||
DeviceFamily::iPhone => (320, 480),
|
||||
DeviceFamily::iPad => (768, 1024),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<u64> for DeviceFamily {
|
||||
type Error = ();
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
1 => Ok(DeviceFamily::iPhone),
|
||||
2 => Ok(DeviceFamily::iPad),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<&str> for DeviceFamily {
|
||||
type Error = ();
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"iphone" => Ok(DeviceFamily::iPhone),
|
||||
"ipad" => Ok(DeviceFamily::iPad),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum DeviceOrientation {
|
||||
Portrait,
|
||||
LandscapeLeft,
|
||||
LandscapeRight,
|
||||
}
|
||||
fn size_for_orientation(orientation: DeviceOrientation, scale_hack: NonZeroU32) -> (u32, u32) {
|
||||
fn size_for_orientation(
|
||||
family: DeviceFamily,
|
||||
orientation: DeviceOrientation,
|
||||
scale_hack: NonZeroU32,
|
||||
) -> (u32, u32) {
|
||||
let (width, height) = family.portrait_size();
|
||||
let scale_hack = scale_hack.get();
|
||||
match orientation {
|
||||
DeviceOrientation::Portrait => (320 * scale_hack, 480 * scale_hack),
|
||||
DeviceOrientation::LandscapeLeft => (480 * scale_hack, 320 * scale_hack),
|
||||
DeviceOrientation::LandscapeRight => (480 * scale_hack, 320 * scale_hack),
|
||||
DeviceOrientation::Portrait => (width * scale_hack, height * scale_hack),
|
||||
DeviceOrientation::LandscapeLeft => (height * scale_hack, width * scale_hack),
|
||||
DeviceOrientation::LandscapeRight => (height * scale_hack, width * scale_hack),
|
||||
}
|
||||
}
|
||||
fn rotate_fullscreen_size(orientation: DeviceOrientation, screen_size: (u32, u32)) -> (u32, u32) {
|
||||
@@ -180,6 +220,7 @@ pub struct Window {
|
||||
scale_hack: NonZeroU32,
|
||||
internal_gl_ins: Option<Box<dyn GLESContext>>,
|
||||
splash_image: Option<Image>,
|
||||
device_family: DeviceFamily,
|
||||
device_orientation: DeviceOrientation,
|
||||
controller_ctx: sdl2::GameControllerSubsystem,
|
||||
controllers: Vec<sdl2::controller::GameController>,
|
||||
@@ -236,6 +277,7 @@ impl Window {
|
||||
let scale_hack = options.scale_hack;
|
||||
// TODO: some apps specify their orientation in Info.plist, we could use
|
||||
// that here.
|
||||
let device_family = options.device_family.unwrap_or(DeviceFamily::iPhone);
|
||||
let device_orientation = options.initial_orientation;
|
||||
let fullscreen = options.fullscreen;
|
||||
|
||||
@@ -261,7 +303,8 @@ impl Window {
|
||||
.unwrap();
|
||||
window
|
||||
} else {
|
||||
let (width, height) = size_for_orientation(device_orientation, scale_hack);
|
||||
let (width, height) =
|
||||
size_for_orientation(device_family, device_orientation, scale_hack);
|
||||
let window = video_ctx
|
||||
.window(title, width, height)
|
||||
.position_centered()
|
||||
@@ -320,6 +363,7 @@ impl Window {
|
||||
scale_hack,
|
||||
internal_gl_ins: None,
|
||||
splash_image: launch_image,
|
||||
device_family,
|
||||
device_orientation,
|
||||
controller_ctx,
|
||||
controllers: Vec::new(),
|
||||
@@ -377,8 +421,11 @@ impl Window {
|
||||
independent_of_viewport: bool,
|
||||
) -> (f32, f32) {
|
||||
let (vx, vy, vw, vh) = if independent_of_viewport {
|
||||
let (width, height) =
|
||||
size_for_orientation(window.device_orientation, NonZeroU32::new(1).unwrap());
|
||||
let (width, height) = size_for_orientation(
|
||||
window.device_family,
|
||||
window.device_orientation,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
);
|
||||
(0, 0, width, height)
|
||||
} else {
|
||||
window.viewport()
|
||||
@@ -1219,7 +1266,7 @@ impl Window {
|
||||
set_sdl2_orientation(new_orientation);
|
||||
rotate_fullscreen_size(new_orientation, self.window.size())
|
||||
} else {
|
||||
size_for_orientation(new_orientation, self.scale_hack)
|
||||
size_for_orientation(self.device_family, new_orientation, self.scale_hack)
|
||||
};
|
||||
|
||||
// macOS quirk: when resizing the window, the new framebuffer's size
|
||||
@@ -1267,6 +1314,10 @@ impl Window {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device_family(&self) -> DeviceFamily {
|
||||
self.device_family
|
||||
}
|
||||
|
||||
/// Returns the current device orientation
|
||||
pub fn current_rotation(&self) -> DeviceOrientation {
|
||||
self.device_orientation
|
||||
@@ -1277,7 +1328,11 @@ impl Window {
|
||||
/// The aspect ratio, scale and orientation reflect the guest app's view of
|
||||
/// the world.
|
||||
pub fn size_unrotated_unscaled(&self) -> (u32, u32) {
|
||||
size_for_orientation(DeviceOrientation::Portrait, NonZeroU32::new(1).unwrap())
|
||||
size_for_orientation(
|
||||
self.device_family,
|
||||
DeviceOrientation::Portrait,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the region of the on-screen window (x, y, width, height) used to
|
||||
@@ -1287,7 +1342,7 @@ impl Window {
|
||||
/// the world, but the scale and orientation might not.
|
||||
pub fn viewport(&self) -> (u32, u32, u32, u32) {
|
||||
let (app_width, app_height) =
|
||||
size_for_orientation(self.device_orientation, self.scale_hack);
|
||||
size_for_orientation(self.device_family, self.device_orientation, self.scale_hack);
|
||||
if !self.fullscreen && !Self::rotatable_fullscreen() {
|
||||
return (0, 0, app_width, app_height);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user