@@ -4,7 +4,12 @@ import (
4 "charm.land/bubbles/v2/key"
4 "charm.land/bubbles/v2/key"
5 tea "charm.land/bubbletea/v2"
5 tea "charm.land/bubbletea/v2"
6 "charm.land/lipgloss/v2"
6 "charm.land/lipgloss/v2"
7− "github.com/charmbracelet/crush/internal/ui/common"
7+)
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"),
10// OverlayKeyMap defines key bindings for dialogs.
15// OverlayKeyMap defines key bindings for dialogs.
@@ -12,35 +17,50 @@ type OverlayKeyMap struct {
12 Close key.Binding
17 Close key.Binding
15−// DefaultOverlayKeyMap returns the default key bindings for dialogs.
20+// ActionType represents the type of action taken by a dialog.
16−func DefaultOverlayKeyMap() OverlayKeyMap {
21+type ActionType int
17− return OverlayKeyMap{
22+
18− Close: key.NewBinding(
23+const (
19− key.WithKeys("esc", "alt+esc"),
24+ // ActionNone indicates no action.
20− ),
25+ ActionNone ActionType = iota
21− }
26+ // ActionClose indicates that the dialog should be closed.
30+// Action represents an action taken by a dialog.
31+// It can be used to signal closing or other operations.
24// Dialog is a component that can be displayed on top of the UI.
37// Dialog is a component that can be displayed on top of the UI.
25type Dialog interface {
38type Dialog interface {
27 ID() string
39 ID() string
40+ Update(msg tea.Msg) (Action, tea.Cmd)
41+ Layer() *lipgloss.Layer
30// Overlay manages multiple dialogs as an overlay.
44// Overlay manages multiple dialogs as an overlay.
31type Overlay struct {
45type Overlay struct {
32 dialogs []Dialog
46 dialogs []Dialog
36// NewOverlay creates a new [Overlay] instance.
49// NewOverlay creates a new [Overlay] instance.
37func NewOverlay(dialogs ...Dialog) *Overlay {
50func NewOverlay(dialogs ...Dialog) *Overlay {
38 return &Overlay{
51 return &Overlay{
39 dialogs: dialogs,
52 dialogs: dialogs,
40− keyMap: DefaultOverlayKeyMap(),
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 {
61+ return d.dialogs[len(d.dialogs)-1].ID() == dialogID
44// HasDialogs checks if there are any active dialogs.
64// HasDialogs checks if there are any active dialogs.
45func (d *Overlay) HasDialogs() bool {
65func (d *Overlay) HasDialogs() bool {
46 return len(d.dialogs) > 0
66 return len(d.dialogs) > 0
@@ -83,54 +103,31 @@ func (d *Overlay) Update(msg tea.Msg) (*Overlay, tea.Cmd) {
83 dialog := d.dialogs[idx]
103 dialog := d.dialogs[idx]
84 switch msg := msg.(type) {
104 switch msg := msg.(type) {
85 case tea.KeyPressMsg:
105 case tea.KeyPressMsg:
86− if key.Matches(msg, d.keyMap.Close) {
106+ if key.Matches(msg, CloseKey) {
87 // Close the current dialog
107 // Close the current dialog
88 d.removeDialog(idx)
108 d.removeDialog(idx)
89 return d, nil
109 return d, nil
93− updatedDialog, cmd := dialog.Update(msg)
113+ action, cmd := dialog.Update(msg)
94− if updatedDialog == nil {
114+ switch action.Type {
95− // Dialog requested to be closed
115+ case ActionClose:
116+ // Close the current dialog
96 d.removeDialog(idx)
117 d.removeDialog(idx)
97 return d, cmd
118 return d, cmd
100− // Update the dialog in the stack
101− d.dialogs[idx] = updatedDialog
103 return d, cmd
121 return d, cmd
106−// View implements [Model].
124+// Layers returns the current stack of dialogs as lipgloss layers.
107−func (d *Overlay) View() string {
125+func (d *Overlay) Layers() []*lipgloss.Layer {
108− if len(d.dialogs) == 0 {
112− // Compose all the dialogs into a single view
113− dialogs := make([]*lipgloss.Layer, len(d.dialogs))
126+ layers := make([]*lipgloss.Layer, len(d.dialogs))
114 for i, dialog := range d.dialogs {
127 for i, dialog := range d.dialogs {
115− dialogs[i] = lipgloss.NewLayer(dialog.View())
128+ layers[i] = dialog.Layer()
118− comp := lipgloss.NewCompositor(dialogs...)
119− return comp.Render()
122−// ShortHelp implements [help.KeyMap].
123−func (d *Overlay) ShortHelp() []key.Binding {
124− return []key.Binding{
129−// FullHelp implements [help.KeyMap].
130−func (d *Overlay) FullHelp() [][]key.Binding {
131− return [][]key.Binding{
136// removeDialog removes a dialog from the stack.
133// removeDialog removes a dialog from the stack.