134 lines
3.6 KiB
Go
134 lines
3.6 KiB
Go
package tui
|
|
|
|
import (
|
|
"charm.land/lipgloss/v2"
|
|
)
|
|
|
|
// ContextMenuItem represents an item in a context menu.
|
|
type ContextMenuItem struct {
|
|
Label string
|
|
Action string
|
|
Shortcut string
|
|
}
|
|
|
|
// ContextMenuState holds the state for a context menu.
|
|
type ContextMenuState struct {
|
|
X, Y int
|
|
Items []ContextMenuItem
|
|
Selected int
|
|
Active bool
|
|
isDark bool
|
|
styles ContextMenuStyles
|
|
}
|
|
|
|
// ContextMenuStyles holds styling for context menus.
|
|
type ContextMenuStyles struct {
|
|
Item lipgloss.Style
|
|
Selected lipgloss.Style
|
|
Shortcut lipgloss.Style
|
|
Border lipgloss.Style
|
|
}
|
|
|
|
// DefaultContextMenuStyles returns default styles.
|
|
func DefaultContextMenuStyles(isDark bool) ContextMenuStyles {
|
|
if isDark {
|
|
return ContextMenuStyles{
|
|
Item: lipgloss.NewStyle().Foreground(lipgloss.Color("#d8dee9")),
|
|
Selected: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#88c0d0")).Background(lipgloss.Color("#3b4252")),
|
|
Shortcut: lipgloss.NewStyle().Foreground(lipgloss.Color("#4c566a")),
|
|
Border: lipgloss.NewStyle().Foreground(lipgloss.Color("#4c566a")),
|
|
}
|
|
}
|
|
return ContextMenuStyles{
|
|
Item: lipgloss.NewStyle().Foreground(lipgloss.Color("#4c566a")),
|
|
Selected: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#4f8f8f")).Background(lipgloss.Color("#e5e9f0")),
|
|
Shortcut: lipgloss.NewStyle().Foreground(lipgloss.Color("#9ca0a8")),
|
|
Border: lipgloss.NewStyle().Foreground(lipgloss.Color("#9ca0a8")),
|
|
}
|
|
}
|
|
|
|
// NewContextMenuState creates a new context menu state.
|
|
func NewContextMenuState(items []ContextMenuItem, x, y int, isDark bool) *ContextMenuState {
|
|
return &ContextMenuState{
|
|
X: x,
|
|
Y: y,
|
|
Items: items,
|
|
Selected: 0,
|
|
Active: true,
|
|
isDark: isDark,
|
|
styles: DefaultContextMenuStyles(isDark),
|
|
}
|
|
}
|
|
|
|
// Activate shows the context menu at position.
|
|
func (cm *ContextMenuState) Activate(x, y int, items []ContextMenuItem) {
|
|
cm.X = x
|
|
cm.Y = y
|
|
cm.Items = items
|
|
cm.Selected = 0
|
|
cm.Active = true
|
|
}
|
|
|
|
// Deactivate hides the context menu.
|
|
func (cm *ContextMenuState) Deactivate() {
|
|
cm.Active = false
|
|
}
|
|
|
|
// IsActive returns true if the menu is visible.
|
|
func (cm *ContextMenuState) IsActive() bool {
|
|
return cm.Active
|
|
}
|
|
|
|
// SelectedAction returns the action of the selected item.
|
|
func (cm *ContextMenuState) SelectedAction() string {
|
|
if cm.Selected >= 0 && cm.Selected < len(cm.Items) {
|
|
return cm.Items[cm.Selected].Action
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// MoveUp selects the previous item.
|
|
func (cm *ContextMenuState) MoveUp() {
|
|
if cm.Selected > 0 {
|
|
cm.Selected--
|
|
}
|
|
}
|
|
|
|
// MoveDown selects the next item.
|
|
func (cm *ContextMenuState) MoveDown() {
|
|
if cm.Selected < len(cm.Items)-1 {
|
|
cm.Selected++
|
|
}
|
|
}
|
|
|
|
// Render returns the context menu view.
|
|
func (cm *ContextMenuState) Render(width int) string {
|
|
if !cm.Active {
|
|
return ""
|
|
}
|
|
|
|
styles := DefaultContextMenuStyles(cm.isDark)
|
|
|
|
var b string
|
|
for i, item := range cm.Items {
|
|
row := " " + item.Label
|
|
if item.Shortcut != "" {
|
|
row += " " + styles.Shortcut.Render(item.Shortcut)
|
|
}
|
|
|
|
if i == cm.Selected {
|
|
b += styles.Selected.Render(row) + "\n"
|
|
} else {
|
|
b += styles.Item.Render(row) + "\n"
|
|
}
|
|
}
|
|
|
|
// Wrap in border
|
|
box := lipgloss.NewStyle().
|
|
Border(lipgloss.RoundedBorder()).
|
|
BorderForeground(lipgloss.Color("#4c566a")).
|
|
Padding(0, 1)
|
|
|
|
return box.Render(b)
|
|
}
|