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