diff --git a/Project.xml b/Project.xml index 8b7f37f19..7225b3883 100644 --- a/Project.xml +++ b/Project.xml @@ -123,63 +123,16 @@ - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -198,6 +151,11 @@ + + + + + @@ -259,8 +217,6 @@ - - diff --git a/hmm.json b/hmm.json index 56c9cc910..0ddaf1618 100644 --- a/hmm.json +++ b/hmm.json @@ -21,6 +21,13 @@ "ref": "a3877f0", "url": "https://github.com/MasterEric/flixel-addons" }, + { + "name": "flixel-addons", + "type": "git", + "dir": null, + "ref": "dev", + "url": "https://github.com/MasterEric/flixel-addons" + }, { "name": "flixel-ui", "type": "haxelib", @@ -33,6 +40,20 @@ "ref": "18b2060", "url": "https://github.com/Dot-Stuff/flxanimate" }, + { + "name": "haxeui-core", + "type": "git", + "dir": null, + "ref": "master", + "url": "https://github.com/haxeui/haxeui-core/" + }, + { + "name": "haxeui-flixel", + "type": "git", + "dir": null, + "ref": "master", + "url": "https://github.com/haxeui/haxeui-flixel" + }, { "name": "hmm", "type": "haxelib", @@ -57,15 +78,15 @@ "name": "lime", "type": "git", "dir": null, - "ref": "770bf0e", + "ref": "develop", "url": "https://github.com/openfl/lime" }, { - "name": "flixel-addons", + "name": "openfl", "type": "git", "dir": null, - "ref": "dev", - "url": "https://github.com/MasterEric/flixel-addons" + "ref": "develop", + "url": "https://github.com/openfl/openfl" }, { "name": "polymod", diff --git a/source/Main.hx b/source/Main.hx index 4b3adf7ff..113a101a2 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -2,8 +2,8 @@ package; import flixel.FlxGame; import flixel.FlxState; -import funkin.InitState; import funkin.MemoryCounter; +import haxe.ui.Toolkit; import openfl.Lib; import openfl.display.FPS; import openfl.display.Sprite; @@ -15,7 +15,7 @@ class Main extends Sprite { var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom). var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom). - var initialState:Class = InitState; // The FlxState the game starts with. + var initialState:Class = funkin.InitState; // The FlxState the game starts with. var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions. #if web var framerate:Int = 60; // How many frames per second the game should run at. @@ -69,20 +69,6 @@ class Main extends Sprite private function setupGame():Void { - // Lib.current.stage.color = null; - - var stageWidth:Int = Lib.current.stage.stageWidth; - var stageHeight:Int = Lib.current.stage.stageHeight; - - // if (zoom == -1) - // { - // var ratioX:Float = stageWidth / gameWidth; - // var ratioY:Float = stageHeight / gameHeight; - // zoom = Math.min(ratioX, ratioY); - // gameWidth = Math.ceil(stageWidth / zoom); - // gameHeight = Math.ceil(stageHeight / zoom); - // } - /** * The `zoom` argument of FlxGame was removed in the dev branch of Flixel, * since it was considered confusing and unintuitive. @@ -92,9 +78,11 @@ class Main extends Sprite */ #if !debug - initialState = TitleState; + initialState = funkin.TitleState; #end + initHaxeUI(); + addChild(new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen)); #if debug @@ -105,53 +93,14 @@ class Main extends Sprite addChild(memoryCounter); #end #end - - /* - video = new Video(); - addChild(video); - - var netConnection = new NetConnection(); - netConnection.connect(null); - - netStream = new NetStream(netConnection); - netStream.client = {onMetaData: client_onMetaData}; - netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, netStream_onAsyncError); - - #if (js && html5) - overlay = new Sprite(); - overlay.graphics.beginFill(0, 0.5); - overlay.graphics.drawRect(0, 0, 560, 320); - overlay.addEventListener(MouseEvent.MOUSE_DOWN, overlay_onMouseDown); - overlay.buttonMode = true; - addChild(overlay); - - netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_onNetStatus); - #else - netStream.play("assets/preload/music/dredd.mp4"); - #end - */ } - /* - private function client_onMetaData(metaData:Dynamic) - { - video.attachNetStream(netStream); - video.width = video.videoWidth; - video.height = video.videoHeight; - } - - private function netStream_onAsyncError(event:AsyncErrorEvent):Void - { - trace("Error loading video"); - } - - private function netConnection_onNetStatus(event:NetStatusEvent):Void - { - } - - private function overlay_onMouseDown(event:MouseEvent):Void - { - netStream.play("assets/preload/music/dredd.mp4"); - } - */ + function initHaxeUI() + { + // Calling this before any HaxeUI components get used is important: + // - It initializes the theme styles. + // - It scans the class path and registers any HaxeUI components. + Toolkit.init(); + Toolkit.theme = "dark"; // don't be cringe + } } diff --git a/source/funkin/MainMenuState.hx b/source/funkin/MainMenuState.hx index ee84e14c8..715793012 100644 --- a/source/funkin/MainMenuState.hx +++ b/source/funkin/MainMenuState.hx @@ -112,17 +112,10 @@ class MainMenuState extends MusicBeatState persistentUpdate = false; openSubState(new FreeplayState()); }); + #if CAN_OPEN_LINKS var hasPopupBlocker = #if web true #else false #end; - - if (VideoState.seenVideo) - { - createMenuItem('kickstarter', 'mainmenu/kickstarter', selectDonate, hasPopupBlocker); - } - else - { - createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker); - } + createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker); #end createMenuItem('options', 'mainmenu/options', function() @@ -195,7 +188,7 @@ class MainMenuState extends MusicBeatState #if CAN_OPEN_LINKS function selectDonate() { - WindowUtil.openURL(Constants.URL_KICKSTARTER); + WindowUtil.openURL(Constants.URL_ITCH); } #end diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 3a0a9fa45..5f4fdcf49 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -10,8 +10,7 @@ import funkin.Conductor.BPMChangeEvent; import funkin.modding.PolymodHandler; import funkin.modding.events.ScriptEvent; import funkin.modding.module.ModuleHandler; -import funkin.play.character.CharacterData.CharacterDataParser; -import funkin.play.stage.StageData.StageDataParser; +import funkin.ui.debug.DebugMenuSubState; import funkin.util.SortUtil; /** @@ -61,6 +60,15 @@ class MusicBeatState extends FlxUIState if (FlxG.keys.justPressed.F5) debug_refreshModules(); + // ` / ~ + if (FlxG.keys.justPressed.GRAVEACCENT) + { + // TODO: Does this break anything? + this.persistentUpdate = false; + this.persistentDraw = false; + FlxG.state.openSubState(new DebugMenuSubState()); + } + // everyStep(); var oldStep:Int = curStep; diff --git a/source/funkin/MusicBeatSubstate.hx b/source/funkin/MusicBeatSubstate.hx index 72c201292..2cf262a8d 100644 --- a/source/funkin/MusicBeatSubstate.hx +++ b/source/funkin/MusicBeatSubstate.hx @@ -1,7 +1,7 @@ package funkin; -import flixel.util.FlxColor; import flixel.FlxSubState; +import flixel.util.FlxColor; import funkin.Conductor.BPMChangeEvent; import funkin.modding.events.ScriptEvent; import funkin.modding.module.ModuleHandler; @@ -73,6 +73,15 @@ class MusicBeatSubstate extends FlxSubState ModuleHandler.callEvent(event); } + /** + * Close this substate and replace it with a different one. + */ + public function switchSubState(substate:FlxSubState):Void + { + this.close(); + this._parentState.openSubState(substate); + } + public function beatHit():Bool { var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep); diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index c80d465aa..e75a46826 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -117,6 +117,11 @@ class Paths return 'assets/fonts/$key'; } + inline static public function ui(key:String, ?library:String) + { + return xml('ui/$key', library); + } + static public function getSparrowAtlas(key:String, ?library:String) { return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library)); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 25569f24b..c14ff6fbe 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -866,8 +866,8 @@ class PlayState extends MusicBeatState for (songNotes in section.sectionNotes) { var daStrumTime:Float = songNotes.strumTime; + // TODO: Replace 4 with strumlineSize var daNoteData:Int = Std.int(songNotes.noteData % 4); - var gottaHitNote:Bool = section.mustHitSection; if (songNotes.highStakes) // noteData > 3 @@ -917,6 +917,7 @@ class PlayState extends MusicBeatState sustainNote.x += FlxG.width / 2; // general offset } + // TODO: Replace 4 with strumlineSize swagNote.mustPress = gottaHitNote; if (swagNote.mustPress) @@ -938,7 +939,7 @@ class PlayState extends MusicBeatState } else { - swagNote.x += FlxG.width / 2; // general offset + // swagNote.x += FlxG.width / 2; // general offset } } } diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 885b22eb0..369adb00c 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -223,6 +223,11 @@ class CharacterDataParser } } + public static function listCharacterIds():Array + { + return [for (x in characterCache.keys()) x]; + } + static function clearCharacterCache():Void { if (characterCache != null) diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index fb86f4e20..a287bc048 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -1,5 +1,8 @@ package funkin.play.song; +import funkin.play.song.SongData.SongDataParser; +import funkin.play.song.SongData.SongMetadata; + /** * This is a data structure managing information about the current song. * This structure is created when the game starts, and includes all the data @@ -9,20 +12,21 @@ package funkin.play.song; * It also receives script events; scripted classes which extend this class * can be used to perform custom gameplay behaviors only on specific songs. */ -class Song implements IPlayStateScriptedClass +class Song // implements IPlayStateScriptedClass { public var songId(default, null):String; public var songName(get, null):String; final _metadata:SongMetadata; - final _chartData:SongChartData; + + // final _chartData:SongChartData; public function new(id:String) { - this.songId = songId; + this.songId = id; - _metadata = SongDataParser.parseSongMetadata(this.songId); + _metadata = SongDataParser.parseSongMetadata(songId); if (_metadata == null) { throw 'Could not find song data for songId: $songId'; @@ -35,4 +39,9 @@ class Song implements IPlayStateScriptedClass return null; return _metadata.name; } + + public function toString():String + { + return 'Song($songId)'; + } } diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx index b4992992e..14758bfcb 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -1,5 +1,11 @@ package funkin.play.song; +import funkin.util.assets.DataAssets; +import openfl.utils.Assets; +import thx.semver.Version; + +using StringTools; + /** * Contains utilities for loading and parsing stage data. */ @@ -15,7 +21,7 @@ class SongDataParser /** * A list containing all the songs available to the game. */ - static final songCache:Map = new Map(); + static final songCache:Map = new Map(); static final DEFAULT_SONG_ID = 'UNKNOWN'; @@ -59,11 +65,10 @@ class SongDataParser trace(' Instantiating ${unscriptedSongIds.length} non-scripted songs...'); for (songId in unscriptedSongIds) { - var song:Song; try { - stage = new Song(songId); - if (stage != null) + var song = new Song(songId); + if (song != null) { trace(' Loaded song data: ${song.songId}'); songCache.set(song.songId, song); @@ -83,7 +88,7 @@ class SongDataParser /** * Retrieves a particular song from the cache. */ - public static function fetchStage(songId:String):Null + public static function fetchSong(songId:String):Null { if (songCache.exists(songId)) { @@ -102,22 +107,20 @@ class SongDataParser { if (songCache != null) { - for (song in songCache) - { - song.destroy(); - } songCache.clear(); } } public static function parseSongMetadata(songId:String):Null { + return null; } static function loadSongMetadataFile(songPath:String, variant:String = ''):String { - var songMetadataFilePath:String = Paths.json('stages/${stagePath}'); - var rawJson = Assets.getText(stageFilePath).trim(); + var songMetadataFilePath:String = (variant != '') ? Paths.json('songs/${songPath}') : Paths.json('songs/${songPath}'); + + var rawJson:String = Assets.getText(songMetadataFilePath).trim(); while (!rawJson.endsWith("}")) { @@ -127,3 +130,25 @@ class SongDataParser return rawJson; } } + +typedef SongMetadata = +{ + var version:Version; + + var songName:String; + var artist:String; + var timeFormat:SongTimeFormat; + var divisions:Int; + var timeChanges:Array; +}; + +typedef SongChartData = +{ +}; + +enum abstract SongTimeFormat(String) from String to String +{ + var TICKS = "ticks"; + var FLOAT = "float"; + var MILLISECONDS = "ms"; +} diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 915009691..55c609ac8 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -375,8 +375,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass // Add the character to the scene. this.add(character); + + #if debug debugIconGroup.add(debugIcon); debugIconGroup.add(debugIcon2); + #end } public inline function getGirlfriendPosition():FlxPoint diff --git a/source/funkin/play/stage/StageData.hx b/source/funkin/play/stage/StageData.hx index 5af337f63..0ecc5043b 100644 --- a/source/funkin/play/stage/StageData.hx +++ b/source/funkin/play/stage/StageData.hx @@ -141,6 +141,11 @@ class StageDataParser return validateStageData(stageId, stageData); } + public static function listStageIds():Array + { + return [for (x in stageCache.keys()) x]; + } + static function loadStageFile(stagePath:String):String { var stageFilePath:String = Paths.json('stages/${stagePath}'); diff --git a/source/funkin/ui/OptionsState.hx b/source/funkin/ui/OptionsState.hx index 3a74ef3f8..a482394a1 100644 --- a/source/funkin/ui/OptionsState.hx +++ b/source/funkin/ui/OptionsState.hx @@ -27,7 +27,7 @@ class OptionsState extends MusicBeatState menuBG.scrollFactor.set(0, 0); add(menuBG); - var options = addPage(Options, new OptionsMenu(false)); + var options = addPage(Options, new OptionsMenu()); var preferences = addPage(Preferences, new PreferencesMenu()); var controls = addPage(Controls, new ControlsMenu()); @@ -167,22 +167,14 @@ class OptionsMenu extends Page { var items:TextMenuList; - public function new(showDonate:Bool) + public function new() { super(); add(items = new TextMenuList()); createItem("PREFERENCES", function() switchPage(Preferences)); createItem("CONTROLS", function() switchPage(Controls)); - // createItem("COLORS", function() switchPage(Colors)); - #if CAN_OPEN_LINKS - if (showDonate) - { - var hasPopupBlocker = #if web true #else false #end; - createItem("DONATE", selectDonate, hasPopupBlocker); - } - #end #if newgrounds if (NGio.isLoggedIn) createItem("LOGOUT", selectLogout); @@ -215,13 +207,6 @@ class OptionsMenu extends Page return items.length > 2; } - #if CAN_OPEN_LINKS - function selectDonate() - { - WindowUtil.openURL(Constants.URL_ITCH); - } - #end - #if newgrounds function selectLogin() { diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx new file mode 100644 index 000000000..65b413f10 --- /dev/null +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -0,0 +1,95 @@ +package funkin.ui.debug; + +import flixel.FlxObject; +import flixel.FlxSprite; +import funkin.MusicBeatSubstate; +import funkin.ui.TextMenuList; +import funkin.ui.debug.charting.ChartEditorState; + +class DebugMenuSubState extends MusicBeatSubstate +{ + var items:TextMenuList; + + /** + * Camera focus point + */ + var camFocusPoint:FlxObject; + + override function create() + { + super.create(); + + // Create an object for the camera to track. + camFocusPoint = new FlxObject(0, 0); + add(camFocusPoint); + + // Follow the camera focus as we scroll. + FlxG.camera.follow(camFocusPoint, null, 0.06); + + // Create the green background. + var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat')); + menuBG.color = 0xFF4CAF50; + menuBG.setGraphicSize(Std.int(menuBG.width * 1.1)); + menuBG.updateHitbox(); + menuBG.screenCenter(); + menuBG.scrollFactor.set(0, 0); + add(menuBG); + + // Create the list for menu items. + items = new TextMenuList(); + // Move the camera when the menu is scrolled. + items.onChange.add(onMenuChange); + add(items); + + // Create each menu item. + // Call onMenuChange when the first item is created to move the camera . + onMenuChange(createItem("CHART EDITOR", openChartEditor)); + createItem("ANIMATION EDITOR", openAnimationEditor); + createItem("STAGE EDITOR", openStageEditor); + } + + function onMenuChange(selected:TextMenuItem) + { + camFocusPoint.setPosition(selected.x + selected.width / 2, selected.y + selected.height / 2); + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + if (controls.BACK) + { + FlxG.sound.play(Paths.sound('cancelMenu')); + exitDebugMenu(); + } + } + + function createItem(name:String, callback:Void->Void, fireInstantly = false) + { + var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback); + item.fireInstantly = fireInstantly; + item.screenCenter(X); + return item; + } + + function openChartEditor() + { + FlxG.switchState(new ChartEditorState()); + } + + function openAnimationEditor() + { + trace('Animation Editor'); + } + + function openStageEditor() + { + trace('Stage Editor'); + } + + function exitDebugMenu() + { + // TODO: Add a transition? + this.close(); + } +} diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx new file mode 100644 index 000000000..b52a4fd9a --- /dev/null +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -0,0 +1,237 @@ +package funkin.ui.debug.charting; + +import flixel.FlxSprite; +import flixel.addons.display.FlxGridOverlay; +import flixel.group.FlxSpriteGroup; +import flixel.util.FlxColor; +import funkin.ui.haxeui.HaxeUIState; +import haxe.ui.containers.dialogs.Dialog; +import haxe.ui.containers.menus.MenuItem; +import haxe.ui.core.Component; +import haxe.ui.events.MouseEvent; +import openfl.display.BitmapData; + +class ChartEditorState extends HaxeUIState +{ + static final CHART_EDITOR_LAYOUT = Paths.ui('chart-editor/main-view'); + + /** + * The number of notes on each character's strumline. + * TODO: Refactor this logic for larger strumlines in the future. + */ + static final STRUMLINE_SIZE = 4; + + /** + * The height of the menu bar in pixels. Used for positioning UI components. + */ + static final MENU_BAR_HEIGHT = 32; + + /** + * The width (and height) of each grid square, in pixels. + */ + static final GRID_SIZE:Int = 40; + + /** + * Pixel distance between the menu bar and the start of the chart grid. + */ + static final GRID_TOP_PAD:Int = 8; + + static final GRID_ALTERNATE:Bool = true; + static final GRID_COLOR_1:FlxColor = 0xFFE7E6E6; + static final GRID_COLOR_2:FlxColor = 0xFFD9D5D5; + + var gridBitmap:BitmapData; + var gridSprites:FlxSpriteGroup; + var gridDividerA:FlxSprite; + var gridDividerB:FlxSprite; + + var menuBG:FlxSprite; + + // TODO: Make the unit of measurement for this non-arbitrary + // to assist with logic later. + var scrollPosition(default, set):Float = -1.0; + + public function new() + { + super(CHART_EDITOR_LAYOUT); + } + + override function create() + { + FlxG.sound.music.stop(); + + buildBackground(); + buildGrid(); + + super.create(); + + setupMenuListeners(); + + scrollPosition = 0; + } + + function buildBackground() + { + menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat')); + add(menuBG); + menuBG.color = 0xFF673ab7; + menuBG.setGraphicSize(Std.int(menuBG.width * 1.1)); + menuBG.updateHitbox(); + menuBG.screenCenter(); + menuBG.scrollFactor.set(0, 0); + } + + function buildGrid() + { + // The checkerboard background image of the chart. + // 2 * (Strumline Size) + 1 grid squares wide, by 2 grid squares tall. + // This gets reused to fill the screen. + gridBitmap = FlxGridOverlay.createGrid(GRID_SIZE, GRID_SIZE, GRID_SIZE * (STRUMLINE_SIZE * 2 + 1), GRID_SIZE * 2, GRID_ALTERNATE, GRID_COLOR_1, + GRID_COLOR_2); + + gridSprites = new FlxSpriteGroup(); + add(gridSprites); + + for (i in 0...10) + { + var gridSprite = new FlxSprite().loadGraphic(gridBitmap); + gridSprite.x = FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; // Center the grid. + gridSprite.y = MENU_BAR_HEIGHT + GRID_TOP_PAD + (i * gridSprite.height); // Push down to account for the menu bar. + gridSprites.add(gridSprite); + } + + // The black divider between the two halves of the chart. + gridDividerA = new FlxSprite(gridSprites.members[0].x + GRID_SIZE * STRUMLINE_SIZE, + MENU_BAR_HEIGHT).makeGraphic(2, FlxG.height - MENU_BAR_HEIGHT, FlxColor.BLACK); + add(gridDividerA); + gridDividerB = new FlxSprite(gridSprites.members[0].x + GRID_SIZE * STRUMLINE_SIZE * 2, + MENU_BAR_HEIGHT).makeGraphic(2, FlxG.height - MENU_BAR_HEIGHT, FlxColor.BLACK); + add(gridDividerB); + } + + public override function update(elapsed:Float) + { + super.update(elapsed); + + FlxG.mouse.visible = true; + + handleScroll(); + + if (FlxG.keys.justPressed.B) + toggleSidebar(); + } + + function handleScroll() + { + var scrollAmount:Float = 0; + + if (FlxG.keys.justPressed.UP) + { + scrollAmount = -10; + } + if (FlxG.keys.justPressed.DOWN) + { + scrollAmount = 10; + } + if (FlxG.mouse.wheel != 0) + { + scrollAmount = -10 * FlxG.mouse.wheel; + } + + if (FlxG.keys.pressed.SHIFT) + { + scrollAmount *= 10; + } + if (FlxG.keys.pressed.CONTROL) + { + scrollAmount /= 10; + } + + this.scrollPosition += scrollAmount; + } + + function set_scrollPosition(value:Float):Float + { + // TODO: Calculate this. + var MAX_SCROLL = 10000; + if (value == scrollPosition || value < 0 || value > MAX_SCROLL) + return scrollPosition; + + this.scrollPosition = value; + + trace('SCROLL: $scrollPosition'); + + // Move the grid sprites to the correct position. + gridSprites.y = -scrollPosition; + + // Nudge the grid dividers down if needed. + if (-gridSprites.y < GRID_TOP_PAD) + { + gridDividerA.y = MENU_BAR_HEIGHT + GRID_TOP_PAD + gridSprites.y; + gridDividerB.y = MENU_BAR_HEIGHT + GRID_TOP_PAD + gridSprites.y; + } + else + { + gridDividerA.y = MENU_BAR_HEIGHT; + gridDividerB.y = MENU_BAR_HEIGHT; + } + + // Rearrange grid sprites so they stay on screen. + gridSprites.forEachAlive(function(sprite:FlxSprite) + { + // If this grid sprite is off the top of the screen... + if (sprite.y + sprite.height < MENU_BAR_HEIGHT) + { + // Move it to the bottom of the screen. + sprite.y += sprite.height * gridSprites.length; + } + // If this grid sprite is off the bottom of the screen... + if (sprite.y > FlxG.height) + { + // Move it to the top of the screen. + sprite.y -= sprite.height * gridSprites.length; + } + }); + + // TODO: Add a clip rectangle to the FlxSpriteGroup to hide the grid sprites that got moved up, + // when we scroll back to the top of the chart. + // Note that clip rectangles on sprite groups are borken right now, so we'll have to wait for that to be fixed. + + return this.scrollPosition; + } + + function toggleSidebar() + { + var sidebar:Component = this.component.findComponent('sidebar', Component); + + sidebar.visible = !sidebar.visible; + } + + function openDialog(key:String, modal:Bool = true) + { + var dialog:Dialog = cast buildComponent(Paths.ui(key)); + + // modal = true makes the background unclickable + dialog.showDialog(modal); + } + + function setupMenuListeners() + { + addMenuListener('menubarItemToggleSidebar', (event:MouseEvent) -> toggleSidebar()); + addMenuListener('menubarItemAbout', (event:MouseEvent) -> openDialog('chart-editor/dialogs/about')); + addMenuListener('menubarItemUserGuide', (event:MouseEvent) -> openDialog('chart-editor/dialogs/user-guide')); + } + + function addMenuListener(key:String, callback:MouseEvent->Void) + { + var menuItem:MenuItem = this.component.findComponent(key, MenuItem); + if (menuItem == null) + { + trace('WARN: Could not locate menu item: $key'); + } + else + { + menuItem.onClick = callback; + } + } +} diff --git a/source/funkin/ui/haxeui/HaxeUIState.hx b/source/funkin/ui/haxeui/HaxeUIState.hx new file mode 100644 index 000000000..e893e7f4a --- /dev/null +++ b/source/funkin/ui/haxeui/HaxeUIState.hx @@ -0,0 +1,48 @@ +package funkin.ui.haxeui; + +import haxe.ui.RuntimeComponentBuilder; +import haxe.ui.core.Component; + +class HaxeUIState extends MusicBeatState +{ + public var component:Component; + + var _componentKey:String; + + public function new(key:String) + { + super(); + _componentKey = key; + } + + override function create() + { + super.create(); + + if (component == null) + component = buildComponent(_componentKey); + if (component != null) + add(component); + } + + public function buildComponent(assetPath:String) + { + try + { + return RuntimeComponentBuilder.fromAsset(assetPath); + } + catch (e) + { + trace('[ERROR] Failed to build component from asset: ' + assetPath); + trace(e); + return null; + } + } + + override function destroy() + { + if (component != null) + remove(component); + component = null; + } +} diff --git a/source/funkin/ui/haxeui/HaxeUISubState.hx b/source/funkin/ui/haxeui/HaxeUISubState.hx new file mode 100644 index 000000000..b512c8451 --- /dev/null +++ b/source/funkin/ui/haxeui/HaxeUISubState.hx @@ -0,0 +1,93 @@ +package funkin.ui.haxeui; + +import haxe.ui.RuntimeComponentBuilder; +import haxe.ui.core.Component; + +class HaxeUISubState extends MusicBeatSubstate +{ + // The component representing the main UI. + public var component:Component; + + var _componentKey:String; + + public function new(key:String) + { + super(); + _componentKey = key; + } + + override function create() + { + super.create(); + + refreshComponent(); + } + + /** + * Builds a component from a given XML file. + * Call this in your code to load additional components at runtime. + */ + public function buildComponent(assetPath:String) + { + trace('Building component $assetPath'); + return RuntimeComponentBuilder.fromAsset(assetPath); + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + // Force quit. + if (FlxG.keys.justPressed.F4) + FlxG.switchState(new MainMenuState()); + + // Refresh the component. + if (FlxG.keys.justPressed.F5) + { + refreshComponent(); + } + } + + function refreshComponent() + { + /* + if (component != null) + { + remove(component); + component = null; + } + + if (component != null) + { + trace('Success!'); + add(component); + } + else + { + trace('Failed to build component $_componentKey'); + } + */ + + if (component == null) + { + component = buildComponent(_componentKey); + add(component); + trace(component); + } + else + { + var component2 = buildComponent(_componentKey); + component2.x += 100; + add(component2); + trace(component2); + remove(component); + } + } + + override function destroy() + { + if (component != null) + remove(component); + component = null; + } +} diff --git a/source/funkin/ui/haxeui/components/TabSideBar.hx b/source/funkin/ui/haxeui/components/TabSideBar.hx new file mode 100644 index 000000000..c2d04c4aa --- /dev/null +++ b/source/funkin/ui/haxeui/components/TabSideBar.hx @@ -0,0 +1,101 @@ +package funkin.ui.haxeui.components; + +import haxe.ui.Toolkit; +import haxe.ui.containers.SideBar; +import haxe.ui.core.Component; +import haxe.ui.core.Screen; +import haxe.ui.styles.elements.AnimationKeyFrame; +import haxe.ui.styles.elements.AnimationKeyFrames; +import haxe.ui.styles.elements.Directive; + +class TabSideBar extends SideBar +{ + var closeButton:Component; + + public function new() + { + super(); + } + + inline function getCloseButton() + { + if (closeButton == null) + { + closeButton = findComponent("closeSideBar", Component); + } + return closeButton; + } + + public override function hide() + { + var animation = Toolkit.styleSheet.findAnimation("sideBarRestoreContent"); + var first:AnimationKeyFrame = animation.keyFrames[0]; + var last:AnimationKeyFrame = animation.keyFrames[animation.keyFrames.length - 1]; + var rootComponent = Screen.instance.rootComponents[0]; + + first.set(new Directive("left", Value.VDimension(Dimension.PX(rootComponent.left)))); + first.set(new Directive("top", Value.VDimension(Dimension.PX(rootComponent.top)))); + first.set(new Directive("width", Value.VDimension(Dimension.PX(rootComponent.width)))); + first.set(new Directive("height", Value.VDimension(Dimension.PX(rootComponent.height)))); + + last.set(new Directive("left", Value.VDimension(Dimension.PX(0)))); + last.set(new Directive("top", Value.VDimension(Dimension.PX(0)))); + last.set(new Directive("width", Value.VDimension(Dimension.PX(Screen.instance.width)))); + last.set(new Directive("height", Value.VDimension(Dimension.PX(Screen.instance.height)))); + + for (r in Screen.instance.rootComponents) + { + if (r.classes.indexOf("sidebar") == -1) + { + r.swapClass("sideBarRestoreContent", "sideBarModifyContent"); + r.onAnimationEnd = function(_) + { + r.restorePercentSizes(); + r.onAnimationEnd = null; + rootComponent.removeClass("sideBarRestoreContent"); + } + } + } + + hideSideBar(); + } + + private override function hideSideBar() + { + var showSideBarClass = null; + var hideSideBarClass = null; + if (position == "left") + { + showSideBarClass = "showSideBarLeft"; + hideSideBarClass = "hideSideBarLeft"; + } + else if (position == "right") + { + showSideBarClass = "showSideBarRight"; + hideSideBarClass = "hideSideBarRight"; + } + else if (position == "top") + { + showSideBarClass = "showSideBarTop"; + hideSideBarClass = "hideSideBarTop"; + } + else if (position == "bottom") + { + showSideBarClass = "showSideBarBottom"; + hideSideBarClass = "hideSideBarBottom"; + } + + this.onAnimationEnd = function(_) + { + this.removeClass(hideSideBarClass); + // onHideAnimationEnd(); + } + + this.swapClass(hideSideBarClass, showSideBarClass); + + if (modal == true) + { + hideModalOverlay(); + } + } +} diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index a93578c20..f201aa839 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -20,6 +20,9 @@ class Constants public static final FREAKY_MENU_BPM = 102; + // Change this if you're making an engine. + public static final TITLE = "Friday Night Funkin'"; + #if debug public static final GIT_HASH = funkin.util.macro.GitCommit.getGitCommitHash(); @@ -35,5 +38,5 @@ class Constants #end public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/"; - public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin"; + public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin/purchase"; } diff --git a/source/module.xml b/source/module.xml new file mode 100644 index 000000000..fcedcb346 --- /dev/null +++ b/source/module.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file