1package tview
2
3import (
4	"github.com/gdamore/tcell"
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// AddButtons()) or it will never disappear.
10//
11// See https://github.com/rivo/tview/wiki/Modal for an example.
12type 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.
33func NewModal() *Modal {
34	m := &Modal{
35		Box:       NewBox(),
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.SetBorder(true).
50		SetBackgroundColor(Styles.ContrastBackgroundColor).
51		SetBorderPadding(1, 1, 1, 1)
52	m.focus = m
53	return m
54}
55
56// SetBackgroundColor sets the color of the modal frame background.
57func (m *Modal) SetBackgroundColor(color tcell.Color) *Modal {
58	m.form.SetBackgroundColor(color)
59	m.frame.SetBackgroundColor(color)
60	return m
61}
62
63// SetTextColor sets the color of the message text.
64func (m *Modal) SetTextColor(color tcell.Color) *Modal {
65	m.textColor = color
66	return m
67}
68
69// SetButtonBackgroundColor sets the background color of the buttons.
70func (m *Modal) SetButtonBackgroundColor(color tcell.Color) *Modal {
71	m.form.SetButtonBackgroundColor(color)
72	return m
73}
74
75// SetButtonTextColor sets the color of the button texts.
76func (m *Modal) SetButtonTextColor(color tcell.Color) *Modal {
77	m.form.SetButtonTextColor(color)
78	return m
79}
80
81// SetDoneFunc sets a handler which is called when one of the buttons was
82// pressed. It receives the index of the button as well as its label text. The
83// handler is also called when the user presses the Escape key. The index will
84// then be negative and the label text an emptry string.
85func (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) *Modal {
86	m.done = handler
87	return m
88}
89
90// SetText sets the message text of the window. The text may contain line
91// breaks. Note that words are wrapped, too, based on the final size of the
92// window.
93func (m *Modal) SetText(text string) *Modal {
94	m.text = text
95	return m
96}
97
98// AddButtons adds buttons to the window. There must be at least one button and
99// a "done" handler so the window can be closed again.
100func (m *Modal) AddButtons(labels []string) *Modal {
101	for index, label := range labels {
102		func(i int, l string) {
103			m.form.AddButton(label, func() {
104				if m.done != nil {
105					m.done(i, l)
106				}
107			})
108			button := m.form.GetButton(m.form.GetButtonCount() - 1)
109			button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
110				switch event.Key() {
111				case tcell.KeyDown, tcell.KeyRight:
112					return tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
113				case tcell.KeyUp, tcell.KeyLeft:
114					return tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
115				}
116				return event
117			})
118		}(index, label)
119	}
120	return m
121}
122
123// ClearButtons removes all buttons from the window.
124func (m *Modal) ClearButtons() *Modal {
125	m.form.ClearButtons()
126	return m
127}
128
129// SetFocus shifts the focus to the button with the given index.
130func (m *Modal) SetFocus(index int) *Modal {
131	m.form.SetFocus(index)
132	return m
133}
134
135// Focus is called when this primitive receives focus.
136func (m *Modal) Focus(delegate func(p Primitive)) {
137	delegate(m.form)
138}
139
140// HasFocus returns whether or not this primitive has focus.
141func (m *Modal) HasFocus() bool {
142	return m.form.HasFocus()
143}
144
145// Draw draws this primitive onto the screen.
146func (m *Modal) Draw(screen tcell.Screen) {
147	// Calculate the width of this modal.
148	buttonsWidth := 0
149	for _, button := range m.form.buttons {
150		buttonsWidth += TaggedStringWidth(button.label) + 4 + 2
151	}
152	buttonsWidth -= 2
153	screenWidth, screenHeight := screen.Size()
154	width := screenWidth / 3
155	if width < buttonsWidth {
156		width = buttonsWidth
157	}
158	// width is now without the box border.
159
160	// Reset the text and find out how wide it is.
161	m.frame.Clear()
162	lines := WordWrap(m.text, width)
163	for _, line := range lines {
164		m.frame.AddText(line, true, AlignCenter, m.textColor)
165	}
166
167	// Set the modal's position and size.
168	height := len(lines) + 6
169	width += 4
170	x := (screenWidth - width) / 2
171	y := (screenHeight - height) / 2
172	m.SetRect(x, y, width, height)
173
174	// Draw the frame.
175	m.frame.SetRect(x, y, width, height)
176	m.frame.Draw(screen)
177}
178
179// MouseHandler returns the mouse handler for this primitive.
180func (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
181	return m.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
182		// Pass mouse events on to the form.
183		consumed, capture = m.form.MouseHandler()(action, event, setFocus)
184		if !consumed && action == MouseLeftClick && m.InRect(event.Position()) {
185			setFocus(m)
186			consumed = true
187		}
188		return
189	})
190}
191