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