diff --git a/assets/shaders/engine/colorPicker.frag b/assets/shaders/engine/colorPicker.frag new file mode 100644 index 00000000..cbe9d535 --- /dev/null +++ b/assets/shaders/engine/colorPicker.frag @@ -0,0 +1,20 @@ +#pragma header + +vec3 hsvToRgb(vec3 hsv){ //hsv.x = hue, hsv.y = saturation, hsv.z = value + vec3 col = vec3(hsv.x, hsv.x + 2.0/3.0, hsv.x + 4.0/3.0); //inputs for r, g, and b + col = clamp(abs(mod(col*2.0, 2.0)-1.0)*3.0 - 1.0, 0.0, 1.0)*hsv.z*hsv.y + hsv.z - hsv.z*hsv.y; //hue function (graph it on desmos) + return col; +} + +uniform float hue; + +void main() +{ + vec2 uv = openfl_TextureCoordv.xy; + vec4 col = texture2D(bitmap, uv); + + if (col.a > 0.0) + col = vec4(hsvToRgb(vec3(hue, uv.x, 1.0-uv.y)), 0.0); + + gl_FragColor = col; +} \ No newline at end of file diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx index 714170f0..833dbf49 100644 --- a/source/funkin/backend/system/MainState.hx +++ b/source/funkin/backend/system/MainState.hx @@ -46,13 +46,14 @@ class MainState extends FlxState { EventsData.reloadEvents(); TitleState.initialized = false; - + /* if (betaWarningShown) FlxG.switchState(new TitleState()); else { FlxG.switchState(new BetaWarningState()); betaWarningShown = true; - } + }*/ + FlxG.switchState(new funkin.editors.charter.Charter("gamebreaker", "normal")); #if sys sys.FileSystem.createDirectory('./.temp/'); diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 236a4d74..3baf3c0e 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -187,7 +187,8 @@ class Charter extends UIState { }, null, { - label: "Edit metadata information" + label: "Edit metadata information", + onSelect: chart_edit_metadata } ] }, @@ -928,6 +929,8 @@ class Charter extends UIState { function _chart_enablescripts(t) { t.icon = (Options.charterEnablePlaytestScripts = !Options.charterEnablePlaytestScripts) ? 1 : 0; } + function chart_edit_metadata(_) + FlxG.state.openSubState(new MetaDataScreen(PlayState.SONG.meta)); function _playback_metronome(t) { t.icon = (Options.charterMetronomeEnabled = !Options.charterMetronomeEnabled) ? 1 : 0; diff --git a/source/funkin/editors/charter/MetaDataScreen.hx b/source/funkin/editors/charter/MetaDataScreen.hx new file mode 100644 index 00000000..a22510dd --- /dev/null +++ b/source/funkin/editors/charter/MetaDataScreen.hx @@ -0,0 +1,142 @@ +package funkin.editors.charter; + +import flixel.math.FlxPoint; +import funkin.backend.chart.ChartData.ChartMetaData; + +using StringTools; + +class MetaDataScreen extends UISubstateWindow { + public var metadata:ChartMetaData; + public var saveButton:UIButton; + + public var songNameTextBox:UITextBox; + public var bpmStepper:UINumericStepper; + public var stepsPerBeatStepper:UINumericStepper; + public var beatsPerMesureStepper:UINumericStepper; + public var needsVoicesCheckbox:UICheckbox; + + public var displayNameTextBox:UITextBox; + public var iconTextBox:UITextBox; + public var iconSprite:FlxSprite; + public var opponentModeCheckbox:UICheckbox; + public var coopAllowedCheckbox:UICheckbox; + public var colorWheel:UIColorwheel; + public var difficulitesTextBox:UITextBox; + + public function new(metadata:ChartMetaData) { + super(); + this.metadata = metadata; + trace(metadata); + } + + public override function create() { + winTitle = "Edit Metadata"; + winWidth = 748 - 32 + 40; + + super.create(); + FlxG.sound.music.pause(); + + function addLabelOn(ui:UISprite, text:String) + add(new UIText(ui.x, ui.y - 24, 0, text)); + + var title:UIText; + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Song Data", 28)); + + songNameTextBox = new UITextBox(title.x, title.y + title.height + 36, metadata.name); + add(songNameTextBox); + addLabelOn(songNameTextBox, "Song Name"); + + bpmStepper = new UINumericStepper(songNameTextBox.x + 320 + 26, songNameTextBox.y, metadata.bpm, 1, 2, 1, null, 90); + add(bpmStepper); + addLabelOn(bpmStepper, "BPM"); + + stepsPerBeatStepper = new UINumericStepper(bpmStepper.x + 60 + 26, bpmStepper.y, metadata.stepsPerBeat, 1, 0, 1, null, 54); + add(stepsPerBeatStepper); + addLabelOn(stepsPerBeatStepper, "Time Signature"); + + add(new UIText(stepsPerBeatStepper.x + 30, stepsPerBeatStepper.y + 3, 0, "/", 22)); + + beatsPerMesureStepper = new UINumericStepper(stepsPerBeatStepper.x + 30 + 24, stepsPerBeatStepper.y, metadata.beatsPerMesure, 1, 0, 1, null, 54); + add(beatsPerMesureStepper); + + needsVoicesCheckbox = new UICheckbox(beatsPerMesureStepper.x + 80 + 26, beatsPerMesureStepper.y, "Voices", metadata.needsVoices); + add(needsVoicesCheckbox); + addLabelOn(needsVoicesCheckbox, "Needs Voices"); + needsVoicesCheckbox.y += 6; needsVoicesCheckbox.x += 4; + + add(title = new UIText(songNameTextBox.x, songNameTextBox.y + 10 + 46, 0, "Menus Data (Freeplay/Story)", 28)); + + displayNameTextBox = new UITextBox(title.x, title.y + title.height + 36, metadata.displayName); + add(displayNameTextBox); + addLabelOn(displayNameTextBox, "Display Name"); + + iconTextBox = new UITextBox(displayNameTextBox.x + 320 + 26, displayNameTextBox.y, metadata.icon, 150); + iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);} + add(iconTextBox); + addLabelOn(iconTextBox, "Icon"); + + updateIcon(metadata.icon); + + opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, "Opponent Mode", metadata.opponentModeAllowed); + add(opponentModeCheckbox); + addLabelOn(opponentModeCheckbox, "Modes Allowed"); + + coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, "Co-op Mode", metadata.coopAllowed); + add(coopAllowedCheckbox); + + colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, metadata.parsedColor); + add(colorWheel); + addLabelOn(colorWheel, "Color"); + + difficulitesTextBox = new UITextBox(opponentModeCheckbox.x, opponentModeCheckbox.y + 6 + 32 + 26, metadata.difficulties.join(", ")); + add(difficulitesTextBox); + addLabelOn(difficulitesTextBox, "Difficulties"); + + for (checkbox in [opponentModeCheckbox, coopAllowedCheckbox]) + {checkbox.y += 6; checkbox.x += 4;} + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, colorWheel.y + 32 + 197 + 26, "Save & Close", function() { + saveMeta(); + close(); + }, 125); + saveButton.x -= saveButton.bWidth; + saveButton.y -= saveButton.bHeight; + add(saveButton); + } + + function updateIcon(icon:String) { + if (iconSprite == null) add(iconSprite = new FlxSprite()); + + if (iconSprite.animation.exists(icon)) return; + @:privateAccess iconSprite.animation.clearAnimations(); + + var path:String = Paths.image('icons/$icon'); + if (!Assets.exists(path)) path = Paths.image('icons/face'); + + iconSprite.loadGraphic(path, true, 150, 150); + iconSprite.animation.add(icon, [0], 0, false); + iconSprite.antialiasing = true; + iconSprite.animation.play(icon); + + iconSprite.scale.set(0.5, 0.5); + iconSprite.updateHitbox(); + iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2)); + } + + public function saveMeta() { + PlayState.SONG.meta = { + name: songNameTextBox.label.text, + bpm: bpmStepper.value, + stepsPerBeat: Std.int(stepsPerBeatStepper.value), + beatsPerMesure: Std.int(beatsPerMesureStepper.value), + needsVoices: needsVoicesCheckbox.checked, + displayName: displayNameTextBox.label.text, + icon: iconTextBox.label.text, + color: colorWheel.curColorString, + parsedColor: colorWheel.curColor, + opponentModeAllowed: opponentModeCheckbox.checked, + coopAllowed: coopAllowedCheckbox.checked, + difficulties: [for (diff in difficulitesTextBox.label.text.split(",")) diff.trim()], + }; + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIColorwheel.hx b/source/funkin/editors/ui/UIColorwheel.hx new file mode 100644 index 00000000..7291a1be --- /dev/null +++ b/source/funkin/editors/ui/UIColorwheel.hx @@ -0,0 +1,173 @@ +package funkin.editors.ui; + +import flixel.math.FlxPoint; +import funkin.backend.shaders.CustomShader; +import flixel.util.FlxColor; + +using flixel.util.FlxSpriteUtil; + +class UIColorwheel extends UISliceSprite { + var colorPicker:FlxSprite; + var colorPickerShader:CustomShader; + var colorPickerSelector:UIColorWheelSelector; + + var colorSlider:FlxSprite; + var colorSliderSelector:UIColorWheelSelector; + + var colorHexTextBox:UITextBox; + var rgbNumSteppers:Array = []; // r,g,b + + public var curColor:Null = FlxColor.WHITE; + public var curColorString:String = "#FFFFFF"; + + public var saturation:Float; + public var brightness:Float; + public var hue:Float; + + public function new (x:Float, y:Float, ?color:Int) { + super(x, y, 299, 125, 'editors/ui/inputbox'); + + curColor = cast color; + hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness; + + colorPickerShader = new CustomShader("engine/colorPicker"); + colorPickerShader.hset("hue", hue); + + colorPicker = new FlxSprite(x + 12.5, (y + 125/2) - (100/2)).makeGraphic(100, 100, FlxColor.TRANSPARENT); + colorPicker.drawRoundRect(0, 0, 100, 100, 10, 10, FlxColor.WHITE); + colorPicker.shader = colorPickerShader; + + colorPickerSelector = new UIColorWheelSelector(colorPicker.x + 100 - 16, colorPicker.y); + + colorSlider = new FlxSprite(colorPicker.x + 100 + 12.5, colorPicker.y).makeGraphic(16, 100, FlxColor.TRANSPARENT); + colorSlider.drawRoundRect(0, 0, 16, 100, 10, 10, FlxColor.WHITE); + + // Im too lazy to use a shader for this if its not even gonna change + colorSlider.pixels.lock(); + for (pixely in 0...Std.int(colorSlider.height)) { + var color:Int = FlxColor.fromHSB(pixely / (colorSlider.height-1) * 360, 1, 1); + for (pixelx in 0...Std.int(colorSlider.width)) + if (colorSlider.pixels.getPixel32(pixelx, pixely) != FlxColor.TRANSPARENT) colorSlider.pixels.setPixel32(pixelx, pixely, color); + } + colorSlider.pixels.unlock(); + + colorSliderSelector = new UIColorWheelSelector(colorSlider.x, colorSlider.y); + + colorHexTextBox = new UITextBox(colorSlider.x + 16 + 12.5, colorSlider.y + 16, "", 100, 28); + colorHexTextBox.onChange = (colorString:String) -> { + curColor = FlxColor.fromString(colorString); + if (curColor == null) curColor = FlxColor.WHITE; + + hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness; + updateWheel(); + } + + var hexLabel:UIText = new UIText(colorHexTextBox.x - 2, colorHexTextBox.y - 18, 0, "Hex (#FFFFFF)", 13); + + for (i in 0...3) { + var numStepper:UINumericStepper = new UINumericStepper(colorSlider.x + 18 + 12.5 + (i * 44), colorHexTextBox.y + 28 + 6 + 13 + 6 + .5, 0, 1, 0, 0, 255, 40 + 28, 28); + numStepper.antialiasing = true; numStepper.ID = i; + numStepper.onChange = (text:String) -> { + @:privateAccess numStepper.__onChange(text); + switch (numStepper.ID) { + default: curColor.red = Std.int(numStepper.value); + case 1: curColor.green = Std.int(numStepper.value); + case 2: curColor.blue = Std.int(numStepper.value); + } + hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness; + updateWheel(); + }; + members.push(numStepper); rgbNumSteppers.push(numStepper); + } + + var rgbLabel:UIText = new UIText(rgbNumSteppers[0].x - 2, rgbNumSteppers[0].y - 18, 0, "RGB (255,255,255)", 13); + + colorPicker.antialiasing = colorSlider.antialiasing = colorHexTextBox.antialiasing = true; + + for (item in [colorPicker, colorPickerSelector, colorSlider, colorSliderSelector, colorHexTextBox, hexLabel, rgbLabel]) + members.push(item); + + updateWheel(); + } + + inline function updateColorPickerSelector() + colorPickerSelector.selector.setPosition(colorPicker.x + (colorPicker.width *saturation) - 8, colorPicker.y + (colorPicker.height + (colorPicker.height * -brightness))- 8); + + inline function updateColorPickerMouse(mousePos:FlxPoint) { + saturation = mousePos.x/colorPicker.width; brightness = 1 + -(mousePos.y/colorPicker.height); + } + + inline function updateColorSliderPickerSelector() + colorSliderSelector.selector.setPosition(colorSlider.x, colorSlider.y + (hue / 360 * (colorSlider.height-1)) - 8); + + inline function updateColorSliderMouse(mousePos:FlxPoint) + hue = (mousePos.y / colorSlider.height) * 360; + + public function updateWheel() { + colorPickerShader.hset("hue", hue / 360); + colorPickerSelector.curColor = color = curColor = FlxColor.fromHSB(hue, saturation, brightness); colorSliderSelector.curColor = FlxColor.fromHSB(hue, 1, 1); + + updateColorPickerSelector(); updateColorSliderPickerSelector(); + colorHexTextBox.label.text = curColorString = curColor.toHexString(false).replace("0x", "#"); + for (numStepper in rgbNumSteppers) { + numStepper.label.text = Std.string(switch (numStepper.ID) { + default: curColor.red; + case 1: curColor.green; + case 2: curColor.blue; + }); + } + } + + // Make the colorwhell feel better + static inline var hitBoxExtenstion:Float = 8; + + public override function update(elapsed:Float) { + if (hovered && FlxG.mouse.pressed) { + var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get()); + + for (sprite in [colorPicker, colorSlider]) { + var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); + + if (((mousePos.x > (spritePos.x - (hitBoxExtenstion/2))) && (mousePos.x < spritePos.x - (hitBoxExtenstion/2) + (sprite.width + hitBoxExtenstion))) && ((mousePos.y > (spritePos.y - (hitBoxExtenstion/2))) && (mousePos.y < spritePos.y - (hitBoxExtenstion/2) + (sprite.height + hitBoxExtenstion)))) { + mousePos -= FlxPoint.weak(spritePos.x, spritePos.y); + mousePos.set(FlxMath.bound(mousePos.x, 0, sprite.width), FlxMath.bound(mousePos.y, 0, sprite.height)); + + if (sprite == colorSlider) updateColorSliderMouse(mousePos); + if (sprite == colorPicker) updateColorPickerMouse(mousePos); + updateWheel(); + + spritePos.put(); + break; + } + } + mousePos.put(); + } + + super.update(elapsed); + } +} + +class UIColorWheelSelector extends FlxTypedGroup { + public var curColor:FlxColor = FlxColor.WHITE; + + public var selector:FlxSprite; + public var colorCircle:FlxSprite; + + public function new(x:Float, y:Float, ?color:Null) { + super(); + if (color != null) curColor = cast color; + + add(selector = new FlxSprite(x, y).loadGraphic(Paths.image("editors/ui/slider"))); + add(colorCircle = new FlxSprite(x + 2, y + 2).makeGraphic(12,12, FlxColor.TRANSPARENT).drawCircle()); + + selector.antialiasing = colorCircle.antialiasing = true; + colorCircle.colorTransform.color = curColor; + } + + public override function update(elapsed:Float) { + super.update(elapsed); + + colorCircle.setPosition(selector.x + 2, selector.y + 2); + colorCircle.colorTransform.color = curColor; + } +} \ No newline at end of file