quit.go

  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}