Add TabbedPanels

This commit is contained in:
Trevor Slocum 2020-10-15 18:50:10 -07:00
parent 460aa5cbd5
commit 50b3201606
12 changed files with 376 additions and 121 deletions

View File

@ -1,5 +1,6 @@
v1.5.1 (WIP)
- Add Slider
- Add TabbedPanels
- Add Application.GetScreen and Application.GetScreenSize
- Add TextView.SetBytes and TextView.GetBytes
- Add TableCell.SetBytes, TableCell.GetBytes and TableCell.GetText

View File

@ -21,13 +21,13 @@ func main() {
modal.AddButtons([]string{"Next", "Quit"})
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonIndex == 0 {
panels.SwitchTo(fmt.Sprintf("panel-%d", (panel+1)%panelCount))
panels.SetCurrentPanel(fmt.Sprintf("panel-%d", (panel+1)%panelCount))
} else {
app.Stop()
}
})
panels.Add(fmt.Sprintf("panel-%d", panel), modal, false, panel == 0)
panels.AddPanel(fmt.Sprintf("panel-%d", panel), modal, false, panel == 0)
}(panel)
}

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -25,7 +25,7 @@ func Flex(nextSlide func()) (title string, content cview.Primitive) {
nextSlide()
modalShown = false
} else {
panels.Show("modal")
panels.ShowPanel("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) {
panels.Hide("modal")
panels.HidePanel("modal")
})
panels.Add("flex", flex, true, true)
panels.Add("modal", modal, false, false)
panels.AddPanel("flex", flex, true, true)
panels.AddPanel("modal", modal, false, false)
return "Flex", panels
}

View File

@ -19,7 +19,7 @@ func Grid(nextSlide func()) (title string, content cview.Primitive) {
nextSlide()
modalShown = false
} else {
panels.Show("modal")
panels.ShowPanel("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) {
panels.Hide("modal")
panels.HidePanel("modal")
})
panels.Add("grid", grid, true, true)
panels.Add("modal", modal, false, false)
panels.AddPanel("grid", grid, true, true)
panels.AddPanel("modal", modal, false, false)
return "Grid", panels
}

View File

@ -64,29 +64,18 @@ func main() {
End,
}
panels := cview.NewPanels()
// The bottom row has some info on where we are.
info := cview.NewTextView()
info.SetDynamicColors(true)
info.SetRegions(true)
info.SetWrap(false)
info.SetHighlightedFunc(func(added, removed, remaining []string) {
panels.SwitchTo(added[0])
})
panels := cview.NewTabbedPanels()
// Create the pages for all slides.
previousSlide := func() {
slide, _ := strconv.Atoi(info.GetHighlights()[0])
slide, _ := strconv.Atoi(panels.GetCurrentTab())
slide = (slide - 1 + len(slides)) % len(slides)
info.Highlight(strconv.Itoa(slide))
info.ScrollToHighlight()
panels.SetCurrentTab(strconv.Itoa(slide))
}
nextSlide := func() {
slide, _ := strconv.Atoi(info.GetHighlights()[0])
slide, _ := strconv.Atoi(panels.GetCurrentTab())
slide = (slide + 1) % len(slides)
info.Highlight(strconv.Itoa(slide))
info.ScrollToHighlight()
panels.SetCurrentTab(strconv.Itoa(slide))
}
cursor := 0
@ -95,18 +84,11 @@ func main() {
slideRegions = append(slideRegions, cursor)
title, primitive := slide(nextSlide)
panels.Add(strconv.Itoa(index), primitive, true, index == 0)
fmt.Fprintf(info, `["%d"][darkcyan] %s [white][""]|`, index, title)
panels.AddTab(strconv.Itoa(index), title, primitive)
cursor += len(title) + 4
}
info.Highlight("0")
// Create the main layout.
layout := cview.NewFlex()
layout.SetDirection(cview.FlexRow)
layout.AddItem(panels, 0, 1, true)
layout.AddItem(info, 1, 1, false)
panels.SetCurrentTab("0")
// Shortcuts to navigate the slides.
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
@ -119,7 +101,7 @@ func main() {
})
// Start the application.
app.SetRoot(layout, true)
app.SetRoot(panels, true)
if err := app.Run(); err != nil {
panic(err)
}

View File

@ -0,0 +1,40 @@
// Demo code for the TabbedPanels primitive.
package main
import (
"fmt"
"gitlab.com/tslocum/cview"
)
const panelCount = 5
func main() {
app := cview.NewApplication()
app.EnableMouse(true)
panels := cview.NewTabbedPanels()
for panel := 0; panel < panelCount; panel++ {
func(panel int) {
form := cview.NewForm()
form.SetBorder(true)
form.SetTitle(fmt.Sprintf("This is tab %d. Choose another tab.", panel+1))
form.AddButton("Next", func() {
panels.SetCurrentTab(fmt.Sprintf("panel-%d", (panel+1)%panelCount))
})
form.AddButton("Quit", func() {
app.Stop()
})
form.SetCancelFunc(func() {
app.Stop()
})
panels.AddTab(fmt.Sprintf("panel-%d", panel), fmt.Sprintf("Panel #%d", panel), form)
}(panel)
}
app.SetRoot(panels, true)
if err := app.Run(); err != nil {
panic(err)
}
}

View File

@ -28,7 +28,7 @@ func main() {
form.SetBorder(true)
form.SetTitle("输入一些内容")
form.SetTitleAlign(cview.AlignLeft)
panels.Add("base", form, true, true)
panels.AddPanel("base", form, true, true)
app.SetRoot(panels, true)
if err := app.Run(); err != nil {
@ -42,9 +42,9 @@ func alert(panels *cview.Panels, id string, message string) {
modal.SetText(message)
modal.AddButtons([]string{"确定"})
modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
panels.Hide(id)
panels.Remove(id)
panels.HidePanel(id)
panels.RemovePanel(id)
})
panels.Add(id, modal, false, true)
panels.AddPanel(id, modal, false, true)
}

View File

@ -1,60 +0,0 @@
package cview
type page = panel
// Pages is a wrapper around Panels. It is provided for backwards compatibility.
// Application developers should use Panels instead.
type Pages struct {
*Panels
}
// NewPages returns a new Panels object.
func NewPages() *Pages {
return &Pages{NewPanels()}
}
// GetPageCount returns the number of panels currently stored in this object.
func (p *Pages) GetPageCount() int {
return p.GetPanelCount()
}
// AddPage adds a new panel with the given name and primitive.
func (p *Pages) AddPage(name string, item Primitive, resize, visible bool) {
p.Add(name, item, resize, visible)
}
// AddAndSwitchToPage calls Add(), then SwitchTo() on that newly added panel.
func (p *Pages) AddAndSwitchToPage(name string, item Primitive, resize bool) {
p.AddAndSwitchTo(name, item, resize)
}
// RemovePage removes the panel with the given name.
func (p *Pages) RemovePage(name string) {
p.Remove(name)
}
// HasPage returns true if a panel with the given name exists in this object.
func (p *Pages) HasPage(name string) bool {
return p.Has(name)
}
// ShowPage sets a panel's visibility to "true".
func (p *Pages) ShowPage(name string) {
p.Show(name)
}
// HidePage sets a panel's visibility to "false".
func (p *Pages) HidePage(name string) {
p.Hide(name)
}
// SwitchToPage sets a panel's visibility to "true" and all other panels'
// visibility to "false".
func (p *Pages) SwitchToPage(name string) {
p.SwitchTo(name)
}
// GetFrontPage returns the front-most visible panel.
func (p *Pages) GetFrontPage() (name string, item Primitive) {
return p.GetFrontPanel()
}

View File

@ -31,9 +31,6 @@ type Panels struct {
// panels changes.
changed func()
// TODO enable tabs
tabs *TextView
sync.RWMutex
}
@ -63,7 +60,7 @@ func (p *Panels) GetPanelCount() int {
return len(p.panels)
}
// Add adds a new panel with the given name and primitive. If there was
// AddPanel 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.
@ -72,7 +69,7 @@ func (p *Panels) GetPanelCount() int {
// 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) {
func (p *Panels) AddPanel(name string, item Primitive, resize, visible bool) {
hasFocus := p.HasFocus()
p.Lock()
@ -97,15 +94,9 @@ func (p *Panels) Add(name string, item Primitive, resize, visible bool) {
}
}
// 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
// RemovePanel 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) {
func (p *Panels) RemovePanel(name string) {
hasFocus := p.HasFocus()
p.Lock()
@ -142,8 +133,8 @@ func (p *Panels) Remove(name string) {
}
}
// Has returns true if a panel with the given name exists in this object.
func (p *Panels) Has(name string) bool {
// HasPanel returns true if a panel with the given name exists in this object.
func (p *Panels) HasPanel(name string) bool {
p.RLock()
defer p.RUnlock()
@ -155,9 +146,9 @@ func (p *Panels) Has(name string) bool {
return false
}
// Show sets a panel's visibility to "true" (in addition to any other panels
// ShowPanel sets a panel's visibility to "true" (in addition to any other panels
// which are already visible).
func (p *Panels) Show(name string) {
func (p *Panels) ShowPanel(name string) {
hasFocus := p.HasFocus()
p.Lock()
@ -181,8 +172,8 @@ func (p *Panels) Show(name string) {
}
}
// Hide sets a panel's visibility to "false".
func (p *Panels) Hide(name string) {
// HidePanel sets a panel's visibility to "false".
func (p *Panels) HidePanel(name string) {
hasFocus := p.HasFocus()
p.Lock()
@ -206,9 +197,9 @@ func (p *Panels) Hide(name string) {
}
}
// SwitchTo sets a panel's visibility to "true" and all other panels'
// SetCurrentPanel sets a panel's visibility to "true" and all other panels'
// visibility to "false".
func (p *Panels) SwitchTo(name string) {
func (p *Panels) SetCurrentPanel(name string) {
hasFocus := p.HasFocus()
p.Lock()
@ -381,3 +372,64 @@ func (p *Panels) MouseHandler() func(action MouseAction, event *tcell.EventMouse
return
})
}
// Support backwards compatibility with Pages.
type page = panel
// Pages is a wrapper around Panels. It is provided for backwards compatibility.
// Application developers should use Panels instead.
type Pages struct {
*Panels
}
// NewPages returns a new Panels object.
func NewPages() *Pages {
return &Pages{NewPanels()}
}
// GetPageCount returns the number of panels currently stored in this object.
func (p *Pages) GetPageCount() int {
return p.GetPanelCount()
}
// AddPage adds a new panel with the given name and primitive.
func (p *Pages) AddPage(name string, item Primitive, resize, visible bool) {
p.AddPanel(name, item, resize, visible)
}
// AddAndSwitchToPage calls Add(), then SwitchTo() on that newly added panel.
func (p *Pages) AddAndSwitchToPage(name string, item Primitive, resize bool) {
p.AddPanel(name, item, resize, true)
p.SetCurrentPanel(name)
}
// RemovePage removes the panel with the given name.
func (p *Pages) RemovePage(name string) {
p.RemovePanel(name)
}
// HasPage returns true if a panel with the given name exists in this object.
func (p *Pages) HasPage(name string) bool {
return p.HasPanel(name)
}
// ShowPage sets a panel's visibility to "true".
func (p *Pages) ShowPage(name string) {
p.ShowPanel(name)
}
// HidePage sets a panel's visibility to "false".
func (p *Pages) HidePage(name string) {
p.HidePanel(name)
}
// SwitchToPage sets a panel's visibility to "true" and all other panels'
// visibility to "false".
func (p *Pages) SwitchToPage(name string) {
p.SetCurrentPanel(name)
}
// GetFrontPage returns the front-most visible panel.
func (p *Pages) GetFrontPage() (name string, item Primitive) {
return p.GetFrontPanel()
}

240
tabbedpanels.go Normal file
View File

@ -0,0 +1,240 @@
package cview
import (
"bytes"
"fmt"
"sync"
"github.com/gdamore/tcell/v2"
)
// TabbedPanels is a tabbed container for other primitives. The tab switcher
// may be displayed at the top or bottom of the container.
type TabbedPanels struct {
*Flex
panels *Panels
tabs *TextView
tabLabels map[string]string
currentTab string
bottomTabSwitcher bool
width, lastWidth int
setFocus func(Primitive)
sync.RWMutex
}
// NewTabbedPanels returns a new TabbedPanels object.
func NewTabbedPanels() *TabbedPanels {
t := &TabbedPanels{
Flex: NewFlex(),
panels: NewPanels(),
tabs: NewTextView(),
tabLabels: make(map[string]string),
}
t.tabs.SetDynamicColors(true)
t.tabs.SetRegions(true)
t.tabs.SetWrap(true)
t.tabs.SetWordWrap(true)
t.tabs.SetHighlightedFunc(func(added, removed, remaining []string) {
t.SetCurrentTab(added[0])
if t.setFocus != nil {
t.setFocus(t.panels)
}
})
f := t.Flex
f.SetDirection(FlexRow)
f.AddItem(t.tabs, 1, 1, false)
f.AddItem(t.panels, 0, 1, true)
return t
}
// AddTab adds a new tab. Tab names should consist only of letters, numbers
// and spaces.
func (t *TabbedPanels) AddTab(name, label string, item Primitive) {
t.Lock()
t.tabLabels[name] = label
t.Unlock()
t.panels.AddPanel(name, item, true, false)
t.updateAll()
}
// RemoveTab removes a tab.
func (t *TabbedPanels) RemoveTab(name, label string, item Primitive) {
t.panels.RemovePanel(name)
t.updateAll()
}
// SetCurrentTab sets the currently visible tab.
func (t *TabbedPanels) SetCurrentTab(name string) {
t.Lock()
if t.currentTab == name {
t.Unlock()
return
}
t.currentTab = name
t.updateAll()
t.Unlock()
t.tabs.Highlight(t.currentTab)
}
// GetCurrentTab returns the currently visible tab.
func (t *TabbedPanels) GetCurrentTab() string {
t.RLock()
defer t.RUnlock()
return t.currentTab
}
// SetTabLabel sets the label of a tab.
func (t *TabbedPanels) SetTabLabel(name, label string) {
t.Lock()
defer t.Unlock()
if t.tabLabels[name] == label {
return
}
t.tabLabels[name] = label
t.updateTabLabels()
}
// SetTabSwitcherPosition sets the position of the tab switcher.
func (t *TabbedPanels) SetTabSwitcherPosition(bottom bool) {
t.Lock()
defer t.Unlock()
if t.bottomTabSwitcher == bottom {
return
}
t.bottomTabSwitcher = bottom
f := t.Flex
f.RemoveItem(t.panels)
f.RemoveItem(t.tabs)
if t.bottomTabSwitcher {
f.AddItem(t.panels, 0, 1, true)
f.AddItem(t.tabs, 1, 1, false)
} else {
f.AddItem(t.tabs, 1, 1, false)
f.AddItem(t.panels, 0, 1, true)
}
t.updateTabLabels()
}
func (t *TabbedPanels) updateTabLabels() {
var b bytes.Buffer
for _, panel := range t.panels.panels {
b.WriteString(fmt.Sprintf(`["%s"][darkcyan] %s [white][""]|`, panel.Name, t.tabLabels[panel.Name]))
}
t.tabs.SetText(b.String())
reqLines := len(WordWrap(t.tabs.GetText(true), t.width))
if reqLines < 1 {
reqLines = 1
}
t.Flex.ResizeItem(t.tabs, reqLines, 1)
}
func (t *TabbedPanels) updateVisibleTabs() {
allPanels := t.panels.panels
var newTab string
var foundCurrent bool
for _, panel := range allPanels {
if panel.Name == t.currentTab {
newTab = panel.Name
foundCurrent = true
break
}
}
if !foundCurrent {
for _, panel := range allPanels {
if panel.Name != "" {
newTab = panel.Name
break
}
}
}
if t.currentTab != newTab {
t.SetCurrentTab(newTab)
return
}
for _, panel := range allPanels {
if panel.Name == t.currentTab {
t.panels.ShowPanel(panel.Name)
} else {
t.panels.HidePanel(panel.Name)
}
}
}
func (t *TabbedPanels) updateAll() {
t.updateTabLabels()
t.updateVisibleTabs()
}
// Draw draws this primitive onto the screen.
func (t *TabbedPanels) Draw(screen tcell.Screen) {
t.Box.Draw(screen)
_, _, t.width, _ = t.GetInnerRect()
if t.width != t.lastWidth {
t.updateTabLabels()
}
t.lastWidth = t.width
t.Flex.Draw(screen)
}
// InputHandler returns the handler for this primitive.
func (t *TabbedPanels) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
if t.setFocus == nil {
t.setFocus = setFocus
}
t.Flex.InputHandler()(event, setFocus)
})
}
// MouseHandler returns the mouse handler for this primitive.
func (t *TabbedPanels) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
if t.setFocus == nil {
t.setFocus = setFocus
}
x, y := event.Position()
if !t.InRect(x, y) {
return false, nil
}
if t.tabs.InRect(x, y) {
if t.setFocus != nil {
defer t.setFocus(t.panels)
}
defer t.tabs.MouseHandler()(action, event, setFocus)
return true, nil
}
return t.Flex.MouseHandler()(action, event, setFocus)
})
}