1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-01-24 21:47:20 +00:00

Work in progress on HaxeUI chart editor

This commit is contained in:
Eric Myllyoja 2022-09-07 19:07:08 -04:00
parent f790cd8fd6
commit eb3ad49a30
21 changed files with 740 additions and 167 deletions

View file

@ -123,63 +123,16 @@
<haxelib name="flixel-addons" />
<haxelib name="hscript" />
<!--In case you want to use the ui package-->
<haxelib name="flixel-ui" />
<!--haxelib name="newgrounds" unless="switch"/> -->
<haxelib name="faxe" if='switch' />
<haxelib name="haxeui-core"/>
<haxelib name="haxeui-flixel"/>
<haxelib name="polymod" />
<haxelib name="flxanimate" />
<haxelib name="thx.semver" />
<!-- <haxelib name="colyseus"/> -->
<!-- <haxelib name="colyseus-websocket" /> -->
<!-- <haxelib name="newgrounds"/> -->
<haxelib name="hxcpp-debug-server" if="desktop debug" />
<!-- swf stufffff -->
<!-- <haxelib name="swf"/> -->
<!-- <library path="assets/tanky.swf" preload="true"/> -->
<!-- <library path="assets/tankBG.swf" preload="true"/> -->
<!-- <haxelib name="flixel-animate" /> -->
<!-- <haxelib name="spinehaxe" /> -->
<!-- https://github.com/ninjamuffin99/Flixel-Animate-Atlas-Player -->
<!--<haxelib name="discord_rpc" if="cpp"/> -->
<!-- foesn't work with neko -->
<!-- <haxelib name="hxcpp-debug-server" if="desktop"/> -->
<!-- <haxelib name="markdown" /> -->
<!-- <haxelib name="HtmlParser" /> -->
<!--In case you want to use nape with flixel-->
<!--<haxelib name="nape-haxe4" />-->
<!-- ______________________________ Haxedefines _____________________________ -->
<!--Enable the Flixel core recording system-->
<!--<haxedef name="FLX_RECORD" />-->
<!--Disable the right and middle mouse buttons-->
<!-- <haxedef name="FLX_NO_MOUSE_ADVANCED" /> -->
<!--Disable the native cursor API on Flash-->
<!--<haxedef name="FLX_NO_NATIVE_CURSOR" />-->
<!--Optimise inputs, be careful you will get null errors if you don't use conditionals in your game-->
<!-- <haxedef name="FLX_NO_MOUSE" if="mobile" /> -->
<!-- <haxedef name="FLX_NO_KEYBOARD" if="mobile" /> -->
<!-- <haxedef name="FLX_NO_TOUCH" if="desktop" /> -->
<!--<haxedef name="FLX_NO_GAMEPAD" />-->
<!--Disable the Flixel core sound tray-->
<!--<haxedef name="FLX_NO_SOUND_TRAY" />-->
<!--Disable the Flixel sound management code-->
<!--<haxedef name="FLX_NO_SOUND_SYSTEM" />-->
<!--Disable the Flixel core focus lost screen-->
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
@ -198,6 +151,11 @@
<haxeflag name="-dce no" />
<haxeflag name="--macro" value="include('funkin')" />
<!-- Ensure all UI components are available at runtime. -->
<haxeflag name="--macro" value="include('haxe.ui.components')" />
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
<!-- Necessary to provide stack traces for HScript. -->
<haxedef name="hscriptPos" />
<haxedef name="HXCPP_CHECK_POINTER" />
@ -259,8 +217,6 @@
<haxedef name="POLYMOD_SCRIPT_LIBRARY" value="scripts" />
<!-- The base path from which scripts should be accessed. -->
<haxedef name="POLYMOD_ROOT_PATH" value="scripts/" />
<!-- Determines the precision required for mods to be compatible. -->
<haxedef name="POLYMOD_API_VERSION_MATCH" value="MATCH_MINOR" />
<!-- Determines the subdirectory of the mod folder used for file appending. -->
<haxedef name="POLYMOD_APPEND_FOLDER" value="_append" />
<!-- Determines the subdirectory of the mod folder used for file merges. -->

View file

@ -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",

View file

@ -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<FlxState> = InitState; // The FlxState the game starts with.
var initialState:Class<FlxState> = 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
}
}

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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));

View file

@ -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
}
}
}

View file

@ -223,6 +223,11 @@ class CharacterDataParser
}
}
public static function listCharacterIds():Array<String>
{
return [for (x in characterCache.keys()) x];
}
static function clearCharacterCache():Void
{
if (characterCache != null)

View file

@ -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)';
}
}

View file

@ -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<String, Stage> = new Map<String, Stage>();
static final songCache:Map<String, Song> = new Map<String, Song>();
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<Song>
public static function fetchSong(songId:String):Null<Song>
{
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<SongMetadata>
{
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<SongTimeChange>;
};
typedef SongChartData =
{
};
enum abstract SongTimeFormat(String) from String to String
{
var TICKS = "ticks";
var FLOAT = "float";
var MILLISECONDS = "ms";
}

View file

@ -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

View file

@ -141,6 +141,11 @@ class StageDataParser
return validateStageData(stageId, stageData);
}
public static function listStageIds():Array<String>
{
return [for (x in stageCache.keys()) x];
}
static function loadStageFile(stagePath:String):String
{
var stageFilePath:String = Paths.json('stages/${stagePath}');

View file

@ -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()
{

View file

@ -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();
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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";
}

22
source/module.xml Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<module>
<!-- A module provides additional behavior and configuration for HaxeUI. -->
<components>
<!--
Ensure all components get included at compilation time.
This needs to be done HERE and not via the `include` macro because `Toolkit.init()`
reads this to build the component registry.
-->
<class package="haxe.ui.core" loadAll="true" />
<class package="haxe.ui.components" loadAll="true" />
<class package="haxe.ui.containers" loadAll="true" />
<class package="haxe.ui.containers.menus" loadAll="true" />
<class package="haxe.ui.containers.dialogs" loadAll="true" />
<class package="haxe.ui.containers.properties" loadAll="true" />
<!-- Custom components. -->
<class package="funkin.ui.haxeui.components" loadAll="true" />
</components>
</module>