forked from tslocum/cview
Rename Pages as Panels
This commit is contained in:
parent
3ad2fdd36a
commit
767e4e474c
|
@ -13,6 +13,7 @@ v1.5.1 (WIP)
|
|||
- Optimize TextView (writing is 90% faster, drawing is 50% faster)
|
||||
- Remove return values from methods which return their primitive (breaks chaining)
|
||||
- Remove Application.ForceDraw (Application.Draw may be called anywhere)
|
||||
- Rename Pages as Panels
|
||||
|
||||
v1.5.0 (2020-10-03)
|
||||
- Add scroll bar to TextView
|
||||
|
|
|
@ -23,7 +23,7 @@ Available widgets:
|
|||
- Selectable __lists__ with __context menus__
|
||||
- Modal __dialogs__
|
||||
- Horizontal and vertical __progress bars__
|
||||
- __Grid__, __Flexbox__ and __page layouts__
|
||||
- __Grid__, __Flexbox__ and __panel layouts__
|
||||
- Sophisticated navigable __table views__
|
||||
- Flexible __tree views__
|
||||
- Draggable and resizable __windows__
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Demo code for the Pages primitive.
|
||||
// Demo code for the Panels primitive.
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -7,31 +7,31 @@ import (
|
|||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
||||
const pageCount = 5
|
||||
const panelCount = 5
|
||||
|
||||
func main() {
|
||||
app := cview.NewApplication()
|
||||
app.EnableMouse(true)
|
||||
|
||||
pages := cview.NewPages()
|
||||
for page := 0; page < pageCount; page++ {
|
||||
func(page int) {
|
||||
panels := cview.NewPanels()
|
||||
for panel := 0; panel < panelCount; panel++ {
|
||||
func(panel int) {
|
||||
modal := cview.NewModal()
|
||||
modal.SetText(fmt.Sprintf("This is page %d. Choose where to go next.", page+1))
|
||||
modal.SetText(fmt.Sprintf("This is page %d. Choose where to go next.", panel+1))
|
||||
modal.AddButtons([]string{"Next", "Quit"})
|
||||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
if buttonIndex == 0 {
|
||||
pages.SwitchToPage(fmt.Sprintf("page-%d", (page+1)%pageCount))
|
||||
panels.SwitchTo(fmt.Sprintf("panel-%d", (panel+1)%panelCount))
|
||||
} else {
|
||||
app.Stop()
|
||||
}
|
||||
})
|
||||
|
||||
pages.AddPage(fmt.Sprintf("page-%d", page), modal, false, page == 0)
|
||||
}(page)
|
||||
panels.Add(fmt.Sprintf("panel-%d", panel), modal, false, panel == 0)
|
||||
}(panel)
|
||||
}
|
||||
|
||||
app.SetRoot(pages, true)
|
||||
app.SetRoot(panels, true)
|
||||
if err := app.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ func demoBox(title string) *cview.Box {
|
|||
// Flex demonstrates flexbox layout.
|
||||
func Flex(nextSlide func()) (title string, content cview.Primitive) {
|
||||
modalShown := false
|
||||
pages := cview.NewPages()
|
||||
panels := cview.NewPanels()
|
||||
|
||||
textView := cview.NewTextView()
|
||||
textView.SetBorder(true)
|
||||
|
@ -25,7 +25,7 @@ func Flex(nextSlide func()) (title string, content cview.Primitive) {
|
|||
nextSlide()
|
||||
modalShown = false
|
||||
} else {
|
||||
pages.ShowPage("modal")
|
||||
panels.Show("modal")
|
||||
modalShown = true
|
||||
}
|
||||
})
|
||||
|
@ -45,10 +45,10 @@ func Flex(nextSlide func()) (title string, content cview.Primitive) {
|
|||
modal.SetText("Resize the window to see the effect of the flexbox parameters")
|
||||
modal.AddButtons([]string{"Ok"})
|
||||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
pages.HidePage("modal")
|
||||
panels.Hide("modal")
|
||||
})
|
||||
|
||||
pages.AddPage("flex", flex, true, true)
|
||||
pages.AddPage("modal", modal, false, false)
|
||||
return "Flex", pages
|
||||
panels.Add("flex", flex, true, true)
|
||||
panels.Add("modal", modal, false, false)
|
||||
return "Flex", panels
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
// Grid demonstrates the grid layout.
|
||||
func Grid(nextSlide func()) (title string, content cview.Primitive) {
|
||||
modalShown := false
|
||||
pages := cview.NewPages()
|
||||
panels := cview.NewPanels()
|
||||
|
||||
newPrimitive := func(text string) cview.Primitive {
|
||||
tv := cview.NewTextView()
|
||||
|
@ -19,7 +19,7 @@ func Grid(nextSlide func()) (title string, content cview.Primitive) {
|
|||
nextSlide()
|
||||
modalShown = false
|
||||
} else {
|
||||
pages.ShowPage("modal")
|
||||
panels.Show("modal")
|
||||
modalShown = true
|
||||
}
|
||||
})
|
||||
|
@ -51,11 +51,11 @@ func Grid(nextSlide func()) (title string, content cview.Primitive) {
|
|||
modal.SetText("Resize the window to see how the grid layout adapts")
|
||||
modal.AddButtons([]string{"Ok"})
|
||||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
pages.HidePage("modal")
|
||||
panels.Hide("modal")
|
||||
})
|
||||
|
||||
pages.AddPage("grid", grid, true, true)
|
||||
pages.AddPage("modal", modal, false, false)
|
||||
panels.Add("grid", grid, true, true)
|
||||
panels.Add("modal", modal, false, false)
|
||||
|
||||
return "Grid", pages
|
||||
return "Grid", panels
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ func main() {
|
|||
End,
|
||||
}
|
||||
|
||||
pages := cview.NewPages()
|
||||
panels := cview.NewPanels()
|
||||
|
||||
// The bottom row has some info on where we are.
|
||||
info := cview.NewTextView()
|
||||
|
@ -72,7 +72,7 @@ func main() {
|
|||
info.SetRegions(true)
|
||||
info.SetWrap(false)
|
||||
info.SetHighlightedFunc(func(added, removed, remaining []string) {
|
||||
pages.SwitchToPage(added[0])
|
||||
panels.SwitchTo(added[0])
|
||||
})
|
||||
|
||||
// Create the pages for all slides.
|
||||
|
@ -95,7 +95,7 @@ func main() {
|
|||
slideRegions = append(slideRegions, cursor)
|
||||
|
||||
title, primitive := slide(nextSlide)
|
||||
pages.AddPage(strconv.Itoa(index), primitive, true, index == 0)
|
||||
panels.Add(strconv.Itoa(index), primitive, true, index == 0)
|
||||
fmt.Fprintf(info, `["%d"][darkcyan] %s [white][""]|`, index, title)
|
||||
|
||||
cursor += len(title) + 4
|
||||
|
@ -105,7 +105,7 @@ func main() {
|
|||
// Create the main layout.
|
||||
layout := cview.NewFlex()
|
||||
layout.SetDirection(cview.FlexRow)
|
||||
layout.AddItem(pages, 0, 1, true)
|
||||
layout.AddItem(panels, 0, 1, true)
|
||||
layout.AddItem(info, 1, 1, false)
|
||||
|
||||
// Shortcuts to navigate the slides.
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func main() {
|
||||
app := cview.NewApplication()
|
||||
pages := cview.NewPages()
|
||||
panels := cview.NewPanels()
|
||||
|
||||
form := cview.NewForm()
|
||||
form.AddDropDownSimple("称谓", 0, nil, "先生", "女士", "博士", "老师", "师傅")
|
||||
|
@ -20,7 +20,7 @@ func main() {
|
|||
_, option := form.GetFormItem(0).(*cview.DropDown).GetCurrentOption()
|
||||
userName := form.GetFormItem(1).(*cview.InputField).GetText()
|
||||
|
||||
alert(pages, "alert-dialog", fmt.Sprintf("保存成功,%s %s!", userName, option.GetText()))
|
||||
alert(panels, "alert-dialog", fmt.Sprintf("保存成功,%s %s!", userName, option.GetText()))
|
||||
})
|
||||
form.AddButton("退出", func() {
|
||||
app.Stop()
|
||||
|
@ -28,23 +28,23 @@ func main() {
|
|||
form.SetBorder(true)
|
||||
form.SetTitle("输入一些内容")
|
||||
form.SetTitleAlign(cview.AlignLeft)
|
||||
pages.AddPage("base", form, true, true)
|
||||
panels.Add("base", form, true, true)
|
||||
|
||||
app.SetRoot(pages, true)
|
||||
app.SetRoot(panels, true)
|
||||
if err := app.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// alert shows a confirmation dialog.
|
||||
func alert(pages *cview.Pages, id string, message string) {
|
||||
func alert(panels *cview.Panels, id string, message string) {
|
||||
modal := cview.NewModal()
|
||||
modal.SetText(message)
|
||||
modal.AddButtons([]string{"确定"})
|
||||
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
pages.HidePage(id)
|
||||
pages.RemovePage(id)
|
||||
panels.Hide(id)
|
||||
panels.Remove(id)
|
||||
})
|
||||
|
||||
pages.AddPage(id, modal, false, true)
|
||||
panels.Add(id, modal, false, true)
|
||||
}
|
||||
|
|
6
doc.go
6
doc.go
|
@ -33,7 +33,7 @@ The following widgets are available:
|
|||
InputField - Single-line text entry field.
|
||||
List - A navigable text list with optional keyboard shortcuts.
|
||||
Modal - A centered window with a text message and one or more buttons.
|
||||
Pages - A page based layout manager.
|
||||
Panels - A panel based layout manager.
|
||||
ProgressBar - Indicates the progress of an operation.
|
||||
Table - A scrollable display of tabular data. Table cells, rows, or columns
|
||||
may also be highlighted.
|
||||
|
@ -107,8 +107,8 @@ square brackets. Examples:
|
|||
|
||||
A color tag changes the color of the characters following that color tag. This
|
||||
applies to almost everything from box titles, list text, form item labels, to
|
||||
table cells. In a TextView, this functionality has to be switched on explicitly.
|
||||
See the TextView documentation for more information.
|
||||
table cells. In a TextView, this functionality must be explicitly enabled. See
|
||||
the TextView documentation for more information.
|
||||
|
||||
Color tags may contain not just the foreground (text) color but also the
|
||||
background color and additional flags. In fact, the full definition of a color
|
||||
|
|
368
pages.go
368
pages.go
|
@ -1,380 +1,60 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
type page = panel
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
// page represents one page of a Pages object.
|
||||
type page struct {
|
||||
Name string // The page's name.
|
||||
Item Primitive // The page's primitive.
|
||||
Resize bool // Whether or not to resize the page when it is drawn.
|
||||
Visible bool // Whether or not this page is visible.
|
||||
}
|
||||
|
||||
// Pages is a container for other primitives often used as the application's
|
||||
// root primitive. It allows to easily switch the visibility of the contained
|
||||
// primitives.
|
||||
// Pages is a wrapper around Panels. It is provided for backwards compatibility.
|
||||
// Application developers should use Panels instead.
|
||||
type Pages struct {
|
||||
*Box
|
||||
|
||||
// The contained pages. (Visible) pages are drawn from back to front.
|
||||
pages []*page
|
||||
|
||||
// We keep a reference to the function which allows us to set the focus to
|
||||
// a newly visible page.
|
||||
setFocus func(p Primitive)
|
||||
|
||||
// An optional handler which is called whenever the visibility or the order of
|
||||
// pages changes.
|
||||
changed func()
|
||||
|
||||
sync.RWMutex
|
||||
*Panels
|
||||
}
|
||||
|
||||
// NewPages returns a new Pages object.
|
||||
// NewPages returns a new Panels object.
|
||||
func NewPages() *Pages {
|
||||
p := &Pages{
|
||||
Box: NewBox(),
|
||||
}
|
||||
p.focus = p
|
||||
return p
|
||||
return &Pages{NewPanels()}
|
||||
}
|
||||
|
||||
// SetChangedFunc sets a handler which is called whenever the visibility or the
|
||||
// order of any visible pages changes. This can be used to redraw the pages.
|
||||
func (p *Pages) SetChangedFunc(handler func()) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.changed = handler
|
||||
}
|
||||
|
||||
// GetPageCount returns the number of pages currently stored in this object.
|
||||
// GetPageCount returns the number of panels currently stored in this object.
|
||||
func (p *Pages) GetPageCount() int {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
return len(p.pages)
|
||||
return p.GetPanelCount()
|
||||
}
|
||||
|
||||
// AddPage adds a new page with the given name and primitive. If there was
|
||||
// previously a page with the same name, it is overwritten. Leaving the name
|
||||
// empty may cause conflicts in other functions so always specify a non-empty
|
||||
// name.
|
||||
//
|
||||
// Visible pages will be drawn in the order they were added (unless that order
|
||||
// was changed in one of the other functions). If "resize" is set to true, the
|
||||
// primitive will be set to the size available to the Pages primitive whenever
|
||||
// the pages are drawn.
|
||||
// AddPage adds a new panel with the given name and primitive.
|
||||
func (p *Pages) AddPage(name string, item Primitive, resize, visible bool) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for index, pg := range p.pages {
|
||||
if pg.Name == name {
|
||||
p.pages = append(p.pages[:index], p.pages[index+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
p.pages = append(p.pages, &page{Item: item, Name: name, Resize: resize, Visible: visible})
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
p.Add(name, item, resize, visible)
|
||||
}
|
||||
|
||||
// AddAndSwitchToPage calls AddPage(), then SwitchToPage() on that newly added
|
||||
// page.
|
||||
// AddAndSwitchToPage calls Add(), then SwitchTo() on that newly added panel.
|
||||
func (p *Pages) AddAndSwitchToPage(name string, item Primitive, resize bool) {
|
||||
p.AddPage(name, item, resize, true)
|
||||
p.SwitchToPage(name)
|
||||
p.AddAndSwitchTo(name, item, resize)
|
||||
}
|
||||
|
||||
// RemovePage removes the page with the given name. If that page was the only
|
||||
// visible page, visibility is assigned to the last page.
|
||||
// RemovePage removes the panel with the given name.
|
||||
func (p *Pages) RemovePage(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
var isVisible bool
|
||||
for index, page := range p.pages {
|
||||
if page.Name == name {
|
||||
isVisible = page.Visible
|
||||
p.pages = append(p.pages[:index], p.pages[index+1:]...)
|
||||
if page.Visible && p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if isVisible {
|
||||
for index, page := range p.pages {
|
||||
if index < len(p.pages)-1 {
|
||||
if page.Visible {
|
||||
break // There is a remaining visible page.
|
||||
}
|
||||
} else {
|
||||
page.Visible = true // We need at least one visible page.
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
p.Remove(name)
|
||||
}
|
||||
|
||||
// HasPage returns true if a page with the given name exists in this object.
|
||||
// HasPage returns true if a panel with the given name exists in this object.
|
||||
func (p *Pages) HasPage(name string) bool {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if page.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return p.Has(name)
|
||||
}
|
||||
|
||||
// ShowPage sets a page's visibility to "true" (in addition to any other pages
|
||||
// which are already visible).
|
||||
// ShowPage sets a panel's visibility to "true".
|
||||
func (p *Pages) ShowPage(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if page.Name == name {
|
||||
page.Visible = true
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
p.Show(name)
|
||||
}
|
||||
|
||||
// HidePage sets a page's visibility to "false".
|
||||
// HidePage sets a panel's visibility to "false".
|
||||
func (p *Pages) HidePage(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if page.Name == name {
|
||||
page.Visible = false
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
p.Hide(name)
|
||||
}
|
||||
|
||||
// SwitchToPage sets a page's visibility to "true" and all other pages'
|
||||
// SwitchToPage sets a panel's visibility to "true" and all other panels'
|
||||
// visibility to "false".
|
||||
func (p *Pages) SwitchToPage(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if page.Name == name {
|
||||
page.Visible = true
|
||||
} else {
|
||||
page.Visible = false
|
||||
}
|
||||
}
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
p.SwitchTo(name)
|
||||
}
|
||||
|
||||
// SendToFront changes the order of the pages such that the page with the given
|
||||
// name comes last, causing it to be drawn last with the next update (if
|
||||
// visible).
|
||||
func (p *Pages) SendToFront(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for index, page := range p.pages {
|
||||
if page.Name == name {
|
||||
if index < len(p.pages)-1 {
|
||||
p.pages = append(append(p.pages[:index], p.pages[index+1:]...), page)
|
||||
}
|
||||
if page.Visible && p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// SendToBack changes the order of the pages such that the page with the given
|
||||
// name comes first, causing it to be drawn first with the next update (if
|
||||
// visible).
|
||||
func (p *Pages) SendToBack(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for index, pg := range p.pages {
|
||||
if pg.Name == name {
|
||||
if index > 0 {
|
||||
p.pages = append(append([]*page{pg}, p.pages[:index]...), p.pages[index+1:]...)
|
||||
}
|
||||
if pg.Visible && p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// GetFrontPage returns the front-most visible page. If there are no visible
|
||||
// pages, ("", nil) is returned.
|
||||
// GetFrontPage returns the front-most visible panel.
|
||||
func (p *Pages) GetFrontPage() (name string, item Primitive) {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
for index := len(p.pages) - 1; index >= 0; index-- {
|
||||
if p.pages[index].Visible {
|
||||
return p.pages[index].Name, p.pages[index].Item
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (p *Pages) HasFocus() bool {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if page.Item.GetFocusable().HasFocus() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Focus is called by the application when the primitive receives focus.
|
||||
func (p *Pages) Focus(delegate func(p Primitive)) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if delegate == nil {
|
||||
return // We cannot delegate so we cannot focus.
|
||||
}
|
||||
p.setFocus = delegate
|
||||
var topItem Primitive
|
||||
for _, page := range p.pages {
|
||||
if page.Visible {
|
||||
topItem = page.Item
|
||||
}
|
||||
}
|
||||
if topItem != nil {
|
||||
p.Unlock()
|
||||
delegate(topItem)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (p *Pages) Draw(screen tcell.Screen) {
|
||||
p.Box.Draw(screen)
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if !page.Visible {
|
||||
continue
|
||||
}
|
||||
if page.Resize {
|
||||
x, y, width, height := p.GetInnerRect()
|
||||
page.Item.SetRect(x, y, width, height)
|
||||
}
|
||||
page.Item.Draw(screen)
|
||||
}
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (p *Pages) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return p.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !p.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Pass mouse events along to the last visible page item that takes it.
|
||||
for index := len(p.pages) - 1; index >= 0; index-- {
|
||||
page := p.pages[index]
|
||||
if page.Visible {
|
||||
consumed, capture = page.Item.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
return p.GetFrontPanel()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,383 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
// panel represents a single panel of a Panels object.
|
||||
type panel struct {
|
||||
Name string // The panel's name.
|
||||
Item Primitive // The panel's primitive.
|
||||
Resize bool // Whether or not to resize the panel when it is drawn.
|
||||
Visible bool // Whether or not this panel is visible.
|
||||
}
|
||||
|
||||
// Panels is a container for other primitives often used as the application's
|
||||
// root primitive. It allows to easily switch the visibility of the contained
|
||||
// primitives.
|
||||
type Panels struct {
|
||||
*Box
|
||||
|
||||
// The contained panels. (Visible) panels are drawn from back to front.
|
||||
panels []*panel
|
||||
|
||||
// We keep a reference to the function which allows us to set the focus to
|
||||
// a newly visible panel.
|
||||
setFocus func(p Primitive)
|
||||
|
||||
// An optional handler which is called whenever the visibility or the order of
|
||||
// panels changes.
|
||||
changed func()
|
||||
|
||||
// TODO enable tabs
|
||||
tabs *TextView
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewPanels returns a new Panels object.
|
||||
func NewPanels() *Panels {
|
||||
p := &Panels{
|
||||
Box: NewBox(),
|
||||
}
|
||||
p.focus = p
|
||||
return p
|
||||
}
|
||||
|
||||
// SetChangedFunc sets a handler which is called whenever the visibility or the
|
||||
// order of any visible panels changes. This can be used to redraw the panels.
|
||||
func (p *Panels) SetChangedFunc(handler func()) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.changed = handler
|
||||
}
|
||||
|
||||
// GetPanelCount returns the number of panels currently stored in this object.
|
||||
func (p *Panels) GetPanelCount() int {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
return len(p.panels)
|
||||
}
|
||||
|
||||
// Add adds a new panel with the given name and primitive. If there was
|
||||
// previously a panel with the same name, it is overwritten. Leaving the name
|
||||
// empty may cause conflicts in other functions so always specify a non-empty
|
||||
// name.
|
||||
//
|
||||
// Visible panels will be drawn in the order they were added (unless that order
|
||||
// was changed in one of the other functions). If "resize" is set to true, the
|
||||
// primitive will be set to the size available to the Panels primitive whenever
|
||||
// the panels are drawn.
|
||||
func (p *Panels) Add(name string, item Primitive, resize, visible bool) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for index, pg := range p.panels {
|
||||
if pg.Name == name {
|
||||
p.panels = append(p.panels[:index], p.panels[index+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
p.panels = append(p.panels, &panel{Item: item, Name: name, Resize: resize, Visible: visible})
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// AddAndSwitchTo calls Add(), then SwitchTo() on the newly added panel.
|
||||
func (p *Panels) AddAndSwitchTo(name string, item Primitive, resize bool) {
|
||||
p.Add(name, item, resize, true)
|
||||
p.SwitchTo(name)
|
||||
}
|
||||
|
||||
// Remove removes the panel with the given name. If that panel was the only
|
||||
// visible panel, visibility is assigned to the last panel.
|
||||
func (p *Panels) Remove(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
var isVisible bool
|
||||
for index, panel := range p.panels {
|
||||
if panel.Name == name {
|
||||
isVisible = panel.Visible
|
||||
p.panels = append(p.panels[:index], p.panels[index+1:]...)
|
||||
if panel.Visible && p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if isVisible {
|
||||
for index, panel := range p.panels {
|
||||
if index < len(p.panels)-1 {
|
||||
if panel.Visible {
|
||||
break // There is a remaining visible panel.
|
||||
}
|
||||
} else {
|
||||
panel.Visible = true // We need at least one visible panel.
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// Has returns true if a panel with the given name exists in this object.
|
||||
func (p *Panels) Has(name string) bool {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
for _, panel := range p.panels {
|
||||
if panel.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Show sets a panel's visibility to "true" (in addition to any other panels
|
||||
// which are already visible).
|
||||
func (p *Panels) Show(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, panel := range p.panels {
|
||||
if panel.Name == name {
|
||||
panel.Visible = true
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// Hide sets a panel's visibility to "false".
|
||||
func (p *Panels) Hide(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, panel := range p.panels {
|
||||
if panel.Name == name {
|
||||
panel.Visible = false
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// SwitchTo sets a panel's visibility to "true" and all other panels'
|
||||
// visibility to "false".
|
||||
func (p *Panels) SwitchTo(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, panel := range p.panels {
|
||||
if panel.Name == name {
|
||||
panel.Visible = true
|
||||
} else {
|
||||
panel.Visible = false
|
||||
}
|
||||
}
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// SendToFront changes the order of the panels such that the panel with the given
|
||||
// name comes last, causing it to be drawn last with the next update (if
|
||||
// visible).
|
||||
func (p *Panels) SendToFront(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for index, panel := range p.panels {
|
||||
if panel.Name == name {
|
||||
if index < len(p.panels)-1 {
|
||||
p.panels = append(append(p.panels[:index], p.panels[index+1:]...), panel)
|
||||
}
|
||||
if panel.Visible && p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// SendToBack changes the order of the panels such that the panel with the given
|
||||
// name comes first, causing it to be drawn first with the next update (if
|
||||
// visible).
|
||||
func (p *Panels) SendToBack(name string) {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for index, pg := range p.panels {
|
||||
if pg.Name == name {
|
||||
if index > 0 {
|
||||
p.panels = append(append([]*panel{pg}, p.panels[:index]...), p.panels[index+1:]...)
|
||||
}
|
||||
if pg.Visible && p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// GetFrontPanel returns the front-most visible panel. If there are no visible
|
||||
// panels, ("", nil) is returned.
|
||||
func (p *Panels) GetFrontPanel() (name string, item Primitive) {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
for index := len(p.panels) - 1; index >= 0; index-- {
|
||||
if p.panels[index].Visible {
|
||||
return p.panels[index].Name, p.panels[index].Item
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (p *Panels) HasFocus() bool {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
for _, panel := range p.panels {
|
||||
if panel.Item.GetFocusable().HasFocus() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Focus is called by the application when the primitive receives focus.
|
||||
func (p *Panels) Focus(delegate func(p Primitive)) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if delegate == nil {
|
||||
return // We cannot delegate so we cannot focus.
|
||||
}
|
||||
p.setFocus = delegate
|
||||
var topItem Primitive
|
||||
for _, panel := range p.panels {
|
||||
if panel.Visible {
|
||||
topItem = panel.Item
|
||||
}
|
||||
}
|
||||
if topItem != nil {
|
||||
p.Unlock()
|
||||
delegate(topItem)
|
||||
p.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (p *Panels) Draw(screen tcell.Screen) {
|
||||
p.Box.Draw(screen)
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
x, y, width, height := p.GetInnerRect()
|
||||
|
||||
for _, panel := range p.panels {
|
||||
if !panel.Visible {
|
||||
continue
|
||||
}
|
||||
if panel.Resize {
|
||||
panel.Item.SetRect(x, y, width, height)
|
||||
}
|
||||
panel.Item.Draw(screen)
|
||||
}
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (p *Panels) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return p.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !p.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Pass mouse events along to the last visible panel item that takes it.
|
||||
for index := len(p.panels) - 1; index >= 0; index-- {
|
||||
panel := p.panels[index]
|
||||
if panel.Visible {
|
||||
consumed, capture = panel.Item.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue