1package dialog
2
3import (
4 "charm.land/bubbles/v2/key"
5 tea "charm.land/bubbletea/v2"
6 "charm.land/lipgloss/v2"
7 "github.com/charmbracelet/crush/internal/ui/common"
8 uv "github.com/charmbracelet/ultraviolet"
9)
10
11// QuitID is the identifier for the quit dialog.
12const QuitID = "quit"
13
14// Quit represents a confirmation dialog for quitting the application.
15type Quit struct {
16 com *common.Common
17 selectedNo bool // true if "No" button is selected
18 keyMap struct {
19 LeftRight,
20 EnterSpace,
21 Yes,
22 No,
23 Tab,
24 Close,
25 Quit key.Binding
26 }
27}
28
29var _ Dialog = (*Quit)(nil)
30
31// NewQuit creates a new quit confirmation dialog.
32func NewQuit(com *common.Common) *Quit {
33 q := &Quit{
34 com: com,
35 selectedNo: true,
36 }
37 q.keyMap.LeftRight = key.NewBinding(
38 key.WithKeys("left", "right"),
39 key.WithHelp("←/→", "switch options"),
40 )
41 q.keyMap.EnterSpace = key.NewBinding(
42 key.WithKeys("enter", " "),
43 key.WithHelp("enter/space", "confirm"),
44 )
45 q.keyMap.Yes = key.NewBinding(
46 key.WithKeys("y", "Y", "ctrl+c"),
47 key.WithHelp("y/Y/ctrl+c", "yes"),
48 )
49 q.keyMap.No = key.NewBinding(
50 key.WithKeys("n", "N"),
51 key.WithHelp("n/N", "no"),
52 )
53 q.keyMap.Tab = key.NewBinding(
54 key.WithKeys("tab"),
55 key.WithHelp("tab", "switch options"),
56 )
57 q.keyMap.Close = CloseKey
58 q.keyMap.Quit = key.NewBinding(
59 key.WithKeys("ctrl+c"),
60 key.WithHelp("ctrl+c", "quit"),
61 )
62 return q
63}
64
65// ID implements [Model].
66func (*Quit) ID() string {
67 return QuitID
68}
69
70// HandleMsg implements [Model].
71func (q *Quit) HandleMsg(msg tea.Msg) Action {
72 switch msg := msg.(type) {
73 case tea.KeyPressMsg:
74 switch {
75 case key.Matches(msg, q.keyMap.Quit):
76 return ActionQuit{}
77 case key.Matches(msg, q.keyMap.Close):
78 return ActionClose{}
79 case key.Matches(msg, q.keyMap.LeftRight, q.keyMap.Tab):
80 q.selectedNo = !q.selectedNo
81 case key.Matches(msg, q.keyMap.EnterSpace):
82 if !q.selectedNo {
83 return ActionQuit{}
84 }
85 return ActionClose{}
86 case key.Matches(msg, q.keyMap.Yes):
87 return ActionQuit{}
88 case key.Matches(msg, q.keyMap.No, q.keyMap.Close):
89 return ActionClose{}
90 }
91 }
92
93 return nil
94}
95
96// Draw implements [Dialog].
97func (q *Quit) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
98 const question = "Are you sure you want to quit?"
99 baseStyle := q.com.Styles.Base
100 buttonOpts := []common.ButtonOpts{
101 {Text: "Yep!", Selected: !q.selectedNo, Padding: 3},
102 {Text: "Nope", Selected: q.selectedNo, Padding: 3},
103 }
104 buttons := common.ButtonGroup(q.com.Styles, buttonOpts, " ")
105 content := baseStyle.Render(
106 lipgloss.JoinVertical(
107 lipgloss.Center,
108 question,
109 "",
110 buttons,
111 ),
112 )
113
114 view := q.com.Styles.BorderFocus.Render(content)
115 DrawCenter(scr, area, view)
116 return nil
117}
118
119// ShortHelp implements [help.KeyMap].
120func (q *Quit) ShortHelp() []key.Binding {
121 return []key.Binding{
122 q.keyMap.LeftRight,
123 q.keyMap.EnterSpace,
124 }
125}
126
127// FullHelp implements [help.KeyMap].
128func (q *Quit) FullHelp() [][]key.Binding {
129 return [][]key.Binding{
130 {q.keyMap.LeftRight, q.keyMap.EnterSpace, q.keyMap.Yes, q.keyMap.No},
131 {q.keyMap.Tab, q.keyMap.Close},
132 }
133}