1package tview
2
3import (
4	"github.com/gdamore/tcell/v2"
5)
6
7// page represents one page of a Pages object.
8type page struct {
9	Name    string    // The page's name.
10	Item    Primitive // The page's primitive.
11	Resize  bool      // Whether or not to resize the page when it is drawn.
12	Visible bool      // Whether or not this page is visible.
13}
14
15// Pages is a container for other primitives often used as the application's
16// root primitive. It allows to easily switch the visibility of the contained
17// primitives.
18//
19// See https://github.com/rivo/tview/wiki/Pages for an example.
20type Pages struct {
21	*Box
22
23	// The contained pages. (Visible) pages are drawn from back to front.
24	pages []*page
25
26	// We keep a reference to the function which allows us to set the focus to
27	// a newly visible page.
28	setFocus func(p Primitive)
29
30	// An optional handler which is called whenever the visibility or the order of
31	// pages changes.
32	changed func()
33}
34
35// NewPages returns a new Pages object.
36func NewPages() *Pages {
37	p := &Pages{
38		Box: NewBox(),
39	}
40	return p
41}
42
43// SetChangedFunc sets a handler which is called whenever the visibility or the
44// order of any visible pages changes. This can be used to redraw the pages.
45func (p *Pages) SetChangedFunc(handler func()) *Pages {
46	p.changed = handler
47	return p
48}
49
50// GetPageCount returns the number of pages currently stored in this object.
51func (p *Pages) GetPageCount() int {
52	return len(p.pages)
53}
54
55// AddPage adds a new page with the given name and primitive. If there was
56// previously a page with the same name, it is overwritten. Leaving the name
57// empty may cause conflicts in other functions so always specify a non-empty
58// name.
59//
60// Visible pages will be drawn in the order they were added (unless that order
61// was changed in one of the other functions). If "resize" is set to true, the
62// primitive will be set to the size available to the Pages primitive whenever
63// the pages are drawn.
64func (p *Pages) AddPage(name string, item Primitive, resize, visible bool) *Pages {
65	hasFocus := p.HasFocus()
66	for index, pg := range p.pages {
67		if pg.Name == name {
68			p.pages = append(p.pages[:index], p.pages[index+1:]...)
69			break
70		}
71	}
72	p.pages = append(p.pages, &page{Item: item, Name: name, Resize: resize, Visible: visible})
73	if p.changed != nil {
74		p.changed()
75	}
76	if hasFocus {
77		p.Focus(p.setFocus)
78	}
79	return p
80}
81
82// AddAndSwitchToPage calls AddPage(), then SwitchToPage() on that newly added
83// page.
84func (p *Pages) AddAndSwitchToPage(name string, item Primitive, resize bool) *Pages {
85	p.AddPage(name, item, resize, true)
86	p.SwitchToPage(name)
87	return p
88}
89
90// RemovePage removes the page with the given name. If that page was the only
91// visible page, visibility is assigned to the last page.
92func (p *Pages) RemovePage(name string) *Pages {
93	var isVisible bool
94	hasFocus := p.HasFocus()
95	for index, page := range p.pages {
96		if page.Name == name {
97			isVisible = page.Visible
98			p.pages = append(p.pages[:index], p.pages[index+1:]...)
99			if page.Visible && p.changed != nil {
100				p.changed()
101			}
102			break
103		}
104	}
105	if isVisible {
106		for index, page := range p.pages {
107			if index < len(p.pages)-1 {
108				if page.Visible {
109					break // There is a remaining visible page.
110				}
111			} else {
112				page.Visible = true // We need at least one visible page.
113			}
114		}
115	}
116	if hasFocus {
117		p.Focus(p.setFocus)
118	}
119	return p
120}
121
122// HasPage returns true if a page with the given name exists in this object.
123func (p *Pages) HasPage(name string) bool {
124	for _, page := range p.pages {
125		if page.Name == name {
126			return true
127		}
128	}
129	return false
130}
131
132// ShowPage sets a page's visibility to "true" (in addition to any other pages
133// which are already visible).
134func (p *Pages) ShowPage(name string) *Pages {
135	for _, page := range p.pages {
136		if page.Name == name {
137			page.Visible = true
138			if p.changed != nil {
139				p.changed()
140			}
141			break
142		}
143	}
144	if p.HasFocus() {
145		p.Focus(p.setFocus)
146	}
147	return p
148}
149
150// HidePage sets a page's visibility to "false".
151func (p *Pages) HidePage(name string) *Pages {
152	for _, page := range p.pages {
153		if page.Name == name {
154			page.Visible = false
155			if p.changed != nil {
156				p.changed()
157			}
158			break
159		}
160	}
161	if p.HasFocus() {
162		p.Focus(p.setFocus)
163	}
164	return p
165}
166
167// SwitchToPage sets a page's visibility to "true" and all other pages'
168// visibility to "false".
169func (p *Pages) SwitchToPage(name string) *Pages {
170	for _, page := range p.pages {
171		if page.Name == name {
172			page.Visible = true
173		} else {
174			page.Visible = false
175		}
176	}
177	if p.changed != nil {
178		p.changed()
179	}
180	if p.HasFocus() {
181		p.Focus(p.setFocus)
182	}
183	return p
184}
185
186// SendToFront changes the order of the pages such that the page with the given
187// name comes last, causing it to be drawn last with the next update (if
188// visible).
189func (p *Pages) SendToFront(name string) *Pages {
190	for index, page := range p.pages {
191		if page.Name == name {
192			if index < len(p.pages)-1 {
193				p.pages = append(append(p.pages[:index], p.pages[index+1:]...), page)
194			}
195			if page.Visible && p.changed != nil {
196				p.changed()
197			}
198			break
199		}
200	}
201	if p.HasFocus() {
202		p.Focus(p.setFocus)
203	}
204	return p
205}
206
207// SendToBack changes the order of the pages such that the page with the given
208// name comes first, causing it to be drawn first with the next update (if
209// visible).
210func (p *Pages) SendToBack(name string) *Pages {
211	for index, pg := range p.pages {
212		if pg.Name == name {
213			if index > 0 {
214				p.pages = append(append([]*page{pg}, p.pages[:index]...), p.pages[index+1:]...)
215			}
216			if pg.Visible && p.changed != nil {
217				p.changed()
218			}
219			break
220		}
221	}
222	if p.HasFocus() {
223		p.Focus(p.setFocus)
224	}
225	return p
226}
227
228// GetFrontPage returns the front-most visible page. If there are no visible
229// pages, ("", nil) is returned.
230func (p *Pages) GetFrontPage() (name string, item Primitive) {
231	for index := len(p.pages) - 1; index >= 0; index-- {
232		if p.pages[index].Visible {
233			return p.pages[index].Name, p.pages[index].Item
234		}
235	}
236	return
237}
238
239// HasFocus returns whether or not this primitive has focus.
240func (p *Pages) HasFocus() bool {
241	for _, page := range p.pages {
242		if page.Item.HasFocus() {
243			return true
244		}
245	}
246	return false
247}
248
249// Focus is called by the application when the primitive receives focus.
250func (p *Pages) Focus(delegate func(p Primitive)) {
251	if delegate == nil {
252		return // We cannot delegate so we cannot focus.
253	}
254	p.setFocus = delegate
255	var topItem Primitive
256	for _, page := range p.pages {
257		if page.Visible {
258			topItem = page.Item
259		}
260	}
261	if topItem != nil {
262		delegate(topItem)
263	}
264}
265
266// Draw draws this primitive onto the screen.
267func (p *Pages) Draw(screen tcell.Screen) {
268	p.Box.DrawForSubclass(screen, p)
269	for _, page := range p.pages {
270		if !page.Visible {
271			continue
272		}
273		if page.Resize {
274			x, y, width, height := p.GetInnerRect()
275			page.Item.SetRect(x, y, width, height)
276		}
277		page.Item.Draw(screen)
278	}
279}
280
281// MouseHandler returns the mouse handler for this primitive.
282func (p *Pages) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
283	return p.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
284		if !p.InRect(event.Position()) {
285			return false, nil
286		}
287
288		// Pass mouse events along to the last visible page item that takes it.
289		for index := len(p.pages) - 1; index >= 0; index-- {
290			page := p.pages[index]
291			if page.Visible {
292				consumed, capture = page.Item.MouseHandler()(action, event, setFocus)
293				if consumed {
294					return
295				}
296			}
297		}
298
299		return
300	})
301}
302
303// InputHandler returns the handler for this primitive.
304func (p *Pages) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
305	return p.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
306		for _, page := range p.pages {
307			if page.Item.HasFocus() {
308				if handler := page.Item.InputHandler(); handler != nil {
309					handler(event, setFocus)
310					return
311				}
312			}
313		}
314	})
315}
316