1package quit
2
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
5 tea "github.com/charmbracelet/bubbletea/v2"
6 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
7 "github.com/charmbracelet/crush/internal/tui/layout"
8 "github.com/charmbracelet/crush/internal/tui/styles"
9 "github.com/charmbracelet/crush/internal/tui/util"
10 "github.com/charmbracelet/lipgloss/v2"
11)
12
13const (
14 question = "Are you sure you want to quit?"
15 QuitDialogID dialogs.DialogID = "quit"
16)
17
18// QuitDialog represents a confirmation dialog for quitting the application.
19type QuitDialog interface {
20 dialogs.DialogModel
21 layout.Bindings
22}
23
24type quitDialogCmp struct {
25 wWidth int
26 wHeight int
27
28 selectedNo bool // true if "No" button is selected
29 keymap KeyMap
30}
31
32// NewQuitDialog creates a new quit confirmation dialog.
33func NewQuitDialog() QuitDialog {
34 return &quitDialogCmp{
35 selectedNo: true, // Default to "No" for safety
36 keymap: DefaultKeymap(),
37 }
38}
39
40func (q *quitDialogCmp) Init() tea.Cmd {
41 return nil
42}
43
44// Update handles keyboard input for the quit dialog.
45func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
46 switch msg := msg.(type) {
47 case tea.WindowSizeMsg:
48 q.wWidth = msg.Width
49 q.wHeight = msg.Height
50 case tea.KeyPressMsg:
51 switch {
52 case key.Matches(msg, q.keymap.LeftRight, q.keymap.Tab):
53 q.selectedNo = !q.selectedNo
54 return q, nil
55 case key.Matches(msg, q.keymap.EnterSpace):
56 if !q.selectedNo {
57 return q, tea.Quit
58 }
59 return q, util.CmdHandler(dialogs.CloseDialogMsg{})
60 case key.Matches(msg, q.keymap.Yes):
61 return q, tea.Quit
62 case key.Matches(msg, q.keymap.No, q.keymap.Close):
63 return q, util.CmdHandler(dialogs.CloseDialogMsg{})
64 }
65 }
66 return q, nil
67}
68
69// View renders the quit dialog with Yes/No buttons.
70func (q *quitDialogCmp) View() tea.View {
71 t := styles.CurrentTheme()
72 baseStyle := t.S().Base
73 yesStyle := t.S().Text
74 noStyle := yesStyle
75
76 if q.selectedNo {
77 noStyle = noStyle.Foreground(t.White).Background(t.Secondary)
78 yesStyle = yesStyle.Background(t.BgSubtle)
79 } else {
80 yesStyle = yesStyle.Foreground(t.White).Background(t.Secondary)
81 noStyle = noStyle.Background(t.BgSubtle)
82 }
83
84 yesButton := yesStyle.Padding(0, 1).Render("Yep!")
85 noButton := noStyle.Padding(0, 1).Render("Nope")
86
87 buttons := baseStyle.Width(lipgloss.Width(question)).Align(lipgloss.Right).Render(
88 lipgloss.JoinHorizontal(lipgloss.Center, yesButton, " ", noButton),
89 )
90
91 content := baseStyle.Render(
92 lipgloss.JoinVertical(
93 lipgloss.Center,
94 question,
95 "",
96 buttons,
97 ),
98 )
99
100 quitDialogStyle := baseStyle.
101 Padding(1, 2).
102 Border(lipgloss.RoundedBorder()).
103 BorderForeground(t.BorderFocus)
104
105 return tea.NewView(
106 quitDialogStyle.Render(content),
107 )
108}
109
110func (q *quitDialogCmp) BindingKeys() []key.Binding {
111 return layout.KeyMapToSlice(q.keymap)
112}
113
114func (q *quitDialogCmp) Position() (int, int) {
115 row := q.wHeight / 2
116 row -= 7 / 2
117 col := q.wWidth / 2
118 col -= (lipgloss.Width(question) + 4) / 2
119
120 return row, col
121}
122
123func (q *quitDialogCmp) ID() dialogs.DialogID {
124 return QuitDialogID
125}