mirror of
https://github.com/anotherhadi/ilovetui.git
synced 2026-08-21 12:05:49 +02:00
init layout, notifications, tabs & more
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"charm.land/bubbles/v2/key"
|
||||
"charm.land/lipgloss/v2"
|
||||
)
|
||||
|
||||
// HelpProvider is how a pane opts into the help bar: implement it and
|
||||
// return whatever bindings you want shown while you're focused. A pane
|
||||
// that doesn't implement it just contributes nothing - the help bar still
|
||||
// shows layout's own controls (ctrl+hjkl, ?), it never disappears entirely.
|
||||
type HelpProvider interface {
|
||||
HelpBindings() []key.Binding
|
||||
}
|
||||
|
||||
// HelpBindings implements HelpProvider by delegating to FocusedHelp, so a
|
||||
// Model embedded as a HelpProvider behaves identically to one consulted as
|
||||
// a Navigable.
|
||||
func (m Model) HelpBindings() []key.Binding {
|
||||
return m.FocusedHelp()
|
||||
}
|
||||
|
||||
// FocusedHelp implements Navigable: the bindings for whatever pane
|
||||
// currently has focus, drilling into a nested Navigable automatically until
|
||||
// it reaches the real pane at the bottom. Returns nil if the focused pane
|
||||
// (at any depth) implements neither Navigable nor HelpProvider.
|
||||
func (m Model) FocusedHelp() []key.Binding {
|
||||
focused, ok := findNode(m.root, m.state.id)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if nav, ok := focused.model.(Navigable); ok {
|
||||
return nav.FocusedHelp()
|
||||
}
|
||||
if hp, ok := focused.model.(HelpProvider); ok {
|
||||
return hp.HelpBindings()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// helpKeyMap adapts a focused pane's flat HelpProvider bindings plus
|
||||
// layout's own KeyMap into the shape bubbles/help.KeyMap expects.
|
||||
type helpKeyMap struct {
|
||||
pane []key.Binding
|
||||
own KeyMap
|
||||
width int
|
||||
}
|
||||
|
||||
// ShortHelp implements help.KeyMap. own leads (its ToggleHelp is always
|
||||
// first, see KeyMap.ShortHelp), the focused pane's own bindings follow.
|
||||
func (h helpKeyMap) ShortHelp() []key.Binding {
|
||||
return append(append([]key.Binding{}, h.own.ShortHelp()...), h.pane...)
|
||||
}
|
||||
|
||||
// FullHelp implements help.KeyMap. Rather than the fixed grouping ShortHelp
|
||||
// mirrors, it flattens every binding into one ordered list - own first, so
|
||||
// ToggleHelp lands in the first column's first row, then the pane's - and
|
||||
// re-flows it into as many columns as fit within width, maximizing columns
|
||||
// to minimize the number of rows the full help view takes.
|
||||
func (h helpKeyMap) FullHelp() [][]key.Binding {
|
||||
all := append(append([]key.Binding{}, flattenGroups(h.own.FullHelp())...), h.pane...)
|
||||
return flowColumns(all, h.width)
|
||||
}
|
||||
|
||||
func flattenGroups(groups [][]key.Binding) []key.Binding {
|
||||
var flat []key.Binding
|
||||
for _, g := range groups {
|
||||
flat = append(flat, g...)
|
||||
}
|
||||
return flat
|
||||
}
|
||||
|
||||
// flowColumns arranges bindings into as many columns as fit within width
|
||||
// without overflowing. It fills each column top-to-bottom before moving to
|
||||
// the next, which is what bubbles/help.FullHelpView expects: one inner
|
||||
// slice per column, rendered as a vertical stack.
|
||||
func flowColumns(bindings []key.Binding, width int) [][]key.Binding {
|
||||
enabled := make([]key.Binding, 0, len(bindings))
|
||||
for _, kb := range bindings {
|
||||
if kb.Enabled() {
|
||||
enabled = append(enabled, kb)
|
||||
}
|
||||
}
|
||||
if len(enabled) == 0 {
|
||||
return nil
|
||||
}
|
||||
if width <= 0 {
|
||||
return [][]key.Binding{enabled}
|
||||
}
|
||||
|
||||
for rows := 1; rows <= len(enabled); rows++ {
|
||||
groups := chunkRows(enabled, rows)
|
||||
if columnsWidth(groups) <= width {
|
||||
return groups
|
||||
}
|
||||
}
|
||||
return [][]key.Binding{enabled}
|
||||
}
|
||||
|
||||
// chunkRows splits bindings into groups of at most rows items each, filling
|
||||
// each group before moving to the next - the column-major order help's
|
||||
// FullHelpView renders (first group is the leftmost column).
|
||||
func chunkRows(bindings []key.Binding, rows int) [][]key.Binding {
|
||||
var groups [][]key.Binding
|
||||
for i := 0; i < len(bindings); i += rows {
|
||||
end := min(i+rows, len(bindings))
|
||||
groups = append(groups, bindings[i:end])
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// columnsWidth mirrors bubbles/help.FullHelpView's own width accounting:
|
||||
// each column is as wide as its longest key plus a space plus its longest
|
||||
// description, columns separated by FullSeparator's width (4 cells, " ").
|
||||
func columnsWidth(groups [][]key.Binding) int {
|
||||
const separatorWidth = 4
|
||||
total := 0
|
||||
for i, group := range groups {
|
||||
if i > 0 {
|
||||
total += separatorWidth
|
||||
}
|
||||
var keyWidth, descWidth int
|
||||
for _, kb := range group {
|
||||
keyWidth = max(keyWidth, lipgloss.Width(kb.Help().Key))
|
||||
descWidth = max(descWidth, lipgloss.Width(kb.Help().Desc))
|
||||
}
|
||||
total += keyWidth + 1 + descWidth
|
||||
}
|
||||
return total
|
||||
}
|
||||
Reference in New Issue
Block a user