diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 399f52498..ac1b3903b 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -50,11 +50,13 @@ class InitState extends FlxState */ public override function create():Void { + // Setup a bunch of important Flixel stuff. setupShit(); - // loadSaveData(); // Moved to Main.hx // Load player options from save data. + // Flixel has already loaded the save data, so we can just use it. Preferences.init(); + // Load controls from save data. PlayerSettings.init(); @@ -198,8 +200,13 @@ class InitState extends FlxState // // FLIXEL PLUGINS // + // Plugins provide a useful interface for globally active Flixel objects, + // that receive update events regardless of the current state. + // TODO: Move Module behavior to a Flixel plugin. funkin.util.plugins.EvacuateDebugPlugin.initialize(); funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); + funkin.util.plugins.ScreenshotPlugin.initialize(); + funkin.util.plugins.VolumePlugin.initialize(); funkin.util.plugins.WatchPlugin.initialize(); // diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx index 6b0911ede..039a4c285 100644 --- a/source/funkin/Preferences.hx +++ b/source/funkin/Preferences.hx @@ -20,7 +20,10 @@ class Preferences static function set_naughtyness(value:Bool):Bool { - return Save.get().options.naughtyness = value; + var save = Save.get(); + save.options.naughtyness = value; + save.flush(); + return value; } /** @@ -36,7 +39,10 @@ class Preferences static function set_downscroll(value:Bool):Bool { - return Save.get().options.downscroll = value; + var save = Save.get(); + save.options.downscroll = value; + save.flush(); + return value; } /** @@ -52,7 +58,10 @@ class Preferences static function set_flashingLights(value:Bool):Bool { - return Save.get().options.flashingLights = value; + var save = Save.get(); + save.options.flashingLights = value; + save.flush(); + return value; } /** @@ -68,7 +77,10 @@ class Preferences static function set_zoomCamera(value:Bool):Bool { - return Save.get().options.zoomCamera = value; + var save = Save.get(); + save.options.zoomCamera = value; + save.flush(); + return value; } /** @@ -89,7 +101,10 @@ class Preferences toggleDebugDisplay(value); } - return Save.get().options.debugDisplay = value; + var save = Save.get(); + save.options.debugDisplay = value; + save.flush(); + return value; } /** @@ -107,7 +122,10 @@ class Preferences { if (value != Save.get().options.autoPause) FlxG.autoPause = value; - return Save.get().options.autoPause = value; + var save = Save.get(); + save.options.autoPause = value; + save.flush(); + return value; } public static function init():Void diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx index 201c222a3..c4760cf5f 100644 --- a/source/funkin/input/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -63,12 +63,10 @@ class Controls extends FlxActionSet var _debug_menu = new FlxActionDigital(Action.DEBUG_MENU); var _debug_chart = new FlxActionDigital(Action.DEBUG_CHART); var _debug_stage = new FlxActionDigital(Action.DEBUG_STAGE); + var _screenshot = new FlxActionDigital(Action.SCREENSHOT); var _volume_up = new FlxActionDigital(Action.VOLUME_UP); var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN); var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE); - #if CAN_CHEAT - var _cheat = new FlxActionDigital(Action.CHEAT); - #end var byName:Map = new Map(); @@ -235,6 +233,11 @@ class Controls extends FlxActionSet inline function get_DEBUG_STAGE() return _debug_stage.check(); + public var SCREENSHOT(get, never):Bool; + + inline function get_SCREENSHOT() + return _screenshot.check(); + public var VOLUME_UP(get, never):Bool; inline function get_VOLUME_UP() @@ -255,13 +258,6 @@ class Controls extends FlxActionSet inline function get_RESET() return _reset.check(); - #if CAN_CHEAT - public var CHEAT(get, never):Bool; - - inline function get_CHEAT() - return _cheat.check(); - #end - public function new(name, scheme:KeyboardScheme = null) { super(name); @@ -295,13 +291,14 @@ class Controls extends FlxActionSet add(_pause); add(_cutscene_advance); add(_cutscene_skip); + add(_debug_menu); + add(_debug_chart); + add(_debug_stage); + add(_screenshot); add(_volume_up); add(_volume_down); add(_volume_mute); add(_reset); - #if CAN_CHEAT - add(_cheat); - #end for (action in digitalActions) byName[action.name] = action; @@ -391,12 +388,10 @@ class Controls extends FlxActionSet case DEBUG_MENU: _debug_menu; case DEBUG_CHART: _debug_chart; case DEBUG_STAGE: _debug_stage; + case SCREENSHOT: _screenshot; case VOLUME_UP: _volume_up; case VOLUME_DOWN: _volume_down; case VOLUME_MUTE: _volume_mute; - #if CAN_CHEAT - case CHEAT: _cheat; - #end } } @@ -464,6 +459,8 @@ class Controls extends FlxActionSet func(_debug_chart, JUST_PRESSED); case DEBUG_STAGE: func(_debug_stage, JUST_PRESSED); + case SCREENSHOT: + func(_screenshot, JUST_PRESSED); case VOLUME_UP: func(_volume_up, JUST_PRESSED); case VOLUME_DOWN: @@ -472,10 +469,6 @@ class Controls extends FlxActionSet func(_volume_mute, JUST_PRESSED); case RESET: func(_reset, JUST_PRESSED); - #if CAN_CHEAT - case CHEAT: - func(_cheat, JUST_PRESSED); - #end } } @@ -666,6 +659,8 @@ class Controls extends FlxActionSet bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU)); bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART)); bindKeys(Control.DEBUG_STAGE, getDefaultKeybinds(scheme, Control.DEBUG_STAGE)); + bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); + bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT)); bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP)); bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN)); bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE)); @@ -693,6 +688,7 @@ class Controls extends FlxActionSet case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; + case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen case Control.VOLUME_UP: return [PLUS, NUMPADPLUS]; case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS]; case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO]; @@ -716,6 +712,7 @@ class Controls extends FlxActionSet case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; + case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.VOLUME_UP: return [PLUS]; case Control.VOLUME_DOWN: return [MINUS]; case Control.VOLUME_MUTE: return [ZERO]; @@ -739,6 +736,7 @@ class Controls extends FlxActionSet case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; + case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.VOLUME_UP: return [NUMPADPLUS]; case Control.VOLUME_DOWN: return [NUMPADMINUS]; case Control.VOLUME_MUTE: return [NUMPADZERO]; @@ -845,6 +843,7 @@ class Controls extends FlxActionSet Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT), Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT), Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE), + // Control.SCREENSHOT => [], // Control.VOLUME_UP => [RIGHT_SHOULDER], // Control.VOLUME_DOWN => [LEFT_SHOULDER], // Control.VOLUME_MUTE => [RIGHT_TRIGGER], @@ -852,8 +851,7 @@ class Controls extends FlxActionSet Control.CUTSCENE_SKIP => getDefaultGamepadBinds(Control.CUTSCENE_SKIP), // Control.DEBUG_MENU // Control.DEBUG_CHART - Control.RESET => getDefaultGamepadBinds(Control.RESET), - #if CAN_CHEAT, Control.CHEAT => getDefaultGamepadBinds(Control.CHEAT) #end + Control.RESET => getDefaultGamepadBinds(Control.RESET) ]); } @@ -870,6 +868,7 @@ class Controls extends FlxActionSet case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT]; case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT]; case Control.PAUSE: return [START]; + case Control.SCREENSHOT: return []; case Control.VOLUME_UP: return []; case Control.VOLUME_DOWN: return []; case Control.VOLUME_MUTE: return []; @@ -878,7 +877,6 @@ class Controls extends FlxActionSet case Control.DEBUG_MENU: return []; case Control.DEBUG_CHART: return []; case Control.RESET: return [RIGHT_SHOULDER]; - #if CAN_CHEAT, Control.CHEAT: return [X]; #end default: // Fallthrough. } @@ -1236,6 +1234,8 @@ enum Control // CUTSCENE CUTSCENE_ADVANCE; CUTSCENE_SKIP; + // SCREENSHOT + SCREENSHOT; // VOLUME VOLUME_UP; VOLUME_DOWN; @@ -1244,9 +1244,6 @@ enum Control DEBUG_MENU; DEBUG_CHART; DEBUG_STAGE; - #if CAN_CHEAT - CHEAT; - #end } enum @@ -1289,13 +1286,12 @@ abstract Action(String) to String from String var VOLUME_UP = "volume_up"; var VOLUME_DOWN = "volume_down"; var VOLUME_MUTE = "volume_mute"; + // SCREENSHOT + var SCREENSHOT = "screenshot"; // DEBUG var DEBUG_MENU = "debug_menu"; var DEBUG_CHART = "debug_chart"; var DEBUG_STAGE = "debug_stage"; - #if CAN_CHEAT - var CHEAT = "cheat"; - #end } enum Device diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 6a4dd048c..5bbded231 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -113,6 +113,9 @@ abstract Save(RawSaveData) }; } + /** + * NOTE: Modifications will not be saved without calling `Save.flush()`! + */ public var options(get, never):SaveDataOptions; function get_options():SaveDataOptions @@ -120,6 +123,9 @@ abstract Save(RawSaveData) return this.options; } + /** + * NOTE: Modifications will not be saved without calling `Save.flush()`! + */ public var modOptions(get, never):Map; function get_modOptions():Map diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index 884fc5061..98197a0e7 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -58,19 +58,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler Conductor.stepHit.remove(this.stepHit); } - function handleControls():Void - { - var isHaxeUIFocused:Bool = haxe.ui.focus.FocusManager.instance?.focus != null; - - if (!isHaxeUIFocused) - { - // Rebindable volume keys. - if (controls.VOLUME_MUTE) FlxG.sound.toggleMuted(); - else if (controls.VOLUME_UP) FlxG.sound.changeVolume(0.1); - else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1); - } - } - function handleFunctionControls():Void { // Emergency exit button. @@ -84,8 +71,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler { super.update(elapsed); - handleControls(); - dispatchEvent(new UpdateScriptEvent(elapsed)); } diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 9a6f1a323..2c8970357 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -53,11 +53,6 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler { super.update(elapsed); - // Rebindable volume keys. - if (controls.VOLUME_MUTE) FlxG.sound.toggleMuted(); - else if (controls.VOLUME_UP) FlxG.sound.changeVolume(0.1); - else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1); - // Emergency exit button. if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index 861e99f6b..375fb8f5c 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -9,6 +9,7 @@ import funkin.ui.debug.charting.ChartEditorState; import funkin.ui.MusicBeatSubState; import funkin.util.logging.CrashHandler; import flixel.addons.transition.FlxTransitionableState; +import funkin.util.FileUtil; class DebugMenuSubState extends MusicBeatSubState { @@ -110,16 +111,7 @@ class DebugMenuSubState extends MusicBeatSubState #if sys function openLogFolder() { - #if windows - Sys.command('explorer', [CrashHandler.LOG_FOLDER]); - #elseif mac - // mac could be fuckie with where the log folder is relative to the game file... - // if this comment is still here... it means it has NOT been verified on mac yet! - Sys.command('open', [CrashHandler.LOG_FOLDER]); - #end - - // TODO: implement linux - // some shit with xdg-open :thinking: emoji... + FileUtil.openFolder(CrashHandler.LOG_FOLDER); } #end diff --git a/source/funkin/util/FileUtil.hx b/source/funkin/util/FileUtil.hx index 612737680..7a7b1422c 100644 --- a/source/funkin/util/FileUtil.hx +++ b/source/funkin/util/FileUtil.hx @@ -20,6 +20,7 @@ class FileUtil { public static final FILE_FILTER_FNFC:FileFilter = new FileFilter("Friday Night Funkin' Chart (.fnfc)", "*.fnfc"); public static final FILE_FILTER_ZIP:FileFilter = new FileFilter("ZIP Archive (.zip)", "*.zip"); + public static final FILE_FILTER_PNG:FileFilter = new FileFilter("PNG Image (.png)", "*.png"); public static final FILE_EXTENSION_INFO_FNFC:FileDialogExtensionInfo = { @@ -31,6 +32,11 @@ class FileUtil extension: 'zip', label: 'ZIP Archive', }; + public static final FILE_EXTENSION_INFO_PNG:FileDialogExtensionInfo = + { + extension: 'png', + label: 'PNG Image', + }; /** * Browses for a single file, then calls `onSelect(fileInfo)` when a file is selected. @@ -639,6 +645,23 @@ class FileUtil }; } + public static function openFolder(pathFolder:String) + { + #if windows + Sys.command('explorer', [pathFolder]); + #elseif mac + // mac could be fuckie with where the log folder is relative to the game file... + // if this comment is still here... it means it has NOT been verified on mac yet! + // + // FileUtil.hx note: this was originally used to open the logs specifically! + // thats why the above comment is there! + Sys.command('open', [pathFolder]); + #end + + // TODO: implement linux + // some shit with xdg-open :thinking: emoji... + } + static function convertTypeFilter(typeFilter:Array):String { var filter:String = null; diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx new file mode 100644 index 000000000..16d0c7244 --- /dev/null +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -0,0 +1,320 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; +import flixel.FlxCamera; +import flixel.FlxG; +import flixel.FlxState; +import flixel.graphics.FlxGraphic; +import flixel.input.keyboard.FlxKey; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; +import flixel.util.FlxSignal; +import flixel.util.FlxTimer; +import funkin.graphics.FunkinSprite; +import funkin.input.Cursor; +import openfl.display.Bitmap; +import openfl.display.Sprite; +import openfl.display.BitmapData; +import openfl.display.PNGEncoderOptions; +import openfl.geom.Matrix; +import openfl.geom.Rectangle; +import openfl.utils.ByteArray; +import openfl.events.MouseEvent; + +typedef ScreenshotPluginParams = +{ + hotkeys:Array, + ?region:Rectangle, + shouldHideMouse:Bool, + flashColor:Null, + fancyPreview:Bool, +}; + +/** + * What if `flixel.addons.plugin.screengrab.FlxScreenGrab` but it's better? + * TODO: Contribute this upstream. + */ +class ScreenshotPlugin extends FlxBasic +{ + public static final SCREENSHOT_FOLDER = 'screenshots'; + + var _hotkeys:Array; + + var _region:Null; + + var _shouldHideMouse:Bool; + + var _flashColor:Null; + + var _fancyPreview:Bool; + + /** + * A signal fired before the screenshot is taken. + */ + public var onPreScreenshot(default, null):FlxTypedSignalVoid>; + + /** + * A signal fired after the screenshot is taken. + * @param bitmap The bitmap that was captured. + */ + public var onPostScreenshot(default, null):FlxTypedSignalVoid>; + + public function new(params:ScreenshotPluginParams) + { + super(); + + _hotkeys = params.hotkeys; + _region = params.region ?? null; + _shouldHideMouse = params.shouldHideMouse; + _flashColor = params.flashColor; + _fancyPreview = params.fancyPreview; + + onPreScreenshot = new FlxTypedSignalVoid>(); + onPostScreenshot = new FlxTypedSignalVoid>(); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (hasPressedScreenshot()) + { + capture(); + } + } + + /** + * Initialize the screenshot plugin. + */ + public static function initialize():Void + { + FlxG.plugins.addPlugin(new ScreenshotPlugin( + { + flashColor: Preferences.flashingLights ? FlxColor.WHITE : null, // Was originally a black flash. + + // TODO: Add a way to configure screenshots from the options menu. + hotkeys: [FlxKey.F3], + shouldHideMouse: false, + fancyPreview: true, + })); + } + + public function hasPressedScreenshot():Bool + { + return PlayerSettings.player1.controls.SCREENSHOT; + } + + public function updatePreferences():Void + { + _flashColor = Preferences.flashingLights ? FlxColor.WHITE : null; + } + + /** + * Defines the region of the screen that should be captured. + * You don't need to call this method if you want to capture the entire screen, that's the default behavior. + */ + public function defineCaptureRegion(x:Int, y:Int, width:Int, height:Int):Void + { + _region = new Rectangle(x, y, width, height); + } + + /** + * Capture the game screen as a bitmap. + */ + public function capture():Void + { + onPreScreenshot.dispatch(); + + var captureRegion = _region != null ? _region : new Rectangle(0, 0, FlxG.stage.stageWidth, FlxG.stage.stageHeight); + + var wasMouseHidden = false; + if (_shouldHideMouse && FlxG.mouse.visible) + { + wasMouseHidden = true; + Cursor.hide(); + } + + // The actual work. + // var bitmap = new Bitmap(new BitmapData(Math.floor(captureRegion.width), Math.floor(captureRegion.height), true, 0x00000000)); // Create a transparent empty bitmap. + // var drawMatrix = new Matrix(1, 0, 0, 1, -captureRegion.x, -captureRegion.y); // Modifying this will scale or skew the bitmap. + // bitmap.bitmapData.draw(FlxG.stage, drawMatrix); + var bitmap = new Bitmap(BitmapData.fromImage(FlxG.stage.window.readPixels())); + + if (wasMouseHidden) + { + Cursor.show(); + } + + // Save the bitmap to a file. + saveScreenshot(bitmap); + + // Show some feedback. + showCaptureFeedback(); + if (_fancyPreview) + { + showFancyPreview(bitmap); + } + + onPostScreenshot.dispatch(bitmap); + } + + final CAMERA_FLASH_DURATION = 0.25; + + /** + * Visual (and audio?) feedback when a screenshot is taken. + */ + function showCaptureFeedback():Void + { + var flashBitmap = new Bitmap(new BitmapData(Std.int(FlxG.stage.width), Std.int(FlxG.stage.height), false, 0xFFFFFFFF)); + var flashSpr = new Sprite(); + flashSpr.addChild(flashBitmap); + FlxG.stage.addChild(flashSpr); + FlxTween.tween(flashSpr, {alpha: 0}, 0.15, {ease: FlxEase.quadOut, onComplete: _ -> FlxG.stage.removeChild(flashSpr)}); + } + + static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in. + static final PREVIEW_FADE_IN_DURATION = 0.3; // How long the preview takes to fade in. + static final PREVIEW_FADE_OUT_DELAY = 1.25; // How long the preview stays on screen. + static final PREVIEW_FADE_OUT_DURATION = 0.3; // How long the preview takes to fade out. + + function showFancyPreview(bitmap:Bitmap):Void + { + // ermmm stealing this?? + var wasMouseHidden = false; + if (!FlxG.mouse.visible) + { + wasMouseHidden = true; + Cursor.show(); + } + + // so that it doesnt change the alpha when tweening in/out + var changingAlpha:Bool = false; + + // fuck it, cursed locally scoped functions, purely because im lazy + // (and so we can check changingAlpha, which is locally scoped.... because I'm lazy...) + var onHover = function(e:MouseEvent) { + if (!changingAlpha) e.target.alpha = 0.6; + }; + + var onHoverOut = function(e:MouseEvent) { + if (!changingAlpha) e.target.alpha = 1; + } + + var scale:Float = 0.25; + var w:Int = Std.int(bitmap.bitmapData.width * scale); + var h:Int = Std.int(bitmap.bitmapData.height * scale); + + var preview:BitmapData = new BitmapData(w, h, true); + var matrix:openfl.geom.Matrix = new openfl.geom.Matrix(); + matrix.scale(scale, scale); + preview.draw(bitmap.bitmapData, matrix); + + // used for movement + button stuff + var previewSprite = new Sprite(); + + previewSprite.buttonMode = true; + previewSprite.addEventListener(MouseEvent.MOUSE_DOWN, openScreenshotsFolder); + previewSprite.addEventListener(MouseEvent.MOUSE_OVER, onHover); + previewSprite.addEventListener(MouseEvent.MOUSE_OUT, onHoverOut); + + FlxG.stage.addChild(previewSprite); + + previewSprite.alpha = 0.0; + previewSprite.y -= 10; + + var previewBitmap = new Bitmap(preview); + previewSprite.addChild(previewBitmap); + + // Wait to fade in. + new FlxTimer().start(PREVIEW_INITIAL_DELAY, function(_) { + // Fade in. + changingAlpha = true; + FlxTween.tween(previewSprite, {alpha: 1.0, y: 0}, PREVIEW_FADE_IN_DURATION, + { + ease: FlxEase.quartOut, + onComplete: function(_) { + changingAlpha = false; + // Wait to fade out. + new FlxTimer().start(PREVIEW_FADE_OUT_DELAY, function(_) { + changingAlpha = true; + // Fade out. + FlxTween.tween(previewSprite, {alpha: 0.0, y: 10}, PREVIEW_FADE_OUT_DURATION, + { + ease: FlxEase.quartInOut, + onComplete: function(_) { + if (wasMouseHidden) + { + Cursor.hide(); + } + + previewSprite.removeEventListener(MouseEvent.MOUSE_DOWN, openScreenshotsFolder); + previewSprite.removeEventListener(MouseEvent.MOUSE_OVER, onHover); + previewSprite.removeEventListener(MouseEvent.MOUSE_OUT, onHoverOut); + + FlxG.stage.removeChild(previewSprite); + } + }); + }); + } + }); + }); + } + + function openScreenshotsFolder(e:MouseEvent):Void + { + FileUtil.openFolder(SCREENSHOT_FOLDER); + } + + static function getCurrentState():FlxState + { + var state = FlxG.state; + while (state.subState != null) + { + state = state.subState; + } + return state; + } + + static function getScreenshotPath():String + { + return '$SCREENSHOT_FOLDER/screenshot-${DateUtil.generateTimestamp()}.png'; + } + + static function makeScreenshotPath():Void + { + FileUtil.createDirIfNotExists(SCREENSHOT_FOLDER); + } + + /** + * Convert a Bitmap to a PNG ByteArray to save to a file. + */ + static function encodePNG(bitmap:Bitmap):ByteArray + { + return bitmap.bitmapData.encode(bitmap.bitmapData.rect, new PNGEncoderOptions()); + } + + /** + * Save the generated bitmap to a file. + * @param bitmap The bitmap to save. + */ + static function saveScreenshot(bitmap:Bitmap) + { + makeScreenshotPath(); + var targetPath:String = getScreenshotPath(); + + var pngData = encodePNG(bitmap); + + if (pngData == null) + { + trace('[WARN] Failed to encode PNG data.'); + return; + } + else + { + trace('Saving screenshot to: ' + targetPath); + // TODO: Make this work on browser. + FileUtil.writeBytesToPath(targetPath, pngData); + } + } +} diff --git a/source/funkin/util/plugins/VolumePlugin.hx b/source/funkin/util/plugins/VolumePlugin.hx new file mode 100644 index 000000000..5dbe60abf --- /dev/null +++ b/source/funkin/util/plugins/VolumePlugin.hx @@ -0,0 +1,34 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * Handles volume control in a way that is compatible with alternate control schemes. + */ +class VolumePlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize() + { + FlxG.plugins.addPlugin(new VolumePlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + var isHaxeUIFocused:Bool = haxe.ui.focus.FocusManager.instance?.focus != null; + + if (!isHaxeUIFocused) + { + // Rebindable volume keys. + if (PlayerSettings.player1.controls.VOLUME_MUTE) FlxG.sound.toggleMuted(); + else if (PlayerSettings.player1.controls.VOLUME_UP) FlxG.sound.changeVolume(0.1); + else if (PlayerSettings.player1.controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1); + } + } +}