Merge pull request #268 from FunkinCrew/feature/chart-editor-event-tooltips

Chart Editor: Tooltips when hovering over chart events
This commit is contained in:
Cameron Taylor 2024-01-18 04:26:03 -05:00 committed by GitHub
commit 8b5f7a701d
5 changed files with 126 additions and 3 deletions

View file

@ -15,7 +15,7 @@ abstract SongEventSchema(SongEventSchemaRaw)
}
@:arrayAccess
public inline function getByName(name:String):SongEventSchemaField
public function getByName(name:String):SongEventSchemaField
{
for (field in this)
{
@ -41,6 +41,32 @@ abstract SongEventSchema(SongEventSchemaRaw)
{
return this[k] = v;
}
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';
}
}
}
typedef SongEventSchemaRaw = Array<SongEventSchemaField>;

View file

@ -1,6 +1,7 @@
package funkin.data.song;
import funkin.data.event.SongEventRegistry;
import funkin.play.event.SongEvent;
import funkin.data.event.SongEventSchema;
import funkin.data.song.SongRegistry;
import thx.semver.Version;
@ -702,6 +703,11 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
}
}
public inline function getHandler():Null<SongEvent>
{
return SongEventRegistry.getEvent(this.event);
}
public inline function getSchema():Null<SongEventSchema>
{
return SongEventRegistry.getEventSchema(this.event);
@ -752,6 +758,39 @@ 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';
if (eventSchema.stringifyFieldValue(key, value) != null) trace(eventSchema.stringifyFieldValue(key, value));
var valueStr = eventSchema.stringifyFieldValue(key, value) ?? 'UnknownValue';
result += '\n- ${title}: ${valueStr}';
}
return result;
}
public function clone():SongEventData
{
return new SongEventData(this.time, this.event, this.value);
}
@:op(A == B)
public function op_equals(other:SongEventData):Bool
{

View file

@ -2256,7 +2256,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;
@ -3445,6 +3445,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)

View file

@ -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();
@ -142,6 +154,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);
}
}
/**

View 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
}
}
}