diff --git a/CHANGES b/CHANGES index 39f786e4b59..2265e9cc7fe 100755 --- a/CHANGES +++ b/CHANGES @@ -13,7 +13,8 @@ - Gdx.files.external on Android now uses app external storage - see wiki article File handling for more information - Improved text, cursor and selection rendering in TextArea. - API Addition: Added setProgrammaticChangeEvents, updateVisualValue, round methods to ProgressBar/Slider. -- Keyboard events working on RoboVM on iOS 13.5 and up, uses same API as on other platforms +- iOS: Keyboard events working on RoboVM on iOS 13.5 and up, uses same API as on other platforms +- iOS: Changed how Retina/hdpi handled on iOS, see #3709 - API Addition: Added AndroidLiveWallpaper.notifyColorsChanged() to communicate visually significant colors back to the wallpaper engine. - API Change: AssetManager invokes the loaded callback when an asset is unloaded from the load queue if the asset is already loaded. - GWT: changed audio backend to WebAudio API. Now working on mobiles, pitch implemented. Configuration change: preferFlash removed. When updating existing projects, you can remove the soundmanager js files from your webapp folder and the references to it from index.html diff --git a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java index b6b49630c48..2254b568cbc 100644 --- a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java +++ b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java @@ -808,16 +808,16 @@ private static class NSArrayExtensions extends NSExtensions { private void toTouchEvents (long touches) { long array = NSSetExtensions.allObjects(touches); int length = (int)NSArrayExtensions.count(array); + final IOSScreenBounds screenBounds = app.getScreenBounds(); for (int i = 0; i < length; i++) { long touchHandle = NSArrayExtensions.objectAtIndex$(array, i); UITouch touch = UI_TOUCH_WRAPPER.wrap(touchHandle); final int locX, locY; // Get and map the location to our drawing space { - CGPoint loc = touch.getLocationInView(touch.getWindow()); - final CGRect bounds = app.getCachedBounds(); - locX = (int)(loc.getX() * app.displayScaleFactor - bounds.getMinX()); - locY = (int)(loc.getY() * app.displayScaleFactor - bounds.getMinY()); + CGPoint loc = touch.getLocationInView(app.graphics.view); + locX = (int)(loc.getX() - screenBounds.x); + locY = (int)(loc.getY() - screenBounds.y); // app.debug("IOSInput","pos= "+loc+" bounds= "+bounds+" x= "+locX+" locY= "+locY); } diff --git a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplication.java b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplication.java index 68e7cff0e01..9d12b54e2a2 100644 --- a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplication.java +++ b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplication.java @@ -32,14 +32,12 @@ import org.robovm.apple.uikit.UIInterfaceOrientation; import org.robovm.apple.uikit.UIPasteboard; import org.robovm.apple.uikit.UIScreen; -import org.robovm.apple.uikit.UIUserInterfaceIdiom; import org.robovm.apple.uikit.UIViewController; import org.robovm.apple.uikit.UIWindow; import org.robovm.rt.bro.Bro; import com.badlogic.gdx.Application; import com.badlogic.gdx.ApplicationListener; -import com.badlogic.gdx.ApplicationLogger; import com.badlogic.gdx.Audio; import com.badlogic.gdx.Files; import com.badlogic.gdx.Gdx; @@ -102,9 +100,9 @@ public void willTerminate (UIApplication application) { ApplicationLogger applicationLogger; /** The display scale factor (1.0f for normal; 2.0f to use retina coordinates/dimensions). */ - float displayScaleFactor; + float pixelsPerPoint; - private CGRect lastScreenBounds = null; + private IOSScreenBounds lastScreenBounds = null; Array runnables = new Array(); Array executedRunnables = new Array(); @@ -124,34 +122,15 @@ final boolean didFinishLaunching (UIApplication uiApp, UIApplicationLaunchOption UIApplication.getSharedApplication().setIdleTimerDisabled(config.preventScreenDimming); Gdx.app.debug("IOSApplication", "iOS version: " + UIDevice.getCurrentDevice().getSystemVersion()); - // fix the scale factor if we have a retina device (NOTE: iOS screen sizes are in "points" not pixels by default!) - Gdx.app.debug("IOSApplication", "Running in " + (Bro.IS_64BIT ? "64-bit" : "32-bit") + " mode"); - float scale = (float) UIScreen.getMainScreen().getNativeScale(); - if (scale >= 2.0f) { - Gdx.app.debug("IOSApplication", "scale: " + scale); - if (UIDevice.getCurrentDevice().getUserInterfaceIdiom() == UIUserInterfaceIdiom.Pad) { - // it's an iPad! - displayScaleFactor = config.displayScaleLargeScreenIfRetina * scale; - } else { - // it's an iPod or iPhone - displayScaleFactor = config.displayScaleSmallScreenIfRetina * scale; - } - } else { - // no retina screen: no scaling! - if (UIDevice.getCurrentDevice().getUserInterfaceIdiom() == UIUserInterfaceIdiom.Pad) { - // it's an iPad! - displayScaleFactor = config.displayScaleLargeScreenIfNonRetina; - } else { - // it's an iPod or iPhone - displayScaleFactor = config.displayScaleSmallScreenIfNonRetina; - } - } + // iOS counts in "points" instead of pixels. Points are logical pixels + pixelsPerPoint = (float)UIScreen.getMainScreen().getNativeScale(); + Gdx.app.debug("IOSApplication", "Pixels per point: " + pixelsPerPoint); // setup libgdx this.input = createInput(); - this.graphics = createGraphics(scale); + this.graphics = createGraphics(); Gdx.gl = Gdx.gl20 = graphics.gl20; Gdx.gl30 = graphics.gl30; this.files = new IOSFiles(); @@ -177,8 +156,8 @@ protected IOSAudio createAudio (IOSApplicationConfiguration config) { return new OALIOSAudio(config); } - protected IOSGraphics createGraphics(float scale) { - return new IOSGraphics(scale, this, config, input, config.useGL30); + protected IOSGraphics createGraphics() { + return new IOSGraphics(this, config, input, config.useGL30); } protected IOSGraphics.IOSUIViewController createUIViewController (IOSGraphics graphics) { @@ -201,11 +180,9 @@ public UIWindow getUIWindow () { return uiWindow; } - /** GL View spans whole screen, that is, even under the status bar. iOS can also rotate the screen, which is not handled - * consistently over iOS versions. This method returns, in pixels, rectangle in which libGDX draws. - * - * @return dimensions of space we draw to, adjusted for device orientation */ - protected CGRect getBounds () { + /** @see IOSScreenBounds for detailed explanation + * @return logical dimensions of space we draw to, adjusted for device orientation */ + protected IOSScreenBounds computeBounds () { final CGRect screenBounds = UIScreen.getMainScreen().getBounds(); final CGRect statusBarFrame = uiApp.getStatusBarFrame(); final UIInterfaceOrientation statusBarOrientation = uiApp.getStatusBarOrientation(); @@ -220,7 +197,8 @@ protected CGRect getBounds () { case LandscapeLeft: case LandscapeRight: if (screenHeight > screenWidth) { - debug("IOSApplication", "Switching reported width and height (w=" + screenWidth + " h=" + screenHeight + ")"); + debug("IOSApplication", "Switching reported width and height (original was w=" + screenWidth + " h=" + + screenHeight + ")"); double tmp = screenHeight; // noinspection SuspiciousNameCombination screenHeight = screenWidth; @@ -228,26 +206,31 @@ protected CGRect getBounds () { } } - // update width/height depending on display scaling selected - screenWidth *= displayScaleFactor; - screenHeight *= displayScaleFactor; - if (statusBarHeight != 0.0) { debug("IOSApplication", "Status bar is visible (height = " + statusBarHeight + ")"); - statusBarHeight *= displayScaleFactor; screenHeight -= statusBarHeight; } else { debug("IOSApplication", "Status bar is not visible"); } + final int offsetX = 0; + final int offsetY = (int)Math.round(statusBarHeight); + + final int width = (int)Math.round(screenWidth); + final int height = (int)Math.round(screenHeight); + + final int backBufferWidth = (int)Math.round(screenWidth * pixelsPerPoint); + final int backBufferHeight = (int)Math.round(screenHeight * pixelsPerPoint); - debug("IOSApplication", "Total computed bounds are w=" + screenWidth + " h=" + screenHeight); + debug("IOSApplication", "Computed bounds are x=" + offsetX + " y=" + offsetY + " w=" + width + " h=" + height + " bbW= " + + backBufferWidth + " bbH= " + backBufferHeight); - return lastScreenBounds = new CGRect(0.0, statusBarHeight, screenWidth, screenHeight); + return lastScreenBounds = new IOSScreenBounds(offsetX, offsetY, width, height, backBufferWidth, backBufferHeight); } - protected CGRect getCachedBounds () { + /** @return area of screen in UIKit points on which libGDX draws, with 0,0 being upper left corner */ + public IOSScreenBounds getScreenBounds () { if (lastScreenBounds == null) - return getBounds(); + return computeBounds(); else return lastScreenBounds; } diff --git a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java index 953c94be121..635c69a9fb6 100644 --- a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java +++ b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java @@ -47,39 +47,6 @@ public class IOSApplicationConfiguration { /** number of frames per second, 60 is default **/ public int preferredFramesPerSecond = 60; - /** Scale factor to use on large screens with retina display, i.e. iPad 3+ (has no effect on non-retina screens). - *
    - *
  • 1.0 = no scaling (everything is in pixels) - *
  • 0.5 = LibGDX will behave as you would only have half the pixels. I.e. instead of 2048x1536 you will work in 1024x768. - * This looks pixel perfect and will save you the trouble to create bigger graphics for the retina display. - *
  • any other value: scales the screens according to your scale factor. A scale factor oof 0.75, 0.8, 1.2, 1.5 etc. works - * very well without any artifacts! - *
*/ - public float displayScaleLargeScreenIfRetina = 1.0f; - /** Scale factor to use on small screens with retina display, i.e. iPhone 4+, iPod 4+ (has no effect on non-retina screens). - *
    - *
  • 1.0 = no scaling (everything is in pixels) - *
  • 0.5 = LibGDX will behave as you would only have half the pixels. I.e. instead of 960x640 you will work in 480x320. This - * looks pixel perfect and will save you the trouble to create bigger graphics for the retina display. - *
  • any other value: scales the screens according to your scale factor. A scale factor of 0.75, 0.8, 1.2, 1.5 etc. works - * very well without any artifacts! - *
*/ - public float displayScaleSmallScreenIfRetina = 1.0f; - /** Scale factor to use on large screens without retina display, i.e. iPad 1+2 (has no effect on retina screens). - *
    - *
  • 1.0 = no scaling (everything is in pixels) - *
  • any other value: scales the screens according to your scale factor. A scale factor of 0.75, 0.8, 1.2, 1.5 etc. works - * very well without any artifacts! - *
*/ - public float displayScaleLargeScreenIfNonRetina = 1.0f; - /** Scale factor to use on small screens without retina display, i.e. iPhone 1-3, iPod 1-3 (has no effect on retina screens). - *
    - *
  • 1.0 = no scaling (everything is in pixels) - *
  • any other value: scales the screens according to your scale factor. A scale factor of 0.75, 0.8, 1.2, 1.5 etc. works - * very well without any artifacts! - *
*/ - public float displayScaleSmallScreenIfNonRetina = 1.0f; - /** whether to use the accelerometer, default true **/ public boolean useAccelerometer = true; /** the update interval to poll the accelerometer with, in seconds **/ diff --git a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSGraphics.java b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSGraphics.java index 366d56a2e12..402d37d0e26 100644 --- a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSGraphics.java +++ b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSGraphics.java @@ -65,7 +65,6 @@ public class IOSGraphics extends NSObject implements Graphics, GLKViewDelegate, public static class IOSUIViewController extends GLKViewController { final IOSApplication app; final IOSGraphics graphics; - boolean created = false; protected IOSUIViewController (IOSApplication app, IOSGraphics graphics) { this.app = app; @@ -124,14 +123,16 @@ public UIRectEdge getPreferredScreenEdgesDeferringSystemGestures() { public void viewDidLayoutSubviews () { super.viewDidLayoutSubviews(); // get the view size and update graphics - CGRect bounds = app.getBounds(); - graphics.width = (int)bounds.getWidth(); - graphics.height = (int)bounds.getHeight(); - graphics.makeCurrent(); - if (graphics.created) { + final IOSScreenBounds oldBounds = graphics.screenBounds; + final IOSScreenBounds newBounds = app.computeBounds(); + graphics.screenBounds = newBounds; + // Layout may happen without bounds changing, don't trigger resize in that case + if (graphics.created && (newBounds.width != oldBounds.width || newBounds.height != oldBounds.height)) { + graphics.makeCurrent(); graphics.updateSafeInsets(); - app.listener.resize(graphics.width, graphics.height); + app.listener.resize(newBounds.width, newBounds.height); } + } @Override @@ -166,19 +167,11 @@ public void pressesEnded(NSSet presses, UIPressesEvent event) { } } - static class IOSUIView extends GLKView { - - public IOSUIView (CGRect frame, EAGLContext context) { - super(frame, context); - } - } - IOSApplication app; IOSInput input; GL20 gl20; GL30 gl30; - int width; - int height; + IOSScreenBounds screenBounds; int safeInsetLeft, safeInsetTop, safeInsetBottom, safeInsetRight; long lastFrameTime; float deltaTime; @@ -205,13 +198,11 @@ public IOSUIView (CGRect frame, EAGLContext context) { GLKView view; IOSUIViewController viewController; - public IOSGraphics (float scale, IOSApplication app, IOSApplicationConfiguration config, IOSInput input, boolean useGLES30) { + public IOSGraphics (IOSApplication app, IOSApplicationConfiguration config, IOSInput input, boolean useGLES30) { this.config = config; - final CGRect bounds = app.getBounds(); // setup view and OpenGL - width = (int)bounds.getWidth(); - height = (int)bounds.getHeight(); + screenBounds = app.computeBounds(); if (useGLES30) { context = new EAGLContext(EAGLRenderingAPI.OpenGLES3); @@ -226,7 +217,7 @@ public IOSGraphics (float scale, IOSApplication app, IOSApplicationConfiguration gl30 = null; } - view = new GLKView(new CGRect(0, 0, bounds.getWidth(), bounds.getHeight()), context) { + view = new GLKView(new CGRect(0, 0, screenBounds.width, screenBounds.height), context) { @Method(selector = "touchesBegan:withEvent:") public void touchesBegan (@Pointer long touches, UIEvent event) { IOSGraphics.this.input.onTouch(touches); @@ -296,7 +287,7 @@ public void draw (CGRect rect) { IOSDevice device = config.knownDevices.get(machineString); if (device == null) app.error(tag, "Machine ID: " + machineString + " not found, please report to LibGDX"); int ppi = device != null ? device.ppi : 163; - density = device != null ? device.ppi/160f : scale; + density = device != null ? device.ppi/160f : app.pixelsPerPoint; ppiX = ppi; ppiY = ppi; ppcX = ppiX / 2.54f; @@ -346,6 +337,8 @@ public void draw (GLKView view, CGRect rect) { gl20.glViewport(IOSGLES20.x, IOSGLES20.y, IOSGLES20.width, IOSGLES20.height); if (!created) { + final int width = screenBounds.width; + final int height = screenBounds.height; gl20.glViewport(0, 0, width, height); String versionString = gl20.glGetString(GL20.GL_VERSION); @@ -437,22 +430,27 @@ public void setGL30 (GL30 gl30) { @Override public int getWidth () { - return width; + return screenBounds.width; } @Override public int getHeight () { - return height; + return screenBounds.height; } @Override public int getBackBufferWidth() { - return width; + return screenBounds.backBufferWidth; } @Override public int getBackBufferHeight() { - return height; + return screenBounds.backBufferHeight; + } + + /** @return amount of pixels per point */ + public float getBackBufferScale() { + return app.pixelsPerPoint; } @Override diff --git a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSScreenBounds.java b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSScreenBounds.java new file mode 100644 index 00000000000..23a3fe371c6 --- /dev/null +++ b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSScreenBounds.java @@ -0,0 +1,42 @@ + +package com.badlogic.gdx.backends.iosrobovm; + +/** Represents the bounds inside GL view to which libGDX draws. These bounds may be same as view's dimensions, but may differ in + * some cases: + *
    + *
  • Status bar is visible - drawing area is not under the status bar
  • + *
  • Screen is rotated - in some iOS versions the rotation reporting behavior is different and this needs to be handled
  • + *
+ * + *

IMPLEMENTATION & WARNING - Read carefully

Accounting for status bar is not completely clean and relies on a + * coincidence, related to coordinate system origins. When status bar is present, x and y grows (in practice only y does) to + * offset the drawing area from the status bar and width and height (and their backBuffer values of course) shrink so the + * remaining surface fits the rest of the screen. + * + * When touch events arrive, IOSInput subtracts x and y from their coordinates to account for the shift. + * + * The unclean part is in the actual rendering - since the offset is essentially faked, there is no way to supply it to the libGDX + * application. But this does not become a problem, as long as Y and HEIGHT add up to the GL view's height (or X and WIDTH, + * although that is not used in practice), because GL's coordinate system (as far as the glViewport is concerned) starts in the + * LOWER left corner. So in practice, the rendering part of libGDX can be completely oblivious to any x/y offsets. + * + * This may become a problem when interfacing with UIKit, for example when placing banner ADs or using UIKit views over the + * libGDX's GL view. In such case, overriding {@link IOSApplication#computeBounds()} and providing custom, correct values is + * recommended. */ +public final class IOSScreenBounds { + /** Offset from top left corner in points */ + public final int x, y; + /** Dimensions of drawing surface in points */ + public final int width, height; + /** Dimensions of drawing surface in pixels */ + public final int backBufferWidth, backBufferHeight; + + public IOSScreenBounds (int x, int y, int width, int height, int backBufferWidth, int backBufferHeight) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.backBufferWidth = backBufferWidth; + this.backBufferHeight = backBufferHeight; + } +}