mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
prefersStatusBarHidden was dead code - it computed an orientation and a (commented out) user preference, then unconditionally returned false. So the status bar was only ever hidden on iPhone in landscape, and only because iOS does that on its own in compact height. Now it honors bImmersiveMode from the DisplayLayoutConfig matching the current orientation, so it also applies in portrait and on iPad. Adds the corresponding checkbox to the iOS system settings, and updates the status bar on rotation and when the setting is toggled. Also fixes a missing break in the ROTATE_UPDATED case in System_Notify, and a static/non-static mismatch on sceKernelLoadModuleBufferUsbWlan that broke the build (the header intentionally exposes it for sceVshBridge). Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
559 lines
18 KiB
Plaintext
559 lines
18 KiB
Plaintext
#import "ios/CameraHelper.h"
|
|
#import "ios/ViewControllerCommon.h"
|
|
#import "ios/Controls.h"
|
|
#import "ios/IAPManager.h"
|
|
#import <Photos/Photos.h>
|
|
#import <objc/runtime.h>
|
|
#include "Common/System/Request.h"
|
|
#include "Common/Input/InputState.h"
|
|
#include "Common/System/NativeApp.h"
|
|
#include "Common/System/Display.h"
|
|
#include "Common/Log.h"
|
|
#include "Core/HLE/sceUsbCam.h"
|
|
#include "Core/HLE/sceUsbGps.h"
|
|
#include "Core/System.h"
|
|
#include "Core/Config.h"
|
|
|
|
@interface PPSSPPBaseViewController () {
|
|
CameraHelper *cameraHelper;
|
|
LocationHelper *locationHelper;
|
|
ICadeTracker g_iCadeTracker;
|
|
TouchTracker g_touchTracker;
|
|
}
|
|
|
|
@property (strong, nonatomic) NSOperationQueue *accelerometerQueue;
|
|
@property (nonatomic) GCController *gameController __attribute__((weak_import));
|
|
@property (strong, nonatomic) CMMotionManager *motionManager;
|
|
|
|
@end
|
|
|
|
@implementation PPSSPPBaseViewController {
|
|
UIScreenEdgePanGestureRecognizer *mBackGestureRecognizer;
|
|
}
|
|
|
|
// Strange idiom for generating unique IDs (within the process, at least).
|
|
static const void *kPickerFilenameKey = &kPickerFilenameKey;
|
|
static const void *kPickerRequestIdKey = &kPickerRequestIdKey;
|
|
|
|
static void SetPickerContext(id picker, NSString *filename, int requestId) {
|
|
objc_setAssociatedObject(picker, kPickerFilenameKey, filename, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
|
objc_setAssociatedObject(picker, kPickerRequestIdKey, @(requestId), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
static NSString *GetPickerFilename(id picker) {
|
|
return (NSString *)objc_getAssociatedObject(picker, kPickerFilenameKey);
|
|
}
|
|
|
|
static int GetPickerRequestId(id picker) {
|
|
NSNumber *requestIdNumber = (NSNumber *)objc_getAssociatedObject(picker, kPickerRequestIdKey);
|
|
return requestIdNumber ? requestIdNumber.intValue : -1;
|
|
}
|
|
|
|
- (void)savePickedImage:(UIImage *)image toFilename:(NSString *)targetFilename requestId:(int)requestId {
|
|
NSData *jpegData = UIImageJPEGRepresentation(image, 0.9);
|
|
if (jpegData) {
|
|
[jpegData writeToFile:targetFilename atomically:YES];
|
|
NSLog(@"Saved JPEG image to %@", targetFilename);
|
|
g_requestManager.PostSystemSuccess(requestId, "", 1);
|
|
} else {
|
|
g_requestManager.PostSystemFailure(requestId);
|
|
}
|
|
}
|
|
|
|
- (id)init {
|
|
self = [super init];
|
|
if (self) {
|
|
sharedViewController = self;
|
|
|
|
g_iCadeTracker.InitKeyMap();
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
|
|
|
|
// Observe orientation changes
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(onOrientationChanged)
|
|
name:UIDeviceOrientationDidChangeNotification
|
|
object:nil];
|
|
}
|
|
self.accelerometerQueue = [[NSOperationQueue alloc] init];
|
|
self.accelerometerQueue.name = @"AccelerometerQueue";
|
|
self.accelerometerQueue.maxConcurrentOperationCount = 1;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)shutdown {
|
|
self.gameController = nil;
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
_dbg_assert_(sharedViewController != nil);
|
|
sharedViewController = nil;
|
|
}
|
|
|
|
- (BOOL)prefersHomeIndicatorAutoHidden {
|
|
if (g_Config.iAppSwitchMode == (int)AppSwitchMode::DOUBLE_SWIPE_INDICATOR) {
|
|
return NO;
|
|
} else {
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
- (void)didBecomeActive {
|
|
if (self.motionManager.accelerometerAvailable) {
|
|
self.motionManager.accelerometerUpdateInterval = 1.0 / 60.0;
|
|
INFO_LOG(Log::G3D, "Starting accelerometer updates.");
|
|
|
|
[self.motionManager startAccelerometerUpdatesToQueue:self.accelerometerQueue
|
|
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
|
|
if (error) {
|
|
NSLog(@"Accelerometer error: %@", error);
|
|
return;
|
|
}
|
|
ProcessAccelerometerData(accelerometerData);
|
|
}];
|
|
} else {
|
|
INFO_LOG(Log::G3D, "No accelerometer available, not starting updates.");
|
|
}
|
|
}
|
|
|
|
- (void)willResignActive {
|
|
// Stop accelerometer updates
|
|
if (self.motionManager.accelerometerActive) {
|
|
INFO_LOG(Log::G3D, "Stopping accelerometer updates");
|
|
[self.motionManager stopAccelerometerUpdates];
|
|
}
|
|
}
|
|
|
|
- (void)appWillTerminate:(NSNotification *)notification {
|
|
[self shutdown];
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated {
|
|
[super viewDidAppear:animated];
|
|
INFO_LOG(Log::G3D, "viewDidAppear");
|
|
[self hideKeyboard];
|
|
[self updateGesture];
|
|
|
|
// This needs to be called really late during startup, unfortunately.
|
|
#if PPSSPP_PLATFORM(IOS_APP_STORE)
|
|
[IAPManager sharedIAPManager]; // Kick off the IAPManager early.
|
|
NSLog(@"Metal viewDidAppear. updating icon");
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
[[IAPManager sharedIAPManager] updateIcon:false];
|
|
[self hideKeyboard];
|
|
});
|
|
#endif // IOS_APP_STORE
|
|
}
|
|
|
|
// Enables tapping for edge area.
|
|
-(UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
|
|
if (GetUIState() == UISTATE_INGAME) {
|
|
// In-game, we need all the control we can get. Though, we could possibly
|
|
// allow the top edge?
|
|
INFO_LOG(Log::System, "Defer system gestures on all edges");
|
|
return UIRectEdgeAll;
|
|
} else {
|
|
INFO_LOG(Log::System, "Allow system gestures on the bottom");
|
|
// Allow task switching gestures to take precedence, without causing
|
|
// scroll events in the UI. Otherwise, we get "ghost" scrolls when switching tasks.
|
|
return UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeRight;
|
|
}
|
|
}
|
|
|
|
- (void)controllerDidConnect:(NSNotification *)note
|
|
{
|
|
if (![[GCController controllers] containsObject:self.gameController]) self.gameController = nil;
|
|
|
|
if (self.gameController != nil) return; // already have a connected controller
|
|
|
|
[self setupController:(GCController *)note.object];
|
|
}
|
|
|
|
- (void)controllerDidDisconnect:(NSNotification *)note
|
|
{
|
|
if (self.gameController == note.object) {
|
|
ShutdownController(self.gameController);
|
|
self.gameController = nil;
|
|
|
|
if ([[GCController controllers] count] > 0) {
|
|
[self setupController:[[GCController controllers] firstObject]];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setupController:(GCController *)controller {
|
|
self.gameController = controller;
|
|
if (!InitController(controller)) {
|
|
self.gameController = nil;
|
|
}
|
|
}
|
|
|
|
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
g_touchTracker.Began(touches, self.view);
|
|
}
|
|
|
|
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
g_touchTracker.Moved(touches, self.view);
|
|
}
|
|
|
|
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
g_touchTracker.Ended(touches, self.view);
|
|
}
|
|
|
|
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
|
{
|
|
g_touchTracker.Cancelled(touches, self.view);
|
|
}
|
|
|
|
- (void)pickPhoto:(NSString *)saveFilename requestId:(int)requestId {
|
|
NSLog(@"Picking photo to save to %@ (id: %d)", saveFilename, requestId);
|
|
NSString *targetFilename = [saveFilename copy];
|
|
|
|
if (@available(iOS 14.0, *)) {
|
|
PHPickerConfiguration *config = [[PHPickerConfiguration alloc] initWithPhotoLibrary:PHPhotoLibrary.sharedPhotoLibrary];
|
|
config.selectionLimit = 1;
|
|
config.filter = [PHPickerFilter imagesFilter];
|
|
|
|
PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:config];
|
|
picker.delegate = self;
|
|
SetPickerContext(picker, targetFilename, requestId);
|
|
[self presentViewController:picker animated:YES completion:nil];
|
|
return;
|
|
}
|
|
|
|
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
|
|
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
|
// Images only keeps the picker from doing extra video-related setup.
|
|
picker.mediaTypes = @[@"public.image"];
|
|
picker.delegate = self;
|
|
SetPickerContext(picker, targetFilename, requestId);
|
|
[self presentViewController:picker animated:YES completion:nil];
|
|
}
|
|
|
|
- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results API_AVAILABLE(ios(14.0)) {
|
|
NSString *targetFilename = GetPickerFilename(picker);
|
|
int requestId = GetPickerRequestId(picker);
|
|
|
|
[picker dismissViewControllerAnimated:YES completion:nil];
|
|
|
|
if (results.count == 0) {
|
|
NSLog(@"User cancelled photo picker");
|
|
g_requestManager.PostSystemFailure(requestId);
|
|
[self hideKeyboard];
|
|
return;
|
|
}
|
|
|
|
NSItemProvider *itemProvider = results.firstObject.itemProvider;
|
|
if (![itemProvider canLoadObjectOfClass:[UIImage class]]) {
|
|
NSLog(@"Photo picker result does not provide UIImage");
|
|
g_requestManager.PostSystemFailure(requestId);
|
|
[self hideKeyboard];
|
|
return;
|
|
}
|
|
|
|
[itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(UIImage *image, NSError *error) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (error || !image) {
|
|
NSLog(@"Failed loading picked image: %@", error);
|
|
g_requestManager.PostSystemFailure(requestId);
|
|
} else {
|
|
[self savePickedImage:image toFilename:targetFilename requestId:requestId];
|
|
}
|
|
[self hideKeyboard];
|
|
});
|
|
}];
|
|
}
|
|
|
|
- (void)imagePickerController:(UIImagePickerController *)picker
|
|
didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
|
|
NSString *targetFilename = GetPickerFilename(picker);
|
|
int requestId = GetPickerRequestId(picker);
|
|
|
|
UIImage *image = info[UIImagePickerControllerOriginalImage];
|
|
[self savePickedImage:image toFilename:targetFilename requestId:requestId];
|
|
|
|
[picker dismissViewControllerAnimated:YES completion:nil];
|
|
|
|
[self hideKeyboard];
|
|
}
|
|
|
|
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
|
|
NSLog(@"User cancelled image picker");
|
|
int requestId = GetPickerRequestId(picker);
|
|
|
|
[picker dismissViewControllerAnimated:YES completion:nil];
|
|
|
|
// You can also call your custom callback or use the requestId here
|
|
g_requestManager.PostSystemFailure(requestId);
|
|
[self hideKeyboard];
|
|
}
|
|
|
|
- (void)handleSwipeFrom:(UIScreenEdgePanGestureRecognizer *)recognizer {
|
|
if (recognizer.state == UIGestureRecognizerStateEnded) {
|
|
KeyInput key;
|
|
key.flags = KeyInputFlags::DOWN | KeyInputFlags::UP;
|
|
key.keyCode = NKCODE_BACK;
|
|
key.deviceId = DEVICE_ID_TOUCH;
|
|
NativeKey(key);
|
|
INFO_LOG(Log::System, "Detected back swipe");
|
|
}
|
|
}
|
|
|
|
- (void)updateGesture {
|
|
INFO_LOG(Log::System, "Updating swipe gesture.");
|
|
|
|
if (mBackGestureRecognizer) {
|
|
INFO_LOG(Log::System, "Removing swipe gesture.");
|
|
[[self view] removeGestureRecognizer:mBackGestureRecognizer];
|
|
mBackGestureRecognizer = nil;
|
|
}
|
|
|
|
if (GetUIState() != UISTATE_INGAME) {
|
|
INFO_LOG(Log::System, "Adding swipe gesture.");
|
|
mBackGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFrom:) ];
|
|
[mBackGestureRecognizer setEdges:UIRectEdgeLeft];
|
|
[[self view] addGestureRecognizer:mBackGestureRecognizer];
|
|
}
|
|
}
|
|
|
|
- (void)uiStateChanged {
|
|
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
|
[self hideKeyboard];
|
|
[self updateGesture];
|
|
}
|
|
|
|
- (void)startVideo:(int)width height:(int)height {
|
|
[cameraHelper startVideo:width h:height];
|
|
}
|
|
|
|
- (void)stopVideo {
|
|
[cameraHelper stopVideo];
|
|
}
|
|
|
|
- (void)PushCameraImageIOS:(long long)len buffer:(unsigned char*)data {
|
|
Camera::pushCameraImage(len, data);
|
|
}
|
|
|
|
- (void)startLocation {
|
|
[locationHelper startLocationUpdates];
|
|
}
|
|
|
|
- (void)stopLocation {
|
|
[locationHelper stopLocationUpdates];
|
|
}
|
|
|
|
- (void)SetGpsDataIOS:(CLLocation *)newLocation {
|
|
GPS::setGpsData((long long)newLocation.timestamp.timeIntervalSince1970,
|
|
newLocation.horizontalAccuracy/5.0,
|
|
newLocation.coordinate.latitude, newLocation.coordinate.longitude,
|
|
newLocation.altitude,
|
|
MAX(newLocation.speed * 3.6, 0.0), /* m/s to km/h */
|
|
0 /* bearing */);
|
|
}
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
|
|
cameraHelper = [[CameraHelper alloc] init];
|
|
[cameraHelper setDelegate:self];
|
|
|
|
locationHelper = [[LocationHelper alloc] init];
|
|
[locationHelper setDelegate:self];
|
|
|
|
self.motionManager = [[CMMotionManager alloc] init];
|
|
}
|
|
|
|
extern float g_safeInsetLeft;
|
|
extern float g_safeInsetRight;
|
|
extern float g_safeInsetTop;
|
|
extern float g_safeInsetBottom;
|
|
|
|
- (void)viewSafeAreaInsetsDidChange {
|
|
[super viewSafeAreaInsetsDidChange];
|
|
|
|
// Converts points to pixels.
|
|
CGFloat scale = UIScreen.mainScreen.scale;
|
|
|
|
float xInsetSum = self.view.safeAreaInsets.left + self.view.safeAreaInsets.right;
|
|
float yInsetSum = self.view.safeAreaInsets.top + self.view.safeAreaInsets.bottom;
|
|
|
|
// Only use the larger set of insets. The other set, we'll handle through layouts.
|
|
// Also, we'll treat the bottom inset differently: We'll add some space at the end of everything
|
|
// that scrolls so that when you're not at the bottom, we use the full vertical area,
|
|
// just looks better.
|
|
if (xInsetSum > yInsetSum) {
|
|
// Landscape mode insets are larger, use those.
|
|
g_safeInsetLeft = self.view.safeAreaInsets.left * scale;
|
|
g_safeInsetRight = self.view.safeAreaInsets.right * scale;
|
|
g_safeInsetTop = 0.0f;
|
|
g_safeInsetBottom = 0.0f;
|
|
} else {
|
|
// Portrait mode insets are larger, use those.
|
|
g_safeInsetLeft = 0.0f;
|
|
g_safeInsetRight = 0.0f;
|
|
g_safeInsetTop = self.view.safeAreaInsets.top * scale;
|
|
g_safeInsetBottom = self.view.safeAreaInsets.bottom * scale;
|
|
}
|
|
}
|
|
|
|
- (void)shareText:(NSString *)text {
|
|
NSArray *items = @[text];
|
|
UIActivityViewController * viewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self presentViewController:viewController animated:YES completion:nil];
|
|
});
|
|
}
|
|
|
|
- (void)appSwitchModeChanged {
|
|
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
|
|
}
|
|
|
|
// The below is inspired by https://stackoverflow.com/questions/7253477/how-to-display-the-iphone-ipad-keyboard-over-a-full-screen-opengl-es-app
|
|
// It's a bit limited but good enough.
|
|
|
|
- (void)deleteBackward {
|
|
KeyInput input{};
|
|
input.deviceId = DEVICE_ID_KEYBOARD;
|
|
input.flags = KeyInputFlags::DOWN | KeyInputFlags::UP;
|
|
input.keyCode = NKCODE_DEL;
|
|
NativeKey(input);
|
|
INFO_LOG(Log::System, "Backspace");
|
|
}
|
|
|
|
- (void)insertText:(NSString *)text {
|
|
std::string str([text UTF8String]);
|
|
INFO_LOG(Log::System, "Chars: %s", str.c_str());
|
|
SendKeyboardChars(str);
|
|
}
|
|
|
|
- (BOOL)hasText {
|
|
return true;
|
|
}
|
|
|
|
- (void)showKeyboard {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
INFO_LOG(Log::System, "becomeFirstResponder");
|
|
[self becomeFirstResponder];
|
|
});
|
|
}
|
|
|
|
- (void)hideKeyboard {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
INFO_LOG(Log::System, "resignFirstResponder");
|
|
[self resignFirstResponder];
|
|
});
|
|
}
|
|
|
|
- (BOOL)canBecomeFirstResponder {
|
|
return YES;
|
|
}
|
|
|
|
- (void)buttonDown:(iCadeState)button
|
|
{
|
|
g_iCadeTracker.ButtonDown(button);
|
|
}
|
|
|
|
- (void)buttonUp:(iCadeState)button
|
|
{
|
|
g_iCadeTracker.ButtonUp(button);
|
|
}
|
|
|
|
// See PPSSPPUIApplication.mm for the other method
|
|
#if PPSSPP_PLATFORM(IOS_APP_STORE)
|
|
|
|
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
|
|
KeyboardPressesBegan(presses, event);
|
|
}
|
|
|
|
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
|
|
KeyboardPressesEnded(presses, event);
|
|
}
|
|
|
|
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
|
|
KeyboardPressesEnded(presses, event);
|
|
}
|
|
|
|
#endif
|
|
#pragma mark - Status Bar Control
|
|
|
|
// The immersive mode setting is per-orientation, so we need to know which way we're facing.
|
|
// Can't just use g_display for this, since it lags behind during rotation.
|
|
// Note: Using viewIfLoaded, since this can get called before the view exists, and we don't want to force it into existence.
|
|
- (DeviceOrientation)currentDeviceOrientation {
|
|
if (@available(iOS 13.0, *)) {
|
|
UIWindowScene *scene = self.viewIfLoaded.window.windowScene;
|
|
if (scene != nil) {
|
|
return UIInterfaceOrientationIsPortrait(scene.interfaceOrientation) ? DeviceOrientation::Portrait : DeviceOrientation::Landscape;
|
|
}
|
|
}
|
|
CGSize size = self.viewIfLoaded.bounds.size;
|
|
return size.height > size.width ? DeviceOrientation::Portrait : DeviceOrientation::Landscape;
|
|
}
|
|
|
|
// iOS calls this to determine whether to hide the status bar.
|
|
// Note that on iPhone, iOS hides it in landscape regardless of what we return here.
|
|
- (BOOL)prefersStatusBarHidden {
|
|
return g_Config.GetDisplayLayoutConfig([self currentDeviceOrientation]).bImmersiveMode ? YES : NO;
|
|
}
|
|
|
|
// Optional: choose light/dark text for the status bar
|
|
- (UIStatusBarStyle)preferredStatusBarStyle {
|
|
return UIStatusBarStyleLightContent;
|
|
}
|
|
|
|
- (void)onOrientationChanged {
|
|
[self setNeedsStatusBarAppearanceUpdate];
|
|
}
|
|
|
|
// Called from the C++ side when the user toggles the immersive mode setting.
|
|
- (void)immersiveModeChanged {
|
|
[self setNeedsStatusBarAppearanceUpdate];
|
|
}
|
|
|
|
- (void)viewWillTransitionToSize:(CGSize)size
|
|
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
|
|
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
|
// The immersive mode setting is per-orientation, so the status bar may need to change along with the rotation.
|
|
[self setNeedsStatusBarAppearanceUpdate];
|
|
}
|
|
|
|
- (void)updateResolutionWithView:(UIView *)view {
|
|
// 1. Get the scale from the window scene (safest for Metal)
|
|
CGFloat scale = 1.0;
|
|
if (view.window.windowScene) {
|
|
scale = view.window.windowScene.screen.nativeScale;
|
|
} else {
|
|
scale = [UITraitCollection currentTraitCollection].displayScale;
|
|
}
|
|
|
|
// 2. Get the ACTUAL bounds of the view (already corrected for orientation)
|
|
CGSize size = view.bounds.size;
|
|
|
|
// Your existing logic
|
|
float dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale;
|
|
const float dpi_scale_x = 240.0f / dpi;
|
|
const float dpi_scale_y = 240.0f / dpi;
|
|
|
|
g_display.Recalculate(size.width * scale, size.height * scale,
|
|
dpi_scale_x, dpi_scale_y,
|
|
UIScaleFactorToMultiplier(g_Config.iUIScaleFactor));
|
|
|
|
[view setContentScaleFactor:scale];
|
|
|
|
// PSP native resize
|
|
PSP_CoreParameter().pixelWidth = g_display.pixel_xres;
|
|
PSP_CoreParameter().pixelHeight = g_display.pixel_yres;
|
|
|
|
NativeResized();
|
|
|
|
NSLog(@"Updated display resolution: (%d, %d) @%.1fx",
|
|
g_display.pixel_xres, g_display.pixel_yres, (float)scale);
|
|
}
|
|
|
|
@end
|