From 07c91ee116172662041d5135795f3bd60e473d66 Mon Sep 17 00:00:00 2001 From: NeeEoo Date: Tue, 16 Apr 2024 16:22:37 +0200 Subject: [PATCH] Added character editing --- .../layouts/stage/characterEditScreen.xml | 111 +++++++++ .../layouts/stage/spriteEditScreen.xml | 75 +++--- source/funkin/editors/stage/StageEditor.hx | 46 ++-- .../editors/stage/StageSpriteEditScreen.hx | 49 ---- .../stage/elements/StageCharacterButton.hx | 57 ++++- .../stage/elements/StageElementButton.hx | 2 +- .../stage/elements/StageSpriteButton.hx | 62 ++++- source/funkin/editors/ui/UISoftcodedWindow.hx | 223 ++++++++++-------- source/funkin/game/Stage.hx | 53 +++++ 9 files changed, 449 insertions(+), 229 deletions(-) create mode 100644 assets/editors/layouts/stage/characterEditScreen.xml delete mode 100644 source/funkin/editors/stage/StageSpriteEditScreen.hx diff --git a/assets/editors/layouts/stage/characterEditScreen.xml b/assets/editors/layouts/stage/characterEditScreen.xml new file mode 100644 index 00000000..654db6ea --- /dev/null +++ b/assets/editors/layouts/stage/characterEditScreen.xml @@ -0,0 +1,111 @@ + + + + + + + function col(c:Int) { + return 20 + (150 + XOFFSET) * c; + } + previewSprite = null; + if(char.animateAtlas == null) { + previewSprite = new FunkinSprite(0, 0); + previewSprite.frame = char.frame; + + self.add(previewSprite); + } + + + + + <section> + <set name="curY" value="title.y + TO" /> + + <stepper name="xStepper" label="X" value="charPos.x" x="col(0)" y="curY" step="1" width="100" /> + <stepper name="yStepper" label="Y" value="charPos.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" step="1" width="100" /> + + <stepper name="scrollXStepper" label="Scroll X" value="charPos.scrollFactor.x" x="col(1)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scrollYStepper" label="Scroll Y" value="charPos.scrollFactor.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + + <stepper name="scaleXStepper" label="Scale X" value="charPos.scale.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scaleYStepper" label="Scale Y" value="charPos.scale.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + </section> + + <section> + <set name="curY" value="xStepper.y + xStepper.bHeight + YOFFSET" /> + + <stepper name="camxStepper" label="Cam X" value="charPos.camxoffset" x="col(0)" y="curY" step="1" width="100" /> + <stepper name="camyStepper" label="Cam Y" value="charPos.camyoffset" x="last.x + last.bWidth + XOFFSET - 20" y="curY" step="1" width="100" /> + + <stepper name="skewXStepper" label="Skew X" value="charPos.skewX" x="col(1)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="skewYStepper" label="Skew Y" value="charPos.skewY" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + + <label name="spacingLabel" text="Spacing (X, Y)" x="col(2)" y="curY" size="15" /> + <stepper name="spacingXStepper" value="charPos.charSpacingX" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="spacingYStepper" value="charPos.charSpacingY" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + </section> + + <section> + <set name="curY" value="camxStepper.y + camxStepper.bHeight + YOFFSET" /> + + <stepper name="alphaStepper" label="Alpha" value="charPos.alpha" x="col(0)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="zoomFactorStepper" label="Zoom Factor" value="charPos.zoomFactor" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + </section> + + <section> + <set name="curY" value="alphaStepper.y + alphaStepper.bHeight + YOFFSET - 20" /> + + <checkbox name="flipXCheckbox" text="Flip X" value="charPos.flipX" x="col(0)" y="curY" /> + + <radio for="memoryCheck" name="highMemoryRadio" text="High Memory Only" value="getEx('highMemory')" x="last.field.x + last.field.width + XOFFSET * 2" y="curY" /> + <radio for="memoryCheck" name="lowMemoryRadio" text="Low Memory Only" value="getEx('lowMemory')" x="last.field.x + last.field.width + XOFFSET * 2" y="curY" /> + </section> + + <section if="previewSprite != null"> + <title name="previewTitle" x="20" y="self.winHeight - 220" text="Preview Transform:" size="15" /> + </section> + <section if="previewSprite == null"> + <exec> + self.windowSpr.bHeight -= 170; + </exec> + </section> + + <exec> + function onUpdate() { + if(previewSprite == null) return; + previewSprite.skew.x = skewXStepper.value; + previewSprite.skew.y = skewYStepper.value; + previewSprite.antialiasing = char.antialiasing; + var ratio = char.frameWidth / char.frameHeight; + previewSprite.setGraphicSize(145 * scaleXStepper.value * ratio, 145 * scaleYStepper.value); + previewSprite.updateHitbox(); + if(flipXCheckbox.checked != (charPos.name == "boyfriend")) + previewSprite.scale.x *= -1; + + previewSprite.x = 30; + previewSprite.y = self.winHeight - previewSprite.height - 30; + } + + function onSave() { + setEx("highMemory", highMemoryRadio.checked); + setEx("lowMemory", lowMemoryRadio.checked); + + charPos.x = xStepper.value; + charPos.y = yStepper.value; + charPos.scrollFactor.set(scrollXStepper.value, scrollYStepper.value); + charPos.scale.set(scaleXStepper.value, scaleYStepper.value); + charPos.camxoffset = camxStepper.value; + charPos.camyoffset = camyStepper.value; + charPos.skewX = skewXStepper.value; + charPos.skewY = skewYStepper.value; + charPos.alpha = alphaStepper.value; + charPos.charSpacingX = spacingXStepper.value; + charPos.charSpacingY = spacingYStepper.value; + charPos.flipX = flipXCheckbox.checked; + charPos.zoomFactor = zoomFactorStepper.value; + + charPos.revertCharacter(char); + charPos.prepareCharacter(char, charPos._id); + } + </exec> +</window> \ No newline at end of file diff --git a/assets/editors/layouts/stage/spriteEditScreen.xml b/assets/editors/layouts/stage/spriteEditScreen.xml index b5bcfd58..534b8598 100644 --- a/assets/editors/layouts/stage/spriteEditScreen.xml +++ b/assets/editors/layouts/stage/spriteEditScreen.xml @@ -1,65 +1,74 @@ -<window width="1056" height="520" title="Editing Sprite"> +<window width="1056" height="520" title="Editing Sprite ${button.getName()}"> <set name="XOFFSET" value="50" /> <set name="YOFFSET" value="40" /> <set name="TO" value="64" /> <exec> - previewSprite = new FunkinSprite(50, self.winHeight - 100); - previewSprite.frame = sprite.frame; + function col(c:Int) { + return 20 + (200 + XOFFSET) * c; + } - self.add(previewSprite); + if(sprite.animateAtlas == null) { + previewSprite = new FunkinSprite(50, self.winHeight - 100); + previewSprite.frame = sprite.frame; + + self.add(previewSprite); + } </exec> <title name="title" x="20" y="30 + 16" text="Sprite Data" /> <set name="curY" value="title.y + TO" /> <!-- ROW 1 --> - <textbox name="nameTextBox" label="Name Identifier" text="${data.getName()}" x="20" y="curY" width="200" height="32" /> - <textbox name="spriteTextBox" label="Image File (${stage.spritesParentFolder})" text="${getEx('imageFile')}" x="last.x + last.bWidth + XOFFSET" y="curY" width="200" height="32" /> + <textbox name="nameTextBox" label="Name Identifier" value="button.getName()" x="col(0)" y="curY" width="200" height="32" /> + <textbox name="spriteTextBox" label="Image File (${stage.spritesParentFolder})" value="getEx('imageFile')" x="last.x + last.bWidth + XOFFSET" y="curY" width="200" height="32" /> <set name="curY" value="nameTextBox.y + nameTextBox.bHeight + YOFFSET" /> <!-- ROW 2 --> - <stepper name="xStepper" label="X" value="sprite.x" x="20" y="curY" step="1" width="100" /> + <stepper name="xStepper" label="X" value="sprite.x" x="col(0)" y="curY" step="1" width="100" /> <stepper name="yStepper" label="Y" value="sprite.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" step="1" width="100" /> <stepper name="scrollXStepper" label="Scroll X" value="sprite.scrollFactor.x" x="spriteTextBox.x" y="curY" precision="2" step="0.01" width="100" /> <stepper name="scrollYStepper" label="Scroll Y" value="sprite.scrollFactor.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> - <stepper name="scaleXStepper" label="Scale X" value="sprite.scale.x" x="20 + (200 + XOFFSET) * 2" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="scaleXStepper" label="Scale X" value="sprite.scale.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> <stepper name="scaleYStepper" label="Scale Y" value="sprite.scale.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> <set name="curY" value="xStepper.y + xStepper.bHeight + YOFFSET" /> <!-- ROW 3 --> - <stepper name="angleStepper" label="Angle" value="sprite.angle" x="20 + (200 + XOFFSET) * 0" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="angleStepper" label="Angle" value="sprite.angle" x="col(0)" y="curY" precision="2" step="0.01" width="100" /> <stepper name="zoomFactorStepper" label="Zoom Factor" value="sprite.zoomFactor" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> - <stepper name="skewXStepper" label="Skew X" value="sprite.skew.x" x="20 + (200 + XOFFSET) * 2" y="curY" precision="2" step="0.01" width="100" /> - <stepper name="skewYStepper" label="Skew Y" value="sprite.skew.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> - <!-- TODO: Simplify this --> - <dropdown name="animType" label="Animation Type" value="sprite.spriteAnimType" x="20 + (200 + XOFFSET) * 1" y="curY" width="150" height="32"> + <dropdown name="animType" label="Animation Type" value="sprite.spriteAnimType" x="col(1)" y="curY" width="150" height="32"> <item label="NONE" value="0" /> <item label="BEAT" value="1" /> <item label="LOOP" value="2" /> </dropdown> + <stepper name="skewXStepper" label="Skew X" value="sprite.skew.x" x="col(2)" y="curY" precision="2" step="0.01" width="100" /> + <stepper name="skewYStepper" label="Skew Y" value="sprite.skew.y" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" /> + <!-- ROW 4 --> <set name="curY" value="angleStepper.y + angleStepper.bHeight + YOFFSET" /> - <checkbox name="antialiasingCheckbox" text="Antialiasing" value="sprite.antialiasing" x="20 + (200 + XOFFSET) * 0" y="curY" /> - <radio for="memoryCheck" name="highMemoryRadio" text="High Memory Only" value="getEx('highMemory')" x="20 + (200 + XOFFSET) * 1" y="curY" /> - <radio for="memoryCheck" name="lowMemoryRadio" text="Low Memory Only" value="getEx('lowMemory')" x="20 + (200 + XOFFSET) * 2" y="curY" /> + <checkbox name="antialiasingCheckbox" text="Antialiasing" value="sprite.antialiasing" x="col(0)" y="curY" /> + <radio for="memoryCheck" name="highMemoryRadio" text="High Memory Only" value="getEx('highMemory')" x="col(1)" y="curY" /> + <radio for="memoryCheck" name="lowMemoryRadio" text="Low Memory Only" value="getEx('lowMemory')" x="col(2)" y="curY" /> <!-- TODO: Animation list --> <!-- Preview Transform --> - <title name="previewTitle" x="20" y="self.winHeight - 150" text="Preview Transform:" size="14" /> + <section if="previewSprite != null"> + <title name="previewTitle" x="20" y="self.winHeight - 150" text="Preview Transform:" size="15" /> + </section> <exec> function onUpdate() { - previewSprite.color = 0xFFFFFFFF; + if(previewSprite == null) return; + previewSprite.color = 0xFFFFFFFF; // TODO: Color Wheel previewSprite.skew.x = skewXStepper.value; previewSprite.skew.y = skewYStepper.value; previewSprite.angle = angleStepper.value; @@ -85,34 +94,34 @@ sprite.angle = angleStepper.value; function saveXY(obj:Dynamic, name:String, x:UINumericStepper, y:UINumericStepper) { - data.xml.remove(name + ""); - data.xml.remove(name + "x"); - data.xml.remove(name + "y"); + button.xml.remove(name + ""); + button.xml.remove(name + "x"); + button.xml.remove(name + "y"); obj.x = x.value; obj.y = y.value; if(x.value == y.value) - data.xml.set(name, x.value); + button.xml.set(name, x.value); else { - data.xml.set(name + "x", x.value); - data.xml.set(name + "y", y.value); + button.xml.set(name + "x", x.value); + button.xml.set(name + "y", y.value); } } - data.xml.set("name", nameTextBox.label.text); - data.xml.set("sprite", spriteTextBox.label.text); - data.xml.set("x", xStepper.value); - data.xml.set("y", yStepper.value); - data.xml.set("zoom", zoomFactorStepper.value); - data.xml.set("type", animType.key); - data.xml.set("antialiasing", antialiasingCheckbox.checked); - data.xml.set("angle", angleStepper.value); + button.xml.set("name", nameTextBox.label.text); + button.xml.set("sprite", spriteTextBox.label.text); + button.xml.set("x", xStepper.value); + button.xml.set("y", yStepper.value); + button.xml.set("zoom", zoomFactorStepper.value); + button.xml.set("type", animType.key); + button.xml.set("antialiasing", antialiasingCheckbox.checked); + button.xml.set("angle", angleStepper.value); saveXY(sprite.scrollFactor, "scroll", scrollXStepper, scrollYStepper); saveXY(sprite.scale, "scale", scaleXStepper, scaleYStepper); saveXY(sprite.skew, "skew", skewXStepper, skewYStepper); - XMLUtil.loadSpriteFromXML(sprite, data.xml, stage.spritesParentFolder); + XMLUtil.loadSpriteFromXML(sprite, button.xml, stage.spritesParentFolder); } </exec> </window> \ No newline at end of file diff --git a/source/funkin/editors/stage/StageEditor.hx b/source/funkin/editors/stage/StageEditor.hx index 51896319..e094cf4f 100644 --- a/source/funkin/editors/stage/StageEditor.hx +++ b/source/funkin/editors/stage/StageEditor.hx @@ -1,6 +1,5 @@ package funkin.editors.stage; -import lime.ui.MouseCursor; import flixel.math.FlxAngle; import flixel.math.FlxPoint; import flixel.math.FlxRect; @@ -9,11 +8,13 @@ import flixel.util.FlxSort; import funkin.backend.system.framerate.Framerate; import funkin.backend.utils.XMLUtil.AnimData; import funkin.editors.stage.elements.*; +import funkin.editors.stage.elements.StageSpriteButton.StageSpriteEditScreen; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; import funkin.game.Character; import funkin.game.Stage; import haxe.xml.Access; import haxe.xml.Printer; +import lime.ui.MouseCursor; import openfl.ui.Mouse; using funkin.backend.utils.MatrixUtil; @@ -78,11 +79,6 @@ class StageEditor extends UIState { { label: "File", childs: [ - { - label: "New", - onSelect: _file_new, - }, - null, { label: "Save", keybind: [CONTROL, S], @@ -215,6 +211,7 @@ class StageEditor extends UIState { } } stage.onNodeLoaded = function(node:Access, sprite:Dynamic):Dynamic { + var parent = new Access(node.x.parent); var name = ""; if(sprite is FlxSprite) { //sprite.forceIsOnScreen = true; // hack @@ -225,7 +222,6 @@ class StageEditor extends UIState { sprite.extra.set(exID("node"), node); sprite.extra.set(exID("type"), node.name); sprite.extra.set(exID("imageFile"), '${node.getAtt("sprite").getDefault(sprite.name)}'); - var parent = new Access(node.x.parent); sprite.extra.set(exID("parentNode"), parent); sprite.extra.set(exID("highMemory"), parent.name == "highMemory"); sprite.extra.set(exID("lowMemory"), parent.name == "lowMemory"); @@ -254,10 +250,17 @@ class StageEditor extends UIState { // Add it to the stage char.visible = true; char.alpha = 0.5; - char.extra.set(exID("node"), node); - char.extra.set(exID("pos"), charPos); - charPos.extra.set(exID("node"), node); - charPos.extra.set(exID("char"), char); + function setEx(name:String, value:Dynamic) { + char.extra.set(exID(name), value); + charPos.extra.set(exID(name), value); + } + setEx("node", node); + setEx("pos", charPos); + setEx("char", char); + + setEx("parentNode", parent); + setEx("highMemory", parent.name == "highMemory"); + setEx("lowMemory", parent.name == "lowMemory"); chars.push(char); } order.push(sprite); @@ -307,12 +310,12 @@ class StageEditor extends UIState { sprite.extra.set(exID("node"), node); sprite.extra.set(exID("type"), node.name); sprite.extra.set(exID("imageFile"), ''); - sprite.extra.set(exID("parentNode"), stage.stageXML.x); + sprite.extra.set(exID("parentNode"), new Access(node.x.parent)); sprite.extra.set(exID("highMemory"), false); sprite.extra.set(exID("lowMemory"), false); xmlMap.set(sprite, node); - var button:StageElementButton = new StageSpriteButton(0, 0, sprite, node); + var button:StageSpriteButton = new StageSpriteButton(0, 0, sprite, node); sprite.extra.set(exID("button"), button); stageSpritesWindow.add(button); @@ -477,9 +480,6 @@ class StageEditor extends UIState { else FlxG.switchState(new StageSelection()); } - function _file_new(_) { - } - function _file_save(_) { #if sys CoolUtil.safeSaveFile( @@ -506,6 +506,7 @@ class StageEditor extends UIState { } function savePointToXml(xml:Xml, name:String, point:FlxPoint, ?defaultValueX:Float, ?defaultValueY:Float) { if (point == null) return xml; + if(defaultValueY == null) defaultValueY = defaultValueX; if(point.x == point.y) { saveToXml(xml, name, point.x, defaultValueX); } else { @@ -545,8 +546,8 @@ class StageEditor extends UIState { saveToXml(spriteXML, "x", sprite.x, 0); saveToXml(spriteXML, "y", sprite.y, 0); saveToXml(spriteXML, "sprite", sprite.extra.get(exID("imageFile"))); - savePointToXml(spriteXML, "scale", sprite.scale, 1, 1); - savePointToXml(spriteXML, "scroll", sprite.scrollFactor, 0, 0); + savePointToXml(spriteXML, "scale", sprite.scale, 1); + savePointToXml(spriteXML, "scroll", sprite.scrollFactor, 0); saveToXml(spriteXML, "skewx", sprite.skew.x, 0); saveToXml(spriteXML, "skewy", sprite.skew.y, 0); saveToXml(spriteXML, "alpha", sprite.alpha, 1); @@ -590,9 +591,10 @@ class StageEditor extends UIState { saveToXml(charXML, "spacingx", charPos.charSpacingX, 20); saveToXml(charXML, "spacingy", charPos.charSpacingY, 0); saveToXml(charXML, "alpha", charPos.alpha, 1); + saveToXml(charXML, "zoomfactor", charPos.zoomFactor, 1); saveToXml(charXML, "flipX", charPos.flipX, defaultPos.flip); - savePointToXml(charXML, "scroll", charPos.scrollFactor, defaultPos.scroll, defaultPos.scroll); - savePointToXml(charXML, "scale", charPos.scale, 1, 1); + savePointToXml(charXML, "scroll", charPos.scrollFactor, defaultPos.scroll); + savePointToXml(charXML, "scale", charPos.scale, 1); xml.addChild(charXML); } else { Logs.trace("Unknown Stage Type : " + Type.getClassName(Type.getClass(button))); @@ -791,7 +793,7 @@ class StageEditor extends UIState { for(char in chars) { var button:StageCharacterButton = char.extra.get(exID("button")); button.isHidden = !showCharacters; - button.updateInfo(button.charPos); + button.updateInfo(); } } #end @@ -972,7 +974,7 @@ class StageEditor extends UIState { // todo: make this origin based relative.rotateByDegrees(sprite.angle); call(mouseMode.toString(), [sprite, relative]); - cast(sprite.extra.get(exID("button")), StageElementButton).updateInfo(sprite); + cast(sprite.extra.get(exID("button")), StageElementButton).updateInfo(); relative.put(); } diff --git a/source/funkin/editors/stage/StageSpriteEditScreen.hx b/source/funkin/editors/stage/StageSpriteEditScreen.hx deleted file mode 100644 index d1b4cf18..00000000 --- a/source/funkin/editors/stage/StageSpriteEditScreen.hx +++ /dev/null @@ -1,49 +0,0 @@ -package funkin.editors.stage; - -import funkin.editors.stage.elements.StageElementButton; -import funkin.editors.ui.UISoftcodedWindow; - -using StringTools; - - -class StageSpriteEditScreen extends UISoftcodedWindow { - public var newSprite:Bool = false; - public var data:StageElementButton; - public var sprite:FunkinSprite; - var isSaving:Bool = false; - - public function new(data:StageElementButton) { - this.data = data; - this.sprite = data.getSprite(); - super("layouts/stage/spriteEditScreen.xml", [ - "stage" => StageEditor.instance.stage, - "sprite" => sprite, - "data" => data, - "exID" => StageEditor.exID, - "getEx" => function(name:String):Dynamic { - return sprite.extra.get(StageEditor.exID(name)); - }, - "setEx" => function(name:String, value:Dynamic) { - sprite.extra.set(StageEditor.exID(name), value); - }, - ]); - } - - public override function create() { - super.create(); - } - - public override function saveData() { - isSaving = true; - super.saveData(); - data.updateInfo(sprite); - } - - public override function close() { - if (!isSaving && newSprite) { - data.onDelete(); - trace("deleting sprite"); - } - super.close(); - } -} \ No newline at end of file diff --git a/source/funkin/editors/stage/elements/StageCharacterButton.hx b/source/funkin/editors/stage/elements/StageCharacterButton.hx index 905cdc99..e28e89d3 100644 --- a/source/funkin/editors/stage/elements/StageCharacterButton.hx +++ b/source/funkin/editors/stage/elements/StageCharacterButton.hx @@ -15,7 +15,7 @@ class StageCharacterButton extends StageElementButton { color = 0xff7aa8ff; - updateInfo(charPos); + updateInfo(); } public override function update(elapsed:Float) { @@ -27,13 +27,10 @@ class StageCharacterButton extends StageElementButton { super.update(elapsed); } - public override function updateInfo(charPos:Dynamic) { - if(charPos is StageCharPos) { - var charPos:StageCharPos = cast charPos; - this.charPos = charPos; - char.visible = !isHidden; - } - super.updateInfo(charPos); + public override function updateInfo() { + char.visible = !isHidden; + char.alpha = 0.5 * charPos.alpha; + super.updateInfo(); } public override function getSprite():FunkinSprite { @@ -46,13 +43,17 @@ class StageCharacterButton extends StageElementButton { } public override function onEdit() { - UIState.state.displayNotification(new UIBaseNotification("Editing a character isnt implemented yet!", 2, BOTTOM_LEFT)); - CoolUtil.playMenuSFX(WARNING, 0.45); + FlxG.state.openSubState(new StageCharacterEditScreen(this)); } + //public override function onEdit() { + // UIState.state.displayNotification(new UIBaseNotification("Editing a character isnt implemented yet!", 2, BOTTOM_LEFT)); + // CoolUtil.playMenuSFX(WARNING, 0.45); + //} + public override function onGhostClick() { isHidden = !isHidden; - updateInfo(this.charPos); + updateInfo(); } public override function getName():String { @@ -66,4 +67,38 @@ class StageCharacterButton extends StageElementButton { public override function updatePos() { super.updatePos(); } +} + +class StageCharacterEditScreen extends UISoftcodedWindow { + public var button:StageCharacterButton; + public var char:Character; + public var charPos:StageCharPos; + + public function new(button:StageCharacterButton) { + this.button = button; + this.char = button.char; + this.charPos = button.charPos; + super("layouts/stage/characterEditScreen.xml", [ + "stage" => StageEditor.instance.stage, + "char" => char, + "charPos" => charPos, + "button" => button, + "exID" => StageEditor.exID, + "getEx" => function(name:String):Dynamic { + return char.extra.get(StageEditor.exID(name)); + }, + "setEx" => function(name:String, value:Dynamic) { + char.extra.set(StageEditor.exID(name), value); + }, + ]); + } + + public override function create() { + super.create(); + } + + public override function saveData() { + super.saveData(); + button.updateInfo(); + } } \ No newline at end of file diff --git a/source/funkin/editors/stage/elements/StageElementButton.hx b/source/funkin/editors/stage/elements/StageElementButton.hx index 49194d5d..11f3edf1 100644 --- a/source/funkin/editors/stage/elements/StageElementButton.hx +++ b/source/funkin/editors/stage/elements/StageElementButton.hx @@ -122,7 +122,7 @@ class StageElementButton extends UIButton { } } - public function updateInfo(sprite:Dynamic) { + public function updateInfo() { field.text = getInfoText(); ghostIcon.animation.play(!isHidden ? "alive" : "dead"); diff --git a/source/funkin/editors/stage/elements/StageSpriteButton.hx b/source/funkin/editors/stage/elements/StageSpriteButton.hx index 09cc061e..5c584548 100644 --- a/source/funkin/editors/stage/elements/StageSpriteButton.hx +++ b/source/funkin/editors/stage/elements/StageSpriteButton.hx @@ -1,8 +1,9 @@ package funkin.editors.stage.elements; -import haxe.xml.Access; -import funkin.editors.stage.StageSpriteEditScreen; import flixel.util.FlxColor; +import funkin.editors.stage.elements.StageElementButton; +import funkin.editors.ui.UISoftcodedWindow; +import haxe.xml.Access; class StageSpriteButton extends StageElementButton { public var sprite:FunkinSprite; @@ -13,20 +14,16 @@ class StageSpriteButton extends StageElementButton { //color = 0xFFD9FF50; - updateInfo(sprite); + updateInfo(); } public override function update(elapsed:Float) { super.update(elapsed); } - public override function updateInfo(sprite:Dynamic) { - if(sprite is FunkinSprite) { - var sprite:FunkinSprite = cast sprite; - this.sprite = sprite; - sprite.visible = !isHidden; - } - super.updateInfo(sprite); + public override function updateInfo() { + sprite.visible = !isHidden; + super.updateInfo(); } public override function getSprite():FunkinSprite { @@ -39,11 +36,10 @@ class StageSpriteButton extends StageElementButton { public override function onGhostClick() { isHidden = !isHidden; - updateInfo(this.sprite); + updateInfo(); } public override function onEdit() { - // TODO: implement FlxG.state.openSubState(new StageSpriteEditScreen(this)); } @@ -66,4 +62,46 @@ class StageSpriteButton extends StageElementButton { public override function updatePos() { super.updatePos(); } +} + +class StageSpriteEditScreen extends UISoftcodedWindow { + public var newSprite:Bool = false; + public var button:StageSpriteButton; + public var sprite:FunkinSprite; + var isSaving:Bool = false; + + public function new(button:StageSpriteButton) { + this.button = button; + this.sprite = button.getSprite(); + super("layouts/stage/spriteEditScreen.xml", [ + "stage" => StageEditor.instance.stage, + "sprite" => sprite, + "button" => button, + "exID" => StageEditor.exID, + "getEx" => function(name:String):Dynamic { + return sprite.extra.get(StageEditor.exID(name)); + }, + "setEx" => function(name:String, value:Dynamic) { + sprite.extra.set(StageEditor.exID(name), value); + }, + ]); + } + + public override function create() { + super.create(); + } + + public override function saveData() { + isSaving = true; + super.saveData(); + button.updateInfo(); + } + + public override function close() { + if (!isSaving && newSprite) { + button.onDelete(); + trace("deleting sprite"); + } + super.close(); + } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UISoftcodedWindow.hx b/source/funkin/editors/ui/UISoftcodedWindow.hx index ff751fec..5e0c71b9 100644 --- a/source/funkin/editors/ui/UISoftcodedWindow.hx +++ b/source/funkin/editors/ui/UISoftcodedWindow.hx @@ -71,7 +71,7 @@ class UISoftcodedWindow extends UISubstateWindow { return CoolUtil.getDefault(exec(code), defaultValue); } - function execAtt(el:Access, key:String, defaultValue:Dynamic):Dynamic { + function execAtt(el:Access, key:String, ?defaultValue:Dynamic):Dynamic { return execDefault(el.getAtt(key), defaultValue); } @@ -101,9 +101,6 @@ class UISoftcodedWindow extends UISubstateWindow { public override function create() { var layout = new Access(Xml.parse(Assets.getText('assets/editors/' + xmlPath)).firstElement()); - winTitle = layout.getAtt("title").getDefault(winTitle); - winWidth = Std.parseInt(layout.getAtt("width")).getDefault(winWidth); - winHeight = Std.parseInt(layout.getAtt("height")).getDefault(winHeight); for(k=>e in Script.getDefaultVariables()) { set(k, e); @@ -119,110 +116,134 @@ class UISoftcodedWindow extends UISubstateWindow { } set("last", null); + winTitle = execString(layout.getAtt("title").getDefault(winTitle)); + winWidth = Std.parseInt(layout.getAtt("width")).getDefault(winWidth); + winHeight = Std.parseInt(layout.getAtt("height")).getDefault(winHeight); + super.create(); function addLabelOn(ui:UISprite, text:String) add(new UIText(ui.x, ui.y - 24, 0, text)); - for(el in layout.elements) { - var type = el.name; - //var type:String = el.getAtt("type"); - var name:String = execString(el.getAtt("name")); - var label:String = execString(el.getAtt("label")); - var text:String = execString(el.getAtt("text")); - var x:Float = execAtt(el, "x", 0); - var y:Float = execAtt(el, "y", 0); - var width:Null<Int> = exec(el.getAtt("width")); - var height:Null<Int> = exec(el.getAtt("height")); - var size:Int = execAtt(el, "size", 28); - - trace(type, name, label, text, x, y, width, height, size); - - var sprite:FlxSprite = switch(type) { - case "set": - set(execString(el.getAtt("name")), exec(el.getAtt("value"))); - continue; - case "image": - var spr = XMLUtil.createSpriteFromXML(el, "", LOOP); - cast add(spr); - case "solid": - var spr = new FunkinSprite(x, y).makeSolid( - execAtt(el, "width", 100), - execAtt(el, "height", 100), - execAtt(el, "color", 0xFFFFFFFF) - ); - cast add(spr); - case "exec": - exec(el.innerData); - continue; - case "textbox": - var textBox = new UITextBox(x, y, text, width, height, execAtt(el, "multiline", false));//(el.getAtt("multiline").getDefault("false") == "true")); - add(textBox); - addLabelOn(textBox, label); - textBox; - case "title": - cast add(new UIText(x, y, 0, text, size)); - case "stepper": - // x:Float, y:Float, value:Float = 0, step:Float = 1, precision:Int = 0, ?min:Float, ?max:Float, w:Int = 180, h:Int = 32 - var stepper = new UINumericStepper( - x, - y, - execAtt(el, "value", 0), - execAtt(el, "step", 1), - execAtt(el, "precision", 0), - execAtt(el, "min", null), - execAtt(el, "max", null), - width, - height - ); - add(stepper); - addLabelOn(stepper, label); - stepper; - case "checkbox": - var checkbox = new UICheckbox(x, y, text, execAtt(el, "value", false)); - add(checkbox); - addLabelOn(checkbox, label); - checkbox; - case "radio": - var radio = new UIRadioButton(x, y, text, execAtt(el, "value", false), execString(el.getAtt("for"))); - radio.parent = this; - add(radio); - addLabelOn(radio, label); - radio; - case "slider": - var slider = new UISlider(x, y, width, execAtt(el, "value", 0), execAtt(el, "segments", []), execAtt(el, "centered", false)); - add(slider); - addLabelOn(slider, label); - slider; - case "dropdown": - var items:Array<DropDownItem> = []; - for(i=>node in el.nodes.item) - items.push({label: execString(node.getAtt("label")), value: execAtt(node, "value", i)}); - var curValue = execAtt(el, "value", null); - var index = UIDropDown.indexOfItemValue(items, curValue); - if (index == -1) index = 0; - - var dropdown = new UIDropDown(x, y, width, height, items, index); - add(dropdown); - addLabelOn(dropdown, label); - dropdown; - // UIColorwheel - // UIAudioPlayer - // UIButtonList - // UIFileExplorer - // UIAutoCompleteTextbox - default: { - Logs.trace("Unknown element type: " + type, ERROR, RED); - Logs.trace("Skipping element: " + el.x.toString(), ERROR, RED); - continue; - } - } - - set("last", sprite); - if(name != null) set(name, sprite); - if(sprite is UISprite) elementMap.set(name, cast sprite); + function getArr(i:Iterator<Access>) { + return [for(e in i) e]; } + function addLayout(elements:Array<Access>) { + for(el in elements) { + var type = el.name; + + switch(type) { + case "section": + if(execAtt(el, "if", true) != true) + continue; + if(execAtt(el, "unless", false) != false) + continue; + addLayout(getArr(el.elements)); + continue; + case "set": + set(execString(el.getAtt("name")), execAtt(el, "value")); + continue; + case "exec": + exec(el.innerData); + continue; + } + + var name:String = execString(el.getAtt("name")); + var label:String = execString(el.getAtt("label")); + var x:Float = execAtt(el, "x", 0); + var y:Float = execAtt(el, "y", 0); + + var sprite:FlxSprite = switch(type) { + case "image": + var spr = XMLUtil.createSpriteFromXML(el, "", LOOP); + spr.x = x; + spr.y = y; + cast add(spr); + case "solid": + var spr = new FunkinSprite(x, y).makeSolid( + execAtt(el, "width", 100), + execAtt(el, "height", 100), + execAtt(el, "color", 0xFFFFFFFF) + ); + cast add(spr); + case "textbox": + var text:String = execString(el.getAtt("text")); + text = Std.string(execAtt(el, "value", text)); + var textBox = new UITextBox(x, y, text, execAtt(el, "width"), execAtt(el, "height"), execAtt(el, "multiline", false));//(el.getAtt("multiline").getDefault("false") == "true")); + add(textBox); + addLabelOn(textBox, label); + textBox; + case "title": + cast add(new UIText(x, y, 0, execString(el.getAtt("text")), execAtt(el, "size", 28))); + case "label": + cast add(new UIText(x, y - 24, 0, execString(el.getAtt("text")), execAtt(el, "size", 15))); + case "text": + cast add(new UIText(x, y, 0, execString(el.getAtt("text")), execAtt(el, "size", 15))); + case "stepper": + // x:Float, y:Float, value:Float = 0, step:Float = 1, precision:Int = 0, ?min:Float, ?max:Float, w:Int = 180, h:Int = 32 + var stepper = new UINumericStepper( + x, + y, + execAtt(el, "value", 0), + execAtt(el, "step", 1), + execAtt(el, "precision", 0), + execAtt(el, "min"), + execAtt(el, "max"), + execAtt(el, "width"), + execAtt(el, "height") + ); + add(stepper); + addLabelOn(stepper, label); + stepper; + case "checkbox": + var checkbox = new UICheckbox(x, y, execString(el.getAtt("text")), execAtt(el, "value", false)); + add(checkbox); + addLabelOn(checkbox, label); + checkbox; + case "radio": + var radio = new UIRadioButton(x, y, execString(el.getAtt("text")), execAtt(el, "value", false), execString(el.getAtt("for"))); + radio.parent = this; + add(radio); + addLabelOn(radio, label); + radio; + case "slider": + var slider = new UISlider(x, y, execAtt(el, "width"), execAtt(el, "value", 0), execAtt(el, "segments", []), execAtt(el, "centered", false)); + add(slider); + addLabelOn(slider, label); + slider; + case "dropdown": + var items:Array<DropDownItem> = []; + for(i=>node in el.nodes.item) + items.push({label: execString(node.getAtt("label")), value: execAtt(node, "value", i)}); + var curValue = execAtt(el, "value"); + var index = UIDropDown.indexOfItemValue(items, curValue); + if (index == -1) index = 0; + + var dropdown = new UIDropDown(x, y, execAtt(el, "width"), execAtt(el, "height"), items, index); + add(dropdown); + addLabelOn(dropdown, label); + dropdown; + // UIColorwheel + // UIAudioPlayer + // UIButtonList + // UIFileExplorer + // UIAutoCompleteTextbox + default: { + Logs.trace("Unknown element type: " + type, ERROR, RED); + Logs.trace("Skipping element: " + el.x.toString(), ERROR, RED); + continue; + } + } + + set("last", sprite); + if(name != null) set(name, sprite); + if(sprite is UISprite) elementMap.set(name, cast sprite); + } + } + + addLayout(getArr(layout.elements)); + if(get("hasSaveButtons")) { saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() { saveData(); diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx index 9d066a23..6a98e173 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -249,6 +249,7 @@ class Stage extends FlxBasic implements IBeatReceiver { charPos.skewY = Std.parseFloat(node.getAtt("skewy")).getDefault(charPos.skewY); charPos.alpha = Std.parseFloat(node.getAtt("alpha")).getDefault(charPos.alpha); charPos.flipX = (node.has.flip || node.has.flipX) ? (node.getAtt("flip") == "true" || node.getAtt("flipX") == "true") : charPos.flipX; + charPos.zoomFactor = Std.parseFloat(node.getAtt("zoomfactor")).getDefault(charPos.zoomFactor); if (node.has.scale) { var scale:Null<Float> = Std.parseFloat(node.att.scale); @@ -337,6 +338,7 @@ class StageCharPos extends FlxObject { public var alpha:Float = 1; public var flipX:Bool = false; public var scale:FlxPoint = FlxPoint.get(1, 1); + public var zoomFactor:Float = 1; public function new() { super(); @@ -349,13 +351,49 @@ class StageCharPos extends FlxObject { super.destroy(); } + private var _id:Float = -1; + + private var oldInfo:OldCharInfo = null; + public function prepareCharacter(char:Character, id:Float = 0) { + _id = id; + oldInfo = getOldInfo(char); char.setPosition(x + (id * charSpacingX), y + (id * charSpacingY)); char.scrollFactor.set(scrollFactor.x, scrollFactor.y); char.scale.x *= scale.x; char.scale.y *= scale.y; char.cameraOffset += FlxPoint.weak(camxoffset, camyoffset); char.skew.x += skewX; char.skew.y += skewY; char.alpha *= alpha; + char.zoomFactor *= zoomFactor; + } + + public function getOldInfo(char:Character) { + return { + x: char.x, y: char.y, + scrollX: char.scrollFactor.x, scrollY: char.scrollFactor.y, + scaleX: char.scale.x, scaleY: char.scale.y, + camxoffset: char.cameraOffset.x, camyoffset: char.cameraOffset.y, + skewX: char.skew.x, skewY: char.skew.y, + alpha: char.alpha, zoomFactor: char.zoomFactor + } + } + + public function revertCharacter(char:Character) { + if(oldInfo == null) return; + for(field in Reflect.fields(oldInfo)) { + switch(field) { + case "scrollX": char.scrollFactor.x = oldInfo.scrollX; + case "scrollY": char.scrollFactor.y = oldInfo.scrollY; + case "scaleX": char.scale.x = oldInfo.scaleX; + case "scaleY": char.scale.y = oldInfo.scaleY; + case "camxoffset": char.cameraOffset.x = oldInfo.camxoffset; + case "camyoffset": char.cameraOffset.y = oldInfo.camyoffset; + case "skewX": char.skew.x = oldInfo.skewX; + case "skewY": char.skew.y = oldInfo.skewY; + default: Reflect.setField(char, field, Reflect.field(oldInfo, field)); + } + } + oldInfo = null; } } typedef StageCharPosInfo = { @@ -363,4 +401,19 @@ typedef StageCharPosInfo = { var y:Float; var flip:Bool; var scroll:Float; +} + +typedef OldCharInfo = { + var x:Float; + var y:Float; + var scrollX:Float; + var scrollY:Float; + var scaleX:Float; + var scaleY:Float; + var camxoffset:Float; + var camyoffset:Float; + var skewX:Float; + var skewY:Float; + var alpha:Float; + var zoomFactor:Float; } \ No newline at end of file