Merge branch 'stage-editor' into new-editors

This commit is contained in:
NeeEoo
2024-04-27 20:26:56 +02:00
72 changed files with 4695 additions and 274 deletions
@@ -0,0 +1,119 @@
<window width="550" height="570" title="Editing Character ${button.getName()}">
<set name="XOFFSET" value="30" />
<set name="YOFFSET" value="40" />
<set name="TO" value="64" />
<exec>
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);
}
</exec>
<title name="title" x="20" y="30 + 16" text="Character Data" />
<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" />
<label name="spacingLabel" text="Spacing (X, Y)" x="col(1)" y="curY" size="15" />
<stepper name="spacingXStepper" value="charPos.charSpacingX" x="col(1)" 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" />
<stepper name="skewXStepper" label="Skew X" value="charPos.skewX" x="col(2)" 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" />
</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="angleStepper" label="Angle" value="charPos.angle" x="last.x + last.bWidth + XOFFSET - 20" y="curY" precision="2" step="0.01" width="100" />
<stepper name="zoomFactorStepper" label="Zoom Factor" value="charPos.zoomFactor" x="col(1)" y="curY" precision="2" step="0.01" width="100" />
<section if="charPos.extra.exists('extraPos')">
<textbox name="charName" label="Position Name" value="charPos.name" x="col(2)" y="curY" width="100"/>
</section>
</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.angle = angleStepper.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.angle = angleStepper.value;
charPos.revertCharacter(char);
charPos.prepareCharacter(char, charPos._id);
}
</exec>
</window>
@@ -0,0 +1,185 @@
<window width="1056" height="520" title="Editing Sprite ${button.getName()}">
<set name="MARGIN" value="20" />
<set name="XOFFSET" value="50" />
<set name="YOFFSET" value="40" />
<set name="TO" value="64" />
<exec>
function col(c:Int) {
return 20 + (200 + XOFFSET) * c;
}
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" />
<section> <!-- ROW 1 -->
<set name="curY" value="title.y + TO" />
<textbox name="nameTextBox" label="Name Identifier" value="button.getName()" x="col(0)" y="curY" width="200" height="32" />
<textbox name="spriteTextBox" value="getEx('imageFile')" x="last.x + last.bWidth + XOFFSET" y="curY" width="200" height="32" />
<stepper name="alphaStepper" label="Alpha" value="sprite.alpha" x="spriteTextBox.x + spriteTextBox.bWidth + XOFFSET" y="curY" precision="2" step="0.01" width="100" />
<label name="imageFile" text="Image File (WIP: needs reload)&#10;In (${stage.spritesParentFolder})" x="spriteTextBox.x" y="spriteTextBox.y - 14" size="15" />
</section>
<section> <!-- ROW 2 -->
<set name="curY" value="nameTextBox.y + nameTextBox.bHeight + YOFFSET" />
<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="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" />
</section>
<section> <!-- ROW 3 -->
<set name="curY" value="xStepper.y + xStepper.bHeight + YOFFSET" />
<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" />
<!-- TODO: Simplify this -->
<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" />
</section>
<section> <!-- ROW 4 -->
<set name="curY" value="angleStepper.y + angleStepper.bHeight + YOFFSET" />
<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 -->
</section>
<section> <!-- RIGHT SIDE -->
<set name="curY" value="title.y + TO" />
<set name="listWidth" value="250" />
<buttonlist name="animations" label="Animations (TODO)" x="col(3)" y="curY" width="listWidth" height="330" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5">
<texture path="editors/ui/inputbox" />
<camSpacing value="0" />
<code>
buttonlist.addButton.exists = false;
</code>
<!-- <code>
import funkin.editors.extra.PropertyButton;
var buttonList = buttonlist; // important for now, since it gets reset after the code is run
var width = buttonList.buttonSize.x;
var height = buttonList.buttonSize.y;
buttonList.addButton.callback = function() {
buttonList.add(new PropertyButton("newProperty", "valueHere", buttonList, width, height, 150, 220, 25));
}
//var defaultAttribs = Stage.DEFAULT_ATTRIBUTES;
//for (i in stage.stageXML.attributes()) {
// trace(i, defaultAttribs);
// if (!defaultAttribs.contains(i))
// buttonList.add(new PropertyButton(i, stage.stageXML.get(i), buttonList, width, height, 150, 220, 25));
//}
for(key =&gt; val in stage.extra)
buttonList.add(new PropertyButton(key, val, buttonList, width, height, 150, 220, 25));
</code> -->
</buttonlist>
<text text="Tip: To access custom properties,&#10;hold shift when clicking edit" x="col(2) - 60" y="self.winHeight - 50" size="15" />
</section>
<!-- Preview Transform -->
<section if="previewSprite != null">
<title name="previewTitle" x="20" y="self.winHeight - 150" text="Preview Transform:" size="15" />
</section>
<exec>
function onUpdate() {
if(previewSprite == null) return;
previewSprite.color = 0xFFFFFFFF; // TODO: Color Wheel
previewSprite.skew.x = skewXStepper.value;
previewSprite.skew.y = skewYStepper.value;
previewSprite.angle = angleStepper.value;
previewSprite.alpha = alphaStepper.value;
previewSprite.antialiasing = antialiasingCheckbox.checked;
var ratio = sprite.width / sprite.height;
previewSprite.setGraphicSize(50 * scaleXStepper.value * ratio, 50 * scaleYStepper.value);
previewSprite.updateHitbox();
}
function onSave() {
sprite.name = nameTextBox.label.text;
setEx("imageFile", spriteTextBox.label.text);
setEx("highMemory", highMemoryRadio.checked);
setEx("lowMemory", lowMemoryRadio.checked);
//sprite.x = xStepper.value;
//sprite.y = yStepper.value;
//sprite.zoomFactor = zoomFactorStepper.value;
//sprite.antialiasing = antialiasingCheckbox.checked;
//sprite.color = colorWheel.color;
//sprite.spriteAnimType = animType.value;
sprite.angle = angleStepper.value;
sprite.alpha = alphaStepper.value;
function saveXY(obj:Dynamic, name:String, x:UINumericStepper, y:UINumericStepper) {
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)
button.xml.set(name, x.value);
else {
button.xml.set(name + "x", x.value);
button.xml.set(name + "y", y.value);
}
}
function save(name:String, value:Dynamic) {
button.xml.remove(name);
button.xml.set(name, value);
}
// TODO: remove old attributes to prevent possible errors
// button.xml.remove();
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);
button.xml.set("alpha", alphaStepper.value);
saveXY(sprite.scrollFactor, "scroll", scrollXStepper, scrollYStepper);
saveXY(sprite.scale, "scale", scaleXStepper, scaleYStepper);
saveXY(sprite.skew, "skew", skewXStepper, skewYStepper);
XMLUtil.loadSpriteFromXML(sprite, button.xml, stage.spritesParentFolder);
}
</exec>
</window>
@@ -0,0 +1,110 @@
<window width="470" height="620" title="Editing Stage Info '${stage.stageName}'">
<set name="MARGIN" value="20" />
<set name="XOFFSET" value="50" />
<set name="YOFFSET" value="40" />
<set name="TO" value="64" />
<set name="storedProps" value="[]" />
<exec>
import haxe.ds.StringMap;
var oldInfo = {
name: stage.stageName,
folder: stage.spritesParentFolder,
startCamX: stage.startCam.x,
startCamY: stage.startCam.y,
zoom: stage.defaultZoom,
attrib: new StringMap()
};
function col(c:Int) {
return MARGIN + (170 + XOFFSET) * c;
}
for(prop =&gt; val in stage.extra) {
if(!StringTools.startsWith(prop, "stageEditor.")) {
storedProps.push(prop);
oldInfo.attrib.set(prop, val);
}
}
</exec>
<title name="title" x="MARGIN" y="30 + 16" text="Stage Info" />
<section> <!-- ROW 1 -->
<set name="curY" value="title.y + TO" />
<textbox name="nameTextBox" label="Stage Name" value="stage.stageName" x="col(0)" y="curY" width="200" height="32" />
<textbox name="spritePathTextBox" value="stage.spritesParentFolder" x="col(1)" y="curY" width="200" height="32" />
<label text="Sprite Path&#10;(Needs Refresh)" x="spritePathTextBox.x" y="spritePathTextBox.y - 14" size="15" />
</section>
<section> <!-- ROW 2 -->
<set name="curY" value="nameTextBox.y + nameTextBox.bHeight + YOFFSET" />
<label text="Start Cam Pos (X, Y)" x="col(0)" y="curY" />
<stepper name="camxStepper" value="stage.startCam.x" x="col(0)" y="curY" step="1" width="120" />
<stepper name="camyStepper" value="stage.startCam.y" x="last.x + last.bWidth + XOFFSET - 30" y="curY" step="1" width="120" />
<stepper name="zoomStepper" label="Zoom" value="stage.defaultZoom" x="col(1)" y="curY" precision="2" step="0.01" width="100" />
</section>
<section>
<set name="curY" value="camxStepper.y + camxStepper.bHeight + YOFFSET" />
<set name="listWidth" value="self.winWidth - MARGIN * 2" />
<buttonlist name="attributesList" label="Custom Attributes" x="col(0)" y="curY" width="listWidth" height="290" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5">
<texture path="editors/ui/inputbox" />
<camSpacing value="0" />
<code>
import funkin.editors.extra.PropertyButton;
var buttonList = buttonlist; // important for now, since it gets reset after the code is run
var width = buttonList.buttonSize.x;
var height = buttonList.buttonSize.y;
buttonList.addButton.callback = function() {
buttonList.add(new PropertyButton("newProperty", "valueHere", buttonList, width, height, 150, 220, 25));
}
//var defaultAttribs = Stage.DEFAULT_ATTRIBUTES;
//for (i in stage.stageXML.attributes()) {
// trace(i, defaultAttribs);
// if (!defaultAttribs.contains(i))
// buttonList.add(new PropertyButton(i, stage.stageXML.get(i), buttonList, width, height, 150, 220, 25));
//}
for(key =&gt; val in stage.extra)
buttonList.add(new PropertyButton(key, val, buttonList, width, height, 150, 220, 25));
</code>
</buttonlist>
</section>
<exec>
function onSave() {
stage.startCam.x = camxStepper.value;
stage.startCam.y = camyStepper.value;
stage.defaultZoom = zoomStepper.value;
stage.stageName = nameTextBox.label.text;
stage.spritesParentFolder = spritePathTextBox.label.text;
for(prop in storedProps)
stage.extra.remove(prop);
var newInfo = {
name: stage.stageName,
folder: stage.spritesParentFolder,
startCamX: stage.startCam.x,
startCamY: stage.startCam.y,
zoom: stage.defaultZoom,
attrib: new StringMap()
};
for(prop in attributesList.buttons.members) {
stage.extra.set(prop.propertyText.label.text, prop.valueText.label.text);
newInfo.attrib.set(prop.propertyText.label.text, prop.valueText.label.text);
}
FlxG.state.undos.addToUndo(CEditInfo(oldInfo, newInfo));
}
</exec>
</window>
@@ -0,0 +1,66 @@
<window width="350" height="570" title="Editing XML <${button.getName()} />">
<set name="MARGIN" value="20" />
<set name="XOFFSET" value="30" />
<set name="YOFFSET" value="40" />
<set name="TO" value="64" />
<exec>
function col(c:Int) {
return MARGIN + (150 + XOFFSET) * c;
}
</exec>
<title name="title" x="MARGIN" y="30 + 16" text="Xml Data" />
<section>
<set name="curY" value="title.y + TO" />
<textbox name="nodeNameTextBox" label="Element Name" value="button.getName()" x="col(0)" y="curY" width="280" height="32" />
</section>
<section>
<set name="curY" value="nodeNameTextBox.y + nodeNameTextBox.bHeight + YOFFSET - 30" />
<radio for="memoryCheck" name="highMemoryRadio" text="High Memory Only" value="button.highMemory" x="col(0)" y="curY" />
<radio for="memoryCheck" name="lowMemoryRadio" text="Low Memory Only" value="button.lowMemory" x="col(0)" y="last.y + last.check.height + YOFFSET - 30" />
</section>
<section>
<set name="curY" value="lowMemoryRadio.y + lowMemoryRadio.check.height + YOFFSET" />
<set name="listWidth" value="self.winWidth - MARGIN * 2" />
<buttonlist name="attributesList" label="Attributes" x="col(0)" y="curY" width="listWidth" height="260" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5">
<texture path="editors/ui/inputbox" />
<camSpacing value="0" />
<code>
import funkin.editors.extra.PropertyButton;
var buttonList = buttonlist; // important for now, since it gets reset after the code is run
var width = buttonList.buttonSize.x;
var height = buttonList.buttonSize.y;
buttonList.addButton.callback = function() {
buttonList.add(new PropertyButton("newProperty", "valueHere", buttonList, width, height, 110, 145, 25));
}
for (i in xml.attributes())
buttonList.add(new PropertyButton(i, xml.get(i), buttonList, width, height, 110, 145, 25));
</code>
</buttonlist>
</section>
<exec>
function onSave() {
button.xml.nodeName = nodeNameTextBox.label.text;
button.highMemory = highMemoryRadio.checked;
button.lowMemory = lowMemoryRadio.checked;
for (i in xml.attributes())
xml.remove(i);
for (val in attributesList.buttons.members)
xml.set(val.propertyText.label.text, val.valueText.label.text);
}
</exec>
</window>
@@ -0,0 +1,51 @@
<window width="450" height="570" title="Editing XML ${type}">
<!-- BASED ON unknownEditScreen.xml -->
<set name="MARGIN" value="20" />
<set name="XOFFSET" value="30" />
<set name="YOFFSET" value="40" />
<set name="TO" value="64" />
<exec>
function col(c:Int) {
return MARGIN + (150 + XOFFSET) * c;
}
</exec>
<title name="title" x="MARGIN" y="30 + 16" text="Xml Data" />
<section>
<set name="curY" value="title.y + TO" />
<set name="listWidth" value="self.winWidth - MARGIN * 2" />
<buttonlist name="attributesList" label="Attributes" x="col(0)" y="curY" width="listWidth" height="400" buttonSizeX="listWidth-10" buttonSizeY="35" buttonSpacing="5">
<texture path="editors/ui/inputbox" />
<camSpacing value="0" />
<code>
import funkin.editors.extra.PropertyButton;
var buttonList = buttonlist; // important for now, since it gets reset after the code is run
var width = buttonList.buttonSize.x;
var height = buttonList.buttonSize.y;
buttonList.addButton.callback = function() {
buttonList.add(new PropertyButton("newProperty", "valueHere", buttonList, width, height, 160, 195, 25));
}
for (i in xml.attributes())
buttonList.add(new PropertyButton(i, xml.get(i), buttonList, width, height, 160, 195, 25));
</code>
</buttonlist>
</section>
<exec>
function onSave() {
for (i in xml.attributes())
xml.remove(i);
for (val in attributesList.buttons.members)
xml.set(val.propertyText.label.text, val.valueText.label.text);
}
</exec>
</window>
+12
View File
@@ -0,0 +1,12 @@
<!-- praying to god we remove this file later on -->
<stage zoom="0.9" name="stage" folder="stages/default/" startCamPosY="600" startCamPosX="1000" example="value">
<sprite name="normBG" sprite="stagefront" x="-500" y="100" scale="0.75" updateHitbox="true" />
<sprite name="noUpdateHitbox" sprite="stagefront" x="-500" y="300" scale="0.75" />
<sprite name="ro-tat-ay" sprite="stagefront" x="-500" y="700" scale="0.75" updateHitbox="true" angle="37" />
<box name="box" x="0" y="0" width="200" height="200" color="0xFF0000" />
<solid name="box2" x="0" y="300" width="200" height="200" color="0x00FF00" />
<unknown-sprite />
<unknown-sprite-1-attr name="test" />
</stage>
+317
View File
@@ -0,0 +1,317 @@
/**
* THIS FILE IS TEMPORARY UNTIL TRANSFORMS ARE AT A USEABLE STATE.
* TODO:
* - !!!! MOVE BACK TO SOURCE !!!!
* - Rotation
* - Skewing
* - Angle + Scale (like already at a angle)
*/
import funkin.editors.stage.StageEditor;
import funkin.editors.ui.UIWarningSubstate;
import funkin.backend.utils.WindowUtils;
import flixel.math.FlxAngle;
import openfl.Lib;
var exID = StageEditor.exID;
function tryUpdateHitbox(sprite) {
var spriteNode = sprite.extra.get(exID("node"));
if (spriteNode.exists("updateHitbox") && spriteNode.get("updateHitbox") == "true") {
sprite.updateHitbox();
return true;
}
if (!FlxG.keys.pressed.ALT) {
sprite.x = storedPos.x - (sprite.frameWidth * (storedScale.x - sprite.scale.x) * 0.5);
sprite.y = storedPos.y - (sprite.frameHeight * (storedScale.y - sprite.scale.y) * 0.5);
}
return false;
}
var allowClose = false;
function create() {
WindowUtils.preventClosing = true;
WindowUtils.resetClosing();
WindowUtils.onClosing = function() {
if(!allowClose)
Lib.application.window.onClose.cancel();
var substate;
substate = new UIWarningSubstate("nuh uh", "you dont need to close goofus doofus", [
{
label: "Actually Exit",
onClick: function(t) {
allowClose = true;
Lib.application.window.close();
}
},
{
label: "Reload State",
onClick: function(t) {
FlxG.switchState(new StageEditor(StageEditor.__stage));
}
},
{
label: "oh whoops",
onClick: function(t) {
substate.close();
}
}
]);
openSubState(substate);
}
}
function destroy() {
WindowUtils.preventClosing = false;
WindowUtils.resetClosing();
WindowUtils.onClosing = null;
}
function update() {
if (FlxG.mouse.justPressed)
lastRelative.set();
if(FlxG.keys.justPressed.R) {
trace("reloading");
FlxG.switchState(new StageEditor(StageEditor.__stage));
}
}
function genericScale(sprite, relative, doX, doY) {
var relativeMult = 1 / (FlxMath.lerp(1, stageCamera.zoom, sprite.zoomFactor) / stageCamera.zoom) * (FlxG.keys.pressed.ALT ? 2 : 1);
relative.x *= relativeMult;
relative.y *= relativeMult;
var width = sprite.frameWidth * storedScale.x;
var height = sprite.frameHeight * storedScale.y;
if(doX) width -= relative.x;
if(doY) height -= relative.y;
CoolUtil.setGraphicSizeFloat(sprite, width, height);
if(FlxG.keys.pressed.SHIFT) {
var nscale = Math.max(sprite.scale.x, sprite.scale.y);
sprite.scale.set(nscale, nscale);
}
var updatedHitbox = tryUpdateHitbox(sprite);
if (FlxG.keys.pressed.ALT) {
sprite.x = storedPos.x;
sprite.y = storedPos.y;
if(updatedHitbox) {
sprite.x += (sprite.frameWidth * (storedScale.x - sprite.scale.x) * 0.5);
sprite.y += (sprite.frameHeight * (storedScale.y - sprite.scale.y) * 0.5);
}
return true;
}
return !updatedHitbox;
}
function genericOppositeScale(sprite, relative, scaleX, scaleY, repositionX, repositionY) {
if(repositionX) relative.x *= -1;
if(repositionY) relative.y *= -1;
var repositioned = genericScale(sprite, relative, scaleX, scaleY);
if (!repositioned) {
if(repositionX) sprite.x = storedPos.x + (sprite.frameWidth * (storedScale.x - sprite.scale.x));
if(repositionY) sprite.y = storedPos.y + (sprite.frameHeight * (storedScale.y - sprite.scale.y));
} else if (!FlxG.keys.pressed.ALT) {
if(repositionX) sprite.x += (sprite.frameWidth * (storedScale.x - sprite.scale.x));
if(repositionY) sprite.y += (sprite.frameHeight * (storedScale.y - sprite.scale.y));
}
}
var oldSpritePos = FlxPoint.get();
function SCALE_BOTTOM_RIGHT(sprite, relative) {
preRotBullshit(sprite, relative);
genericScale(sprite, relative, true, true);
postRotBullshit(sprite, relative);
}
/**
ROTATION
**/
function preRotBullshit(sprite, relative) {
if (sprite.angle != 0)
relative = rotateAround(relative, FlxPoint.weak(0, 0), -sprite.angle);
if (FlxG.mouse.justPressed) {
oldSpritePos.x = sprite.x;
oldSpritePos.y = sprite.y;
}
}
function postRotBullshit(sprite, relative) {
if (sprite.angle != 0) {
var p = rotateAround(FlxPoint.get(sprite.x, sprite.y), oldSpritePos, sprite.angle);
sprite.x = p.x;
sprite.y = p.y;
}
}
function rotateAround(p, origin, angle) {
var rel = FlxPoint.get(p.x - origin.x, p.y - origin.y);
rotateByDegrees(rel, angle);
p.x = origin.x + rel.x;
p.y = origin.y + rel.y;
rel.put();
return p;
}
function rotateByDegrees(p, angle) {
var rads = angle * FlxAngle.TO_RAD;
var s:Float = Math.sin(rads);
var c:Float = Math.cos(rads);
var tempX:Float = p.x;
p.x = tempX * c - p.y * s;
p.y = tempX * s + p.y * c;
}
/**
END OF ROTATION
**/
function SCALE_TOP_RIGHT(sprite, relative) {
preRotBullshit(sprite, relative);
genericOppositeScale(sprite, relative, true, true, false, true);
postRotBullshit(sprite, relative);
}
function MOVE_CENTER(sprite, relative) {
preRotBullshit(sprite, relative);
sprite.x = storedPos.x-relative.x;
sprite.y = storedPos.y-relative.y;
postRotBullshit(sprite, relative);
}
function SCALE_TOP_LEFT(sprite, relative) {
preRotBullshit(sprite, relative);
genericOppositeScale(sprite, relative, true, true, true, true);
postRotBullshit(sprite, relative);
}
function SCALE_BOTTOM_LEFT(sprite, relative) {
preRotBullshit(sprite, relative);
genericOppositeScale(sprite, relative, true, true, true, false);
postRotBullshit(sprite, relative);
}
function SCALE_LEFT(sprite, relative) {
preRotBullshit(sprite, relative);
genericOppositeScale(sprite, relative, true, false, true, false);
postRotBullshit(sprite, relative);
}
function SCALE_RIGHT(sprite, relative) {
preRotBullshit(sprite, relative);
genericScale(sprite, relative, true, false);
postRotBullshit(sprite, relative);
}
function SCALE_TOP(sprite, relative) {
preRotBullshit(sprite, relative);
genericOppositeScale(sprite, relative, false, true, false, true);
postRotBullshit(sprite, relative);
}
function SCALE_BOTTOM(sprite, relative) {
preRotBullshit(sprite, relative);
genericScale(sprite, relative, false, true);
postRotBullshit(sprite, relative);
}
var lastRelative = FlxPoint.get();
function SKEW_LEFT(sprite, relative) {
preRotBullshit(sprite, relative);
if (!FlxG.keys.pressed.SHIFT)
genericOppositeScale(sprite, relative, true, false, true, false);
var topLeft = sprite.extra.get(exID("buttonBoxes"))[0];
var topRight = sprite.extra.get(exID("buttonBoxes"))[1];
sprite.y = storedPos.y - relative.y * 0.5;
sprite.skew.y = Math.atan2(
topRight.y - (topLeft.y + (lastRelative.y - relative.y)),
topRight.x - (topLeft.x + (FlxG.keys.pressed.SHIFT ? 0 : relative.x - lastRelative.x))
) * FlxAngle.TO_DEG;
lastRelative.set((FlxG.keys.pressed.SHIFT ? lastRelative.x : relative.x), relative.y);
postRotBullshit(sprite, relative);
}
function SKEW_BOTTOM(sprite, relative) {
preRotBullshit(sprite, relative);
if (!FlxG.keys.pressed.SHIFT)
genericScale(sprite, relative, false, true);
var topLeft = sprite.extra.get(exID("buttonBoxes"))[0];
var bottomLeft = sprite.extra.get(exID("buttonBoxes"))[2];
sprite.x = storedPos.x - relative.x * 0.5;
sprite.skew.x = Math.atan2(
topLeft.x - (bottomLeft.x + (lastRelative.x - relative.x)),
topLeft.y - (bottomLeft.y + (FlxG.keys.pressed.SHIFT ? 0 : lastRelative.y - relative.y))
) * FlxAngle.TO_DEG;
lastRelative.set(relative.x, (FlxG.keys.pressed.SHIFT ? lastRelative.y : relative.y));
postRotBullshit(sprite, relative);
}
function SKEW_TOP(sprite, relative) {
preRotBullshit(sprite, relative);
if (!FlxG.keys.pressed.SHIFT)
genericOppositeScale(sprite, relative, false, true, false, true);
var topLeft = sprite.extra.get(exID("buttonBoxes"))[0];
var bottomLeft = sprite.extra.get(exID("buttonBoxes"))[2];
sprite.x = storedPos.x - relative.x * 0.5;
sprite.skew.x = Math.atan2(
bottomLeft.x - (topLeft.x + (lastRelative.x - relative.x)),
bottomLeft.y - (topLeft.y + (FlxG.keys.pressed.SHIFT ? 0 : relative.y - lastRelative.y))
) * FlxAngle.TO_DEG;
lastRelative.set(relative.x, (FlxG.keys.pressed.SHIFT ? lastRelative.y : relative.y));
postRotBullshit(sprite, relative);
}
function SKEW_RIGHT(sprite, relative) {
preRotBullshit(sprite, relative);
if (!FlxG.keys.pressed.SHIFT)
genericScale(sprite, relative, true, false);
var topLeft = sprite.extra.get(exID("buttonBoxes"))[0];
var topRight = sprite.extra.get(exID("buttonBoxes"))[1];
sprite.y = storedPos.y - relative.y * 0.5;
sprite.skew.y = Math.atan2(
topLeft.y - (topRight.y + (lastRelative.y - relative.y)),
topLeft.x - (topRight.x + (FlxG.keys.pressed.SHIFT ? 0 : lastRelative.x - relative.x))
) * FlxAngle.TO_DEG;
lastRelative.set((FlxG.keys.pressed.SHIFT ? lastRelative.x : relative.x), relative.y);
postRotBullshit(sprite, relative);
}
function ROTATE(sprite, relative) {
var buttonBoxes:Array<FlxPoint> = sprite.extra.get(exID("buttonBoxes"));
var p:FlxPoint = buttonBoxes[8];
FlxG.mouse.getWorldPosition(stageCamera, _point);
var dx:Float = _point.x - p.x;
var dy:Float = _point.y - p.y;
var angle = FlxAngle.angleFromOrigin(dx, dy, true) + angleOffset;
if(FlxG.keys.pressed.SHIFT) angle = Std.int(angle / 45) * 45;
sprite.angle = angle;
}
/*function postDraw() {
if(storedCenter.x != 0 || storedCenter.y != 0) {
drawLine(storedCenter, storedRelative, 0.3);
}
}*/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+22 -4
View File
@@ -49,7 +49,8 @@ class Main {
"If you want a full build which contains all assets, run \"cne release\" or \"cne test-release\"",
"Additional arguments will be sent to the lime compiler.",
"",
"-debug : Builds a debug build."
"-debug : Builds a debug build.",
"-clean : Compiled files will be deleted before compiling."
].join("\n")
},
{
@@ -67,7 +68,20 @@ class Main {
"If you want a full build which contains all assets, run \"cne release\" or \"cne test-release\"",
"Additional arguments will be sent to the lime compiler.",
"",
"-debug : Builds a debug build."
"-debug : Builds a debug build.",
"-clean : Compiled files will be deleted before compiling."
].join("\n")
},
{
names: ["run"],
doc: "Runs the last build that was created.",
func: Compiler.run,
dDoc: [
"Usage: run <optional arguments>",
"",
"This will run the last build that was created.",
"If the last build was a debug build, you need to pass the -debug argument to run it.",
"Additional arguments will be sent to the lime compiler."
].join("\n")
},
{
@@ -79,7 +93,9 @@ class Main {
"",
"This will create and run a final ready-for-release build,",
"which means this build will be able to be release on websites such as GameBanana without worrying about source-dependant stuff.",
"Additional arguments will be sent to the lime compiler."
"Additional arguments will be sent to the lime compiler.",
"",
"-clean : Compiled files will be deleted before compiling."
].join("\n")
},
{
@@ -91,7 +107,9 @@ class Main {
"",
"This will create and run a final ready-for-release build,",
"which means this build will be able to be release on websites such as GameBanana without worrying about source-dependant stuff.",
"Additional arguments will be sent to the lime compiler."
"Additional arguments will be sent to the lime compiler.",
"",
"-clean : Compiled files will be deleted before compiling."
].join("\n")
},
{
+9 -5
View File
@@ -2,19 +2,23 @@ package commands;
class Compiler {
public static function test(args:Array<String>) {
__build(args, ["test", getBuildTarget(), "-DTEST_BUILD"]);
__runLime(args, ["test", getBuildTarget(), "-DTEST_BUILD"]);
}
public static function build(args:Array<String>) {
__build(args, ["build", getBuildTarget(), "-DTEST_BUILD"]);
__runLime(args, ["build", getBuildTarget(), "-DTEST_BUILD"]);
}
public static function release(args:Array<String>) {
__build(args, ["build", getBuildTarget()]);
__runLime(args, ["build", getBuildTarget()]);
}
public static function testRelease(args:Array<String>) {
__build(args, ["test", getBuildTarget()]);
__runLime(args, ["test", getBuildTarget()]);
}
private static function __build(args:Array<String>, arg:Array<String>) {
public static function run(args:Array<String>) {
__runLime(args, ["run", getBuildTarget()]);
}
private static function __runLime(args:Array<String>, arg:Array<String>) {
for(a in args)
arg.push(a);
Sys.command("lime", arg);
+1 -1
View File
@@ -9,7 +9,7 @@
<git name="flixel-addons" url="https://github.com/FNF-CNE-Devs/flixel-addons" />
<!-- Other Libraries -->
<git name="hscript-improved" url="https://github.com/FNF-CNE-Devs/hscript-improved" ref="custom-classes" />
<git name="hscript-improved" url="https://github.com/FNF-CNE-Devs/hscript-improved" />
<git name="flxanimate" url="https://github.com/FNF-CNE-Devs/flxanimate" />
<git name="hxdiscord_rpc" url="https://github.com/FNF-CNE-Devs/hxdiscord_rpc" />
<lib name="hxvlc" />
+12
View File
@@ -0,0 +1,12 @@
package external;
#if mac
@:build(external.LinkerMacro.xml('external_code.xml'))
@:include('Mac.h')
@:native('ExternalMac')
extern class ExternalMac
{
@:native('ExternalMac::setCursorIcon')
public static function setCursorIcon(icon:Int, customCursor:cpp.ConstCharStar, customX:Single, customY:Single):Bool;
}
#end
+40
View File
@@ -0,0 +1,40 @@
package external;
import haxe.io.Path;
import haxe.macro.Expr;
import haxe.macro.Context;
import haxe.xml.Printer;
import sys.FileSystem;
class LinkerMacro
{
public static macro function xml(filename:String = 'Build.xml'):Array<Field>
{
var pos:Position = Context.currentPos();
var sourcePath:String = Path.directory(Context.getPosInfos(pos).file);
if (!Path.isAbsolute(sourcePath))
sourcePath = FileSystem.absolutePath(sourcePath);
sourcePath = Path.removeTrailingSlashes(Path.normalize(sourcePath));
var includeElement:Xml = Xml.createElement('include');
includeElement.set('name', Path.join([sourcePath, filename]));
@:privateAccess
{
var printer:Printer = new Printer(true);
printer.writeNode(includeElement, '\n');
Context.getLocalClass().get().meta.add(':buildXml', [{
expr: EConst(CString(printer.output.toString())),
pos: pos
}], pos);
}
return Context.getBuildFields();
}
}
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<pragma once="true" />
<files id="haxe">
<compilerflag value="-I${this_dir}/include/mac" if="macos" />
</files>
<files id="__main__">
<compilerflag value="-I${this_dir}/include/mac" if="macos" />
</files>
<files id="external-code">
<section if="macos">
<compilerflag value="-I${this_dir}/include/mac" />
<file name="${this_dir}/src/mac/CursorHelper.mm" />
<file name="${this_dir}/src/mac/Mac.mm" />
</section>
</files>
<target id="haxe">
<section if="macos">
<vflag name="-framework" value="IOKit" />
<vflag name="-framework" value="AppKit" />
<vflag name="-framework" value="Cocoa" />
<vflag name="-framework" value="Foundation" />
</section>
<files id="external-code" />
</target>
</xml>
+7
View File
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>
@interface CursorHelper : NSObject
+ (NSCursor *)getCursorForSelector:(SEL)selector defaultCursor:(NSCursor *)defaultCursor;
@end
+13
View File
@@ -0,0 +1,13 @@
#ifndef MAC_H
#define MAC_H
#if defined(__APPLE__)
namespace ExternalMac
{
bool setCursorIcon(int icon, const char *customCursor, float customX, float customY);
}
#endif
#endif
+24
View File
@@ -0,0 +1,24 @@
#import <Cocoa/Cocoa.h>
#import "CursorHelper.h"
@implementation CursorHelper
+ (NSCursor *)getCursorForSelector:(SEL)selector defaultCursor:(NSCursor *)defaultCursor
{
NSCursor *cursor = nil;
if ([NSCursor respondsToSelector:selector])
{
cursor = [NSCursor performSelector:selector];
}
else
{
NSLog(@"CursorHelper: selector %@ not found", NSStringFromSelector(selector));
cursor = defaultCursor;
}
return cursor;
}
@end
+140
View File
@@ -0,0 +1,140 @@
#include <unordered_map>
#import <Cocoa/Cocoa.h>
#import "CursorHelper.h"
#define ARROW 0
#define CROSSHAIR 1
#define CLICK 2
#define IBEAM 3
#define MOVE 4
#define HAND 5
#define DRAG 6
#define DRAG_OPEN 7
#define WAIT 8
#define WAIT_ARROW 9
#define DISABLED 10
#define RESIZE_TR 11
#define RESIZE_BL 12
#define RESIZE_TL 13
#define RESIZE_BR 14
#define RESIZE_H 15
#define RESIZE_V 16
#define RESIZE_T 17
#define RESIZE_B 18
#define RESIZE_L 19
#define RESIZE_R 20
#define RESIZE_TLBR 21
#define RESIZE_TRBL 22
#define CUSTOM -1
namespace ExternalMac
{
std::unordered_map<const char *, NSCursor *> customCursorCache;
bool setCursorIcon(int icon, const char *customCursor, float customX, float customY)
{
NSCursor *cursor = nil;
switch (icon)
{
case ARROW:
cursor = [NSCursor arrowCursor];
break;
case CLICK:
cursor = [NSCursor pointingHandCursor];
break;
case CROSSHAIR:
cursor = [NSCursor crosshairCursor];
break;
case HAND:
cursor = [NSCursor closedHandCursor];
break;
case IBEAM:
cursor = [NSCursor IBeamCursor];
break;
case RESIZE_H:
cursor = [NSCursor resizeLeftRightCursor];
break;
case RESIZE_V:
cursor = [NSCursor resizeUpDownCursor];
break;
// TODO: DEFAULTS
case RESIZE_TL:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthWestCursor)defaultCursor:nil];
break;
case RESIZE_TR:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthEastCursor)defaultCursor:nil];
break;
case RESIZE_BL:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeSouthWestCursor)defaultCursor:nil];
break;
case RESIZE_BR:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeSouthEastCursor)defaultCursor:nil];
break;
case RESIZE_T:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthCursor)defaultCursor:nil];
break;
case RESIZE_B:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeSouthCursor)defaultCursor:nil];
break;
case RESIZE_L:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeWestCursor)defaultCursor:nil];
break;
case RESIZE_R:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeEastCursor)defaultCursor:nil];
break;
case RESIZE_TLBR:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthWestSouthEastCursor)defaultCursor:nil];
break;
case RESIZE_TRBL:
cursor = [CursorHelper getCursorForSelector:@selector(_windowResizeNorthEastSouthWestCursor)defaultCursor:nil];
break;
case WAIT:
cursor = [CursorHelper getCursorForSelector:@selector(_waitCursor)defaultCursor:nil];
break;
case WAIT_ARROW:
cursor = [CursorHelper getCursorForSelector:@selector(_waitCursor)defaultCursor:nil];
break;
case DISABLED:
cursor = [NSCursor operationNotAllowedCursor];
break;
case DRAG:
cursor = [NSCursor closedHandCursor];
break;
case DRAG_OPEN:
cursor = [NSCursor openHandCursor];
break;
case CUSTOM:
if (customCursorCache.find(customCursor) != customCursorCache.end())
cursor = customCursorCache[customCursor];
else
{
NSString *cursorImageName = [NSString stringWithUTF8String:customCursor];
NSImage *tmpImage = [[NSImage alloc] initWithContentsOfFile:cursorImageName];
if (tmpImage != nil)
{
cursor = [[NSCursor alloc] initWithImage:tmpImage hotSpot:NSMakePoint(customX, customY)];
customCursorCache[customCursor] = cursor;
[tmpImage release];
}
else
NSLog(@"Failed to load image %@", cursorImageName);
[cursorImageName release];
}
break;
}
if (cursor != nil)
{
[cursor set];
return true;
}
return false;
}
}
+65 -64
View File
@@ -29,6 +29,15 @@ enum abstract XMLAnimType(Int)
default: def;
}
}
@:to public function toString():String {
return switch (cast this)
{
case NONE: "none";
case BEAT: "beat";
case LOOP: "loop";
}
}
}
class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements IOffsetCompatible implements IXMLEvents
@@ -39,9 +48,10 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
public var beatAnims:Array<BeatAnim> = [];
public var name:String;
public var zoomFactor:Float = 1;
public var initialZoom:Float = 1;
public var debugMode:Bool = false;
public var animDatas:Map<String, AnimData> = [];
public var animEnabled:Bool = true;
public var zoomFactorEnabled:Bool = true;
/**
* ODD interval -> asynced; EVEN interval -> synced
@@ -86,6 +96,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
spr.skew.set(source.skew.x, source.skew.y);
spr.transformMatrix = source.transformMatrix;
spr.matrixExposed = source.matrixExposed;
spr.zoomFactor = source.zoomFactor;
spr.animOffsets = source.animOffsets.copy();
}
return spr;
@@ -120,6 +131,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
private var countedBeat = 0;
public function beatHit(curBeat:Int)
{
if(!animEnabled) return;
if (beatAnims.length > 0 && (curBeat + beatOffset) % beatInterval == 0)
{
if(skipNegativeBeats && curBeat < 0) return;
@@ -138,49 +150,6 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
{
}
public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect
{
__doPreZoomScaleProcedure(camera);
var r = super.getScreenBounds(newRect, camera);
__doPostZoomScaleProcedure();
return r;
}
public override function drawComplex(camera:FlxCamera)
{
super.drawComplex(camera);
}
public override function doAdditionalMatrixStuff(matrix:FlxMatrix, camera:FlxCamera)
{
super.doAdditionalMatrixStuff(matrix, camera);
matrix.translate(-camera.width / 2, -camera.height / 2);
var requestedZoom = FlxMath.lerp(1, camera.zoom, zoomFactor);
var diff = requestedZoom / camera.zoom;
matrix.scale(diff, diff);
matrix.translate(camera.width / 2, camera.height / 2);
}
public override function getScreenPosition(?point:FlxPoint, ?Camera:FlxCamera):FlxPoint
{
if (__shouldDoScaleProcedure())
{
__oldScrollFactor.set(scrollFactor.x, scrollFactor.y);
var requestedZoom = FlxMath.lerp(initialZoom, camera.zoom, zoomFactor);
var diff = requestedZoom / camera.zoom;
scrollFactor.scale(1 / diff);
var r = super.getScreenPosition(point, Camera);
scrollFactor.set(__oldScrollFactor.x, __oldScrollFactor.y);
return r;
}
return super.getScreenPosition(point, Camera);
}
// ANIMATE ATLAS DRAWING
#if REGION
public override function draw()
@@ -236,33 +205,60 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
}
#end
// SCALING FUNCS
#if REGION
private inline function __shouldDoScaleProcedure()
return zoomFactor != 1;
// ZOOM FACTOR
private inline function __shouldDoZoomFactor()
return zoomFactorEnabled && zoomFactor != 1;
static var __oldScrollFactor:FlxPoint = new FlxPoint();
static var __oldScale:FlxPoint = new FlxPoint();
var __skipZoomProcedure:Bool = false;
private function __doPreZoomScaleProcedure(camera:FlxCamera)
public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect
{
if (__skipZoomProcedure = !__shouldDoScaleProcedure())
return;
__oldScale.set(scale.x, scale.y);
var requestedZoom = FlxMath.lerp(initialZoom, camera.zoom, zoomFactor);
var diff = requestedZoom * camera.zoom;
if (camera == null)
camera = FlxG.camera;
scale.scale(diff);
var r = super.getScreenBounds(newRect, camera);
if(__shouldDoZoomFactor()) {
r.x -= camera.width / 2;
r.y -= camera.height / 2;
var ratio = (camera.zoom > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.zoom, 1, zoomFactor));
r.x *= ratio;
r.y *= ratio;
r.width *= ratio;
r.height *= ratio;
r.x += camera.width / 2;
r.y += camera.height / 2;
}
return r;
}
private function __doPostZoomScaleProcedure()
override public function isOnScreen(?camera:FlxCamera):Bool
{
if (__skipZoomProcedure)
return;
scale.set(__oldScale.x, __oldScale.y);
if (forceIsOnScreen)
return true;
if (camera == null)
camera = FlxG.camera;
var bounds = getScreenBounds(_rect, camera);
if (bounds.width == 0 && bounds.height == 0)
return false;
return camera.containsRect(bounds);
}
// ZOOM FACTOR RENDERING
public override function doAdditionalMatrixStuff(matrix:FlxMatrix, camera:FlxCamera)
{
super.doAdditionalMatrixStuff(matrix, camera);
if(__shouldDoZoomFactor()) {
matrix.translate(-camera.width / 2, -camera.height / 2);
var requestedZoom = (camera.zoom >= 0 ? Math.max : Math.min)(FlxMath.lerp(1, camera.zoom, zoomFactor), 0);
var diff = requestedZoom / camera.zoom;
matrix.scale(diff, diff);
matrix.translate(camera.width / 2, camera.height / 2);
}
}
#end
// OFFSETTING
#if REGION
@@ -350,6 +346,11 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
public inline function isAnimFinished() {
return animateAtlas != null ? (animateAtlas.anim.finished) : (animation.curAnim != null ? animation.curAnim.finished : true);
}
override function updateAnimation(elapsed:Float) {
if (animEnabled)
super.updateAnimation(elapsed);
}
#end
// Getter / Setters
+2 -3
View File
@@ -97,13 +97,12 @@ class Paths
return getPath('images/$key.$ext', library);
}
inline static public function script(key:String, ?library:String, isAssetsPath:Bool = false) {
static public function script(key:String, ?library:String, isAssetsPath:Bool = false) {
var scriptPath = isAssetsPath ? key : getPath(key, library);
if (!OpenFlAssets.exists(scriptPath)) {
var p:String;
for(ex in Script.scriptExtensions) {
p = '$scriptPath.$ex';
if (OpenFlAssets.exists(p)) {
if (OpenFlAssets.exists(p = scriptPath + '.' + ex)) {
scriptPath = p;
break;
}
+1 -1
View File
@@ -76,7 +76,7 @@ class EventsData {
for (file in Paths.getFolderContent('data/events/', true, BOTH)) {
if (Path.extension(file) != "json" && Path.extension(file) != "pack") continue;
var eventName:String = Path.withoutExtension(Path.withoutDirectory(file));
var eventName:String = CoolUtil.getFilename(file);
var fileTxt:String = Assets.getText(file);
if (Path.extension(file) == "pack") {
@@ -0,0 +1,38 @@
package funkin.backend.system;
import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxFrame;
import flixel.graphics.tile.FlxDrawTrianglesItem;
import flixel.math.FlxMatrix;
import flixel.math.FlxPoint;
import flixel.system.FlxAssets.FlxShader;
import openfl.display.BitmapData;
import openfl.display.BlendMode;
import openfl.geom.ColorTransform;
import openfl.geom.Point;
import openfl.geom.Rectangle;
class FakeCamera extends FlxCamera {
public static final instance = new FakeCamera();
public function new() {
super();
visible = false;
}
override function startTrianglesBatch(graphic:FlxGraphic, smoothing:Bool = false, isColored:Bool = false, ?blend:BlendMode, ?hasColorOffsets:Bool, ?shader:FlxShader) { return null;}
override function startQuadBatch(graphic:FlxGraphic, colored:Bool, hasColorOffsets:Bool = false, ?blend:BlendMode, smooth:Bool = false, ?shader:FlxShader) { return null;}
override function clearDrawStack() {}
override function render() {}
public override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {}
public override function copyPixels(?frame:FlxFrame, ?pixels:BitmapData, ?sourceRect:Rectangle, destPoint:Point, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {}
public override function drawTriangles(graphic:FlxGraphic, vertices:DrawData<Float>, indices:DrawData<Int>, uvtData:DrawData<Float>, ?colors:DrawData<Int>, ?position:FlxPoint, ?blend:BlendMode, repeat:Bool = false, smoothing:Bool = false, ?transform:ColorTransform, ?shader:FlxShader):Void {}
public override function update(elapsed:Float) {}
// override function set_zoom(Zoom:Float):Float return zoom = (Zoom == 0) ? FlxCamera.defaultZoom : Zoom;
public override function setScale(X:Float, Y:Float):Void {}
}
@@ -17,7 +17,7 @@ class SystemInfo extends FramerateCategory {
static var __formattedSysText:String = "";
public static inline function init() {
public static function init() {
#if linux
var process = new HiddenProcess("cat", ["/etc/os-release"]);
if (process.exitCode() != 0) Logs.trace('Unable to grab OS Label', ERROR, RED);
+31 -7
View File
@@ -133,10 +133,8 @@ class CoolUtil
* Whenever a value is NaN or not.
* @param v Value
*/
public static inline function isNaN(v:Dynamic) {
if (v is Float || v is Int)
return Math.isNaN(cast(v, Float));
return false;
public static inline function isNaN(v:Dynamic):Bool {
return (v is Float) ? Math.isNaN(cast(v, Float)) : false;
}
/**
@@ -185,20 +183,21 @@ class CoolUtil
return str;
}
private static var sizeLabels:Array<String> = ["B", "KB", "MB", "GB", "TB"];
/**
* Returns a string representation of a size, following this format: `1.02 GB`, `134.00 MB`
* @param size Size to convert to string
* @return String Result string representation
*/
public static function getSizeString(size:Float):String {
var labels = ["B", "KB", "MB", "GB", "TB"];
var rSize:Float = size;
var label:Int = 0;
while(rSize > 1024 && label < labels.length-1) {
while(rSize > 1024 && label < sizeLabels.length-1) {
label++;
rSize /= 1024;
}
return '${Std.int(rSize) + "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2)}${labels[label]}';
return Std.int(rSize) + "." + addZeros(Std.string(Std.int((rSize % 1) * 100)), 2) + sizeLabels[label];
}
/**
@@ -416,6 +415,21 @@ class CoolUtil
sprite.scale.set(nScale, nScale);
}
public static function setGraphicSizeFloat(sprite:FlxSprite, Width:Float = 0, Height:Float = 0):Void
{
if (Width <= 0 && Height <= 0)
return;
var newScaleX:Float = Width / sprite.frameWidth;
var newScaleY:Float = Height / sprite.frameHeight;
sprite.scale.set(newScaleX, newScaleY);
if (Width <= 0)
sprite.scale.x = newScaleY;
else if (Height <= 0)
sprite.scale.y = newScaleX;
}
/**
* Returns a simple string representation of a FlxKey. Used in Controls options.
* @param key Key
@@ -705,6 +719,16 @@ class CoolUtil
@:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String)
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix));
/*
* Returns the filename of a path, without the extension.
* @param path Path to get the filename from
* @return Filename
*/
@:noUsing public static inline function getFilename(file:String) {
var file = new haxe.io.Path(file);
return file.file;
}
public static function sortAlphabetically(array:Array<String>, ?lowercase:Bool=false) {
array.sort(function(a1, a2):Int {
if(lowercase) {
+42
View File
@@ -0,0 +1,42 @@
package funkin.backend.utils;
import flixel.FlxCamera;
import flixel.math.FlxPoint;
import flixel.util.typeLimit.OneOfTwo;
class MatrixUtil {
public static function getMatrixPosition(sprite:FlxSprite, points:OneOfTwo<FlxPoint, Array<FlxPoint>>, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1):Array<FlxPoint>
{
//if(_width == -1) _width = sprite.width;
//if(_height == -1) _height = sprite.height;
if(camera == null) camera = sprite.camera;
if(points is FlxBasePoint) points = [points];
var nc = funkin.backend.system.FakeCamera.instance;
nc.zoom = camera.zoom;
nc.scroll.set(camera.scroll.x, camera.scroll.y);
nc.pixelPerfectRender = camera.pixelPerfectRender;
@:privateAccess sprite.drawComplex(nc);
var isFunkinSprite = sprite is FunkinSprite;
var points:Array<FlxPoint> = cast points;
@:privateAccess for(point in points) {
var x = sprite._matrix.__transformX(point.x * _width, point.y * _height);
var y = sprite._matrix.__transformY(point.x * _width, point.y * _height);
// reset to ingame coords
x += camera.scroll.x;
y += camera.scroll.y;
if(isFunkinSprite) {
var sprite:FunkinSprite = cast sprite;
var ratio = 1 - FlxMath.lerp(1 / camera.zoom, 1, sprite.zoomFactor);
x += camera.width / 2 * ratio;
y += camera.height / 2 * ratio;
}
point.set(x, y);
}
return points;
}
}
+112
View File
@@ -1,5 +1,8 @@
package funkin.backend.utils;
import haxe.macro.Expr.Case;
import openfl.ui.Mouse;
import openfl.ui.MouseCursor;
import funkin.backend.utils.native.*;
/**
@@ -72,6 +75,17 @@ class NativeAPI {
#end
}
/**
* Set cursor icon.
**/
public static function setCursorIcon(icon:CodeCursor) {
#if mac
Mac.setMouseCursorIcon(cast icon);
#else
Mouse.cursor = icon.toOpenFL();
#end
}
public static function consoleColorToANSI(color:ConsoleColor) {
return switch(color) {
case BLACK: 30;
@@ -141,4 +155,102 @@ enum abstract MessageBoxIcon(Int) {
var MSG_QUESTION = 0x00000020;
var MSG_WARNING = 0x00000030;
var MSG_INFORMATION = 0x00000040;
}
enum abstract CodeCursor(String) {
var CUSTOM;// = "arrow";
var ARROW;// = "arrow";
var CLICK;// = "click";
var CROSSHAIR;// = "crosshair";
var HAND;// = "hand";
var IBEAM;// = "ibeam";
var MOVE;// = "move";
var RESIZE_H;// = "resize_we";
var RESIZE_V;// = "resize_ns";
var RESIZE_TL;// = "resize_nw";
var RESIZE_TR;// = "resize_ne";
var RESIZE_BL;// = "resize_sw";
var RESIZE_BR;// = "resize_se";
var RESIZE_T;// = "resize_n";
var RESIZE_B;// = "resize_b";
var RESIZE_L;// = "resize_w";
var RESIZE_R;// = "resize_e";
var RESIZE_TLBR;// = "resize_nw_se";
var RESIZE_TRBL;// = "resize_ne_sw";
var WAIT;// = "wait";
var WAIT_ARROW;// = "waitarrow";
var DISABLED;// = "disabled";
var DRAG;// = "drag";
var DRAG_OPEN;// = "dragopen";
@:to public function toOpenFL():MouseCursor {
return @:privateAccess switch(cast this) {
case ARROW: MouseCursor.ARROW;
case CROSSHAIR: MouseCursor.__CROSSHAIR;
case CLICK: MouseCursor.BUTTON;
case IBEAM: MouseCursor.IBEAM;
case MOVE: MouseCursor.__MOVE;
case HAND: MouseCursor.HAND;
case DRAG: MouseCursor.HAND;
case DRAG_OPEN: MouseCursor.ARROW; // Could be HAND, but it might be better to use ARROW since on windows it would be weird to have the dragging cursor
case WAIT: MouseCursor.__WAIT;
case WAIT_ARROW: MouseCursor.__WAIT_ARROW;
case DISABLED: MouseCursor.ARROW;
case RESIZE_TR: MouseCursor.__RESIZE_NESW;
case RESIZE_BL: MouseCursor.__RESIZE_NESW;
case RESIZE_TL: MouseCursor.__RESIZE_NWSE;
case RESIZE_BR: MouseCursor.__RESIZE_NWSE;
case RESIZE_H: MouseCursor.__RESIZE_WE;
case RESIZE_V: MouseCursor.__RESIZE_NS;
case RESIZE_T: MouseCursor.__RESIZE_NS;
case RESIZE_B: MouseCursor.__RESIZE_NS;
case RESIZE_L: MouseCursor.__RESIZE_WE;
case RESIZE_R: MouseCursor.__RESIZE_WE;
case RESIZE_TLBR: MouseCursor.__RESIZE_NWSE;
case RESIZE_TRBL: MouseCursor.__RESIZE_NESW;
case CUSTOM: MouseCursor.__CUSTOM;
//default: ARROW;
}
}
@:to public function toInt():Int {
return switch(cast this) {
case ARROW: 0;
case CROSSHAIR: 1;
case CLICK: 2;
case IBEAM: 3;
case MOVE: 4;
case HAND: 5;
case DRAG: 6;
case DRAG_OPEN: 7;
case WAIT: 8;
case WAIT_ARROW: 9;
case DISABLED: 10;
case RESIZE_TR: 11;
case RESIZE_BL: 12;
case RESIZE_TL: 13;
case RESIZE_BR: 14;
case RESIZE_H: 15;
case RESIZE_V: 16;
case RESIZE_T: 17;
case RESIZE_B: 18;
case RESIZE_L: 19;
case RESIZE_R: 20;
case RESIZE_TLBR: 21;
case RESIZE_TRBL: 22;
case CUSTOM: -1;
}
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ class WindowUtils {
static var __triedClosing:Bool = false;
public static inline function resetClosing() __triedClosing = false;
public static inline function init() {
public static function init() {
resetTitle();
resetClosing();
+5
View File
@@ -150,10 +150,15 @@ class XMLUtil {
if(node.has.color)
spr.color = FlxColor.fromString(node.getAtt("color")).getDefault(0xFFFFFFFF);
if(node.has.angle)
spr.angle = Std.parseFloat(node.getAtt("angle")).getDefault(spr.angle);
if (node.has.playOnCountdown)
spr.skipNegativeBeats = node.att.playOnCountdown == "true";
if (node.has.beatInterval)
spr.beatInterval = Std.parseInt(node.att.beatInterval);
if (node.has.interval)
spr.beatInterval = Std.parseInt(node.att.interval);
if (node.has.beatOffset)
spr.beatOffset = Std.parseInt(node.att.beatOffset);
+41 -3
View File
@@ -1,8 +1,12 @@
package funkin.backend.utils.native;
#if mac
@:cppFileCode("#include <sys/sysctl.h>")
class Mac {
import funkin.backend.utils.NativeAPI.CodeCursor;
import openfl.ui.Mouse;
@:headerInclude('sys/sysctl.h')
class Mac
{
@:functionCode('
int mib [] = { CTL_HW, HW_MEMSIZE };
int64_t value = 0;
@@ -17,5 +21,39 @@ class Mac {
{
return 0;
}
public static function setMouseCursorIcon(icon:CodeCursor):Void
{
final valid:Bool = external.ExternalMac.setCursorIcon(icon.toInt(), null, 0, 0);
if (!valid)
Mouse.cursor = icon.toOpenFL();
}
}
#end
#end
/*
Cursor
Description
The arrow cursor (arrow)
The I-beam cursor for indicating insertion points (iBeam)
The cross-hair cursor (crosshair)
The closed-hand cursor (closedHand)
The open-hand cursor (openHand)
The pointing-hand cursor (pointingHand)
The resize-left cursor (resizeLeft)
The resize-right cursor (resizeRight)
The resize-left-and-right cursor (resizeLeftRight)
The resize-up cursor (resizeUp)
The resize-down cursor (resizeDown)
The resize-up-and-down cursor (resizeUpDown)
The disappearing item cursor (disappearingItem)
The I-Beam text cursor for vertical layout (iBeamCursorForVerticalLayout).
The not allowed cursor (operationNotAllowed).
The drag link cursor (dragLink).
The drag copy cursor (dragCopy).
The contextual menu cursor (contextualMenu).
*/
+1 -1
View File
@@ -20,7 +20,7 @@ class EditorPicker extends MusicBeatSubstate {
{
name: "Stage Editor",
iconID: 2,
state: null
state: funkin.editors.stage.StageSelection
},
{
name: "UI Debug State",
@@ -44,7 +44,10 @@ class CharacterAnimButtons extends UIButton {
editButton.autoAlpha = false;
members.push(editButton);
editIcon = new FlxSprite(editButton.x + 8, editButton.y + 8).loadGraphic(Paths.image('editors/character/edit-button'));
editIcon = new FlxSprite(editButton.x + 8, editButton.y + 8).loadGraphic(Paths.image('editors/character/edit-button'), true, 16, 16);
editIcon.animation.add("edit", [0]);
editIcon.animation.add("advanced", [1]);
editIcon.animation.play("edit");
editIcon.antialiasing = false;
members.push(editIcon);
+2 -2
View File
@@ -804,7 +804,7 @@ class Charter extends UIState {
}
if (s is CharterNote) cast(s, CharterNote).snappedToStrumline = true;
if (s is UISprite) cast(s, UISprite).cursor = BUTTON;
if (s is UISprite) cast(s, UISprite).cursor = CLICK;
}
if (!(verticalChange == 0 && horizontalChange == 0)) {
notesGroup.sortNotes(); eventsGroup.sortEvents();
@@ -866,7 +866,7 @@ class Charter extends UIState {
case SUSTAIN_DRAG:
selectionDragging = FlxG.mouse.pressed;
if (selectionDragging) {
currentCursor = BUTTON;
currentCursor = CLICK;
selection.loop(function (n:CharterNote) {
var change:Float = Math.max((mousePos.y-(FlxG.keys.pressed.SHIFT ? dragStartPos.y : quantStep(dragStartPos.y))) / 40, -n.susLength);
n.tempSusLength = change;
@@ -23,7 +23,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
this.step = step;
this.events = events.getDefault([]);
cursor = BUTTON;
cursor = CLICK;
}
public override function update(elapsed:Float) {
@@ -13,7 +13,7 @@ class CharterEventAdd extends UISliceSprite {
text = new UIText(0, 0, 0, "");
members.push(text);
cursor = BUTTON;
cursor = CLICK;
}
public override function onHovered() {
+2 -2
View File
@@ -44,7 +44,7 @@ class CharterNote extends UISprite implements ICharterSelectable {
typeText = new UIText(x, y, 0, Std.string(type));
cursor = sustainSpr.cursor = BUTTON;
cursor = sustainSpr.cursor = CLICK;
moves = false;
}
@@ -139,7 +139,7 @@ class CharterNote extends UISprite implements ICharterSelectable {
sustainDraggable = false;
if (!hovered && susLength != 0) {
UIState.state.updateSpriteRect(sustainSpr);
sustainSpr.updateSpriteRect();
sustainDraggable = UIState.state.isOverlapping(sustainSpr, @:privateAccess sustainSpr.__rect);
}
@@ -64,7 +64,7 @@ class CharterStrumline extends UISprite {
draggingSprite.alpha = 0.4;
draggingSprite.y = 9;
draggingSprite.antialiasing = true;
draggingSprite.cursor = BUTTON;
draggingSprite.cursor = CLICK;
members.push(draggingSprite);
button = new CharterStrumlineOptions(this);
@@ -85,7 +85,7 @@ class CharterStrumline extends UISprite {
healthIcons.follow(this, ((40 * 4) - healthIcons.width) / 2, 7 + (__healthYOffset = FlxMath.lerp(__healthYOffset, draggable ? 8 : 0, 1/20)));
draggingSprite.selectable = draggable;
UIState.state.updateSpriteRect(draggingSprite);
draggingSprite.updateSpriteRect();
var dragScale:Float = FlxMath.lerp(draggingSprite.scale.x, draggable ? 1 : 0.8, 1/16);
draggingSprite.scale.set(dragScale, dragScale);
@@ -24,7 +24,7 @@ class CharterStrumlineButton extends UISprite {
button.scale.set(0.85,0.85);
button.updateHitbox();
button.antialiasing = true;
button.cursor = BUTTON;
button.cursor = CLICK;
members.push(button);
this.text = new UIText(0,0, 160, text);
@@ -58,7 +58,7 @@ class CharterStrumlineButton extends UISprite {
super.update(elapsed);
UIState.state.updateSpriteRect(button);
button.updateSpriteRect();
if(UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect)) {
buttonScale.set(0.95, 0.95);
if (FlxG.mouse.justPressed && onClick != null) {
+19 -11
View File
@@ -6,28 +6,36 @@ class PropertyButton extends UIButton {
public var deleteButton:UIButton;
public var deleteIcon:FlxSprite;
public function new(property, value, parent) {
super(0, 0, '', function () {}, 280, 35);
members.push(propertyText = new UITextBox(5, 5, property, 100, 25));
members.push(valueText = new UITextBox(propertyText.x + propertyText.bWidth + 10, 5, value, 135, 25));
public function new(property, value, parent, width:Int = 280, height:Int = 35, nameWidth:Int = 100, valueWidth:Int = 135, inputHeight:Int = 25) {
super(0, 0, '', function () {}, width, height);
members.push(propertyText = new UITextBox(5, 5, property, nameWidth, inputHeight));
members.push(valueText = new UITextBox(propertyText.x + propertyText.bWidth + 10, 5, value, valueWidth, inputHeight));
deleteButton = new UIButton(valueText.x + 135, bHeight/2 - (25/2), "", function () {
var deleteSize = height - 5 * 2;
deleteButton = new UIButton(valueText.x + 135, bHeight/2 - (deleteSize/2), "", function () {
parent.remove(this);
}, 25, 25);
}, deleteSize, deleteSize);
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 = new FlxSprite(deleteButton.x, deleteButton.y).loadGraphic(Paths.image('editors/delete-button'));
deleteIcon.antialiasing = false;
members.push(deleteIcon);
updatePos();
}
public override function update(elapsed) {
super.update(elapsed);
propertyText.follow(this, 5, 5);
valueText.follow(this, propertyText.x + propertyText.bWidth, 5);
deleteButton.follow(this, valueText.x + 135, bHeight/2 - (25/2));
deleteIcon.follow(deleteButton, 5, 5);
updatePos();
}
public function updatePos() {
propertyText.follow(this, 5, bHeight/2 - (propertyText.bHeight/2));
valueText.follow(this, propertyText.x + propertyText.bWidth, bHeight/2 - (valueText.bHeight/2));
//deleteButton.follow(this, valueText.x + 135, bHeight/2 - (25/2));
deleteButton.follow(this, bWidth - deleteButton.bWidth - 5, bHeight/2 - deleteButton.bHeight/2);
deleteIcon.follow(deleteButton, deleteButton.bWidth / 2 - deleteIcon.width / 2, deleteButton.bHeight / 2 - deleteIcon.height / 2);
}
}
+1
View File
@@ -3,6 +3,7 @@ package funkin.editors;
import flixel.math.FlxPoint;
import funkin.editors.ui.*;
import funkin.editors.ui.notifications.*;
using funkin.editors.ui.UIUtil;
#end
@@ -0,0 +1,62 @@
package funkin.editors.stage;
typedef StageCreationData = {
var name:String;
var path:String;
}
class StageCreationScreen extends UISubstateWindow {
private var onSave:Null<StageCreationData> -> Void = null;
public var stageNameTextBox:UITextBox;
public var stagePathTextBox:UITextBox;
public var voicesExplorer:UIFileExplorer;
public var saveButton:UIButton;
public var closeButton:UIButton;
public function new(?onSave:StageCreationData->Void) {
super();
if (onSave != null) this.onSave = onSave;
}
public override function create() {
winTitle = "Creating New Stage";
winWidth = 748 - 32 + 40;
winHeight = 220;
super.create();
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;
}
var stageInfo:UIText;
add(stageInfo = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Stage Info", 28));
add(stageNameTextBox = new UITextBox(stageInfo.x, stageInfo.y + stageInfo.height + 36, "Stage Name"));
addLabelOn(stageNameTextBox, "Stage Name");
add(stagePathTextBox = new UITextBox(stageNameTextBox.x + 320 + 26, stageNameTextBox.y, "Stage Path"));
addLabelOn(stagePathTextBox, "Stage Path");
add(saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
saveStageInfo();
close();
}, 125));
add(closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", close, 125));
closeButton.color = 0xFFFF0000;
}
function saveStageInfo() {
if (onSave != null) onSave({
name: stageNameTextBox.label.text,
path: stagePathTextBox.label.text
});
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,65 @@
package funkin.editors.stage;
import funkin.editors.stage.StageCreationScreen.StageCreationData;
import funkin.game.Stage;
import funkin.options.OptionsScreen;
import funkin.options.type.NewOption;
import funkin.options.type.OptionType;
import funkin.options.type.TextOption;
class StageSelection extends EditorTreeMenu
{
public var stages:Array<String> = [];
public override function create()
{
bgType = "charter";
super.create();
var modsList:Array<String> = Stage.getList(true, true);
var list:Array<OptionType> = [
for (stage in (modsList.length == 0 ? Stage.getList(false, true) : modsList)) {
stages.push(stage);
new TextOption(stage, "Press ACCEPT to edit this stage.",
function() {
FlxG.switchState(new StageEditor(stage));
});
}
];
list.insert(0, new NewOption("New Stage", "New Stage", function() {
FlxG.state.openSubState(new StageCreationScreen(saveStage));
}));
main = new OptionsScreen("Stage Editor", "Select a stage to edit. HSCRIPT only stages are not supported.", list);
//DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]);
}
public function saveStage(creation:StageCreationData) {
if ([for (s in stages) s.toLowerCase()].contains(creation.name.toLowerCase())) {
openSubState(new UIWarningSubstate("Creating Stage: Error!", "The stage you are trying to create alreadly exists, if you would like to override it delete the stage first!", [
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
]));
return;
}
#if sys
// Save File
CoolUtil.safeSaveFile('${Paths.getAssetsRoot()}/data/stages/${creation.name}.xml', '<!DOCTYPE codename-engine-stage>\n<stage folder="${creation.path}">\n</stage>');
#end
// Add to List
var option = new TextOption(creation.name, "Press ACCEPT to edit this stage.", function() {
FlxG.switchState(new StageEditor(creation.name));
});
optionsTree.members[optionsTree.members.length-1].insert(1, option);
}
override function createPost() {
super.createPost();
main.changeSelection(1);
}
}
@@ -0,0 +1,119 @@
package funkin.editors.stage.elements;
import funkin.editors.stage.StageEditor.StageXMLEditScreen;
import funkin.game.Character;
import funkin.game.Stage.StageCharPos;
import haxe.xml.Access;
class StageCharacterButton extends StageElementButton {
public var charPos:StageCharPos;
public var char:Character;
public function new(x:Float,y:Float, charPos:StageCharPos, xml:Access) {
this.charPos = charPos;
this.char = charPos.extra.get(StageEditor.exID("char"));
super(x,y, xml);
color = 0xff7aa8ff;
hasAdvancedEdit = true;
updateInfo();
}
public override function update(elapsed:Float) {
super.update(elapsed);
}
public override function updateInfo() {
char.visible = !isHidden;
char.alpha = 0.5 * charPos.alpha;
var charScale = (char.xml.has.scale ? Std.parseFloat(char.xml.att.scale).getDefault(1) : 1);
charPos.scale.x = char.scale.x / charScale;
charPos.scale.y = char.scale.y / charScale;
charPos.x = char.x;
charPos.y = char.y;
char.scale.copyFrom(charPos.scale);
super.updateInfo();
char.scale.scale(charScale);
}
public override function getSprite():FunkinSprite {
return char;
}
public override function onSelect() {
StageEditor.instance.selectSprite(char);
}
/*public override function onSelect() {
UIState.state.displayNotification(new UIBaseNotification("Selecting a character isnt implemented yet!", 2, BOTTOM_LEFT));
CoolUtil.playMenuSFX(WARNING, 0.45);
}*/
public override function onEdit() {
if(!FlxG.keys.pressed.SHIFT) {
FlxG.state.openSubState(new StageCharacterEditScreen(this));
} else {
FlxG.state.openSubState(new StageXMLEditScreen(this.xml, updateInfo, "Character"));
}
}
//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 onVisiblityToggle() {
isHidden = !isHidden;
updateInfo();
}
public override function getName():String {
return charPos.name;
}
public override function getPos():FlxPoint {
return FlxPoint.get(charPos.x, charPos.y);
}
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", [
"stage" => StageEditor.instance.stage,
"char" => char,
"charPos" => charPos,
"button" => button,
"xml" => button.xml,
"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);
charPos.extra.set(StageEditor.exID(name), value);
},
]);
}
public override function create() {
super.create();
}
public override function saveData() {
super.saveData();
button.updateInfo();
}
}
@@ -0,0 +1,218 @@
package funkin.editors.stage.elements;
import flixel.util.FlxColor;
import haxe.xml.Access;
using flixel.util.FlxColorTransformUtil;
class StageElementButton extends UIButton {
public var xml:Access;
public var editButton:UIButton;
public var editIcon:FlxSprite;
public var visibilityButton:UIButton;
public var visibilityIcon:FlxSprite;
public var deleteButton:UIButton;
public var deleteIcon:FlxSprite;
public var isHidden:Bool = false;
public var hasAdvancedEdit:Bool = false;
public var selected:Bool = false;
public var tagColor:UISliceSprite;
public function new(x:Float,y:Float, xml:Access) {
this.xml = xml;
super(x,y, getInfoText(), function () {
onSelect();
}, StageEditor.SPRITE_WINDOW_WIDTH, StageEditor.SPRITE_WINDOW_BUTTON_HEIGHT);
autoAlpha = false;
tagColor = new UISliceSprite(x, y, 10, StageEditor.SPRITE_WINDOW_BUTTON_HEIGHT, 'editors/ui/button');
tagColor.alpha = 1; // Make entire sprite transparent
tagColor.selectable = false;
tagColor.active = false;
//members.push(tagColor);
topAlpha = middleAlpha = bottomAlpha = 0.7;
field.alignment = LEFT;
visibilityButton = new UIButton(x+282+17, y, "", function () {
onVisiblityToggle();
}, 32);
visibilityButton.autoAlpha = false;
members.push(visibilityButton);
visibilityIcon = new FlxSprite(visibilityButton.x + 8, visibilityButton.y + 8).loadGraphic(Paths.image('editors/stage/visible-icon'), true, 16, 16);
visibilityIcon.animation.add("visible", [0]);
visibilityIcon.animation.add("invisible", [1]);
visibilityIcon.animation.play("visible");
visibilityIcon.antialiasing = false;
visibilityIcon.updateHitbox();
members.push(visibilityIcon);
editButton = new UIButton(visibilityButton.x+32+17, y, "", function () {
onEdit();
}, 32);
editButton.frames = Paths.getFrames("editors/ui/grayscale-button");
editButton.color = FlxColor.YELLOW;
editButton.autoAlpha = false;
members.push(editButton);
editIcon = new FlxSprite(editButton.x + 8, editButton.y + 8).loadGraphic(Paths.image('editors/character/edit-button'), true, 16, 16);
editIcon.animation.add("edit", [0]);
editIcon.animation.add("advanced", [1]);
editIcon.antialiasing = false;
members.push(editIcon);
deleteButton = new UIButton(editButton.x+32+17, y, "", function () {
onDelete();
}, 32);
deleteButton.color = FlxColor.RED;
deleteButton.autoAlpha = false;
members.push(deleteButton);
deleteIcon = new FlxSprite(deleteButton.x + 8, deleteButton.y + 8).loadGraphic(Paths.image('editors/stage/trash-icon'));
deleteIcon.antialiasing = false;
members.push(deleteIcon);
setEditNormal();
}
public function setEditNormal() {
editIcon.animation.play("edit");
editButton.color = FlxColor.YELLOW;
}
public function setEditAdvanced() {
editIcon.animation.play("advanced");
editButton.color = 0xFFFF5B0F;
}
var _lastSelected:Bool = false;
public override function update(elapsed:Float) {
editButton.selectable = visibilityButton.selectable = deleteButton.selectable = selectable;
editButton.shouldPress = visibilityButton.shouldPress = deleteButton.shouldPress = shouldPress;
hovered = !deleteButton.hovered;
updatePos();
super.update(elapsed);
field.x += 12;
if(hasAdvancedEdit) {
if(FlxG.keys.pressed.SHIFT) {
setEditAdvanced();
} else {
setEditNormal();
}
}
tagColor.color = color;
}
//private var _lastHovered:Bool = false;
public override function draw() {
//if(_lastHovered != (_lastHovered = hovered && !pressed)) {
if(_lastSelected != (_lastSelected = selected)) {
updateColorTransform();
}
super.draw();
}
/*public override function setFrameOffset() {
var _frameOffset = 0;
if(selected) _frameOffset = 9;
if(hovered && pressed) _frameOffset = 18;
framesOffset = _frameOffset;
}*/
public override function updateColorTransform() {
super.updateColorTransform();
if(selected) {
useColorTransform = true;
colorTransform.setOffsets(70, 70, 70, 0);
} else {
colorTransform.setOffsets(0, 0, 0, 0);
}
}
public function updateInfo() {
field.text = getInfoText();
visibilityIcon.animation.play(isHidden ? "invisible" : "visible");
visibilityIcon.alpha = !isHidden ? 1 : 0.5;
}
public function updatePos() {
// buttons
var spacing = 8;
var buttonY = y + (bHeight - 32) / 2;
deleteButton.x = bWidth - deleteButton.bWidth - spacing;
deleteButton.y = buttonY;
editButton.x = deleteButton.x - editButton.bWidth - spacing;
editButton.y = buttonY;
visibilityButton.x = editButton.x - visibilityButton.bWidth - spacing;
visibilityButton.y = buttonY;
//deleteButton.x = (editButton.x = (visibilityButton.x = (x+282+17))+32+17)+32+17;
//deleteButton.y = editButton.y = visibilityButton.y = y;
// icons
visibilityIcon.x = visibilityButton.x + 8; visibilityIcon.y = visibilityButton.y + 8;
editIcon.x = editButton.x + 8; editIcon.y = editButton.y + 8;
deleteIcon.x = deleteButton.x + 8; deleteIcon.y = deleteButton.y + 8;
tagColor.x = x;// + bWidth - tagColor.bWidth;
tagColor.y = y;
}
public function getSprite():FunkinSprite {
return null;
}
public function canRender() {
return true;
}
public function getName():String {
return "UNKNOWN";
}
public function onSelect() {
// TODO: implement
}
public function onVisiblityToggle() {
// TODO: implement
}
public function onEdit() {
// TODO: implement
}
public function onDelete() {
// TODO: implement
}
public function getPos():FlxPoint {
return FlxPoint.get(-1, -1);
}
public function getInfoText():String {
var pos = getPos();
var text = '${getName()} (${CoolUtil.quantize(pos.x, 100)}, ${CoolUtil.quantize(pos.y, 100)})';
var sprite = getSprite();
if(sprite != null) {
text += '\nScale: (${CoolUtil.quantize(sprite.scale.x, 100)}, ${CoolUtil.quantize(sprite.scale.y, 100)})';
text += '\nScroll: (${CoolUtil.quantize(sprite.scrollFactor.x, 100)}, ${CoolUtil.quantize(sprite.scrollFactor.y, 100)})';
}
pos.put();
return text;
}
}
@@ -0,0 +1,16 @@
package funkin.editors.stage.elements;
import flixel.util.FlxColor;
import haxe.xml.Access;
class StageSolidButton extends StageSpriteButton {
public function new(x:Float,y:Float, sprite:FunkinSprite, xml:Access) {
super(x,y, sprite, xml);
color = 0xFFD9FF50;
}
public override function onEdit() {
// TODO: implement
}
}
@@ -0,0 +1,114 @@
package funkin.editors.stage.elements;
import funkin.editors.stage.StageEditor.StageXMLEditScreen;
import funkin.editors.stage.elements.StageElementButton;
import funkin.editors.ui.UISoftcodedWindow;
import haxe.xml.Access;
class StageSpriteButton extends StageElementButton {
public var sprite:FunkinSprite;
public function new(x:Float,y:Float, sprite:FunkinSprite, xml:Access) {
this.sprite = sprite;
super(x,y, xml);
//color = 0xFFD9FF50;
hasAdvancedEdit = true;
updateInfo();
}
public override function update(elapsed:Float) {
super.update(elapsed);
}
public override function updateInfo() {
sprite.visible = !isHidden;
super.updateInfo();
}
public override function getSprite():FunkinSprite {
return sprite;
}
public override function onSelect() {
StageEditor.instance.selectSprite(sprite);
}
public override function onVisiblityToggle() {
isHidden = !isHidden;
updateInfo();
}
public override function onEdit() {
if(!FlxG.keys.pressed.SHIFT) {
FlxG.state.openSubState(new StageSpriteEditScreen(this));
} else {
FlxG.state.openSubState(new StageXMLEditScreen(this.xml, updateInfo, "Sprite"));
}
}
public override function onDelete() {
sprite.destroy();
xml.x.parent.removeChild(xml.x);
StageEditor.instance.stage.stageSprites.remove(sprite.name);
StageEditor.instance.xmlMap.remove(sprite);
StageEditor.instance.stageSpritesWindow.remove(this);
}
public override function getName():String {
return xml.att.name;
}
public override function getPos():FlxPoint {
return FlxPoint.get(sprite.x, sprite.y);
}
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", [
"stage" => StageEditor.instance.stage,
"sprite" => sprite,
"button" => button,
"xml" => button.xml,
"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();
}
}
@@ -0,0 +1,105 @@
package funkin.editors.stage.elements;
import funkin.game.Character;
import funkin.game.Stage.StageCharPos;
import haxe.xml.Access;
class StageUnknownButton extends StageElementButton {
public var lowMemory:Bool = false;
public var highMemory:Bool = false;
public function new(x:Float,y:Float, xml:Access) {
super(x,y, xml);
if(xml.x.parent != null) {
lowMemory = xml.x.parent.nodeName == "low-memory";
highMemory = xml.x.parent.nodeName == "high-memory";
}
color = 0xff000000;
visibilityButton.exists = false;
members.remove(visibilityButton);
visibilityIcon.exists = false;
members.remove(visibilityIcon);
setEditAdvanced();
updateInfo();
}
public override function update(elapsed:Float) {
super.update(elapsed);
}
public override function updateInfo() {
super.updateInfo();
}
public override function getSprite():FunkinSprite {
var sprite = new FunkinSprite(); // to allow it to be saved
sprite.extra.set(StageEditor.exID("lowMemory"), lowMemory);
sprite.extra.set(StageEditor.exID("highMemory"), highMemory);
sprite.extra.set(StageEditor.exID("button"), this);
return sprite;
}
public override function canRender() {
return false;
}
public override function onSelect() {
}
public override function onEdit() {
FlxG.state.openSubState(new StageUnknownEditScreen(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 onVisiblityToggle() {
}
public override function getName():String {
return xml.name;
}
public override function getPos():FlxPoint {
return FlxPoint.get(0, 0);
}
public override function getInfoText():String {
return 'Unknown\n<${getName()} />';
}
public override function updatePos() {
super.updatePos();
}
}
class StageUnknownEditScreen extends UISoftcodedWindow {
public var button:StageUnknownButton;
public function new(button:StageUnknownButton) {
this.button = button;
super("layouts/stage/unknownEditScreen", [
"stage" => StageEditor.instance.stage,
"button" => button,
"xml" => button.xml,
"exID" => StageEditor.exID
]);
}
public override function create() {
super.create();
}
public override function saveData() {
super.saveData();
button.updateInfo();
}
}
+2 -2
View File
@@ -57,7 +57,7 @@ class UIAudioPlayer extends UIButton {
members.push(timeBarPlayer);
timeBarSpr = cast new UISprite(timeBar.x, timeBar.y).makeSolid(timeBar.barWidth, timeBar.barHeight, 0x00FFFFFF);
timeBarSpr.cursor = BUTTON;
timeBarSpr.cursor = CLICK;
members.push(timeBarSpr);
volumeBar = new FlxBar(timeBar.x + timeBar.barWidth - 56, y + 6, LEFT_TO_RIGHT, 56, 10, sound, "volume", 0, 1);
@@ -67,7 +67,7 @@ class UIAudioPlayer extends UIButton {
members.push(volumeBar);
volumeBarSpr = cast new UISprite(volumeBar.x, volumeBar.y).makeSolid(volumeBar.barWidth, volumeBar.barHeight, 0x00FFFFFF);
volumeBarSpr.cursor = BUTTON;
volumeBarSpr.cursor = CLICK;
members.push(volumeBarSpr);
volumeIcon = new FlxSprite(volumeBar.x - 12 - 8, volumeBar.y-1).loadGraphic(Paths.image('editors/ui/audio-icon'));
+6 -2
View File
@@ -13,7 +13,7 @@ class UIButton extends UISliceSprite {
field.alignment = CENTER;
field.fieldWidth = w;
cursor = BUTTON;
cursor = CLICK;
}
public override function resize(w:Int, h:Int) {
@@ -38,7 +38,11 @@ class UIButton extends UISliceSprite {
}
public override function draw() {
framesOffset = hovered ? (pressed ? 18 : 9) : 0;
setFrameOffset();
super.draw();
}
public function setFrameOffset() {
framesOffset = hovered ? (pressed ? 18 : 9) : 0;
}
}
+6 -5
View File
@@ -14,7 +14,7 @@ class UIButtonList<T:UIButton> extends UIWindow {
public var cameraSpacing = 30;
public var buttonSpacing:Float = 16;
public var buttonSize:FlxPoint = FlxPoint.get();
public var buttonSize:FlxPoint = null;
public var buttonOffset:FlxPoint = FlxPoint.get();
public var dragging:Bool = false;
@@ -41,14 +41,14 @@ class UIButtonList<T:UIButton> extends UIWindow {
addIcon = new FlxSprite(addButton.x + addButton.bHeight / 2, addButton.y + (32/2) - 8).loadGraphic(Paths.image('editors/charter/add-button'));
addIcon.antialiasing = false;
addButton.members.push(addIcon);
members.push(addButton);
content.add(addButton);
members.push(buttons);
content.add(buttons);
nextscrollY = buttonCameras.scroll.y = -this.buttonSpacing;
}
public inline function add(button:T) {
button.ID = buttons.members.length-1;
button.ID = buttons.members.length;
buttons.add(button);
curMovingInterval = 0;
nextscrollY += button.bHeight;
@@ -95,7 +95,8 @@ class UIButtonList<T:UIButton> extends UIWindow {
curMovingInterval = 0;
}
}
addIcon.x = addButton.x + addButton.bWidth / 2 - addIcon.width / 2; addIcon.y = addButton.y + addButton.bHeight / 2 - addIcon.height / 2;
addIcon.x = addButton.x + addButton.bWidth / 2 - addIcon.width / 2;
addIcon.y = addButton.y + addButton.bHeight / 2 - addIcon.height / 2;
}
var nextscrollY:Float = 0;
public override function update(elapsed:Float) {
+3 -3
View File
@@ -10,19 +10,19 @@ class UICheckbox extends UISprite {
public function new(x:Float, y:Float, text:String, checked:Bool = false, w:Int = 0) {
super(x, y);
loadGraphic(Paths.image('editors/ui/checkbox'), true, 20, 20);
for(frame=>name in ["normal", "hover", "pressed", "checkmark"])
for(frame=>name in ["normal", "hover", "pressed", "selected"])
animation.add(name, [frame], 0, false);
this.checked = checked;
field = new UIText(x, y, w, text);
check = new FlxSprite().loadGraphicFromSprite(this);
check.animation.play("checkmark");
check.animation.play("selected");
members.push(check);
members.push(field);
cursor = BUTTON;
cursor = CLICK;
}
public override function update(elapsed:Float) {
+4 -2
View File
@@ -7,6 +7,7 @@ class UIContextMenu extends MusicBeatSubstate {
public var options:Array<UIContextMenuOption>;
var x:Float;
var y:Float;
var w:Int = 100;
var contextCam:FlxCamera;
var bg:UISliceSprite;
@@ -21,11 +22,12 @@ class UIContextMenu extends MusicBeatSubstate {
__oobDeletion = false;
}
public function new(options:Array<UIContextMenuOption>, callback:UIContextMenuCallback, x:Float, y:Float) {
public function new(options:Array<UIContextMenuOption>, callback:UIContextMenuCallback, x:Float, y:Float, ?w:Int = 100) {
super();
this.options = options.getDefault([]);
this.x = x;
this.y = y;
this.w = w;
this.callback = callback;
}
@@ -37,7 +39,7 @@ class UIContextMenu extends MusicBeatSubstate {
contextCam.scroll.set(0, 7.5);
FlxG.cameras.add(contextCam, false);
bg = new UISliceSprite(x, y, 100, 100, 'editors/ui/context-bg');
bg = new UISliceSprite(x, y, w, 100, 'editors/ui/context-bg');
bg.cameras = [contextCam];
add(bg);
+46 -7
View File
@@ -1,34 +1,68 @@
package funkin.editors.ui;
import flixel.util.typeLimit.OneOfTwo;
class UIDropDown extends UISliceSprite {
public var dropButton:UIButton;
public var label:UIText;
public var index:Int = 0;
public var options:Array<String>;
public var items:Array<DropDownItem>;
public var key(get, never):String;
inline function get_key():String {
return items[index].label;
}
public var value(get, never):Dynamic;
inline function get_value():Dynamic {
return items[index].value;
}
public var onChange:Int->Void;
var curMenu:UIContextMenu = null;
public function new(x:Float, y:Float, width:Int = 320, height:Int = 32, options:Array<String>, index:Int = 0) {
public static function getItems(options:Array<String>):Array<DropDownItem> {
return [for(i=>o in options) {label: o, value: i}];
}
public function new(x:Float, y:Float, width:Int = 320, height:Int = 32, _options:Array<OneOfTwo<DropDownItem, String>>, index:Int = 0) {
super(x, y, width - height, height, 'editors/ui/inputbox'); // using same sprite cause fuck you
this.options = options;
var _items:Array<DropDownItem> = [];
if(_options.length > 0) {
if(_options[0] is String) {
_items = getItems(_options);
} else {
_items = cast _options;
}
}
this.items = _items;
this.options = [for(o in _items) o.label]; // backwards compatibility
this.index = index;
cursor = BUTTON;
cursor = CLICK;
label = new UIText(0, 0, width - height, options[index]);
label = new UIText(0, 0, width - height, items[index].label);
members.push(label);
dropButton = new UIButton(0, 0, "V", null, height, height);
members.push(dropButton);
}
public static function indexOfItemValue(items:Array<DropDownItem>, value:Dynamic):Int {
return [for(o in items) o.value].indexOf(value);
}
public static function indexOfItemLabel(items:Array<DropDownItem>, str:String):Int {
return [for(o in items) o.label].indexOf(str);
}
public function setOption(newIndex:Int) {
if (index != (index = newIndex)) {
label.text = options[index];
label.text = items[index].label;
if (onChange != null)
onChange(index);
}
@@ -53,12 +87,17 @@ class UIDropDown extends UISliceSprite {
public function openContextMenu() {
var screenPos = getScreenPosition(null, __lastDrawCameras[0] == null ? FlxG.camera : __lastDrawCameras[0]);
curMenu = UIState.state.openContextMenu([
for(k=>o in options) {
for(k=>o in items) {
icon: (k == index) ? 1 : 0,
label: o
label: o.label
}
], function(_, i, _) {
setOption(i);
}, __lastDrawCameras[0].x + screenPos.x, __lastDrawCameras[0].y + screenPos.y + bHeight);
}
}
typedef DropDownItem = {
var label:String;
var value:Dynamic;
}
+105
View File
@@ -0,0 +1,105 @@
package funkin.editors.ui;
import flixel.util.typeLimit.OneOfTwo;
class UIRadioButton extends UISprite {
public var checked:Bool = false;
public var onChecked:Bool->Void = null;
public var field:UIText;
public var check:FlxSprite;
public var parent:OneOfTwo<UIWindow, UISubstateWindow> = null;
public var forID:String = null;
public function new(x:Float, y:Float, text:String, checked:Bool = false, forID:String = null, w:Int = 0) {
super(x, y);
this.forID = forID;
loadGraphic(Paths.image('editors/ui/radiobutton'), true, 20, 20);
for(frame=>name in ["normal", "hover", "pressed", "selected"])
animation.add(name, [frame], 0, false);
this.checked = checked;
this.targetScale = checked ? 1 : 0;
field = new UIText(x, y, w, text);
check = new FlxSprite().loadGraphicFromSprite(this);
check.animation.play("selected");
check.scale.set(targetScale, targetScale);
members.push(check);
members.push(field);
cursor = CLICK;
autoAlpha = false;
}
public var targetScale:Float = 1;
public override function update(elapsed:Float) {
// ANIMATION HANDLING
animation.play(hovered ? (pressed ? "pressed" : "hover") : "normal");
// CHECKMARK HANDLING
if(autoAlpha)
check.alpha = checked ? 1 : 0;
check.scale.x = CoolUtil.fpsLerp(check.scale.x, targetScale, 0.25);
check.scale.y = CoolUtil.fpsLerp(check.scale.y, targetScale, 0.25);
// POSITION HANDLING
updatePositions();
super.update(elapsed);
}
public inline function updatePositions() {
check.follow(this);
field.follow(this, 25, 0);
}
public override function draw() {
updatePositions();
super.draw();
}
public override function onHovered() {
super.onHovered();
if (FlxG.mouse.justReleased) {
// clicked
checked = !checked;
check.scale.set(1.25, 1.25);
targetScale = checked ? 1 : 0;
if (Options.editorSFX)
CoolUtil.playMenuSFX(checked ? CHECKED : UNCHECKED, 0.5);
if(parent != null) {
var members = (parent is UIWindow) ? (cast(parent, UIWindow)).members : (cast(parent, UISubstateWindow)).members;
var radios:Array<UIRadioButton> = cast members.filter((o) -> o is UIRadioButton && o != this);
//trace(radios);
for(radio in radios) {
if(radio.forID != null && radio.forID == forID) {
radio.checked = false;
radio.targetScale = 0;
}
}
}
if (onChecked != null)
onChecked(checked);
}
}
public override function updateButton() {
__rect.x = x;
__rect.y = y;
__rect.width = field.width + 30;
__rect.height = field.height > height ? field.height : height;
UIState.state.updateRectButtonHandler(this, __rect, onHovered);
}
public override function toString() {
return "RadioButton(" + field.text + ", " + checked + ", " + forID + ")";
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ class UIScrollBar extends UISprite {
this.length = length;
thumb = new UISliceSprite(0, 0, w, h, 'editors/ui/scrollbar');
thumb.cursor = BUTTON;
thumb.cursor = CLICK;
members.push(thumb);
thumbIcon = new FlxSprite(0, 0, Paths.image('editors/ui/scrollbar-icon'));
+59 -11
View File
@@ -5,7 +5,7 @@ import flixel.graphics.frames.FlxFrame;
class UISliceSprite extends UISprite {
public var bWidth:Int = 120;
public var bHeight:Int = 20;
public var framesOffset:Int = 0;
public var framesOffset(default, set):Int = 0;
public var incorporeal:Bool = false;
@@ -14,6 +14,7 @@ class UISliceSprite extends UISprite {
frames = Paths.getFrames(path);
resize(w, h);
calculateFrames();
}
public override function updateButton() {
@@ -30,28 +31,71 @@ class UISliceSprite extends UISprite {
bHeight = h;
}
public var topAlpha:Null<Float> = null;
public var middleAlpha:Null<Float> = null;
public var bottomAlpha:Null<Float> = null;
public var drawTop:Bool = true;
public var drawMiddle:Bool = true;
public var drawBottom:Bool = true;
var topleft:FlxFrame = null;
var top:FlxFrame = null;
var topright:FlxFrame = null;
var middleleft:FlxFrame = null;
var middle:FlxFrame = null;
var middleright:FlxFrame = null;
var bottomleft:FlxFrame = null;
var bottom:FlxFrame = null;
var bottomright:FlxFrame = null;
public var topHeight:Int = 0;
public var bottomHeight:Int = 0;
public var leftWidth:Int = 0;
public var rightWidth:Int = 0;
override function set_frames(val) {
super.set_frames(val);
calculateFrames();
return val;
}
function set_framesOffset(value:Int) {
if(value != framesOffset) {
framesOffset = value;
calculateFrames();
}
return value;
}
function calculateFrames() {
if(frames == null) return;
topleft = frames.frames[framesOffset];
top = frames.frames[framesOffset + 1];
topright = frames.frames[framesOffset + 2];
middleleft = frames.frames[framesOffset + 3];
middle = frames.frames[framesOffset + 4];
middleright = frames.frames[framesOffset + 5];
bottomleft = frames.frames[framesOffset + 6];
bottom = frames.frames[framesOffset + 7];
bottomright = frames.frames[framesOffset + 8];
leftWidth = Std.int(Math.max(topleft.frame.width, Math.max(middleleft.frame.width, bottomleft.frame.width)));
rightWidth = Std.int(Math.max(topright.frame.width, Math.max(middleright.frame.width, bottomright.frame.width)));
topHeight = Std.int(Math.max(topleft.frame.height, Math.max(top.frame.height, topright.frame.height)));
bottomHeight = Std.int(Math.max(topleft.frame.height, Math.max(top.frame.height, topright.frame.height)));
}
public override function draw() @:privateAccess {
var x:Float = this.x;
var y:Float = this.y;
if (visible && !(bWidth == 0 || bHeight == 0)) {
var topleft:FlxFrame = frames.frames[framesOffset];
var top:FlxFrame = frames.frames[framesOffset + 1];
var topright:FlxFrame = frames.frames[framesOffset + 2];
var middleleft:FlxFrame = frames.frames[framesOffset + 3];
var middle:FlxFrame = frames.frames[framesOffset + 4];
var middleright:FlxFrame = frames.frames[framesOffset + 5];
var bottomleft:FlxFrame = frames.frames[framesOffset + 6];
var bottom:FlxFrame = frames.frames[framesOffset + 7];
var bottomright:FlxFrame = frames.frames[framesOffset + 8];
var oldAlpha = alpha;
// TOP
if (drawTop) {
// TOP LEFT
if(topAlpha != null) alpha = topAlpha;
frame = topleft;
setPosition(x, y);
__setSize(
@@ -80,6 +124,7 @@ class UISliceSprite extends UISprite {
// MIDDLE
if (drawMiddle && bHeight > top.frame.height + bottom.frame.height) {
if(middleAlpha != null) alpha = middleAlpha;
var middleHeight:Float = bHeight - (topleft.frame.height * Math.min(bHeight/(topleft.frame.height*2), 1)) -
bottomleft.frame.height * Math.min(bHeight/(bottomleft.frame.height*2), 1);
@@ -106,6 +151,7 @@ class UISliceSprite extends UISprite {
// BOTTOM
if (drawBottom) {
if(bottomAlpha != null) alpha = bottomAlpha;
// BOTTOM LEFT
frame = bottomleft;
setPosition(x, y + bHeight - (bottomleft.frame.height * Math.min(bHeight/(bottomleft.frame.height*2), 1)));
@@ -134,7 +180,9 @@ class UISliceSprite extends UISprite {
bottomright.frame.height * Math.min(bHeight/(bottomright.frame.height*2), 1)
);
super.drawSuper();
}
alpha = oldAlpha;
}
setPosition(x, y);
+3 -3
View File
@@ -45,7 +45,7 @@ class UISlider extends UISprite {
makeGraphic(barWidth, 12, 0x00000000, true);
this.drawRoundRect(0, 0, barWidth, 12, 8, 6, 0xFFC0C0C0);
this.drawRoundRect(1, 1, barWidth-2, 12-2, 8, 5, 0xFF140013);
cursor = BUTTON;
cursor = CLICK;
progressbar = new UISprite(centered ? barWidth/2 : 0);
progressbar.makeGraphic(barWidth, 8, 0x00000000, true);
@@ -63,7 +63,7 @@ class UISlider extends UISprite {
var selectableBar:UISprite = new UISprite(x,y);
selectableBar.loadGraphic(Paths.image("editors/ui/slider"));
selectableBar.antialiasing = true;
selectableBar.cursor = BUTTON;
selectableBar.cursor = CLICK;
members.push(selectableBar);
switch (i) {
@@ -74,7 +74,7 @@ class UISlider extends UISprite {
selectableHitbox = new UISprite(x,y);
selectableHitbox.makeSolid(barWidth, 18, -1);
selectableHitbox.cursor = BUTTON;
selectableHitbox.cursor = CLICK;
selectableHitbox.alpha = 0;
members.push(selectableHitbox);
@@ -0,0 +1,340 @@
package funkin.editors.ui;
import flixel.math.FlxPoint;
import funkin.backend.chart.ChartData.ChartMetaData;
import funkin.backend.scripting.HScript;
import funkin.backend.scripting.Script;
import funkin.editors.extra.PropertyButton;
import funkin.editors.stage.elements.StageElementButton;
import funkin.editors.ui.UIDropDown;
import haxe.xml.Access;
import hscript.*;
import hscript.Expr.Error;
import hscript.Interp;
import hscript.Parser;
import haxe.io.Path;
using StringTools;
class UISoftcodedWindow extends UISubstateWindow {
public var saveButton:UIButton;
public var closeButton:UIButton;
var elementMap:Map<String, UISprite> = [];
var xmlPath:String;
var filename:String;
var customVariables:Map<String, Dynamic>;
public function new(xmlPath:String, customVariables:Map<String, Dynamic> = null) {
super();
this.xmlPath = xmlPath;
this.filename = Path.withoutDirectory(xmlPath);
this.customVariables = customVariables;
}
var parser = HScript.initParser();
var interp = new Interp();
function set(name:String, value:Dynamic) {
interp.variables.set(name, value);
}
function get(name:String):Dynamic {
return interp.variables.get(name);
}
function varExists(name:String):Bool {
return interp.variables.exists(name);
}
function execString(input:String):String {
if(input == null) return null;
if(input.indexOf("${") == -1) return input;
var text = "";
while(input.length > 0) {
var idx = input.indexOf("${");
if(idx == -1) {
text += input;
break;
}
text += input.substring(0, idx);
var ei = input.indexOf("}", idx);
if(ei == -1) {
text += input.substring(idx);
break;
}
var block = input.substring(idx+2, ei);
text += exec(block);
input = input.substring(ei + 1);
}
return text;
}
function execDefault(code:String, defaultValue:Dynamic):Dynamic {
return CoolUtil.getDefault(exec(code), defaultValue);
}
function execAtt(el:Access, key:String, ?defaultValue:Dynamic):Dynamic {
return execDefault(el.getAtt(key), defaultValue);
}
function exec(code:String):Dynamic {
try {
if (code != null && code.trim() != "")
return interp.execute(parser.parseString(code, filename));
} catch(e:Error) {
_errorHandler(e);
} catch(e) {
_errorHandler(new Error(ECustom(e.toString()), 0, 0, filename, 0));
}
return null;
}
function _errorHandler(error:Error) {
var fileName = error.origin;
var fn = '$fileName:${error.line}: ';
var err = error.toString();
if (err.startsWith(fn)) err = err.substr(fn.length);
Logs.traceColored([
Logs.logText(fn, GREEN),
Logs.logText(err, RED)
], ERROR);
}
public override function create() {
var layout = new Access(Xml.parse(Assets.getText(Paths.xml("editors/" + xmlPath))).firstElement());
for(k=>e in Script.getDefaultVariables()) {
set(k, e);
}
set("self", this);
set("hasSaveButton", true);
set("hasCloseButton", true);
set("getRadioButtons", function(forID:String) {
var radios:Array<UIRadioButton> = cast members.filter((o) -> o is UIRadioButton);
return cast radios.filter((o) -> o.forID == forID);
});
for(k=>v in customVariables) {
set(k, v);
}
set("last", null);
if(varExists("winTitle")) winTitle = get("winTitle");
if(varExists("winWidth")) winWidth = get("winWidth");
if(varExists("winHeight")) winHeight = get("winHeight");
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));
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, execAtt(el, "width", 0), execString(el.getAtt("text")), execAtt(el, "size", 28)));
case "label":
cast add(new UIText(x, y - 24, execAtt(el, "width", 0), execString(el.getAtt("text")), execAtt(el, "size", 15)));
case "text":
cast add(new UIText(x, y, execAtt(el, "width", 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;
case "buttonlist":
//x:Float, y:Float, width:Int, height:Int, windowName:String, buttonSize:FlxPoint, ?buttonOffset:FlxPoint, ?buttonSpacing:Float
var buttonlist = new UIButtonList<UIButton>(
x,
y,
execAtt(el, "width"),
execAtt(el, "height"),
execString(el.getAtt("title")).getDefault(""),
FlxPoint.get(execAtt(el, "buttonSizeX", 0), execAtt(el, "buttonSizeY", 0)),
FlxPoint.get(execAtt(el, "buttonOffsetX", 0), execAtt(el, "buttonOffsetY", 0)),
execAtt(el, "buttonSpacing")
);
var old = get("buttonlist");
set("buttonlist", buttonlist);
var texture = "editors/ui/inputbox";
var code = "";
for(node in getArr(el.elements)) {
switch(node.name) {
case "camSpacing": buttonlist.cameraSpacing = execAtt(node, "value", 0);
case "texture": texture = execString(node.getAtt("path")).getDefault(texture);
case "code": code = node.innerData;
}
}
buttonlist.frames = Paths.getFrames(texture);
if(code != "")
exec(code);
add(buttonlist);
addLabelOn(buttonlist, label);
set("buttonlist", old);
buttonlist;
// 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("hasSaveButton")) {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() {
saveData();
close();
}, 125);
saveButton.x -= saveButton.bWidth;
saveButton.y -= saveButton.bHeight;
add(saveButton);
}
if(get("hasCloseButton")) {
var x = saveButton != null ? saveButton.x - 20 : windowSpr.x + windowSpr.bWidth - 20;
var y = saveButton != null ? saveButton.y : windowSpr.y + windowSpr.bHeight - 20;
closeButton = new UIButton(x, y, "Close", function() {
close();
}, 125);
if(saveButton != null)
closeButton.color = 0xFFFF0000;
closeButton.x -= closeButton.bWidth;
if(saveButton == null)
closeButton.y -= closeButton.bHeight;
add(closeButton);
}
}
function getElement(name:String):UISprite {
return elementMap.get(name);
}
function callFunc(name:String, args:Array<Dynamic>) {
if (interp == null) return null;
if (!interp.variables.exists(name)) return null;
var func = interp.variables.get(name);
if (func != null && Reflect.isFunction(func))
return Reflect.callMethod(null, func, args);
return null;
}
override function update(elapsed:Float) {
super.update(elapsed);
callFunc("onUpdate", [elapsed]);
}
public function saveData() {
for (el in elementMap) {
if(el is UINumericStepper) {
var el:UINumericStepper = cast el;
@:privateAccess el.__onChange(el.label.text);
}
}
callFunc("onSave", []);
}
}
+11 -2
View File
@@ -1,6 +1,7 @@
package funkin.editors.ui;
import flixel.math.FlxRect;
import funkin.backend.utils.NativeAPI.CodeCursor;
import openfl.ui.MouseCursor;
@:allow(funkin.editors.ui.UIState)
@@ -21,7 +22,7 @@ class UISprite extends FlxSprite {
public var hoverCallback:Void->Void = null;
public var cursor:MouseCursor = ARROW;
public var cursor:CodeCursor = ARROW;
public var focused(get, set):Bool;
public var selectable:Bool = true;
@@ -55,7 +56,8 @@ class UISprite extends FlxSprite {
FlxCamera._defaultCameras = cameras;
for(m in members)
m.update(elapsed);
if(m.exists && m.active)
m.update(elapsed);
FlxCamera._defaultCameras = __oldDefCams;
}
@@ -114,4 +116,11 @@ class UISprite extends FlxSprite {
if (hoverCallback != null)
hoverCallback();
}
public function updateSpriteRect() {
__rect.x = x;
__rect.y = y;
__rect.width = width;
__rect.height = height;
}
}
+30 -22
View File
@@ -1,8 +1,10 @@
package funkin.editors.ui;
import funkin.editors.ui.notifications.UIBaseNotification;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import funkin.backend.system.framerate.Framerate;
import funkin.backend.utils.NativeAPI.CodeCursor;
import funkin.editors.ui.UIContextMenu.UIContextMenuCallback;
import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
import lime.ui.KeyCode;
@@ -19,7 +21,9 @@ class UIState extends MusicBeatState {
public var hoveredSprite:UISprite = null;
public var currentFocus:IUIFocusable = null;
public var currentCursor:MouseCursor = ARROW;
public var currentCursor:CodeCursor = ARROW;
public var uiCameras:Array<FlxCamera> = [];
private var __rect:FlxRect;
private var __mousePos:FlxPoint;
@@ -59,28 +63,15 @@ class UIState extends MusicBeatState {
currentFocus.onTextEdit(str, start, end);
}
public inline function updateSpriteRect(spr:UISprite) {
spr.__rect.x = spr.x;
spr.__rect.y = spr.y;
spr.__rect.width = spr.width;
spr.__rect.height = spr.height;
}
public function updateButtonHandler(spr:UISprite, buttonHandler:Void->Void) {
spr.__rect.x = spr.x;
spr.__rect.y = spr.y;
spr.__rect.width = spr.width;
spr.__rect.height = spr.height;
spr.updateSpriteRect();
updateRectButtonHandler(spr, spr.__rect, buttonHandler);
}
public function isOverlapping(spr:UISprite, rect:FlxRect) {
for(camera in spr.__lastDrawCameras) {
var pos = FlxG.mouse.getScreenPosition(camera, FlxPoint.get());
__rect.x = rect.x;
__rect.y = rect.y;
__rect.width = rect.width;
__rect.height = rect.height;
__rect.copyFrom(rect);
__rect.x -= camera.scroll.x * spr.scrollFactor.x;
__rect.y -= camera.scroll.y * spr.scrollFactor.y;
@@ -117,12 +108,12 @@ class UIState extends MusicBeatState {
FlxG.sound.keysAllowed = currentFocus != null ? !(currentFocus is UITextBox) : true;
if (hoveredSprite != null) {
Mouse.cursor = hoveredSprite.cursor;
hoveredSprite = null;
if (hoveredSprite != null && hoveredSprite.cursor != null) {
NativeAPI.setCursorIcon(hoveredSprite.cursor);
} else {
Mouse.cursor = currentCursor;
NativeAPI.setCursorIcon(currentCursor);
}
hoveredSprite = null;
}
public override function destroy() {
@@ -145,7 +136,7 @@ class UIState extends MusicBeatState {
}
}
public function openContextMenu(options:Array<UIContextMenuOption>, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float) {
public function openContextMenu(options:Array<UIContextMenuOption>, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float, ?w:Int) {
var state = FlxG.state;
while(state.subState != null && !(state._requestSubStateReset && state._requestedSubState == null))
state = state.subState;
@@ -153,7 +144,24 @@ class UIState extends MusicBeatState {
state.persistentDraw = true;
state.persistentUpdate = true;
state.openSubState(curContextMenu = new UIContextMenu(options, callback, x.getDefault(__mousePos.x), y.getDefault(__mousePos.y)));
state.openSubState(curContextMenu = new UIContextMenu(options, callback, x.getDefault(__mousePos.x), y.getDefault(__mousePos.y), w));
return curContextMenu;
}
public function displayNotification(notification:UIBaseNotification) {
notification.cameras = uiCameras;
notification.onRemove = (notif) -> {
notification.onRemove = null;
remove(notif, true);
};
add(notification);
notification.update(0);
notification.appearAnimation();
// TODO: future tooltips
//notification.x = __mousePos.x;
//notification.y = __mousePos.y;
//notification.alpha = 0;
//notification.appearAnimation();
//FlxTween.tween(notification, {x: __mousePos.x, y: __mousePos.y, alpha: 1}, .3, {ease: FlxEase.circInOut});
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ class UITopMenuButton extends UISliceSprite {
super(x, y, 0, 23, "editors/ui/menu-item");
this.contextMenu = contextMenu;
this.parent = parent;
cursor = BUTTON;
cursor = CLICK;
this.label = new UIText(4, 0, 0, label);
this.label.alignment = CENTER;
+15 -8
View File
@@ -1,5 +1,6 @@
package funkin.editors.ui;
import flixel.input.FlxInput.FlxInputState;
import flixel.input.keyboard.FlxKey;
import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
@@ -14,6 +15,19 @@ class UIUtil {
return contextMenu != null && UIState.state.curContextMenu == contextMenu;
}
@:noUsing public static function fixKey(key:FlxKey):FlxKey {
return switch(key) {
#if mac
case CONTROL: WINDOWS; // Remap control to the meta key
#end
default: key;
}
}
public static function getKeyState(key:FlxKey, Status:FlxInputState):Bool {
return FlxG.keys.checkStatus(fixKey(key), Status);
}
/**
* Process all options with shortcuts present in a `Array<UIContextMenuOption>`. Also checks childrens.
* @param topMenuOptions
@@ -41,14 +55,7 @@ class UIUtil {
var shouldPress = Std.int(key) > 0;
if(!shouldPress) key = -key;
var k = switch(key) {
#if mac
case CONTROL:
WINDOWS;
#end
default:
key;
}
var k = fixKey(key);
if (FlxG.keys.checkStatus(k, shouldPress ? JUST_PRESSED : JUST_RELEASED)) {
justPressed = true;
} else if (!FlxG.keys.checkStatus(k, shouldPress ? PRESSED : RELEASED)) {
+31 -2
View File
@@ -2,19 +2,48 @@ package funkin.editors.ui;
class UIWindow extends UISliceSprite {
public var titleSpr:UIText;
public var collapsable:Bool = false;
public var content:FlxTypedGroup<FlxBasic>;
public override function new(x:Float, y:Float, w:Int, h:Int, title:String) {
super(x, y, w, h, "editors/ui/normal-popup");
members.push(titleSpr = new UIText(x + 25, y, bWidth - 50, title, 15, -1));
titleSpr.y = y + ((30 - titleSpr.height) / 2);
content = new FlxTypedGroup<FlxBasic>();
members.push(content);
}
public override function update(elapsed:Float) {
super.update(elapsed);
__rect.x = x; __rect.y = y+23;
__rect.width = bWidth; __rect.height = bHeight-23;
cursor = ARROW;
if(collapsable) {
__rect.x = x; __rect.y = y;
__rect.width = bWidth; __rect.height = topHeight;
if(UIState.state.isOverlapping(this, __rect)) {
cursor = CLICK;
if(FlxG.mouse.justPressed) {
content.exists = !content.exists;
drawMiddle = !drawMiddle;
drawBottom = !drawBottom;
}
} else {
if(!content.exists) {
cursor = null;
}
}
}
__rect.x = x;
__rect.width = bWidth;
if(content.exists) {
__rect.y = y+topHeight;
__rect.height = bHeight-topHeight;
}
hovered = UIState.state.isOverlapping(this, __rect);
}
}
@@ -0,0 +1,166 @@
package funkin.editors.ui.notifications;
import flixel.group.FlxGroup;
import flixel.math.FlxRect;
import flixel.tweens.FlxTween;
import flixel.util.FlxTimer;
using flixel.util.FlxSpriteUtil;
class UIBaseNotification extends UISliceSprite {
public var textField:UIText;
public var closeButton:UIButton;
public var onRemove:(notif:UIBaseNotification)->Void = null;
public var showedAnimation:Bool = false;
public var closed:Bool = false;
public function new(text:String, showTimer:Float = 5, corner:Corner = BOTTOM_LEFT) {
super(x, y, 340, 46, "editors/ui/inputbox");
closeButton = new UIButton(0, 0, "X", () -> {
if (closed) return;
closed = true;
(new FlxTimer()).start(0.2, (_) -> {disappearAnimation();});
}, 32, 32);
closeButton.hoverCallback = () -> {
closeButton.color = 0xFFAC3D3D;
closeButton.field.color = 0xFFffffff;
//closeButton.field.color = closeButton.hovered ? 0xFFffffff : 0xFF7f7f7f;
}
closeButton.autoAlpha = false;
closeButton.alpha = 0.0;
textField = new UIText(0, 0, 0, text, 14);
textField.alignment = LEFT;
textField.fieldWidth = bWidth - 32 - 8;
members.push(textField);
if(textField.height > bHeight)
bHeight = Std.int(textField.height);
alpha = 0;
textField.alpha = 0;
showingTimer = showTimer;
members.push(closeButton);
var margin = 20;
switch(corner) {
case TOP_LEFT:
x = margin;
y = margin;
case TOP_RIGHT:
x = FlxG.width - bWidth - margin;
y = margin;
case BOTTOM_LEFT:
x = margin;
y = FlxG.height - bHeight - margin;
case BOTTOM_RIGHT:
x = FlxG.width - bWidth - margin;
y = FlxG.height - bHeight - margin;
}
defaultX = x;
defaultY = y;
switch(corner) {
case TOP_LEFT | BOTTOM_LEFT:
x -= bWidth - margin - 20;
case TOP_RIGHT | BOTTOM_RIGHT:
x += bWidth - margin - 20;
}
closingX = x;
closingY = y;
alpha = 0;
}
public var defaultX:Float = 0;
public var defaultY:Float = 0;
public var closingX:Float = 0;
public var closingY:Float = 0;
public var showingTimer:Float = 5;
public var timerActive:Bool = false;
public var progress:Float = 0;
public override function update(elapsed:Float) {
closeButton.color = 0xFFFFFFFF;
closeButton.field.color = 0xFF7f7f7f;
super.update(elapsed);
if(timerActive) {
showingTimer -= elapsed;
if(showingTimer <= 0) {
timerActive = false;
idleDisappearAnimation();
}
}
textField.follow(this, 5, bHeight / 2 - textField.height / 2);
closeButton.follow(this, bWidth-closeButton.bWidth - 7, bHeight / 2 - closeButton.bHeight / 2);
closeButton.field.alpha = alpha * .7;
//closeButton.alpha = alpha;
textField.alpha = alpha;
}
public function appearAnimation() {
x = closingX; alpha=0; FlxTween.cancelTweensOf(this, ["x", "alpha"]);
FlxTween.tween(this, {x: defaultX}, .4, {ease: FlxEase.circInOut});
FlxTween.tween(this, {alpha: 1}, .3, {ease: FlxEase.sineOut, startDelay: .1, onComplete: (_) -> {
if(showingTimer > 0) timerActive = true;
}});
showedAnimation = true;
}
public var isClosing:Bool = false;
public function disappearAnimation() {
if (isClosing) return;
isClosing = true;
// TODO: make it so you can click through the notification
closeButton.active = false;
closeButton.selectable = false;
closeButton.canBeHovered = false;
selectable = false;
canBeHovered = false;
x = defaultX; alpha = 1; FlxTween.cancelTweensOf(this, ["x", "alpha"]);
FlxTween.tween(this, {alpha: 0}, .3, {ease: FlxEase.sineOut, onComplete: (_) -> {
if(onRemove != null) onRemove(this);
destroy();
}});
showedAnimation = false;
}
public function idleDisappearAnimation() {
if (isClosing) return;
isClosing = true;
x = defaultX; alpha = 1; FlxTween.cancelTweensOf(this, ["x", "alpha"]);
FlxTween.tween(this, {alpha: 0}, .3, {ease: FlxEase.sineOut});
FlxTween.tween(this, {x: closingX}, .4, {ease: FlxEase.circInOut, onComplete: (_) -> {
if(onRemove != null) onRemove(this);
destroy();
}});
showedAnimation = false;
}
}
enum abstract Corner(Int) {
var TOP_LEFT = 0;
var TOP_RIGHT = 1;
var BOTTOM_LEFT = 2;
var BOTTOM_RIGHT = 3;
}
+8 -3
View File
@@ -78,7 +78,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
playAnim(event.animName, event.force, event.context, event.reversed, event.frame);
}
public function new(x:Float, y:Float, ?character:String = "bf", isPlayer:Bool = false, switchAnims:Bool = true)
public function new(x:Float, y:Float, ?character:String = "bf", isPlayer:Bool = false, switchAnims:Bool = true, disableScripts:Bool = false)
{
super(x, y);
@@ -114,7 +114,12 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
continue;
}
// Loads the script
script = Script.create(Paths.script(Path.withoutExtension(xmlPath), null, true));
if(!disableScripts) {
script = Script.create(Paths.script(Path.withoutExtension(xmlPath), null, true));
} else {
script = new DummyScript(curCharacter);
}
script.setParent(this);
script.load();
applyXML(xml);
@@ -539,7 +544,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
public static function getList(?mods:Bool = false):Array<String> {
return [
for (path in Paths.getFolderContent('data/characters/', true, mods ? MODS : BOTH))
if (Path.extension(path) == "xml") Path.withoutDirectory(Path.withoutExtension(path))
if (Path.extension(path) == "xml") CoolUtil.getFilename(path)
];
}
}
+1 -1
View File
@@ -587,7 +587,7 @@ class PlayState extends MusicBeatState
for (event in SONG.events) songEvents.pushOnce(event.name);
for (file in Paths.getFolderContent('data/events/', true, fromMods ? MODS : BOTH)) {
var fileName:String = Path.withoutExtension(Path.withoutDirectory(file));
var fileName:String = CoolUtil.getFilename(file);
if (EventsData.eventsList.contains(fileName) && songEvents.contains(fileName)) {
addScript(file);
}
+1
View File
@@ -37,6 +37,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
// immediatly draw once and put image in GPU to prevent freezes
// TODO: change to graphics cache
@:privateAccess
splash.drawComplex(FlxG.camera);
} catch(e:Dynamic) {
Logs.trace('Couldn\'t parse splash data for "${path}": ${e.toString()}', ERROR);
+211 -86
View File
@@ -1,5 +1,6 @@
package funkin.game;
import flixel.util.typeLimit.OneOfTwo;
import flixel.FlxState;
import flixel.math.FlxPoint;
import funkin.backend.scripting.Script;
@@ -12,65 +13,103 @@ import haxe.xml.Access;
using StringTools;
class Stage extends FlxBasic implements IBeatReceiver {
public var extra:Map<String, Dynamic> = [];
public var stageXML:Access;
public var stagePath:String;
public var stageFile:String;
public var stageName:String;
public var stageSprites:Map<String, FlxSprite> = [];
public var stageScript:Script;
public var state:FlxState;
public var characterPoses:Map<String, StageCharPos> = [];
private var spritesParentFolder = "";
public var defaultZoom:Float = 1.05;
public var startCam = new FlxPoint();
public function getSprite(name:String) {
public var onXMLLoaded:(Access, Array<Access>)->Array<Access> = null;
public var onNodeLoaded:(Access, Dynamic)->Dynamic = null;
public var onNodeFinished:(Access, Dynamic)->Void = null;
public var onAddSprite:(FlxObject)->Void = null;
public var spritesParentFolder = "";
public inline function getSprite(name:String) {
return stageSprites[name];
}
public function new(stage:String, ?state:FlxState) {
public function new(stage:String, ?state:FlxState, autoLoad:Bool = true) {
super();
if (state == null) state = PlayState.instance;
if (state == null) state = FlxG.state;
this.state = state;
stagePath = Paths.xml('stages/$stage');
stageFile = stage;
stagePath = Paths.xml('stages/$stageFile');
try {
if (Assets.exists(stagePath)) stageXML = new Access(Xml.parse(Assets.getText(stagePath)).firstElement());
} catch(e) {
Logs.trace('Couldn\'t load stage "$stage": ${e.message}', ERROR);
Logs.trace('Couldn\'t load stage "$stageFile": ${e.message}', ERROR);
}
if (PlayState.instance != null) {
stageScript = Script.create(Paths.script('data/stages/$stage'));
if (autoLoad) loadXml(stageXML);
}
public static var DEFAULT_ATTRIBUTES:Array<String> = ["name", "startCamPosX", "startCamPosY", "zoom", "folder"];
public function loadXml(xml:Access, forceLoadAll:Bool = false) {
if (PlayState.instance == state) {
stageScript = Script.create(Paths.script('data/stages/$stageFile'));
PlayState.instance.scripts.add(stageScript);
stageScript.load();
}
if (stageXML != null) {
if (PlayState.instance != null) {
var parsed:Null<Float>;
if(stageXML.has.startCamPosX && (parsed = Std.parseFloat(stageXML.att.startCamPosX)) != null) PlayState.instance.camFollow.x = parsed;
if(stageXML.has.startCamPosY && (parsed = Std.parseFloat(stageXML.att.startCamPosY)) != null) PlayState.instance.camFollow.y = parsed;
if(stageXML.has.zoom && (parsed = Std.parseFloat(stageXML.att.zoom)) != null) PlayState.instance.defaultCamZoom = parsed;
PlayState.instance.curStage = stageXML.has.name ? stageXML.att.name : stage;
if (xml != null) {
var parsed:Null<Float>;
if((parsed = Std.parseFloat(xml.getAtt("startCamPosX"))).isNotNull()) startCam.x = parsed;
if((parsed = Std.parseFloat(xml.getAtt("startCamPosY"))).isNotNull()) startCam.y = parsed;
if((parsed = Std.parseFloat(xml.getAtt("zoom"))).isNotNull()) defaultZoom = parsed;
stageName = xml.getAtt("name").getDefault(stageFile);
if (PlayState.instance == state) {
if(xml.has.startCamPosX) PlayState.instance.camFollow.x = startCam.x;
if(xml.has.startCamPosY) PlayState.instance.camFollow.y = startCam.y;
if(xml.has.zoom) PlayState.instance.defaultCamZoom = defaultZoom;
PlayState.instance.curStage = stageName;
}
if (stageXML.has.folder) {
spritesParentFolder = stageXML.att.folder;
if (xml.has.folder) {
spritesParentFolder = xml.att.folder;
if (!spritesParentFolder.endsWith("/")) spritesParentFolder += "/";
}
var elems = [];
for(node in stageXML.elements) {
if (node.name == "high-memory" && !Options.lowMemoryMode)
// Load custom attributes
for(att in xml.x.attributes())
if(!DEFAULT_ATTRIBUTES.contains(att))
extra.set(att, xml.x.get(att));
var elems:Array<Access> = [];
// some way to tag that the sprites are from the group
for(node in xml.elements) {
if (node.name == "high-memory" && (!Options.lowMemoryMode || forceLoadAll))
for(e in node.elements)
elems.push(e);
else if (node.name == "low-memory" && (Options.lowMemoryMode || forceLoadAll))
for(e in node.elements)
elems.push(e);
else
elems.push(node);
}
if (PlayState.instance != null) {
var event = PlayState.instance.scripts.event("onStageXMLParsed", EventManager.get(StageXMLEvent).recycle(this, stageXML, elems));
if (PlayState.instance == state) {
var event = PlayState.instance.scripts.event("onStageXMLParsed", EventManager.get(StageXMLEvent).recycle(this, xml, elems));
elems = event.elems;
}
if(onXMLLoaded != null) {
elems = onXMLLoaded(xml, elems);
}
for(node in elems) {
var sprite:Dynamic = switch(node.name) {
@@ -79,19 +118,16 @@ class Stage extends FlxBasic implements IBeatReceiver {
var spr = XMLUtil.createSpriteFromXML(node, spritesParentFolder, LOOP);
if (!node.has.zoomfactor && PlayState.instance != null)
spr.initialZoom = PlayState.instance.defaultCamZoom;
stageSprites.set(spr.name, spr);
state.add(spr);
spr;
addSprite(spr);
case "box" | "solid":
if ( !node.has.name || !node.has.width || !node.has.height) continue;
var spr = new FlxSprite(
var spr = new FunkinSprite(
(node.has.x) ? Std.parseFloat(node.att.x).getDefault(0) : 0,
(node.has.y) ? Std.parseFloat(node.att.y).getDefault(0) : 0
);
spr.name = node.getAtt("name");
(node.name == "solid" ? spr.makeSolid : spr.makeGraphic)(
Std.parseInt(node.att.width),
@@ -99,35 +135,19 @@ class Stage extends FlxBasic implements IBeatReceiver {
(node.has.color) ? CoolUtil.getColorFromDynamic(node.att.color) : -1
);
stageSprites.set(node.getAtt("name"), spr);
state.add(spr);
spr;
stageSprites.set(spr.name, spr);
addSprite(spr);
case "boyfriend" | "bf" | "player":
addCharPos("boyfriend", node, {
x: 770,
y: 100,
scroll: 1,
flip: true
});
addCharPos("boyfriend", node, getDefaultPos("boyfriend"));
case "girlfriend" | "gf":
addCharPos("girlfriend", node, {
x: 400,
y: 130,
scroll: 0.95,
flip: false
});
addCharPos("girlfriend", node, getDefaultPos("girlfriend"));
case "dad" | "opponent":
addCharPos("dad", node, {
x: 100,
y: 100,
scroll: 1,
flip: false
});
addCharPos("dad", node, getDefaultPos("dad"));
case "character" | "char":
if (!node.has.name) continue;
addCharPos(node.att.name, node);
case "ratings" | "combo":
if (PlayState.instance == null) continue;
if (PlayState.instance != state) continue;
PlayState.instance.comboGroup.setPosition(
Std.parseFloat(node.getAtt("x")).getDefault(PlayState.instance.comboGroup.x),
Std.parseFloat(node.getAtt("y")).getDefault(PlayState.instance.comboGroup.y)
@@ -137,50 +157,72 @@ class Stage extends FlxBasic implements IBeatReceiver {
default: null;
}
if(PlayState.instance != null) {
if(PlayState.instance == state) {
sprite = PlayState.instance.scripts.event("onStageNodeParsed", EventManager.get(StageNodeEvent).recycle(this, node, sprite, node.name)).sprite;
}
if(onNodeLoaded != null) {
sprite = onNodeLoaded(node, sprite);
}
if (sprite != null) {
for(e in node.nodes.property)
XMLUtil.applyXMLProperty(sprite, e);
}
if(onNodeFinished != null) {
onNodeFinished(node, sprite);
}
}
}
if (characterPoses["girlfriend"] == null)
addCharPos("girlfriend", null, {
x: 400,
y: 130,
scroll: 0.95,
flip: false
});
addCharPos("girlfriend", null, getDefaultPos("girlfriend"));
if (characterPoses["dad"] == null)
addCharPos("dad", null, {
x: 100,
y: 100,
scroll: 1,
flip: false
});
addCharPos("dad", null, getDefaultPos("dad"));
if (characterPoses["boyfriend"] == null)
addCharPos("boyfriend", null, {
addCharPos("boyfriend", null, getDefaultPos("boyfriend"));
if (PlayState.instance != state) return;
for(k=>e in stageSprites) {
stageScript.set(k, e);
}
}
public static function getDefaultPos(name:String):StageCharPosInfo {
return switch(name) {
case "boyfriend" | "bf" | "player": {
x: 770,
y: 100,
scroll: 1,
flip: true
});
if (PlayState.instance == null) return;
for(k=>e in stageSprites) {
stageScript.set(k, e);
};
case "girlfriend" | "gf": {
x: 400,
y: 130,
scroll: 0.95,
flip: false
};
case "dad" | "opponent": {
x: 100,
y: 100,
scroll: 1,
flip: false
};
default: {
x: 0,
y: 0,
scroll: 1,
flip: false
};
}
}
public function addCharPos(name:String, node:Access, ?nonXMLInfo:StageCharPosInfo):StageCharPos {
var charPos = new StageCharPos();
charPos.visible = charPos.active = false;
charPos.name = name;
if (nonXMLInfo != null) {
charPos.setPosition(nonXMLInfo.x, nonXMLInfo.y);
@@ -191,33 +233,51 @@ class Stage extends FlxBasic implements IBeatReceiver {
if (node != null) {
charPos.x = Std.parseFloat(node.getAtt("x")).getDefault(charPos.x);
charPos.y = Std.parseFloat(node.getAtt("y")).getDefault(charPos.y);
charPos.charSpacingX = Std.parseFloat(node.getAtt("spacingx")).getDefault(charPos.charSpacingX);
charPos.charSpacingY = Std.parseFloat(node.getAtt("spacingy")).getDefault(charPos.charSpacingY);
charPos.camxoffset = Std.parseFloat(node.getAtt("camxoffset")).getDefault(charPos.camxoffset);
charPos.camyoffset = Std.parseFloat(node.getAtt("camyoffset")).getDefault(charPos.camyoffset);
charPos.skewX = Std.parseFloat(node.getAtt("skewx")).getDefault(charPos.skewX);
charPos.skewY = Std.parseFloat(node.getAtt("skewy")).getDefault(charPos.skewY);
charPos.alpha = Std.parseFloat(node.getAtt("alpha")).getDefault(charPos.alpha);
charPos.angle = Std.parseFloat(node.getAtt("angle")).getDefault(charPos.angle);
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);
var scale = Std.parseFloat(node.getAtt("scale")).getDefault(charPos.scale.x);
charPos.scale.set(scale, scale);
if (node.has.scale) {
var scale:Null<Float> = Std.parseFloat(node.att.scale);
if (scale.isNotNull()) charPos.scale.set(scale, scale);
}
if (node.has.scalex) {
var scale:Null<Float> = Std.parseFloat(node.att.scalex);
if (scale.isNotNull()) charPos.scale.x = scale;
}
if (node.has.scaley) {
var scale:Null<Float> = Std.parseFloat(node.att.scaley);
if (scale.isNotNull()) charPos.scale.y = scale;
}
if (node.has.scroll) {
var scroll:Null<Float> = Std.parseFloat(node.att.scroll);
if (scroll != null) charPos.scrollFactor.set(scroll, scroll);
} else {
if (node.has.scrollx) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrollx);
if (scroll != null) charPos.scrollFactor.x = scroll;
}
if (node.has.scrolly) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrolly);
if (scroll != null) charPos.scrollFactor.y = scroll;
}
}
if (node.has.scrollx) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrollx);
if (scroll != null) charPos.scrollFactor.x = scroll;
}
if (node.has.scrolly) {
var scroll:Null<Float> = Std.parseFloat(node.att.scrolly);
if (scroll != null) charPos.scrollFactor.y = scroll;
}
}
state.add(charPos);
return characterPoses[name] = charPos;
return addSprite(characterPoses[name] = charPos);
}
function addSprite<T:FlxObject>(sprite:T):T {
state.add(sprite);
if(onAddSprite != null) onAddSprite(sprite);
return sprite;
}
public inline function isCharFlipped(posName:String, def:Bool = false)
@@ -239,19 +299,29 @@ class Stage extends FlxBasic implements IBeatReceiver {
public function measureHit(curMeasure:Int) {}
public static function getList(?mods:Bool = false):Array<String> {
public static function getList(?mods:Bool = false, ?xmlOnly:Bool = false):Array<String> {
var list:Array<String> = [];
for (path in Paths.getFolderContent('data/stages/', true, mods ? MODS : BOTH))
if (Path.extension(path) == "xml" || Path.extension(path) == "hx") {
var file:String = Path.withoutDirectory(Path.withoutExtension(path));
list.pushOnce(file);
var extensions:Array<String> = ["xml"];
if (!xmlOnly) extensions.push("hx");
for (path in Paths.getFolderContent("data/stages/", false, mods ? MODS : BOTH)) {
var extension = Path.extension(path);
if (extensions.contains(extension)) {
list.pushOnce("test");
list.pushOnce(Path.withoutExtension(path));
}
}
trace(list);
return list;
}
}
class StageCharPos extends FlxObject {
public var extra:Map<String, Dynamic> = [];
public var name:String;
public var charSpacingX:Float = 20;
public var charSpacingY:Float = 0;
public var camxoffset:Float = 0;
@@ -261,6 +331,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();
@@ -273,13 +344,51 @@ 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.angle += angle;
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,
angle: char.angle
}
}
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.setProperty(char, field, Reflect.field(oldInfo, field));
}
}
oldInfo = null;
}
}
typedef StageCharPosInfo = {
@@ -287,4 +396,20 @@ 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;
var angle:Float;
}