v3.2.4: added support for 'medium' display cutouts like dynamic island

This commit is contained in:
Evan Debenham
2025-09-05 16:31:48 -04:00
parent 291f11bc4e
commit 687b97c3a9
4 changed files with 68 additions and 13 deletions

View File

@@ -53,9 +53,9 @@ public abstract class PlatformSupport {
);
}
//returns a display cutout (if one is present) in device pixels, or null is none is present
//returns a display cutout (if one is present) in device pixels, or empty if none is present
public RectF getDisplayCutout(){
return null;
return new RectF();
}
public abstract void updateSystemUI();

View File

@@ -66,6 +66,35 @@ public class AndroidPlatformSupport extends PlatformSupport {
}
}
@Override
public RectF getDisplayCutout() {
RectF cutoutRect = new RectF();
//some extra logic here is because cutouts can apparently be returned inverted
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
DisplayCutout cutout = AndroidLauncher.instance.getApplicationWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
Rect largest = null;
if (cutout != null) {
for (Rect r : cutout.getBoundingRects()) {
if (largest == null
|| Math.abs(r.height() * r.width()) > Math.abs(largest.height() * largest.width())) {
largest = r;
}
}
}
if (largest != null){
cutoutRect.left = Math.min(largest.left, largest.right);
cutoutRect.right = Math.max(largest.left, largest.right);
cutoutRect.top = Math.min(largest.top, largest.bottom);
cutoutRect.bottom = Math.max(largest.top, largest.bottom);
}
}
return cutoutRect;
}
@Override
public RectF getSafeInsets( int level ) {
RectF insets = new RectF();
@@ -90,6 +119,7 @@ public class AndroidPlatformSupport extends PlatformSupport {
boolean largeCutout = false;
int screenSize = Game.width * Game.height;
for (Rect r : cutout.getBoundingRects()){
//use abs as some cutouts can apparently be returned inverted
int cutoutSize = Math.abs(r.height() * r.width());
//display cutouts are considered large if they take up more than 0.605% of the screen
//in reality we want less than about 0.5%, but some cutouts over-report their size

View File

@@ -140,6 +140,7 @@ import com.watabou.noosa.audio.Sample;
import com.watabou.noosa.particles.Emitter;
import com.watabou.noosa.tweeners.Tweener;
import com.watabou.utils.Callback;
import com.watabou.utils.DeviceCompat;
import com.watabou.utils.GameMath;
import com.watabou.utils.PlatformSupport;
import com.watabou.utils.Point;
@@ -234,7 +235,8 @@ public class GameScene extends PixelScene {
}
RectF insets = getCommonInsets();
insets.top = Game.platform.getSafeInsets(PlatformSupport.INSET_LRG).scale(1f/defaultZoom).top;
//we want to check if large is the same as blocking here
float largeInsetTop = Game.platform.getSafeInsets(PlatformSupport.INSET_LRG).scale(1f/defaultZoom).top;
scene = this;
@@ -368,32 +370,52 @@ public class GameScene extends PixelScene {
menu = new MenuPane();
menu.camera = uiCamera;
menu.setPos( uiCamera.width-MenuPane.WIDTH-insets.right, insets.top);
menu.setPos( uiCamera.width-MenuPane.WIDTH-insets.right, largeInsetTop);
add(menu);
//TODO buff indicator and boss HP need to be moved down slightly on iOS devices with dynamic island
//TODO buff indicator probably cut off on some Android devices, get display cutout size and check?
//TODO need to reject inputs on the bars (just turn then into hotareas?
//TODO make top bar transparent and add 1px of top status and menu bar to it?
//most cutouts supported by the game are small
// but some are more 'medium' can can be supported with a little UI offsetting
float mediumCutoutOffset = 0;
if (largeInsetTop != insets.top){
//most notably iOS's Dynamic island, which must exist in this case
if (DeviceCompat.isiOS()){
//TODO we should handle this logic in platformSupport, not hardcode it here
mediumCutoutOffset = 7;
} else if (DeviceCompat.isAndroid()) {
//some android hole punches can also be big too
RectF cutout = Game.platform.getDisplayCutout().scale(1f / defaultZoom);
//if the cutout is positioned to obstruct the buff bar
//TODO could buff bar just be squished in some cases here?
if (cutout.left < 80
&& cutout.top < 10
&& cutout.right > 32
&& cutout.bottom > 11) {
mediumCutoutOffset = (int) Math.floor(cutout.bottom - 11);
}
}
}
status = new StatusPane( SPDSettings.interfaceSize() > 0 );
status.camera = uiCamera;
status.setRect(insets.left, uiSize > 0 ? uiCamera.height-39-insets.bottom : insets.top, uiCamera.width - insets.left - insets.right, 0 );
StatusPane.cutoutOffset = mediumCutoutOffset;
status.setRect(insets.left, uiSize > 0 ? uiCamera.height-39-insets.bottom : largeInsetTop, uiCamera.width - insets.left - insets.right, 0 );
add(status);
if (uiSize < 2 && insets.top > 0) {
SkinnedBlock bar = new SkinnedBlock(uiCamera.width, insets.top, TextureCache.createSolid(0xFF1C1E18));
if (uiSize < 2 && largeInsetTop != 0) {
SkinnedBlock bar = new SkinnedBlock(uiCamera.width, largeInsetTop, TextureCache.createSolid(0xFF1C1E18));
bar.camera = uiCamera;
add(bar);
PointerArea blocker = new PointerArea(0, 0, uiCamera.width, insets.top);
PointerArea blocker = new PointerArea(0, 0, uiCamera.width, largeInsetTop);
blocker.camera = uiCamera;
add(blocker);
}
boss = new BossHealthBar();
boss.camera = uiCamera;
boss.setPos( insets.left + 6 + (uiCamera.width - insets.left - insets.right - boss.width())/2, insets.top + 21);
boss.setPos( insets.left + 6 + (uiCamera.width - insets.left - insets.right - boss.width())/2, largeInsetTop + 21 + mediumCutoutOffset);
add(boss);
resume = new ResumeIndicator();

View File

@@ -78,6 +78,9 @@ public class StatusPane extends Component {
private boolean large;
//lower the buff indicator to avoid larger cutouts (e.g. iPhone dynamic island)
public static float cutoutOffset;
public StatusPane( boolean large ){
super();
@@ -229,7 +232,7 @@ public class StatusPane extends Component {
heroInfoOnBar.setRect(heroInfo.right(), y, 50, 9);
buffs.setRect( x + 31, y + 7, 50, 8 );
buffs.setRect( x + 31, y + 7 + cutoutOffset, 50, 8 );
busy.x = x + 1;
busy.y = y + 37;