From 38634ef5ca80b6e40ff4c0c6e9b799cebdfc910f Mon Sep 17 00:00:00 2001 From: Karim Akra Date: Sat, 15 Nov 2025 17:43:51 +0200 Subject: [PATCH] Prevent the game bounds from breaking when resizing the window --- source/funkin/ui/FullScreenScaleMode.hx | 112 +++++++++++++++++++++++- source/funkin/ui/MusicBeatState.hx | 3 + 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/FullScreenScaleMode.hx b/source/funkin/ui/FullScreenScaleMode.hx index 6f878c37c..369572350 100644 --- a/source/funkin/ui/FullScreenScaleMode.hx +++ b/source/funkin/ui/FullScreenScaleMode.hx @@ -4,10 +4,13 @@ import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.math.FlxPoint; import flixel.util.FlxAxes; +import flixel.util.FlxHorizontalAlign; +import flixel.util.FlxVerticalAlign; import flixel.FlxG; import openfl.display.Bitmap; import openfl.display.BitmapData; import funkin.util.MathUtil; +import funkin.graphics.FunkinCamera; class FullScreenScaleMode extends flixel.system.scaleModes.BaseScaleMode { @@ -95,6 +98,15 @@ class FullScreenScaleMode extends flixel.system.scaleModes.BaseScaleMode @:noCompletion private static var cutoutBitmaps:Array = [null, null]; + @:noCompletion + private static var mustAwait:Bool = false; + + @:noCompletion + private static var awaitedSize:FlxPoint = FlxPoint.get(0, 0); + + @:noCompletion + private static var finishingAwait:Bool = false; + /** * Constructor for `FullScreenScaleMode`. * @@ -119,6 +131,62 @@ class FullScreenScaleMode extends flixel.system.scaleModes.BaseScaleMode */ override public function onMeasure(Width:Int, Height:Int):Void { + if (mustAwait) + { + onMeasureAwait(Width, Height); + } + else + { + onMeasureInstant(Width, Height); + mustAwait = true; + } + } + + /** + * Locks the game to the current aspect ratio and assignes the requested resolution as awaited for later. + * @param Width The width of the screen. + * @param Height The height of the screen. + */ + public function onMeasureAwait(Width:Int, Height:Int):Void + { + horizontalAlign = CENTER; + verticalAlign = CENTER; + + updateGameSize(FlxG.width, FlxG.height); + updateDeviceSize(Width, Height); + updateDeviceCutout(Width, Height); + #if mobile + updateDeviceNotch(funkin.mobile.util.ScreenUtil.getNotchRect()); + #end + updateScaleOffset(); + updateGamePosition(); + + awaitedSize.set(Width, Height); + } + + /** + * Unlock the game resolution and swap into the awaited one. + */ + public function onMeasurePostAwait():Void + { + if (awaitedSize.x == 0 && awaitedSize.y == 0) return; + + horizontalAlign = enabled ? LEFT : CENTER; + verticalAlign = enabled ? TOP : CENTER; + onMeasureInstant(Math.ceil(awaitedSize.x), Math.ceil(awaitedSize.y)); + FlxG.cameras.reset(new FunkinCamera('default')); + + awaitedSize.set(0, 0); + } + + /** + * Instantly apply the measured resolution to the game + * @param Width The width of the screen. + * @param Height The height of the screen. + */ + public function onMeasureInstant(Width:Int, Height:Int):Void + { + finishingAwait = true; untyped FlxG.width = FlxG.initialWidth; untyped FlxG.height = FlxG.initialHeight; @@ -132,6 +200,8 @@ class FullScreenScaleMode extends flixel.system.scaleModes.BaseScaleMode updateGamePosition(); adjustGameSize(); + + finishingAwait = false; } /** @@ -265,12 +335,50 @@ class FullScreenScaleMode extends flixel.system.scaleModes.BaseScaleMode override public function updateScaleOffset():Void { - scale.x = ratioAxis == X ? logicalSize.x / FlxG.width : deviceSize.x / FlxG.width; - scale.y = ratioAxis == Y ? logicalSize.y / FlxG.height : deviceSize.y / FlxG.height; + if (finishingAwait) + { + scale.x = ratioAxis == X ? logicalSize.x / FlxG.width : deviceSize.x / FlxG.width; + scale.y = ratioAxis == Y ? logicalSize.y / FlxG.height : deviceSize.y / FlxG.height; + } + else + { + scale.x = deviceSize.x / FlxG.width; + scale.y = deviceSize.y / FlxG.height; + + if (scale.x > scale.y) scale.x = scale.y; + else + scale.y = scale.x; + } updateOffsetX(); updateOffsetY(); } + override function updateOffsetX():Void + { + offset.x = switch (horizontalAlign) + { + case FlxHorizontalAlign.LEFT: + 0; + case FlxHorizontalAlign.CENTER: + Math.ceil(finishingAwait ? (deviceSize.x - gameSize.x) : (deviceSize.x - (gameSize.x * scale.x)) * 0.5); + case FlxHorizontalAlign.RIGHT: + deviceSize.x - gameSize.x; + } + } + + override function updateOffsetY():Void + { + offset.y = switch (verticalAlign) + { + case FlxVerticalAlign.TOP: + 0; + case FlxVerticalAlign.CENTER: + Math.ceil(finishingAwait ? (deviceSize.y - gameSize.y) : (deviceSize.y - (gameSize.y * scale.y)) * 0.5); + case FlxVerticalAlign.BOTTOM: + deviceSize.y - gameSize.y; + } + } + #if mobile private function updateDeviceNotch(notch:lime.math.Rectangle):Void { diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index b7953af9f..fe8fefb55 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -14,6 +14,7 @@ import funkin.modding.module.ModuleHandler; import funkin.util.SortUtil; import funkin.util.WindowUtil; import funkin.input.Controls; +import funkin.ui.FullScreenScaleMode; #if mobile import funkin.graphics.FunkinCamera; import funkin.mobile.ui.FunkinHitbox; @@ -55,6 +56,8 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler public function new() { + if (FullScreenScaleMode.instance != null) FullScreenScaleMode.instance.onMeasurePostAwait(); + super(); initCallbacks();