nt

A sensible note-taking program
git clone git://git.laack.co/nt.git
Log | Files | Refs | README

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 }