1package tview
2
3import (
4	"github.com/gdamore/tcell"
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.
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	p.focus = p
41	return p
42}
43
44// SetChangedFunc sets a handler which is called whenever the visibility or the
45// order of any visible pages changes. This can be used to redraw the pages.
46func (p *Pages) SetChangedFunc(handler func()) *Pages {
47	p.changed = handler
48	return p
49}
50
51// GetPageCount returns the number of pages currently stored in this object.
52func (p *Pages) GetPageCount() int {
53	return len(p.pages)
54}
55
56// AddPage adds a new page with the given name and primitive. If there was
57// previously a page with the same name, it is overwritten. Leaving the name
58// empty may cause conflicts in other functions so always specify a non-empty
59// name.
60//
61// Visible pages will be drawn in the order they were added (unless that order
62// was changed in one of the other functions). If "resize" is set to true, the
63// primitive will be set to the size available to the Pages primitive whenever
64// the pages are drawn.
65func (p *Pages) AddPage(name string, item Primitive, resize, visible bool) *Pages {
66	hasFocus := p.HasFocus()
67	for index, pg := range p.pages {
68		if pg.Name == name {
69			p.pages = append(p.pages[:index], p.pages[index+1:]...)
70			break
71		}
72	}
73	p.pages = append(p.pages, &page{Item: item, Name: name, Resize: resize, Visible: visible})
74	if p.changed != nil {
75		p.changed()
76	}
77	if hasFocus {
78		p.Focus(p.setFocus)
79	}
80	return p
81}
82
83// AddAndSwitchToPage calls AddPage(), then SwitchToPage() on that newly added
84// page.
85func (p *Pages) AddAndSwitchToPage(name string, item Primitive, resize bool) *Pages {
86	p.AddPage(name, item, resize, true)
87	p.SwitchToPage(name)
88	return p
89}
90
91// RemovePage removes the page with the given name. If that page was the only
92// visible page, visibility is assigned to the last page.
93func (p *Pages) RemovePage(name string) *Pages {
94	var isVisible bool
95	hasFocus := p.HasFocus()
96	for index, page := range p.pages {
97		if page.Name == name {
98			isVisible = page.Visible
99			p.pages = append(p.pages[:index], p.pages[index+1:]...)
100			if page.Visible && p.changed != nil {
101				p.changed()
102			}
103			break
104		}
105	}
106	if isVisible {
107		for index, page := range p.pages {
108			if index < len(p.pages)-1 {
109				if page.Visible {
110					break // There is a remaining visible page.
111				}
112			} else {
113				page.Visible = true // We need at least one visible page.
114			}
115		}
116	}
117	if hasFocus {
118		p.Focus(p.setFocus)
119	}
120	return p
121}
122
123// HasPage returns true if a page with the given name exists in this object.
124func (p *Pages) HasPage(name string) bool {
125	for _, page := range p.pages {
126		if page.Name == name {
127			return true
128		}
129	}
130	return false
131}
132
133// ShowPage sets a page's visibility to "true" (in addition to any other pages
134// which are already visible).
135func (p *Pages) ShowPage(name string) *Pages {
136	for _, page := range p.pages {
137		if page.Name == name {
138			page.Visible = true
139			if p.changed != nil {
140				p.changed()
141			}
142			break
143		}
144	}
145	if p.HasFocus() {
146		p.Focus(p.setFocus)
147	}
148	return p
149}
150
151// HidePage sets a page's visibility to "false".
152func (p *Pages) HidePage(name string) *Pages {
153	for _, page := range p.pages {
154		if page.Name == name {
155			page.Visible = false
156			if p.changed != nil {
157				p.changed()
158			}
159			break
160		}
161	}
162	if p.HasFocus() {
163		p.Focus(p.setFocus)
164	}
165	return p
166}
167
168// SwitchToPage sets a page's visibility to "true" and all other pages'
169// visibility to "false".
170func (p *Pages) SwitchToPage(name string) *Pages {
171	for _, page := range p.pages {
172		if page.Name == name {
173			page.Visible = true
174		} else {
175			page.Visible = false
176		}
177	}
178	if p.changed != nil {
179		p.changed()
180	}
181	if p.HasFocus() {
182		p.Focus(p.setFocus)
183	}
184	return p
185}
186
187// SendToFront changes the order of the pages such that the page with the given
188// name comes last, causing it to be drawn last with the next update (if
189// visible).
190func (p *Pages) SendToFront(name string) *Pages {
191	for index, page := range p.pages {
192		if page.Name == name {
193			if index < len(p.pages)-1 {
194				p.pages = append(append(p.pages[:index], p.pages[index+1:]...), page)
195			}
196			if page.Visible && p.changed != nil {
197				p.changed()
198			}
199			break
200		}
201	}
202	if p.HasFocus() {
203		p.Focus(p.setFocus)
204	}
205	return p
206}
207
208// SendToBack changes the order of the pages such that the page with the given
209// name comes first, causing it to be drawn first with the next update (if
210// visible).
211func (p *Pages) SendToBack(name string) *Pages {
212	for index, pg := range p.pages {
213		if pg.Name == name {
214			if index > 0 {
215				p.pages = append(append([]*page{pg}, p.pages[:index]...), p.pages[index+1:]...)
216			}
217			if pg.Visible && p.changed != nil {
218				p.changed()
219			}
220			break
221		}
222	}
223	if p.HasFocus() {
224		p.Focus(p.setFocus)
225	}
226	return p
227}
228
229// HasFocus returns whether or not this primitive has focus.
230func (p *Pages) HasFocus() bool {
231	for _, page := range p.pages {
232		if page.Item.GetFocusable().HasFocus() {
233			return true
234		}
235	}
236	return false
237}
238
239// Focus is called by the application when the primitive receives focus.
240func (p *Pages) Focus(delegate func(p Primitive)) {
241	if delegate == nil {
242		return // We cannot delegate so we cannot focus.
243	}
244	p.setFocus = delegate
245	var topItem Primitive
246	for _, page := range p.pages {
247		if page.Visible {
248			topItem = page.Item
249		}
250	}
251	if topItem != nil {
252		delegate(topItem)
253	}
254}
255
256// Draw draws this primitive onto the screen.
257func (p *Pages) Draw(screen tcell.Screen) {
258	p.Box.Draw(screen)
259	for _, page := range p.pages {
260		if !page.Visible {
261			continue
262		}
263		if page.Resize {
264			x, y, width, height := p.GetInnerRect()
265			page.Item.SetRect(x, y, width, height)
266		}
267		page.Item.Draw(screen)
268	}
269}
270