turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

🛟 Updated. 28d5985 · on v0.9.0 · k33g · 17h ago
menu.go · 139 lines · 4.1 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package ui

// The menu bar's types and its state. What it draws is in menu_draw.go, what it
// answers to is in menu_events.go, and the second-level panels are in
// submenu.go.

// MenuItem is one line of a drop-down menu.
//
// Label may carry a hot key written with tildes, as in "~O~pen…". Shortcut is
// the key combination shown right-aligned; the menu only displays it, since
// the shortcut itself is handled wherever global keys are.
type MenuItem struct {
	Label     string
	Shortcut  string
	Action    func()
	Enabled   func() bool
	Separator bool

	// Items makes this a submenu: choosing it opens a second drop-down beside
	// the first rather than running anything, and Action is ignored.
	//
	// One level deep is all there is. The menus that need it — snippets
	// grouped by kind — are exactly one level, and a general depth would mean
	// replacing the bar's two indices with a path for a need nobody has.
	Items []*MenuItem
}

// hasSubmenu reports whether choosing this item opens another drop-down.
func (m *MenuItem) hasSubmenu() bool { return len(m.Items) > 0 }

// enabled reports whether the item can be chosen right now. An item with no
// Enabled function is always available, which is the common case.
func (m *MenuItem) enabled() bool {
	if m.Separator {
		return false
	}
	if m.Action == nil && !m.hasSubmenu() {
		return false
	}
	return m.Enabled == nil || m.Enabled()
}

// width returns how many cells the item needs, shortcut or marker included.
func (m *MenuItem) width() int {
	if m.Separator {
		return 0
	}

	width := LabelWidth(m.Label)
	switch {
	case m.Shortcut != "":
		width += len([]rune(m.Shortcut)) + 3
	case m.hasSubmenu():
		width += len([]rune(submenuMarker)) + 2
	}
	return width
}

// Menu is one drop-down: a title on the bar and the items under it.
type Menu struct {
	Label string
	Items []*MenuItem

	// OnOpen is called just before the menu drops down, so its owner can
	// refill Items first.
	//
	// A menu built from a file, or from what the front window holds, cannot be
	// decided once at start-up. This is the hook for that, and it runs at the
	// only moment the contents are about to be seen.
	OnOpen func()
}

// MenuBar is the row of menu titles across the top of the screen, and the
// drop-down currently open under it.
//
// It is drawn last and offered events first, so an open menu takes precedence
// over everything behind it.
type MenuBar struct {
	Box
	menus []*Menu

	openMenu  int // -1 when nothing is open
	highlight int

	// openItem is the index of the drop-down item whose submenu is showing, or
	// -1 when none is, and subHighlight is the highlighted line inside it.
	openItem     int
	subHighlight int
}

// NewMenuBar returns a closed menu bar holding the given menus.
func NewMenuBar(menus ...*Menu) *MenuBar {
	return &MenuBar{menus: menus, openMenu: -1, openItem: -1}
}

// Menus returns the menus on the bar.
func (b *MenuBar) Menus() []*Menu { return b.menus }

// SetMenus replaces the menus on the bar, closing whatever was open.
//
// Menu.OnOpen refills one menu; this is for the rarer case where the *set* of
// menus changes — a project file that names menus of its own can add or remove
// one while the editor is running. Closing first matters: the open index refers
// to the old slice, and keeping it would drop down whichever menu happened to
// land at that position.
//
//	bar.SetMenus(append(fixedMenus(), toolMenus()...))
func (b *MenuBar) SetMenus(menus []*Menu) {
	b.Close()
	b.menus = menus
}

// Open reports whether a drop-down is currently showing.
func (b *MenuBar) Open() bool { return b.openMenu >= 0 }

// OpenMenu drops down menu i and highlights its first usable item.
//
// The menu's OnOpen runs first, so a menu whose contents come from elsewhere is
// filled in before anyone sees it.
func (b *MenuBar) OpenMenu(i int) {
	if i < 0 || i >= len(b.menus) {
		return
	}
	if b.menus[i].OnOpen != nil {
		b.menus[i].OnOpen()
	}

	b.openMenu = i
	b.openItem = -1
	b.highlight = b.nextUsableIn(b.menus[i].Items, -1, +1)
}

// Close puts the open drop-down away, and the submenu with it.
func (b *MenuBar) Close() {
	b.openMenu = -1
	b.openItem = -1
	b.highlight = 0
	b.subHighlight = 0
}