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