modal.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package tview
  2. import (
  3. "strings"
  4. "github.com/gdamore/tcell/v2"
  5. )
  6. // Modal is a centered message window used to inform the user or prompt them
  7. // for an immediate decision. It needs to have at least one button (added via
  8. // AddButtons()) or it will never disappear.
  9. //
  10. // See https://github.com/rivo/tview/wiki/Modal for an example.
  11. type Modal struct {
  12. *Box
  13. // The frame embedded in the modal.
  14. frame *Frame
  15. // The form embedded in the modal's frame.
  16. form *Form
  17. // The message text (original, not word-wrapped).
  18. text string
  19. // The text color.
  20. textColor tcell.Color
  21. // The optional callback for when the user clicked one of the buttons. It
  22. // receives the index of the clicked button and the button's label.
  23. done func(buttonIndex int, buttonLabel string)
  24. }
  25. // NewModal returns a new modal message window.
  26. func NewModal() *Modal {
  27. m := &Modal{
  28. Box: NewBox(),
  29. textColor: Styles.PrimaryTextColor,
  30. }
  31. m.form = NewForm().
  32. SetButtonsAlign(AlignCenter).
  33. SetButtonBackgroundColor(Styles.PrimitiveBackgroundColor).
  34. SetButtonTextColor(Styles.PrimaryTextColor)
  35. m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)
  36. m.form.SetCancelFunc(func() {
  37. if m.done != nil {
  38. m.done(-1, "")
  39. }
  40. })
  41. m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
  42. m.frame.SetBorder(true).
  43. SetBackgroundColor(Styles.ContrastBackgroundColor).
  44. SetBorderPadding(1, 1, 1, 1)
  45. return m
  46. }
  47. // SetBackgroundColor sets the color of the modal frame background.
  48. func (m *Modal) SetBackgroundColor(color tcell.Color) *Modal {
  49. m.form.SetBackgroundColor(color)
  50. m.frame.SetBackgroundColor(color)
  51. return m
  52. }
  53. // SetTextColor sets the color of the message text.
  54. func (m *Modal) SetTextColor(color tcell.Color) *Modal {
  55. m.textColor = color
  56. return m
  57. }
  58. // SetButtonBackgroundColor sets the background color of the buttons.
  59. func (m *Modal) SetButtonBackgroundColor(color tcell.Color) *Modal {
  60. m.form.SetButtonBackgroundColor(color)
  61. return m
  62. }
  63. // SetButtonTextColor sets the color of the button texts.
  64. func (m *Modal) SetButtonTextColor(color tcell.Color) *Modal {
  65. m.form.SetButtonTextColor(color)
  66. return m
  67. }
  68. // SetDoneFunc sets a handler which is called when one of the buttons was
  69. // pressed. It receives the index of the button as well as its label text. The
  70. // handler is also called when the user presses the Escape key. The index will
  71. // then be negative and the label text an emptry string.
  72. func (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) *Modal {
  73. m.done = handler
  74. return m
  75. }
  76. // SetText sets the message text of the window. The text may contain line
  77. // breaks but color tag states will not transfer to following lines. Note that
  78. // words are wrapped, too, based on the final size of the window.
  79. func (m *Modal) SetText(text string) *Modal {
  80. m.text = text
  81. return m
  82. }
  83. // AddButtons adds buttons to the window. There must be at least one button and
  84. // a "done" handler so the window can be closed again.
  85. func (m *Modal) AddButtons(labels []string) *Modal {
  86. for index, label := range labels {
  87. func(i int, l string) {
  88. m.form.AddButton(label, func() {
  89. if m.done != nil {
  90. m.done(i, l)
  91. }
  92. })
  93. button := m.form.GetButton(m.form.GetButtonCount() - 1)
  94. button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
  95. switch event.Key() {
  96. case tcell.KeyDown, tcell.KeyRight:
  97. return tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
  98. case tcell.KeyUp, tcell.KeyLeft:
  99. return tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
  100. }
  101. return event
  102. })
  103. }(index, label)
  104. }
  105. return m
  106. }
  107. // ClearButtons removes all buttons from the window.
  108. func (m *Modal) ClearButtons() *Modal {
  109. m.form.ClearButtons()
  110. return m
  111. }
  112. // SetFocus shifts the focus to the button with the given index.
  113. func (m *Modal) SetFocus(index int) *Modal {
  114. m.form.SetFocus(index)
  115. return m
  116. }
  117. // Focus is called when this primitive receives focus.
  118. func (m *Modal) Focus(delegate func(p Primitive)) {
  119. delegate(m.form)
  120. }
  121. // HasFocus returns whether or not this primitive has focus.
  122. func (m *Modal) HasFocus() bool {
  123. return m.form.HasFocus()
  124. }
  125. // Draw draws this primitive onto the screen.
  126. func (m *Modal) Draw(screen tcell.Screen) {
  127. // Calculate the width of this modal.
  128. buttonsWidth := 0
  129. for _, button := range m.form.buttons {
  130. buttonsWidth += TaggedStringWidth(button.text) + 4 + 2
  131. }
  132. buttonsWidth -= 2
  133. screenWidth, screenHeight := screen.Size()
  134. width := screenWidth / 3
  135. if width < buttonsWidth {
  136. width = buttonsWidth
  137. }
  138. // width is now without the box border.
  139. // Reset the text and find out how wide it is.
  140. m.frame.Clear()
  141. var lines []string
  142. for _, line := range strings.Split(m.text, "\n") {
  143. if len(line) == 0 {
  144. lines = append(lines, "")
  145. continue
  146. }
  147. lines = append(lines, WordWrap(line, width)...)
  148. }
  149. //lines := WordWrap(m.text, width)
  150. for _, line := range lines {
  151. m.frame.AddText(line, true, AlignCenter, m.textColor)
  152. }
  153. // Set the modal's position and size.
  154. height := len(lines) + 6
  155. width += 4
  156. x := (screenWidth - width) / 2
  157. y := (screenHeight - height) / 2
  158. m.SetRect(x, y, width, height)
  159. // Draw the frame.
  160. m.frame.SetRect(x, y, width, height)
  161. m.frame.Draw(screen)
  162. }
  163. // MouseHandler returns the mouse handler for this primitive.
  164. func (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  165. return m.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  166. // Pass mouse events on to the form.
  167. consumed, capture = m.form.MouseHandler()(action, event, setFocus)
  168. if !consumed && action == MouseLeftDown && m.InRect(event.Position()) {
  169. setFocus(m)
  170. consumed = true
  171. }
  172. return
  173. })
  174. }
  175. // InputHandler returns the handler for this primitive.
  176. func (m *Modal) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
  177. return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
  178. if m.frame.HasFocus() {
  179. if handler := m.frame.InputHandler(); handler != nil {
  180. handler(event, setFocus)
  181. return
  182. }
  183. }
  184. })
  185. }