diff --git a/source/funkin/backend/chart/ChartData.hx b/source/funkin/backend/chart/ChartData.hx index 3c0a57a7..ba1b7ae7 100644 --- a/source/funkin/backend/chart/ChartData.hx +++ b/source/funkin/backend/chart/ChartData.hx @@ -10,6 +10,7 @@ typedef ChartData = { public var stage:String; public var scrollSpeed:Float; public var noteTypes:Array; + public var ?bookmarks:Array; public var ?chartVersion:String; public var ?fromMods:Bool; @@ -53,6 +54,12 @@ typedef ChartNote = { var sLen:Float; // sustain length of the note (ms) } +typedef ChartBookmark = { + var time:Float; + var name:String; + var color:String; +} + typedef ChartEvent = { var name:String; var time:Float; diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index 8f083956..065efb6b 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -1152,6 +1152,15 @@ final class CoolUtil return result; } + /** + * Gets the luminance of the given color + * @param color Color to use + * @return Number between 0 and 1 + */ + public static function getLuminance(color:FlxColor):Float { + return (0.2126*color.redFloat + 0.7152*color.greenFloat + 0.0722*color.blueFloat); + } + /** * ! REQUIRES FULL PATH!!! * @param path diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 3163cb07..69411caa 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -23,6 +23,7 @@ import haxe.Json; #if sys import sys.FileSystem; #end +import flixel.util.FlxColor; class Charter extends UIState { public static var __song:String; @@ -45,6 +46,7 @@ class Charter extends UIState { @:noCompletion private var playbackIndex:Int = 7; @:noCompletion private var snapIndex:Int = 6; @:noCompletion private var noteIndex:Int = 5; + @:noCompletion private var songIndex:Int = 4; public var scrollBar:UIScrollBar; public var songPosInfo:UIText; @@ -331,27 +333,7 @@ class Charter extends UIState { }, { label: "Song", - childs: [ - { - label: "Go back to the start", - keybind: [HOME], - onSelect: _song_start - }, - { - label: "Go to the end", - keybind: [END], - onSelect: _song_end - }, - null, - { - label: "Mute instrumental", - onSelect: _song_muteinst - }, - { - label: "Mute voices", - onSelect: _song_mutevoices - } - ] + childs: buildSongUI() }, { label: "Note >", @@ -668,6 +650,7 @@ class Charter extends UIState { } buildNoteTypesUI(); + updateBookmarks(); refreshBPMSensitive(); // Just for now until i add event stacking -lunar @@ -1200,6 +1183,8 @@ class Charter extends UIState { createSelection(toBeCreated, false); } + updateBookmarks(); + if (addToUndo) undos.addToUndo(CCreateStrumLine(strumLineID, strL)); } @@ -1223,6 +1208,8 @@ class Charter extends UIState { strumLines.refreshStrumlineIDs(); strumLines.snapStrums(); + updateBookmarks(); + if (addToUndo) { var newStrL = Reflect.copy(strL); newStrL.notes = undoNotes; @@ -1294,6 +1281,19 @@ class Charter extends UIState { updateNoteLogic(elapsed); updateAutoSaving(elapsed); + for (bs in __bookmarkObjects) + { + var bars:Array = bs[0]; + var text:UIText = bs[1]; + if (bars == null || bars.length == 0) continue; + for (i => spr in bars) { + if (spr == null || strumLines.members[i] == null) continue; + spr.x = strumLines.members[i].x; + } + if (text != null) + text.x = strumLines.members[0].x + 4; + } + if (FlxG.sound.music.playing || __firstFrame) { gridBackdrops.conductorSprY = curStepFloat * 40; } else { @@ -1442,6 +1442,7 @@ class Charter extends UIState { playBackSlider.x = width - 160 - 26 - 20; updateDisplaySprites(); + updateBookmarks(); charterBG.screenCenter(); } @@ -1675,6 +1676,9 @@ class Charter extends UIState { case CEditNoteTypes(oldArray, newArray): noteTypes = oldArray; changeNoteType(null, false); + case CEditBookmarks(oldArray, newArray): + PlayState.SONG.bookmarks = oldArray; + updateBookmarks(); case CEditSpecNotesType(notes, oldTypes, newTypes): for(i=>note in notes) note.updatePos(note.step, note.id, note.susLength, oldTypes[i]); case CChangeBundle(changes): @@ -1731,6 +1735,9 @@ class Charter extends UIState { case CEditNoteTypes(oldArray, newArray): noteTypes = newArray; changeNoteType(null, false); + case CEditBookmarks(oldArray, newArray): + PlayState.SONG.bookmarks = newArray; + updateBookmarks(); case CEditSpecNotesType(notes, oldTypes, newTypes): for(i=>note in notes) note.updatePos(note.step, note.id, note.susLength, newTypes[i]); case CChangeBundle(changes): @@ -1824,6 +1831,171 @@ class Charter extends UIState { if (FlxG.sound.music.playing) return; Conductor.songPosition = FlxG.sound.music.length; } + + public function getBookmarkList():Array { + var bookmarks:Array = []; + try { + if (PlayState.SONG.bookmarks != null) + bookmarks = PlayState.SONG.bookmarks; + } catch (e) {} + + return bookmarks; + } + + function _bookmarks_add(_) { + var addBookmarkAt = function(name:String, color:FlxColor, daStep:Float) + { + var currentBookmarks:Array = getBookmarkList(); + var newBookmarks:Array = getBookmarkList(); + newBookmarks.push({time: daStep, name: name, color: color.toWebString()}); + + PlayState.SONG.bookmarks = newBookmarks; + updateBookmarks(); + undos.addToUndo(CEditBookmarks(currentBookmarks, newBookmarks)); + } + + if (FlxG.keys.pressed.SHIFT) + addBookmarkAt("New Bookmark", 0xFF911DD9, curStepFloat); + else { + FlxG.state.openSubState(new CharterBookmarkCreation(curStepFloat, (success, n, c, s) -> { + if (success) + addBookmarkAt(n, c, s); + })); + } + } + function _bookmarks_edit_list(_) + FlxG.state.openSubState(new CharterBookmarkList()); //idk why its FlxG.state but it looks so off lmfao + + public var __bookmarkObjects:Array = []; + public var __scrollbarBookmarks:Array = []; + public function updateBookmarks() + { + for (bs in __bookmarkObjects) + { + var bars:Array = bs[0]; + var text:UIText = bs[1]; + + if (bars != null) { + for (spr in bars) { + if (spr == null) continue; + remove(spr); + spr.kill(); + } + } + if (text != null) { + remove(text); + text.kill(); + } + } + for (bar in __scrollbarBookmarks) { + if (bar == null) continue; + uiGroup.remove(bar); + bar.kill(); + } + __bookmarkObjects.clear(); + __scrollbarBookmarks.clear(); + for (b in getBookmarkList()) + { + var bookmarkcolor:FlxColor = b.color != null ? FlxColor.fromString(b.color) : 0xff9d00ff; + var luminance = CoolUtil.getLuminance(bookmarkcolor); + + var sprites = []; + for (str in strumLines.members) + { + var bookmarkspr = new FlxSprite(str.x, (b.time * 40)).makeSolid(str.keyCount * 40, 4, bookmarkcolor); + bookmarkspr.updateHitbox(); + bookmarkspr.camera = charterCamera; + add(bookmarkspr); + sprites.push(bookmarkspr); + } + + var bookmarkText = new UIText(strumLines.members[0].x + 4, 0, 400, b.name, 15, bookmarkcolor, true); + bookmarkText.y = sprites[0].y - (bookmarkText.height + 2); + bookmarkText.camera = charterCamera; + add(bookmarkText); + + if (luminance < 0.5) + bookmarkText.borderColor = 0x88FFFFFF; + + __bookmarkObjects.push([sprites, bookmarkText]); + + var yPos = scrollBar.y + CoolUtil.bound( + FlxMath.remapToRange( + b.time, + 0, + scrollBar.length + scrollBar.size, + 0, + scrollBar.height + ), + 0, + scrollBar.height + ); + + var bookmarkspr = new FlxSprite(scrollBar.x - 10, yPos).makeSolid(40, 4, bookmarkcolor); + uiGroup.add(bookmarkspr); + sprites.push(bookmarkspr); + __scrollbarBookmarks.push(bookmarkspr); + } + + buildSongUI(); + } + + function buildSongUI():Array { + var songTopButton:UITopMenuButton = topMenuSpr == null ? null : cast topMenuSpr.members[songIndex]; + var newChilds:Array = [ + { + label: "Go back to the start", + keybind: [HOME], + onSelect: _song_start + }, + { + label: "Go to the end", + keybind: [END], + onSelect: _song_end + }, + null, + { + label: "Add Bookmark Here", + onSelect: _bookmarks_add + }, + { + label: "Edit Bookmark List", + color: 0xFF959829, icon: 4, + onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2; button.icon.offset.y = -1;}, + onSelect: _bookmarks_edit_list + }, + null + ]; + + var bookmarks:Array = getBookmarkList(); + + if (bookmarks.length > 0) + { + for (b in bookmarks) + { + newChilds.push({ + label: "Go To \"" + b.name + "\"", + onSelect: function(_) { Conductor.songPosition = Conductor.getTimeForStep(b.time); } + }); + } + newChilds.push(null); + } + + + newChilds.push({ + label: "Mute instrumental", + onSelect: _song_muteinst + }); + + newChilds.push({ + label: "Mute voices", + onSelect: _song_mutevoices + }); + + if (songTopButton != null) songTopButton.contextMenu = newChilds; + return newChilds; + } + function _view_zoomin(_) { zoom += 0.25; __camZoom = Math.pow(2, zoom); @@ -2220,6 +2392,7 @@ enum CharterChange { CEditEventGroups(events:Array); CEditChartData(oldData:{stage:String, speed:Float}, newData:{stage:String, speed:Float}); CEditNoteTypes(oldArray:Array, newArray:Array); + CEditBookmarks(oldArray:Array, newArray:Array); CEditSpecNotesType(notes:Array, oldNoteTypes:Array, newNoteTypes:Array); CChangeBundle(changes:Array); diff --git a/source/funkin/editors/charter/CharterBookmarkCreation.hx b/source/funkin/editors/charter/CharterBookmarkCreation.hx new file mode 100644 index 00000000..a0a7ab19 --- /dev/null +++ b/source/funkin/editors/charter/CharterBookmarkCreation.hx @@ -0,0 +1,65 @@ +package funkin.editors.charter; + +import flixel.util.FlxColor; +import flixel.math.FlxPoint; +import funkin.backend.chart.ChartData.ChartBookmark; + +class CharterBookmarkCreation extends UISubstateWindow { + public var saveButton:UIButton; + public var closeButton:UIButton; + public var textBox:UITextBox; + public var colorPicker:UICompactColorwheel; + + var callback:Bool->String->FlxColor->Float->Void; + + var bookmarkStep:Float = 0; + + override public function new(step:Float, close:Bool->String->FlxColor->Float->Void) + { + super(); + + winWidth = 320; + winHeight = 250; + winTitle = 'Creating New Bookmark'; + + callback = close; + + bookmarkStep = step; + } + + public override function create() { + FlxG.sound.music.pause(); + Charter.instance.vocals.pause(); + + function addLabelOn(ui:UISprite, text:String):UIText { + var text:UIText = new UIText(ui.x, ui.y - 24, 0, text); + ui.members.push(text); + return text; + } + + super.create(); + + textBox = new UITextBox(windowSpr.x + 20, windowSpr.y + 70, "New Bookmark", 276); + textBox.antialiasing = true; + add(textBox); + addLabelOn(textBox, "Bookmark Name:"); + + colorPicker = new UICompactColorwheel(textBox.x, textBox.y + textBox.bHeight + 34, flixel.util.FlxColor.RED); + add(colorPicker); + addLabelOn(colorPicker, "Bookmark Color:"); + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + callback(true, textBox.label.text, colorPicker.curColor, bookmarkStep); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() { + callback(false, "", FlxColor.TRANSPARENT, -1); + close(); + }, 125); + add(closeButton); + closeButton.color = 0xFFFF0000; + closeButton.x -= closeButton.bWidth; + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterBookmarkList.hx b/source/funkin/editors/charter/CharterBookmarkList.hx new file mode 100644 index 00000000..032177db --- /dev/null +++ b/source/funkin/editors/charter/CharterBookmarkList.hx @@ -0,0 +1,95 @@ +package funkin.editors.charter; + +import flixel.math.FlxPoint; +import funkin.backend.chart.ChartData.ChartBookmark; + +class CharterBookmarkList extends UISubstateWindow { + public var bookmarkList:UIButtonList; + public var saveButton:UIButton; + public var closeButton:UIButton; + + public override function create() { + FlxG.sound.music.pause(); + Charter.instance.vocals.pause(); + + winTitle = 'Bookmark List Editor'; + winWidth = 380; + winHeight = 390; + + super.create(); + + var title:UIText; + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Bookmarks", 28)); + + bookmarkList = new UIButtonList(20, title.y + title.height + 10, winWidth - 40, 342 - 85 - 16, null, FlxPoint.get(winWidth - 40, (342 - 85 - 16)/4), null, 0); + bookmarkList.cameraSpacing = 0; + + for (b in Charter.instance.getBookmarkList()) + bookmarkList.add(new BookmarkButton(0, 0, b, bookmarkList)); + + add(bookmarkList); + bookmarkList.frames = Paths.getFrames('editors/ui/inputbox'); + + bookmarkList.content.remove(bookmarkList.addButton); //i dont need it + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveList(); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() { + close(); + }, 125); + add(closeButton); + closeButton.color = 0xFFFF0000; + closeButton.x -= closeButton.bWidth; + } + + public function saveList() { + var oldList:Array = Charter.instance.getBookmarkList(); + var newList:Array = [for (btn in bookmarkList.buttons.members) {time: btn.bookmark.time, name: btn.textBox.label.text, color: btn.bookmark.color}]; + + PlayState.SONG.bookmarks = newList; + Charter.instance.updateBookmarks(); + Charter.undos.addToUndo(CEditBookmarks(oldList, newList)); + } +} + +class BookmarkButton extends UIButton { + public var textBox:UITextBox; + public var deleteButton:UIButton; + public var deleteIcon:FlxSprite; + public var bookmark:ChartBookmark; + + public function new(x:Float, y:Float, bookmark:ChartBookmark, parent:UIButtonList) { + super(x, y, '', null, Std.int(parent.buttonSize.x), Std.int(parent.buttonSize.y)); + this.bookmark = bookmark; + autoAlpha = false; + + members.push(textBox = new UITextBox(10, 10, bookmark.name, 276)); + textBox.antialiasing = true; + + deleteButton = new UIButton(textBox.x + textBox.label.width + 10, textBox.y, null, function () { + parent.remove(this); + }, 32); + deleteButton.color = 0xFFFF0000; + deleteButton.autoAlpha = false; + members.push(deleteButton); + + deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/delete-button')); + deleteIcon.antialiasing = false; + members.push(deleteIcon); + } + + override function update(elapsed:Float) { + textBox.y = (y + bHeight/2) - (textBox.bHeight/2); + deleteButton.x = textBox.x + textBox.bWidth + 14; deleteButton.y = textBox.y; + deleteIcon.x = deleteButton.x + (15/2); deleteIcon.y = deleteButton.y + 8; + + deleteButton.selectable = selectable; + deleteButton.shouldPress = shouldPress; + + super.update(elapsed); + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UICompactColorwheel.hx b/source/funkin/editors/ui/UICompactColorwheel.hx new file mode 100644 index 00000000..7623a1de --- /dev/null +++ b/source/funkin/editors/ui/UICompactColorwheel.hx @@ -0,0 +1,103 @@ +package funkin.editors.ui; + +import flixel.util.FlxColor; +import flixel.math.FlxPoint; +import funkin.editors.ui.UIColorwheel.UIColorWheelSelector; + +using flixel.util.FlxSpriteUtil; + +class UICompactColorwheel extends UISliceSprite { + var colorSlider:FlxSprite; + var colorSliderSelector:UIColorWheelSelector; + + var whiteColorButton:UIButton; + + public var curColor:Null; + public var curColorString:String = "#FFFFFF"; + + public var hue:Float; + + public function new (x:Float, y:Float, ?color:Int) { + super(x, y, Std.int((12.5 * 3) + 116), Std.int((12.5 * 2) + (16)), 'editors/ui/inputbox'); + + curColor = cast color; + hue = curColor.hue; + + colorSlider = new FlxSprite(x + 12.5, y + 12.5).makeGraphic(100, 16, FlxColor.TRANSPARENT); + colorSlider.drawRoundRect(0, 0, 100, 16, 10, 10, FlxColor.WHITE); + + colorSlider.pixels.lock(); + for (pixelx in 0...Std.int(colorSlider.width)) { + var color:Int = FlxColor.fromHSB(pixelx / (colorSlider.width-1) * 360, 1, 1); + for (pixely in 0...Std.int(colorSlider.height)) + if (colorSlider.pixels.getPixel32(pixelx, pixely) != FlxColor.TRANSPARENT) colorSlider.pixels.setPixel32(pixelx, pixely, color); + } + colorSlider.pixels.unlock(); + + whiteColorButton = new UIButton(colorSlider.x + colorSlider.width + 12.5, colorSlider.y, null, () -> { + setColor(FlxColor.WHITE); + }, 16, 16); + whiteColorButton.colorTransform.color = FlxColor.WHITE; + + colorSliderSelector = new UIColorWheelSelector(colorSlider.x, colorSlider.y); + + colorSlider.antialiasing = true; + + for (item in [colorSlider, colorSliderSelector, whiteColorButton]) + members.push(item); + + updateColor(false); + } + + inline function updateColorSliderPickerSelector() + colorSliderSelector.selector.setPosition(colorSlider.x + (hue / 360 * (colorSlider.width-1)) - 8, colorSlider.y); + + inline function updateColorSliderMouse(mousePos:FlxPoint) + hue = (mousePos.x / colorSlider.width) * 360; + + function setColor(newColor:FlxColor) { + this.color = curColor = colorSliderSelector.curColor = newColor; + } + + public function updateColor(checkChanged:Bool = true) { + if(checkChanged) colorChanged = true; + setColor(FlxColor.fromHSB(hue, 1, 1)); + + updateColorSliderPickerSelector(); + curColorString = curColor.toWebString(); + } + + // For Character Editor + public var colorChanged:Bool = false; + + // Make the colorwheel feel better + static inline var hitBoxExtenstion:Float = 8; + + var selectedSprite = null; + public override function update(elapsed:Float) { + var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get()); + if (hovered && FlxG.mouse.justPressed) { + for (sprite in [colorSlider]) { + var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); + if (FlxMath.inBounds(mousePos.x, spritePos.x - (hitBoxExtenstion/2), spritePos.x - (hitBoxExtenstion/2) + (sprite.width + hitBoxExtenstion)) && FlxMath.inBounds(mousePos.y, spritePos.y - (hitBoxExtenstion/2), spritePos.y - (hitBoxExtenstion/2) + (sprite.height + hitBoxExtenstion))) { + selectedSprite = sprite; + break; + } + } + } + + if (selectedSprite != null) { + var spritePos:FlxPoint = selectedSprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); + mousePos -= FlxPoint.weak(spritePos.x, spritePos.y); + mousePos.set(CoolUtil.bound(mousePos.x, 0, selectedSprite.width), CoolUtil.bound(mousePos.y, 0, selectedSprite.height)); + + if (selectedSprite == colorSlider) updateColorSliderMouse(mousePos); + updateColor(); + spritePos.put(); + + if (FlxG.mouse.justReleased) selectedSprite = null; + } + mousePos.put(); + super.update(elapsed); + } +} \ No newline at end of file