quit.go 3.0 KB

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