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