mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-01-04 19:28:30 +00:00
Tooltips when hovering over chart events
This commit is contained in:
parent
e1b92e8829
commit
336810b628
4
hmm.json
4
hmm.json
|
@ -54,14 +54,14 @@
|
|||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "e765a3e0b7a653823e8dec765e04623f27f573f8",
|
||||
"ref": "67c5700e253ff8892589a95945a7799f34ae4df0",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "7a517d561eff49d8123c128bf9f5c1123b84d014",
|
||||
"ref": "2b9cff727999b53ed292b1675ac1c9089ac77600",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -161,35 +161,71 @@ class SongEventParser
|
|||
}
|
||||
}
|
||||
|
||||
enum abstract SongEventFieldType(String) from String to String
|
||||
@:forward(name, title, type, keys, min, max, step, defaultValue, iterator)
|
||||
abstract SongEventSchema(SongEventSchemaRaw)
|
||||
{
|
||||
/**
|
||||
* The STRING type will display as a text field.
|
||||
*/
|
||||
var STRING = "string";
|
||||
public function new(?fields:Array<SongEventSchemaField>)
|
||||
{
|
||||
this = fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* The INTEGER type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var INTEGER = "integer";
|
||||
@:arrayAccess
|
||||
public function getByName(name:String):SongEventSchemaField
|
||||
{
|
||||
for (field in this)
|
||||
{
|
||||
if (field.name == name) return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* The FLOAT type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var FLOAT = "float";
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BOOL type will display as a checkbox.
|
||||
*/
|
||||
var BOOL = "bool";
|
||||
public function getFirstField():SongEventSchemaField
|
||||
{
|
||||
return this[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* The ENUM type will display as a dropdown.
|
||||
* Make sure to specify the `keys` field in the schema.
|
||||
*/
|
||||
var ENUM = "enum";
|
||||
public function stringifyFieldValue(name:String, value:Dynamic):String
|
||||
{
|
||||
var field:SongEventSchemaField = getByName(name);
|
||||
if (field == null) return 'Unknown';
|
||||
|
||||
switch (field.type)
|
||||
{
|
||||
case SongEventFieldType.STRING:
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.INTEGER:
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.FLOAT:
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.BOOL:
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.ENUM:
|
||||
for (key in field.keys.keys())
|
||||
{
|
||||
if (field.keys.get(key) == value) return key;
|
||||
}
|
||||
return Std.string(value);
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
@:arrayAccess
|
||||
public inline function get(key:Int)
|
||||
{
|
||||
return this[key];
|
||||
}
|
||||
|
||||
@:arrayAccess
|
||||
public inline function arrayWrite(k:Int, v:SongEventSchemaField):SongEventSchemaField
|
||||
{
|
||||
return this[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
typedef SongEventSchemaRaw = Array<SongEventSchemaField>;
|
||||
|
||||
typedef SongEventSchemaField =
|
||||
{
|
||||
/**
|
||||
|
@ -240,4 +276,31 @@ typedef SongEventSchemaField =
|
|||
?defaultValue:Dynamic,
|
||||
}
|
||||
|
||||
typedef SongEventSchema = Array<SongEventSchemaField>;
|
||||
enum abstract SongEventFieldType(String) from String to String
|
||||
{
|
||||
/**
|
||||
* The STRING type will display as a text field.
|
||||
*/
|
||||
var STRING = "string";
|
||||
|
||||
/**
|
||||
* The INTEGER type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var INTEGER = "integer";
|
||||
|
||||
/**
|
||||
* The FLOAT type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var FLOAT = "float";
|
||||
|
||||
/**
|
||||
* The BOOL type will display as a checkbox.
|
||||
*/
|
||||
var BOOL = "bool";
|
||||
|
||||
/**
|
||||
* The ENUM type will display as a dropdown.
|
||||
* Make sure to specify the `keys` field in the schema.
|
||||
*/
|
||||
var ENUM = "enum";
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package funkin.data.song;
|
||||
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import thx.semver.Version;
|
||||
|
||||
|
@ -617,6 +620,38 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
|||
this = new SongEventDataRaw(time, event, value);
|
||||
}
|
||||
|
||||
public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
||||
{
|
||||
if (this.value == null) return {};
|
||||
if (Std.isOfType(this.value, Array))
|
||||
{
|
||||
var result:haxe.DynamicAccess<Dynamic> = {};
|
||||
result.set(defaultKey, this.value);
|
||||
return cast result;
|
||||
}
|
||||
else if (Reflect.isObject(this.value))
|
||||
{
|
||||
// We enter this case if the value is a struct.
|
||||
return cast this.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result:haxe.DynamicAccess<Dynamic> = {};
|
||||
result.set(defaultKey, this.value);
|
||||
return cast result;
|
||||
}
|
||||
}
|
||||
|
||||
public inline function getHandler():Null<SongEvent>
|
||||
{
|
||||
return SongEventParser.getEvent(this.event);
|
||||
}
|
||||
|
||||
public inline function getSchema():Null<SongEventSchema>
|
||||
{
|
||||
return SongEventParser.getEventSchema(this.event);
|
||||
}
|
||||
|
||||
public inline function getDynamic(key:String):Null<Dynamic>
|
||||
{
|
||||
return this.value == null ? null : Reflect.field(this.value, key);
|
||||
|
@ -662,6 +697,32 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
|||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public function buildTooltip():String
|
||||
{
|
||||
var eventHandler = getHandler();
|
||||
var eventSchema = getSchema();
|
||||
|
||||
if (eventSchema == null) return 'Unknown Event: ${this.event}';
|
||||
|
||||
var result = '${eventHandler.getTitle()}';
|
||||
|
||||
var defaultKey = eventSchema.getFirstField()?.name;
|
||||
var valueStruct:haxe.DynamicAccess<Dynamic> = valueAsStruct(defaultKey);
|
||||
|
||||
for (pair in valueStruct.keyValueIterator())
|
||||
{
|
||||
var key = pair.key;
|
||||
var value = pair.value;
|
||||
|
||||
var title = eventSchema.getByName(key)?.title ?? 'UnknownField';
|
||||
var valueStr = eventSchema.stringifyFieldValue(key, value);
|
||||
|
||||
result += '\n- ${title}: ${valueStr}';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public function clone():SongEventData
|
||||
{
|
||||
return new SongEventData(this.time, this.event, this.value);
|
||||
|
|
|
@ -132,7 +132,7 @@ class FocusCameraSongEvent extends SongEvent
|
|||
*/
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return [
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: "char",
|
||||
title: "Character",
|
||||
|
@ -154,6 +154,6 @@ class FocusCameraSongEvent extends SongEvent
|
|||
step: 10.0,
|
||||
type: SongEventFieldType.FLOAT,
|
||||
}
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ class PlayAnimationSongEvent extends SongEvent
|
|||
*/
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return [
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: 'target',
|
||||
title: 'Target',
|
||||
|
@ -108,6 +108,6 @@ class PlayAnimationSongEvent extends SongEvent
|
|||
type: SongEventFieldType.BOOL,
|
||||
defaultValue: false
|
||||
}
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ class SetCameraBopSongEvent extends SongEvent
|
|||
*/
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return [
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: 'intensity',
|
||||
title: 'Intensity',
|
||||
|
@ -87,6 +87,6 @@ class SetCameraBopSongEvent extends SongEvent
|
|||
step: 1,
|
||||
type: SongEventFieldType.INTEGER,
|
||||
}
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ class ZoomCameraSongEvent extends SongEvent
|
|||
*/
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return [
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: 'zoom',
|
||||
title: 'Zoom Level',
|
||||
|
@ -145,6 +145,6 @@ class ZoomCameraSongEvent extends SongEvent
|
|||
'Elastic In/Out' => 'elasticInOut',
|
||||
]
|
||||
}
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2113,7 +2113,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
add(gridGhostHoldNote);
|
||||
gridGhostHoldNote.zIndex = 11;
|
||||
|
||||
gridGhostEvent = new ChartEditorEventSprite(this);
|
||||
gridGhostEvent = new ChartEditorEventSprite(this, true);
|
||||
gridGhostEvent.alpha = 0.6;
|
||||
gridGhostEvent.eventData = new SongEventData(-1, '', {});
|
||||
gridGhostEvent.visible = false;
|
||||
|
@ -3127,6 +3127,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
// Setting event data resets position relative to the grid so we fix that.
|
||||
eventSprite.x += renderedEvents.x;
|
||||
eventSprite.y += renderedEvents.y;
|
||||
eventSprite.updateTooltipPosition();
|
||||
}
|
||||
|
||||
// Add hold notes that have been made visible (but not their parents)
|
||||
|
|
|
@ -11,6 +11,9 @@ import flixel.graphics.frames.FlxFramesCollection;
|
|||
import flixel.graphics.frames.FlxTileFrames;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import haxe.ui.tooltips.ToolTipRegionOptions;
|
||||
import funkin.util.HaxeUIUtil;
|
||||
import haxe.ui.tooltips.ToolTipManager;
|
||||
|
||||
/**
|
||||
* A sprite that can be used to display a song event in a chart.
|
||||
|
@ -36,6 +39,13 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
|
||||
public var overrideStepTime(default, set):Null<Float> = null;
|
||||
|
||||
public var tooltip:ToolTipRegionOptions;
|
||||
|
||||
/**
|
||||
* Whether this sprite is a "ghost" sprite used when hovering to place a new event.
|
||||
*/
|
||||
public var isGhost:Bool = false;
|
||||
|
||||
function set_overrideStepTime(value:Null<Float>):Null<Float>
|
||||
{
|
||||
if (overrideStepTime == value) return overrideStepTime;
|
||||
|
@ -45,12 +55,14 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
return overrideStepTime;
|
||||
}
|
||||
|
||||
public function new(parent:ChartEditorState)
|
||||
public function new(parent:ChartEditorState, isGhost:Bool = false)
|
||||
{
|
||||
super();
|
||||
|
||||
this.parentState = parent;
|
||||
this.isGhost = isGhost;
|
||||
|
||||
this.tooltip = HaxeUIUtil.buildTooltip('N/A');
|
||||
this.frames = buildFrames();
|
||||
|
||||
buildAnimations();
|
||||
|
@ -140,6 +152,7 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
// Disown parent. MAKE SURE TO REVIVE BEFORE REUSING
|
||||
this.kill();
|
||||
this.visible = false;
|
||||
updateTooltipPosition();
|
||||
return null;
|
||||
}
|
||||
else
|
||||
|
@ -151,6 +164,8 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
this.eventData = value;
|
||||
// Update the position to match the note data.
|
||||
updateEventPosition();
|
||||
// Update the tooltip text.
|
||||
this.tooltip.tipData = {text: this.eventData.buildTooltip()};
|
||||
return this.eventData;
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +184,31 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
this.x += origin.x;
|
||||
this.y += origin.y;
|
||||
}
|
||||
|
||||
this.updateTooltipPosition();
|
||||
}
|
||||
|
||||
public function updateTooltipPosition():Void
|
||||
{
|
||||
// No tooltip for ghost sprites.
|
||||
if (this.isGhost) return;
|
||||
|
||||
if (this.eventData == null)
|
||||
{
|
||||
// Disable the tooltip.
|
||||
ToolTipManager.instance.unregisterTooltipRegion(this.tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the position.
|
||||
this.tooltip.left = this.x;
|
||||
this.tooltip.top = this.y;
|
||||
this.tooltip.width = this.width;
|
||||
this.tooltip.height = this.height;
|
||||
|
||||
// Enable the tooltip.
|
||||
ToolTipManager.instance.registerTooltipRegion(this.tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
17
source/funkin/util/HaxeUIUtil.hx
Normal file
17
source/funkin/util/HaxeUIUtil.hx
Normal file
|
@ -0,0 +1,17 @@
|
|||
package funkin.util;
|
||||
|
||||
import haxe.ui.tooltips.ToolTipRegionOptions;
|
||||
|
||||
class HaxeUIUtil
|
||||
{
|
||||
public static function buildTooltip(text:String, ?left:Float, ?top:Float, ?width:Float, ?height:Float):ToolTipRegionOptions
|
||||
{
|
||||
return {
|
||||
tipData: {text: text},
|
||||
left: left ?? 0.0,
|
||||
top: top ?? 0.0,
|
||||
width: width ?? 0.0,
|
||||
height: height ?? 0.0
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue