quit.go

  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}