Files
aego/editor/shell/nativemenu.go
sanspie 5dd9cc91bb editor: refine agent, scene controls and curves
Improve mentions and the default Design workspace, clarify scene lighting controls, and polish editor workflows for 0.12.5-dev.
2026-11-05 20:14:53 +03:00

167 lines
4.5 KiB
Go

package shell
import (
"hash/fnv"
"aego/engine/i18n"
"aego/platform"
)
const appMenuTitle = "Aego Editor"
type nativeMenu struct {
bar platform.MenuBar
acts []func()
clicks []int
sum uint64
next float64
}
func (a *App) initNativeMenu() {
if a.eng.Config().Headless {
return
}
if mb, ok := a.eng.Window().(platform.MenuBar); ok {
a.native = &nativeMenu{bar: mb}
}
}
func flag(b bool) byte {
if b {
return 1
}
return 0
}
func hashMenus(menus []platform.Menu) uint64 {
h := fnv.New64a()
var walk func([]platform.MenuItem)
walk = func(items []platform.MenuItem) {
for _, it := range items {
h.Write([]byte(it.Label))
h.Write([]byte{0})
h.Write([]byte(it.Shortcut))
h.Write([]byte{flag(it.Enabled), flag(it.Checked), flag(it.Separator), byte(it.Role), 1})
walk(it.Items)
h.Write([]byte{2})
}
}
for _, m := range menus {
h.Write([]byte(m.Title))
h.Write([]byte{3})
walk(m.Items)
}
return h.Sum64()
}
func (a *App) buildNativeMenu() ([]platform.Menu, []func()) {
reg := a.ctx.Commands
var acts []func()
act := func(fn func()) int {
acts = append(acts, fn)
return len(acts) - 1
}
menus := []platform.Menu{{Title: appMenuTitle, Items: []platform.MenuItem{
{Role: platform.RoleHide, Label: i18n.Tr("editor.shell.hide_appmenutitle", "Hide {appmenutitle}", i18n.Str("appmenutitle", appMenuTitle))},
{Role: platform.RoleHideOthers, Label: i18n.Tr("editor.shell.hide_others", "Hide Others")},
{Role: platform.RoleShowAll, Label: i18n.Tr("editor.shell.show_all", "Show All")},
{Separator: true},
{ID: act(func() { reg.Run(CommandQuit) }), Label: i18n.Tr("editor.shell.quit_appmenutitle", "Quit {appmenutitle}", i18n.Str("appmenutitle", appMenuTitle)), Shortcut: reg.Shortcut(CommandQuit), Enabled: true},
}}}
for _, cat := range a.menuCategories() {
if cat == "Create" {
continue
}
m := platform.Menu{Title: menuTitle(cat)}
for _, g := range a.groups(cat) {
var items []platform.MenuItem
last := -1
for _, d := range g.items {
if g.title == "" && last >= 0 && d.Order/100 != last/100 {
items = append(items, platform.MenuItem{Separator: true})
}
last = d.Order
id := d.ID
if cat == "Help" && id == CommandReportBug {
items = append(items, platform.MenuItem{Label: d.Title(), Enabled: true, Items: []platform.MenuItem{
{ID: act(func() { reg.Run(CommandReportBug) }), Label: i18n.Tr("editor.shell.report_issue", "Create an issue…"), Enabled: true},
{ID: act(func() { a.openSupportURL(telegramURL) }), Label: i18n.Tr("editor.shell.contact_telegram", "Telegram: @sanspie"), Enabled: true},
}})
continue
}
items = append(items, platform.MenuItem{ID: act(func() { reg.Run(id) }), Label: d.Title(), Shortcut: reg.Shortcut(id), Enabled: reg.Enabled(id), Checked: d.Checked != nil && d.Checked()})
}
if len(items) == 0 {
continue
}
if g.title != "" {
m.Items = append(m.Items, platform.MenuItem{Label: g.title, Enabled: true, Items: items})
} else {
m.Items = append(m.Items, items...)
}
}
if cat == "Window" {
for i, sc := range []string{themeCategory, languageCategory} {
if !reg.HasCategory(sc) {
continue
}
sub := platform.MenuItem{Label: menuTitle(sc), Enabled: true}
for _, d := range reg.Category(sc) {
id := d.ID
sub.Items = append(sub.Items, platform.MenuItem{
ID: act(func() { reg.Run(id) }), Label: subLabel(sc, d.Title()), Enabled: true, Checked: d.Checked(),
})
}
if i == 0 {
m.Items = append(m.Items, platform.MenuItem{Separator: true})
}
m.Items = append(m.Items, sub)
}
}
if cat == "Scene" {
docs := a.ctx.Docs.Docs()
if len(docs) > 0 {
m.Items = append(m.Items, platform.MenuItem{Separator: true})
}
active := a.ctx.Docs.Edit()
for _, d := range docs {
name := d.Path
if d.Dirty() {
name += " *"
}
doc := d.ID
m.Items = append(m.Items, platform.MenuItem{
ID: act(func() { a.ctx.Docs.SetActive(doc) }), Label: name, Enabled: true,
Checked: active != nil && d.ID == active.ID,
})
}
}
if len(m.Items) > 0 {
menus = append(menus, m)
}
}
return menus, acts
}
func (a *App) syncNativeMenu(now float64) {
n := a.native
if n == nil {
return
}
n.clicks = n.bar.MenuClicks(n.clicks[:0])
for _, c := range n.clicks {
if c >= 0 && c < len(n.acts) {
n.acts[c]()
}
}
if n.sum != 0 && now < n.next {
return
}
n.next = now + 0.25
menus, acts := a.buildNativeMenu()
if s := hashMenus(menus); s != n.sum {
n.sum, n.acts = s, acts
n.bar.SetMenuBar(appMenuTitle, menus)
}
}