Files

116 lines
3.9 KiB
Go

package editor
import (
"slices"
"strconv"
"aego/core/mathx"
"aego/editor/core/context"
"aego/editor/ext"
"aego/engine/i18n"
"aego/modules/animation"
"aego/ui/widgets"
)
// Hook adds the custom event list to the module's project section.
func Hook(r *ext.Registry) error {
labels := []i18n.Text{
i18n.T("editor.animation_settings.events", "Animation Events"),
i18n.T("editor.animation_settings.payload", "Payload"),
}
// Search by either language or the technical section ID, independently of
// the UI language. Values are snapshots, so no global language switch is
// needed and no mutable translation catalog is retained.
keywords := []i18n.Text{{En: animation.ID}}
seen := map[string]bool{animation.ID: true}
for _, label := range labels {
for _, text := range append(i18n.Default().Translations(label.Key), label.En) {
if text != "" && !seen[text] {
keywords = append(keywords, i18n.Text{En: text})
seen[text] = true
}
}
}
if err := r.SettingsPage(animation.ID, draw, keywords...); err != nil {
return err
}
for i := range r.Editor().Pages {
if r.Editor().Pages[i].Section == animation.ID {
r.Editor().Pages[i].Label = i18n.T("editor.animation.title", "Animation")
break
}
}
return nil
}
func nextName(events []animation.EventDesc) string {
for i := 1; ; i++ {
name := "Event" + strconv.Itoa(i)
if !slices.ContainsFunc(events, func(e animation.EventDesc) bool { return e.Name == name }) {
return name
}
}
}
func validationMessage(s *animation.Settings) string {
seen := make(map[string]bool, len(s.Events))
for _, event := range s.Events {
switch {
case event.Name == "":
return i18n.Tr("editor.animation_settings.empty_name", "An event needs a name")
case seen[event.Name]:
return i18n.Tr("editor.animation_settings.duplicate", "Event {name} is declared twice", i18n.Str("name", event.Name))
case event.Payload > animation.PayloadBool:
return i18n.Tr("editor.animation_settings.invalid_payload", "Event {name} has an unknown payload", i18n.Str("name", event.Name))
}
seen[event.Name] = true
}
return ""
}
func draw(ed *context.Editor, draft any) bool {
s, ok := draft.(*animation.Settings)
if !ok || ed == nil || ed.UI == nil {
return false
}
ui := ed.UI
widgets.Label(ui, i18n.Tr("editor.animation_settings.events", "Animation Events"))
widgets.LabelDim(ui, i18n.Tr("editor.animation_settings.built_in", "Built in: Footstep (none), Hit (float)"))
widgets.LabelDim(ui, i18n.Tr("editor.animation_settings.built_in_readonly", "Built-in events cannot be overridden"))
changed := false
if widgets.ButtonRect(ui, ui.ID("add-event"), ui.Cursor().Next(0), i18n.Tr("editor.animation_settings.add_event", "Add event")) {
s.Events = append(s.Events, animation.EventDesc{Name: nextName(s.Events)})
changed = true
}
for i := range s.Events {
ui.PushIDInt(i)
widgets.Separator(ui)
event := &s.Events[i]
// Live edits avoid losing the most recent text when Apply is clicked.
if edited, _ := widgets.TextFieldRect(ui, ui.ID("name"), widgets.Field(ui, i18n.Tr("editor.animation_settings.name", "Name")), &event.Name, true); edited {
changed = true
}
payload := int(event.Payload)
if widgets.Combo(ui, ui.ID("payload"), widgets.Field(ui, i18n.Tr("editor.animation_settings.payload", "Payload")), &payload,
[]string{i18n.Tr("editor.animation_settings.none", "None"), "int", "float", "bool"}, false) {
event.Payload = animation.PayloadKind(payload)
changed = true
}
if widgets.ButtonRect(ui, ui.ID("remove"), ui.Cursor().Next(0), i18n.Tr("editor.animation_settings.delete_event", "Delete event")) {
ui.CancelFocus()
s.Events = slices.Delete(s.Events, i, i+1)
ui.PopID()
changed = true
break
}
ui.PopID()
}
if msg := validationMessage(s); msg != "" {
r := ui.Cursor().Next(0)
ui.PushClip(r)
ui.Text(msg, mathx.Vec2{X: r.X, Y: ui.TextY(r)}, ui.Theme.Error)
ui.PopClip()
}
return changed
}