@@ -4,7 +4,12 @@ import (
44 "charm.land/bubbles/v2/key"
55 tea "charm.land/bubbletea/v2"
66 "charm.land/lipgloss/v2"
7− "github.com/charmbracelet/crush/internal/ui/common"
7+)
8+
9+// CloseKey is the default key binding to close dialogs.
10+var CloseKey = key.NewBinding(
11+ key.WithKeys("esc", "alt+esc"),
12+ key.WithHelp("esc", "exit"),
813)
914
1015// OverlayKeyMap defines key bindings for dialogs.
@@ -12,35 +17,50 @@ type OverlayKeyMap struct {
1217 Close key.Binding
1318}
1419
15−// DefaultOverlayKeyMap returns the default key bindings for dialogs.
16−func DefaultOverlayKeyMap() OverlayKeyMap {
17− return OverlayKeyMap{
18− Close: key.NewBinding(
19− key.WithKeys("esc", "alt+esc"),
20− ),
21− }
20+// ActionType represents the type of action taken by a dialog.
21+type ActionType int
22+
23+const (
24+ // ActionNone indicates no action.
25+ ActionNone ActionType = iota
26+ // ActionClose indicates that the dialog should be closed.
27+ ActionClose
28+)
29+
30+// Action represents an action taken by a dialog.
31+// It can be used to signal closing or other operations.
32+type Action struct {
33+ Type ActionType
34+ Payload any
2235}
2336
2437// Dialog is a component that can be displayed on top of the UI.
2538type Dialog interface {
26− common.Model[Dialog]
2739 ID() string
40+ Update(msg tea.Msg) (Action, tea.Cmd)
41+ Layer() *lipgloss.Layer
2842}
2943
3044// Overlay manages multiple dialogs as an overlay.
3145type Overlay struct {
3246 dialogs []Dialog
33− keyMap OverlayKeyMap
3447}
3548
3649// NewOverlay creates a new [Overlay] instance.
3750func NewOverlay(dialogs ...Dialog) *Overlay {
3851 return &Overlay{
3952 dialogs: dialogs,
40− keyMap: DefaultOverlayKeyMap(),
4153 }
4254}
4355
56+// IsFrontDialog checks if the dialog with the specified ID is at the front.
57+func (d *Overlay) IsFrontDialog(dialogID string) bool {
58+ if len(d.dialogs) == 0 {
59+ return false
60+ }
61+ return d.dialogs[len(d.dialogs)-1].ID() == dialogID
62+}
63+
4464// HasDialogs checks if there are any active dialogs.
4565func (d *Overlay) HasDialogs() bool {
4666 return len(d.dialogs) > 0
@@ -83,54 +103,31 @@ func (d *Overlay) Update(msg tea.Msg) (*Overlay, tea.Cmd) {
83103 dialog := d.dialogs[idx]
84104 switch msg := msg.(type) {
85105 case tea.KeyPressMsg:
86− if key.Matches(msg, d.keyMap.Close) {
106+ if key.Matches(msg, CloseKey) {
87107 // Close the current dialog
88108 d.removeDialog(idx)
89109 return d, nil
90110 }
91111 }
92112
93− updatedDialog, cmd := dialog.Update(msg)
94− if updatedDialog == nil {
95− // Dialog requested to be closed
113+ action, cmd := dialog.Update(msg)
114+ switch action.Type {
115+ case ActionClose:
116+ // Close the current dialog
96117 d.removeDialog(idx)
97118 return d, cmd
98119 }
99120
100− // Update the dialog in the stack
101− d.dialogs[idx] = updatedDialog
102−
103121 return d, cmd
104122}
105123
106−// View implements [Model].
107−func (d *Overlay) View() string {
108− if len(d.dialogs) == 0 {
109− return ""
110− }
111−
112− // Compose all the dialogs into a single view
113− dialogs := make([]*lipgloss.Layer, len(d.dialogs))
124+// Layers returns the current stack of dialogs as lipgloss layers.
125+func (d *Overlay) Layers() []*lipgloss.Layer {
126+ layers := make([]*lipgloss.Layer, len(d.dialogs))
114127 for i, dialog := range d.dialogs {
115− dialogs[i] = lipgloss.NewLayer(dialog.View())
116− }
117−
118− comp := lipgloss.NewCompositor(dialogs...)
119− return comp.Render()
120−}
121−
122−// ShortHelp implements [help.KeyMap].
123−func (d *Overlay) ShortHelp() []key.Binding {
124− return []key.Binding{
125− d.keyMap.Close,
126− }
127−}
128−
129−// FullHelp implements [help.KeyMap].
130−func (d *Overlay) FullHelp() [][]key.Binding {
131− return [][]key.Binding{
132− {d.keyMap.Close},
128+ layers[i] = dialog.Layer()
133129 }
130+ return layers
134131}
135132
136133// removeDialog removes a dialog from the stack.