From b4c5f2045252c5cef46aff0609fe8b3c40f5e44e Mon Sep 17 00:00:00 2001 From: Ryan Bruels Date: Sat, 13 Dec 2025 12:33:37 -0800 Subject: [PATCH] Support new fullscreen mode --- Emulator/Screen/TIOSScreenManager.h | 11 ++ Resources/iOSEinstein Storyboards-Info.plist | 4 +- app/iEinstein/Classes/iEinsteinAppDelegate.mm | 9 +- app/iEinstein/Classes/iEinsteinView.h | 3 +- app/iEinstein/Classes/iEinsteinView.mm | 117 +++++++++++------- .../Classes/iEinsteinViewController.h | 2 + .../Classes/iEinsteinViewController.mm | 101 +++++++++++---- app/iEinstein/Main.storyboard | 34 ++--- app/iEinstein/Settings.bundle/Root.plist | 22 ++-- 9 files changed, 203 insertions(+), 100 deletions(-) diff --git a/Emulator/Screen/TIOSScreenManager.h b/Emulator/Screen/TIOSScreenManager.h index fecc2a0c6..0a9f08e2e 100644 --- a/Emulator/Screen/TIOSScreenManager.h +++ b/Emulator/Screen/TIOSScreenManager.h @@ -162,6 +162,17 @@ class TIOSScreenManager return mDataProviderRef; } + /// + /// Accessor to the image buffer. + /// + /// \return a pointer to the image buffer. + /// + KUInt32* + GetImageBuffer(void) + { + return mImageBuffer; + } + /// /// Insert a sample with pen down. /// diff --git a/Resources/iOSEinstein Storyboards-Info.plist b/Resources/iOSEinstein Storyboards-Info.plist index 39c3197ee..f1dce1eb1 100644 --- a/Resources/iOSEinstein Storyboards-Info.plist +++ b/Resources/iOSEinstein Storyboards-Info.plist @@ -43,10 +43,10 @@ UISupportedInterfaceOrientations - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown diff --git a/app/iEinstein/Classes/iEinsteinAppDelegate.mm b/app/iEinstein/Classes/iEinsteinAppDelegate.mm index de89a50d8..1f17b6fc4 100644 --- a/app/iEinstein/Classes/iEinsteinAppDelegate.mm +++ b/app/iEinstein/Classes/iEinsteinAppDelegate.mm @@ -37,10 +37,11 @@ @implementation iEinsteinAppDelegate + (void)initialize { NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInt:0], @"screen_resolution", - [NSNumber numberWithBool:NO], @"clear_flash_ram", - @"{8,9.88897}", @"printable_size", - nil]; + [NSNumber numberWithInt:0], @"screen_resolution", + [NSNumber numberWithBool:NO], @"use_full_screen", + [NSNumber numberWithBool:NO], @"clear_flash_ram", + @"{8,9.88897}", @"printable_size", + nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; [[NSUserDefaults standardUserDefaults] synchronize]; diff --git a/app/iEinstein/Classes/iEinsteinView.h b/app/iEinstein/Classes/iEinsteinView.h index 544ff111b..6ab8882e2 100644 --- a/app/iEinstein/Classes/iEinsteinView.h +++ b/app/iEinstein/Classes/iEinsteinView.h @@ -38,10 +38,11 @@ class TEmulator; // EOrientation mOrientation; // KUInt32 mPreviousMods; bool applePencilMode; + bool useFullScreen; } - (void)reset; -- (void)setScreenManager:(TScreenManager*)sm; +- (void)setScreenManager:(TScreenManager*)sm useFullScreen:(BOOL)fullScreen; - (void)setEmulator:(TEmulator*)em; diff --git a/app/iEinstein/Classes/iEinsteinView.mm b/app/iEinstein/Classes/iEinsteinView.mm index bce6bb731..b0ab516df 100644 --- a/app/iEinstein/Classes/iEinsteinView.mm +++ b/app/iEinstein/Classes/iEinsteinView.mm @@ -80,9 +80,10 @@ - (void)didRotate:(NSNotification*)notification [self setNeedsDisplay]; } -- (void)setScreenManager:(TScreenManager*)sm +- (void)setScreenManager:(TScreenManager*)sm useFullScreen:(BOOL)fullScreen { mScreenManager = sm; + useFullScreen = fullScreen; [self setNeedsDisplay]; } @@ -116,14 +117,14 @@ - (void)layoutSubviews - (void)drawRect:(CGRect)rect { CGContextRef theContext = UIGraphicsGetCurrentContext(); + CGRect bounds = [self bounds]; if (mScreenManager == NULL) { - // Just fill black + // Fill with black when no screen manager CGFloat black[] = { 0.0, 0.0, 0.0, 1.0 }; - CGRect frame = [self frame]; CGContextSetFillColor(theContext, black); - CGContextFillRect(theContext, frame); + CGContextFillRect(theContext, bounds); } else { if (mScreenImage == NULL) @@ -144,59 +145,91 @@ - (void)drawRect:(CGRect)rect CGColorSpaceRelease(theColorSpace); - // CGRect screenBounds = [[UIScreen mainScreen] bounds]; //OLD - CGRect screenBounds = [self bounds]; - CGRect r = [self frame]; - - if (screenBounds.size.width > newtonScreenWidth && screenBounds.size.height > newtonScreenHeight) - { - if (newtonScreenWidth == newtonScreenHeight) + if (useFullScreen) { + // Full screen mode: leave room for border on all sides + // On iPad with 2x scaling, double the padding to maintain proportions + CGFloat borderPadding = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? 30.0 : 15.0; + screenImageRect = CGRectInset(bounds, borderPadding, borderPadding); + } else { + // Classic mode: letterbox/center the Newton screen with integer scaling + CGRect screenBounds = bounds; + CGRect r = bounds; + + if (screenBounds.size.width > newtonScreenWidth && screenBounds.size.height > newtonScreenHeight) { - // Newton screen resolution is square (like 320x320) - - int mod = (int) screenBounds.size.width % newtonScreenWidth; - r.size.width -= mod; - r.size.height = r.size.width; - } else - { - // Newton screen resolution is rectangular (like 320x480) - - int wmod = (int) r.size.width % newtonScreenWidth; - int hmod = (int) r.size.height % newtonScreenHeight; - - if (wmod > hmod) + if (newtonScreenWidth == newtonScreenHeight) { - r.size.width -= wmod; - - int scale = (int) r.size.width / newtonScreenWidth; - r.size.height = newtonScreenHeight * scale; + // Newton screen resolution is square (like 320x320) + int mod = (int) screenBounds.size.width % newtonScreenWidth; + r.size.width -= mod; + r.size.height = r.size.width; } else { - r.size.height -= hmod; - - int scale = (int) r.size.height / newtonScreenHeight; - r.size.width = newtonScreenWidth * scale; + // Newton screen resolution is rectangular (like 320x480) + int wmod = (int) r.size.width % newtonScreenWidth; + int hmod = (int) r.size.height % newtonScreenHeight; + + if (wmod > hmod) + { + r.size.width -= wmod; + int scale = (int) r.size.width / newtonScreenWidth; + r.size.height = newtonScreenHeight * scale; + } else + { + r.size.height -= hmod; + int scale = (int) r.size.height / newtonScreenHeight; + r.size.width = newtonScreenWidth * scale; + } } - } - // Center image on screen + // Center image on screen + r.origin.x += (screenBounds.size.width - r.size.width) / 2; + r.origin.y += (screenBounds.size.height - r.size.height) / 2; + } - r.origin.x += (screenBounds.size.width - r.size.width) / 2; - r.origin.y += (screenBounds.size.height - r.size.height) / 2; + screenImageRect = r; + screenImageRect.origin.x = floor(screenImageRect.origin.x); + screenImageRect.origin.y = floor(screenImageRect.origin.y); + screenImageRect.size.width = floor(screenImageRect.size.width); + screenImageRect.size.height = floor(screenImageRect.size.height); } - - screenImageRect = r; - screenImageRect.origin.x = floor(screenImageRect.origin.x); - screenImageRect.origin.y = floor(screenImageRect.origin.y); - screenImageRect.size.width = floor(screenImageRect.size.width); - screenImageRect.size.height = floor(screenImageRect.size.height); } + // Draw the background of this view and the superview + [self drawBackgroundInContext:theContext withBounds:bounds]; + CGContextSetInterpolationQuality(theContext, kCGInterpolationNone); CGContextDrawImage(theContext, screenImageRect, mScreenImage); } } +- (void)drawBackgroundInContext:(CGContextRef)context withBounds:(CGRect)bounds +{ + static UIColor *defaultBgColor = [UIColor colorWithRed:(176/255.0) green:(191/255.0) blue:(169/255.0) alpha:1.0]; + static UIColor *fullScreenBgColor = [UIColor blackColor]; + + // In classic mode, we use the storyboard's color (a muted green). + // In full-screen mode, we fill background with black. + UIColor* bgColor = useFullScreen ? fullScreenBgColor : defaultBgColor; + + if (mScreenManager == NULL || newtonScreenWidth == 0 || newtonScreenHeight == 0) { + if (self.superview) { + self.superview.backgroundColor = bgColor; + } + return; + } + + // Set superview background to match (fills safe areas on notched devices) + if (self.superview) { + self.superview.backgroundColor = bgColor; + } + + // Fill the entire view bounds with the background color + // The Newton screen image will be drawn on top + CGContextSetFillColorWithColor(context, bgColor.CGColor); + CGContextFillRect(context, bounds); +} + - (void)reset { mEmulator = NULL; diff --git a/app/iEinstein/Classes/iEinsteinViewController.h b/app/iEinstein/Classes/iEinsteinViewController.h index 957abd6ac..e3957cc03 100644 --- a/app/iEinstein/Classes/iEinsteinViewController.h +++ b/app/iEinstein/Classes/iEinsteinViewController.h @@ -46,7 +46,9 @@ class TLog; TEmulator* mEmulator; TPlatformManager* mPlatformManager; TLog* mLog; + BOOL mEmulatorInitialized; int lastKnownScreenResolution; + BOOL lastKnownFullScreenMode; } @property (retain, nonatomic) IBOutlet iEinsteinView* einsteinView; diff --git a/app/iEinstein/Classes/iEinsteinViewController.mm b/app/iEinstein/Classes/iEinsteinViewController.mm index 9acc50054..1db58115e 100644 --- a/app/iEinstein/Classes/iEinsteinViewController.mm +++ b/app/iEinstein/Classes/iEinsteinViewController.mm @@ -44,9 +44,7 @@ @implementation iEinsteinViewController - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; -#ifdef USE_STORYBOARDS - [self initEmulator]; -#endif + // Initialization moved to viewDidLayoutSubviews to ensure correct bounds } - (void)viewDidAppear:(BOOL)animated @@ -54,6 +52,18 @@ - (void)viewDidAppear:(BOOL)animated [super viewDidAppear:animated]; } +- (void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; +#ifdef USE_STORYBOARDS + // Only initialize once, after layout is complete so we have correct bounds + if (!mEmulatorInitialized) { + mEmulatorInitialized = YES; + [self initEmulator]; + } +#endif +} + // Action sheet delegate method. - (void)actionSheet:(UIActionSheet*)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { @@ -326,25 +336,60 @@ - (int)initEmulator mSoundManager = new TCoreAudioSoundManager(mLog); - // iPad is 1024x768. This size, and some appropriate scaling factors, should be selectable from - // the 'Settings' panel. - - static int widthLUT[] = { 320, 640, 384, 786, 640, 320, 750, 375, 1080, 540 }; - static int heightLUT[] = { 480, 960, 512, 1024, 1136, 568, 1134, 567, 1920, 960 }; - - NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults]; - int index = [(NSNumber*) [prefs objectForKey:@"screen_resolution"] intValue]; - int newtonScreenWidth = widthLUT[index]; - int newtonScreenHeight = heightLUT[index]; - #ifdef USE_STORYBOARDS - // When using storyboards as configured, the einsteinView is a subview of self.view - // This facilitates other subviews in the future. iEinsteinView* einsteinView = self.einsteinView; #else - // When using NIBs, the einsteinView is self.view iEinsteinView* einsteinView = (iEinsteinView*) [self view]; #endif + + // Determine screen resolution based on use_full_screen setting + NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults]; + BOOL useFullScreen = [(NSNumber*) [prefs objectForKey:@"use_full_screen"] boolValue]; + int newtonScreenWidth, newtonScreenHeight; + + if (useFullScreen) { + // Full screen mode: use native view bounds for resolution + CGRect viewBounds = [einsteinView bounds]; + + // Use point dimensions (not pixels) for the Newton resolution. + // The Newton is always in portrait orientation internally (width < height). + int viewWidth = (int)viewBounds.size.width; + int viewHeight = (int)viewBounds.size.height; + + // Ensure width is the smaller dimension (portrait mode for Newton) + if (viewWidth < viewHeight) { + newtonScreenWidth = viewWidth; + newtonScreenHeight = viewHeight; + } else { + newtonScreenWidth = viewHeight; + newtonScreenHeight = viewWidth; + } + + // On iPad, use 2x scaling (halve resolution, scale up when drawing) + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + newtonScreenWidth /= 2.0; + newtonScreenHeight /= 2.0; + } else { + newtonScreenWidth /= 1.25; + newtonScreenHeight /= 1.25; + } + + // Round width to an even number (required for pixel operations) + newtonScreenWidth = (newtonScreenWidth + 1) & ~1; + } else { + // Classic mode: use LUT-based resolution from Settings + static int widthLUT[] = { 320, 640, 384, 786, 640, 320, 750, 375, 1080, 540 }; + static int heightLUT[] = { 480, 960, 512, 1024, 1136, 568, 1134, 567, 1920, 960 }; + + int index = [(NSNumber*) [prefs objectForKey:@"screen_resolution"] intValue]; + newtonScreenWidth = widthLUT[index]; + newtonScreenHeight = heightLUT[index]; + } + + // Store current settings for change detection + lastKnownFullScreenMode = useFullScreen; + lastKnownScreenResolution = [(NSNumber*) [prefs objectForKey:@"screen_resolution"] intValue]; + Boolean isLandscape = (newtonScreenWidth > newtonScreenHeight); mScreenManager = new TIOSScreenManager( @@ -355,7 +400,7 @@ - (int)initEmulator true, isLandscape); - [einsteinView setScreenManager:mScreenManager]; + [einsteinView setScreenManager:mScreenManager useFullScreen:useFullScreen]; // Create the printer manager. mPrinterManager = new TIOSPrinterManager(mLog); @@ -386,23 +431,31 @@ - (int)initEmulator - (void)startEmulator { - // See if screen resolution has changed since last time - + // Check if screen settings have changed since last time NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults]; [prefs synchronize]; + + BOOL currentFullScreenMode = [(NSNumber*) [prefs objectForKey:@"use_full_screen"] boolValue]; int currentScreenResolution = [(NSNumber*) [prefs objectForKey:@"screen_resolution"] intValue]; - if (currentScreenResolution != lastKnownScreenResolution) - { - // Reboot emulator + BOOL needsReset = NO; + if (currentFullScreenMode != lastKnownFullScreenMode) { + mLog->LogLine("Full screen mode changed by Settings."); + needsReset = YES; + } else if (!currentFullScreenMode && currentScreenResolution != lastKnownScreenResolution) { + // Only check resolution change in classic mode (not full screen) mLog->LogLine("Newton screen resolution changed by Settings."); + needsReset = YES; + } + + if (needsReset) { + lastKnownFullScreenMode = currentFullScreenMode; lastKnownScreenResolution = currentScreenResolution; [self resetEmulator]; } // Start the thread. - mLog->LogLine("Detaching emulator thread"); [NSThread detachNewThreadSelector:@selector(emulatorThread) toTarget:self withObject:nil]; } diff --git a/app/iEinstein/Main.storyboard b/app/iEinstein/Main.storyboard index 29a4ac39a..74a97fe9f 100644 --- a/app/iEinstein/Main.storyboard +++ b/app/iEinstein/Main.storyboard @@ -1,35 +1,35 @@ - - + + + - + + + + - + - - - - - + - - + + - + + - - - - + + + + - diff --git a/app/iEinstein/Settings.bundle/Root.plist b/app/iEinstein/Settings.bundle/Root.plist index b5631b0cc..3e02e0088 100644 --- a/app/iEinstein/Settings.bundle/Root.plist +++ b/app/iEinstein/Settings.bundle/Root.plist @@ -11,14 +11,24 @@ Type PSGroupSpecifier + FooterText + Full Screen scales NewtonOS to fill your device's screen. + + + Type + PSToggleSwitchSpecifier Title - Screen Settings + Use Full Screen + Key + use_full_screen + DefaultValue + Type PSMultiValueSpecifier Title - Resolution + Screen Resolution Key screen_resolution DefaultValue @@ -78,14 +88,6 @@ {7.76771,10.58188} - - Type - PSTextFieldSpecifier - Title - - Key - - Type PSGroupSpecifier