Merge branch 'thread' into 'master'
Make cview thread-safe Closes #5 See merge request tslocum/cview!1
This commit is contained in:
commit
49d23a331a
|
@ -1,3 +1,6 @@
|
|||
v1.4.5 (WIP)
|
||||
- Add multithreading support
|
||||
|
||||
v1.4.4 (2020-02-24)
|
||||
- Fix panic when navigating empty list
|
||||
- Fix resize event dimensions on Windows
|
||||
|
|
4
FORK.md
4
FORK.md
|
@ -26,6 +26,10 @@ maintainers and allowing code changes which may be outside of tview's scope.
|
|||
|
||||
# Differences
|
||||
|
||||
## cview is thread-safe
|
||||
|
||||
tview [is not thread-safe](https://godoc.org/github.com/rivo/tview#hdr-Concurrency).
|
||||
|
||||
## Application.QueueUpdate and Application.QueueUpdateDraw do not block
|
||||
|
||||
tview [blocks until the queued function returns](https://github.com/rivo/tview/blob/fe3052019536251fd145835dbaa225b33b7d3088/application.go#L510).
|
||||
|
|
|
@ -67,9 +67,6 @@ the program in the "demos/presentation" subdirectory.
|
|||
|
||||
Package documentation is available via [godoc](https://docs.rocketnine.space/gitlab.com/tslocum/cview).
|
||||
|
||||
**This package is not thread-safe.** Most functions may only be called from the
|
||||
main thread, as documented in [Concurrency](https://docs.rocketnine.space/gitlab.com/tslocum/cview/#hdr-Concurrency).
|
||||
|
||||
An [introduction tutorial](https://rocketnine.space/post/tview-and-you/) is also available.
|
||||
|
||||
## Dependencies
|
||||
|
|
|
@ -27,8 +27,6 @@ const resizeEventThrottle = 200 * time.Millisecond
|
|||
// panic(err)
|
||||
// }
|
||||
type Application struct {
|
||||
sync.RWMutex
|
||||
|
||||
// The application's screen. Apart from Run(), this variable should never be
|
||||
// set directly. Always use the screenReplacement channel after calling
|
||||
// Fini(), to set a new screen (or nil to stop the application).
|
||||
|
@ -94,6 +92,8 @@ type Application struct {
|
|||
lastMouseX, lastMouseY int
|
||||
lastMouseBtn tcell.ButtonMask
|
||||
lastMouseTarget Primitive // nil if none
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewApplication creates and returns a new application.
|
||||
|
@ -115,6 +115,9 @@ func NewApplication() *Application {
|
|||
// itself: Such a handler can intercept the Ctrl-C event which closes the
|
||||
// application.
|
||||
func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
a.inputCapture = capture
|
||||
return a
|
||||
}
|
||||
|
@ -122,6 +125,9 @@ func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell
|
|||
// GetInputCapture returns the function installed with SetInputCapture() or nil
|
||||
// if no such function has been installed.
|
||||
func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
return a.inputCapture
|
||||
}
|
||||
|
||||
|
@ -139,6 +145,9 @@ func (a *Application) SetMouseCapture(capture func(event *EventMouse) *EventMous
|
|||
// GetMouseCapture returns the function installed with SetMouseCapture() or nil
|
||||
// if no such function has been installed.
|
||||
func (a *Application) GetMouseCapture() func(event *EventMouse) *EventMouse {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
return a.mouseCapture
|
||||
}
|
||||
|
||||
|
@ -155,6 +164,9 @@ func (a *Application) SetTemporaryMouseCapture(capture func(event *EventMouse) *
|
|||
// GetTemporaryMouseCapture returns the function installed with
|
||||
// SetTemporaryMouseCapture() or nil if no such function has been installed.
|
||||
func (a *Application) GetTemporaryMouseCapture() func(event *EventMouse) *EventMouse {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
return a.tempMouseCapture
|
||||
}
|
||||
|
||||
|
@ -478,6 +490,7 @@ func findAtPoint(atX, atY int, p Primitive, capture func(p Primitive)) Primitive
|
|||
func (a *Application) GetPrimitiveAtPoint(atX, atY int) Primitive {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
return findAtPoint(atX, atY, a.root, nil)
|
||||
}
|
||||
|
||||
|
@ -486,6 +499,7 @@ func (a *Application) GetPrimitiveAtPoint(atX, atY int) Primitive {
|
|||
func (a *Application) appendStackAtPoint(buf []Primitive, atX, atY int) []Primitive {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
findAtPoint(atX, atY, a.root, func(p Primitive) {
|
||||
buf = append(buf, p)
|
||||
})
|
||||
|
@ -496,6 +510,7 @@ func (a *Application) appendStackAtPoint(buf []Primitive, atX, atY int) []Primit
|
|||
func (a *Application) Stop() {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
screen := a.screen
|
||||
if screen == nil {
|
||||
return
|
||||
|
@ -566,7 +581,6 @@ func (a *Application) ForceDraw() *Application {
|
|||
// draw actually does what Draw() promises to do.
|
||||
func (a *Application) draw() *Application {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
screen := a.screen
|
||||
root := a.root
|
||||
|
@ -576,6 +590,7 @@ func (a *Application) draw() *Application {
|
|||
|
||||
// Maybe we're not ready yet or not anymore.
|
||||
if screen == nil || root == nil {
|
||||
a.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
|
@ -587,10 +602,13 @@ func (a *Application) draw() *Application {
|
|||
|
||||
// Call before handler if there is one.
|
||||
if before != nil {
|
||||
a.Unlock()
|
||||
if before(screen) {
|
||||
screen.Show()
|
||||
return a
|
||||
}
|
||||
} else {
|
||||
a.Unlock()
|
||||
}
|
||||
|
||||
// Draw all primitives.
|
||||
|
@ -617,6 +635,9 @@ func (a *Application) draw() *Application {
|
|||
//
|
||||
// Provide nil to uninstall the callback function.
|
||||
func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) *Application {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
a.beforeDraw = handler
|
||||
return a
|
||||
}
|
||||
|
@ -624,6 +645,9 @@ func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool)
|
|||
// GetBeforeDrawFunc returns the callback function installed with
|
||||
// SetBeforeDrawFunc() or nil if none has been installed.
|
||||
func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
return a.beforeDraw
|
||||
}
|
||||
|
||||
|
@ -632,6 +656,9 @@ func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
|
|||
//
|
||||
// Provide nil to uninstall the callback function.
|
||||
func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) *Application {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
a.afterDraw = handler
|
||||
return a
|
||||
}
|
||||
|
@ -639,6 +666,9 @@ func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) *Appli
|
|||
// GetAfterDrawFunc returns the callback function installed with
|
||||
// SetAfterDrawFunc() or nil if none has been installed.
|
||||
func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
return a.afterDraw
|
||||
}
|
||||
|
||||
|
@ -680,6 +710,9 @@ func (a *Application) ResizeToFullScreen(p Primitive) *Application {
|
|||
//
|
||||
// Provide nil to uninstall the callback function.
|
||||
func (a *Application) SetAfterResizeFunc(handler func(width int, height int)) *Application {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
a.afterResize = handler
|
||||
return a
|
||||
}
|
||||
|
@ -687,6 +720,9 @@ func (a *Application) SetAfterResizeFunc(handler func(width int, height int)) *A
|
|||
// GetAfterResizeFunc returns the callback function installed with
|
||||
// SetAfterResizeFunc() or nil if none has been installed.
|
||||
func (a *Application) GetAfterResizeFunc() func(width int, height int) {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
return a.afterResize
|
||||
}
|
||||
|
||||
|
@ -720,13 +756,11 @@ func (a *Application) SetFocus(p Primitive) *Application {
|
|||
func (a *Application) GetFocus() Primitive {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
return a.focus
|
||||
}
|
||||
|
||||
// QueueUpdate is used to synchronize access to primitives from non-main
|
||||
// goroutines. The provided function will be executed as part of the event loop
|
||||
// and thus will not cause race conditions with other such update functions or
|
||||
// the Draw() function.
|
||||
// QueueUpdate queues a function to be executed as part of the event loop.
|
||||
//
|
||||
// Note that Draw() is not implicitly called after the execution of f as that
|
||||
// may not be desirable. You can call Draw() from f if the screen should be
|
||||
|
|
103
box.go
103
box.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -60,6 +62,8 @@ type Box struct {
|
|||
// event to be forwarded to the primitive's default mouse event handler (nil if
|
||||
// nothing should be forwarded).
|
||||
mouseCapture func(event *EventMouse) *EventMouse
|
||||
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
// NewBox returns a Box without a border.
|
||||
|
@ -79,6 +83,9 @@ func NewBox() *Box {
|
|||
|
||||
// SetBorderPadding sets the size of the borders around the box content.
|
||||
func (b *Box) SetBorderPadding(top, bottom, left, right int) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight = top, bottom, left, right
|
||||
return b
|
||||
}
|
||||
|
@ -86,6 +93,9 @@ func (b *Box) SetBorderPadding(top, bottom, left, right int) *Box {
|
|||
// GetRect returns the current position of the rectangle, x, y, width, and
|
||||
// height.
|
||||
func (b *Box) GetRect() (int, int, int, int) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.x, b.y, b.width, b.height
|
||||
}
|
||||
|
||||
|
@ -93,10 +103,15 @@ func (b *Box) GetRect() (int, int, int, int) {
|
|||
// height), without the border and without any padding. Width and height values
|
||||
// will clamp to 0 and thus never be negative.
|
||||
func (b *Box) GetInnerRect() (int, int, int, int) {
|
||||
b.l.RLock()
|
||||
if b.innerX >= 0 {
|
||||
defer b.l.RUnlock()
|
||||
return b.innerX, b.innerY, b.innerWidth, b.innerHeight
|
||||
}
|
||||
b.l.RUnlock()
|
||||
|
||||
x, y, width, height := b.GetRect()
|
||||
b.l.RLock()
|
||||
if b.border {
|
||||
x++
|
||||
y++
|
||||
|
@ -113,6 +128,7 @@ func (b *Box) GetInnerRect() (int, int, int, int) {
|
|||
if height < 0 {
|
||||
height = 0
|
||||
}
|
||||
b.l.RUnlock()
|
||||
return x, y, width, height
|
||||
}
|
||||
|
||||
|
@ -122,6 +138,9 @@ func (b *Box) GetInnerRect() (int, int, int, int) {
|
|||
//
|
||||
// application.SetRoot(b, true)
|
||||
func (b *Box) SetRect(x, y, width, height int) {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.x = x
|
||||
b.y = y
|
||||
b.width = width
|
||||
|
@ -138,6 +157,9 @@ func (b *Box) SetRect(x, y, width, height int) {
|
|||
// returned by GetInnerRect(), used by descendent primitives to draw their own
|
||||
// content.
|
||||
func (b *Box) SetDrawFunc(handler func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.draw = handler
|
||||
return b
|
||||
}
|
||||
|
@ -145,6 +167,9 @@ func (b *Box) SetDrawFunc(handler func(screen tcell.Screen, x, y, width, height
|
|||
// GetDrawFunc returns the callback function which was installed with
|
||||
// SetDrawFunc() or nil if no such function has been installed.
|
||||
func (b *Box) GetDrawFunc() func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.draw
|
||||
}
|
||||
|
||||
|
@ -166,6 +191,9 @@ func (b *Box) WrapInputHandler(inputHandler func(*tcell.EventKey, func(p Primiti
|
|||
|
||||
// InputHandler returns nil.
|
||||
func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.WrapInputHandler(nil)
|
||||
}
|
||||
|
||||
|
@ -184,6 +212,9 @@ func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primiti
|
|||
// to their contained primitives and thus never receive any key events
|
||||
// themselves. Therefore, they cannot intercept key events.
|
||||
func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.inputCapture = capture
|
||||
return b
|
||||
}
|
||||
|
@ -191,6 +222,9 @@ func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKe
|
|||
// GetInputCapture returns the function installed with SetInputCapture() or nil
|
||||
// if no such function has been installed.
|
||||
func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.inputCapture
|
||||
}
|
||||
|
||||
|
@ -212,6 +246,9 @@ func (b *Box) WrapMouseHandler(mouseHandler func(*EventMouse)) func(*EventMouse)
|
|||
|
||||
// MouseHandler returns nil.
|
||||
func (b *Box) MouseHandler() func(event *EventMouse) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.WrapMouseHandler(nil)
|
||||
}
|
||||
|
||||
|
@ -223,6 +260,9 @@ func (b *Box) MouseHandler() func(event *EventMouse) {
|
|||
//
|
||||
// Providing a nil handler will remove a previously existing handler.
|
||||
func (b *Box) SetMouseCapture(capture func(*EventMouse) *EventMouse) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.mouseCapture = capture
|
||||
return b
|
||||
}
|
||||
|
@ -230,11 +270,17 @@ func (b *Box) SetMouseCapture(capture func(*EventMouse) *EventMouse) *Box {
|
|||
// GetMouseCapture returns the function installed with SetMouseCapture() or nil
|
||||
// if no such function has been installed.
|
||||
func (b *Box) GetMouseCapture() func(*EventMouse) *EventMouse {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.mouseCapture
|
||||
}
|
||||
|
||||
// SetBackgroundColor sets the box's background color.
|
||||
func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.backgroundColor = color
|
||||
return b
|
||||
}
|
||||
|
@ -242,12 +288,18 @@ func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
|
|||
// SetBorder sets the flag indicating whether or not the box should have a
|
||||
// border.
|
||||
func (b *Box) SetBorder(show bool) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.border = show
|
||||
return b
|
||||
}
|
||||
|
||||
// SetBorderColor sets the box's border color.
|
||||
func (b *Box) SetBorderColor(color tcell.Color) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.borderColor = color
|
||||
return b
|
||||
}
|
||||
|
@ -257,23 +309,35 @@ func (b *Box) SetBorderColor(color tcell.Color) *Box {
|
|||
//
|
||||
// box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
|
||||
func (b *Box) SetBorderAttributes(attr tcell.AttrMask) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.borderAttributes = attr
|
||||
return b
|
||||
}
|
||||
|
||||
// SetTitle sets the box's title.
|
||||
func (b *Box) SetTitle(title string) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.title = title
|
||||
return b
|
||||
}
|
||||
|
||||
// GetTitle returns the box's current title.
|
||||
func (b *Box) GetTitle() string {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.title
|
||||
}
|
||||
|
||||
// SetTitleColor sets the box's title color.
|
||||
func (b *Box) SetTitleColor(color tcell.Color) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.titleColor = color
|
||||
return b
|
||||
}
|
||||
|
@ -281,14 +345,20 @@ func (b *Box) SetTitleColor(color tcell.Color) *Box {
|
|||
// SetTitleAlign sets the alignment of the title, one of AlignLeft, AlignCenter,
|
||||
// or AlignRight.
|
||||
func (b *Box) SetTitleAlign(align int) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.titleAlign = align
|
||||
return b
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (b *Box) Draw(screen tcell.Screen) {
|
||||
b.l.Lock()
|
||||
|
||||
// Don't draw anything if there is no space.
|
||||
if b.width <= 0 || b.height <= 0 {
|
||||
b.l.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -306,7 +376,14 @@ func (b *Box) Draw(screen tcell.Screen) {
|
|||
if b.border && b.width >= 2 && b.height >= 2 {
|
||||
border := background.Foreground(b.borderColor) | tcell.Style(b.borderAttributes)
|
||||
var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
|
||||
if b.focus.HasFocus() {
|
||||
|
||||
var hasFocus bool
|
||||
if b.focus == b {
|
||||
hasFocus = b.hasFocus
|
||||
} else {
|
||||
hasFocus = b.focus.HasFocus()
|
||||
}
|
||||
if hasFocus {
|
||||
horizontal = Borders.HorizontalFocus
|
||||
vertical = Borders.VerticalFocus
|
||||
topLeft = Borders.TopLeftFocus
|
||||
|
@ -347,11 +424,17 @@ func (b *Box) Draw(screen tcell.Screen) {
|
|||
|
||||
// Call custom draw function.
|
||||
if b.draw != nil {
|
||||
b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height)
|
||||
b.l.Unlock()
|
||||
newX, newY, newWidth, newHeight := b.draw(screen, b.x, b.y, b.width, b.height)
|
||||
b.l.Lock()
|
||||
b.innerX, b.innerY, b.innerWidth, b.innerHeight = newX, newY, newWidth, newHeight
|
||||
} else {
|
||||
// Remember the inner rect.
|
||||
b.innerX = -1
|
||||
b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.GetInnerRect()
|
||||
b.l.Unlock()
|
||||
newX, newY, newWidth, newHeight := b.GetInnerRect()
|
||||
b.l.Lock()
|
||||
b.innerX, b.innerY, b.innerWidth, b.innerHeight = newX, newY, newWidth, newHeight
|
||||
}
|
||||
|
||||
// Clamp inner rect to screen.
|
||||
|
@ -376,25 +459,39 @@ func (b *Box) Draw(screen tcell.Screen) {
|
|||
if b.innerHeight < 0 {
|
||||
b.innerHeight = 0
|
||||
}
|
||||
|
||||
b.l.Unlock()
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (b *Box) Focus(delegate func(p Primitive)) {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.hasFocus = true
|
||||
}
|
||||
|
||||
// Blur is called when this primitive loses focus.
|
||||
func (b *Box) Blur() {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
b.hasFocus = false
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (b *Box) HasFocus() bool {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.hasFocus
|
||||
}
|
||||
|
||||
// GetFocusable returns the item's Focusable.
|
||||
func (b *Box) GetFocusable() Focusable {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.focus
|
||||
}
|
||||
|
||||
|
|
30
button.go
30
button.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -28,6 +30,8 @@ type Button struct {
|
|||
// An optional function which is called when the user leaves the button. A
|
||||
// key is provided indicating which key was pressed to leave (tab or backtab).
|
||||
blur func(tcell.Key)
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewButton returns a new input field.
|
||||
|
@ -45,17 +49,26 @@ func NewButton(label string) *Button {
|
|||
|
||||
// SetLabel sets the button text.
|
||||
func (b *Button) SetLabel(label string) *Button {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.label = label
|
||||
return b
|
||||
}
|
||||
|
||||
// GetLabel returns the button text.
|
||||
func (b *Button) GetLabel() string {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
return b.label
|
||||
}
|
||||
|
||||
// SetLabelColor sets the color of the button text.
|
||||
func (b *Button) SetLabelColor(color tcell.Color) *Button {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.labelColor = color
|
||||
return b
|
||||
}
|
||||
|
@ -63,6 +76,9 @@ func (b *Button) SetLabelColor(color tcell.Color) *Button {
|
|||
// SetLabelColorActivated sets the color of the button text when the button is
|
||||
// in focus.
|
||||
func (b *Button) SetLabelColorActivated(color tcell.Color) *Button {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.labelColorActivated = color
|
||||
return b
|
||||
}
|
||||
|
@ -70,12 +86,18 @@ func (b *Button) SetLabelColorActivated(color tcell.Color) *Button {
|
|||
// SetBackgroundColorActivated sets the background color of the button text when
|
||||
// the button is in focus.
|
||||
func (b *Button) SetBackgroundColorActivated(color tcell.Color) *Button {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.backgroundColorActivated = color
|
||||
return b
|
||||
}
|
||||
|
||||
// SetSelectedFunc sets a handler which is called when the button was selected.
|
||||
func (b *Button) SetSelectedFunc(handler func()) *Button {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.selected = handler
|
||||
return b
|
||||
}
|
||||
|
@ -88,12 +110,18 @@ func (b *Button) SetSelectedFunc(handler func()) *Button {
|
|||
// - KeyTab: Move to the next field.
|
||||
// - KeyBacktab: Move to the previous field.
|
||||
func (b *Button) SetBlurFunc(handler func(key tcell.Key)) *Button {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.blur = handler
|
||||
return b
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (b *Button) Draw(screen tcell.Screen) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
// Draw the box.
|
||||
borderColor := b.borderColor
|
||||
backgroundColor := b.backgroundColor
|
||||
|
@ -104,7 +132,9 @@ func (b *Button) Draw(screen tcell.Screen) {
|
|||
b.borderColor = borderColor
|
||||
}()
|
||||
}
|
||||
b.Unlock()
|
||||
b.Box.Draw(screen)
|
||||
b.Lock()
|
||||
b.backgroundColor = backgroundColor
|
||||
|
||||
// Draw label.
|
||||
|
|
56
checkbox.go
56
checkbox.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -45,6 +47,8 @@ type Checkbox struct {
|
|||
// A callback function set by the Form class and called when the user leaves
|
||||
// this form item.
|
||||
finished func(tcell.Key)
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewCheckbox returns a new input field.
|
||||
|
@ -59,64 +63,97 @@ func NewCheckbox() *Checkbox {
|
|||
|
||||
// SetChecked sets the state of the checkbox.
|
||||
func (c *Checkbox) SetChecked(checked bool) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.checked = checked
|
||||
return c
|
||||
}
|
||||
|
||||
// IsChecked returns whether or not the box is checked.
|
||||
func (c *Checkbox) IsChecked() bool {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.checked
|
||||
}
|
||||
|
||||
// SetLabel sets the text to be displayed before the input area.
|
||||
func (c *Checkbox) SetLabel(label string) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.label = label
|
||||
return c
|
||||
}
|
||||
|
||||
// GetLabel returns the text to be displayed before the input area.
|
||||
func (c *Checkbox) GetLabel() string {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.label
|
||||
}
|
||||
|
||||
// SetMessage sets the text to be displayed after the checkbox
|
||||
func (c *Checkbox) SetMessage(message string) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.message = message
|
||||
return c
|
||||
}
|
||||
|
||||
// GetMessage returns the text to be displayed after the checkbox
|
||||
func (c *Checkbox) GetMessage() string {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.message
|
||||
}
|
||||
|
||||
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
|
||||
// primitive to use the width of the label string.
|
||||
func (c *Checkbox) SetLabelWidth(width int) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.labelWidth = width
|
||||
return c
|
||||
}
|
||||
|
||||
// SetLabelColor sets the color of the label.
|
||||
func (c *Checkbox) SetLabelColor(color tcell.Color) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.labelColor = color
|
||||
return c
|
||||
}
|
||||
|
||||
// SetFieldBackgroundColor sets the background color of the input area.
|
||||
func (c *Checkbox) SetFieldBackgroundColor(color tcell.Color) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.fieldBackgroundColor = color
|
||||
return c
|
||||
}
|
||||
|
||||
// SetFieldTextColor sets the text color of the input area.
|
||||
func (c *Checkbox) SetFieldTextColor(color tcell.Color) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.fieldTextColor = color
|
||||
return c
|
||||
}
|
||||
|
||||
// SetFormAttributes sets attributes shared by all form items.
|
||||
func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.labelWidth = labelWidth
|
||||
c.labelColor = labelColor
|
||||
c.backgroundColor = bgColor
|
||||
|
@ -127,6 +164,9 @@ func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldT
|
|||
|
||||
// GetFieldWidth returns this primitive's field width.
|
||||
func (c *Checkbox) GetFieldWidth() int {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if c.message == "" {
|
||||
return 1
|
||||
}
|
||||
|
@ -138,6 +178,9 @@ func (c *Checkbox) GetFieldWidth() int {
|
|||
// checkbox was changed by the user. The handler function receives the new
|
||||
// state.
|
||||
func (c *Checkbox) SetChangedFunc(handler func(checked bool)) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.changed = handler
|
||||
return c
|
||||
}
|
||||
|
@ -150,12 +193,18 @@ func (c *Checkbox) SetChangedFunc(handler func(checked bool)) *Checkbox {
|
|||
// - KeyTab: Move to the next field.
|
||||
// - KeyBacktab: Move to the previous field.
|
||||
func (c *Checkbox) SetDoneFunc(handler func(key tcell.Key)) *Checkbox {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.done = handler
|
||||
return c
|
||||
}
|
||||
|
||||
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
|
||||
func (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.finished = handler
|
||||
return c
|
||||
}
|
||||
|
@ -164,6 +213,9 @@ func (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
|
|||
func (c *Checkbox) Draw(screen tcell.Screen) {
|
||||
c.Box.Draw(screen)
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
// Prepare
|
||||
x, y, width, height := c.GetInnerRect()
|
||||
rightLimit := x + width
|
||||
|
@ -209,7 +261,9 @@ func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
|||
if key == tcell.KeyRune && event.Rune() != ' ' {
|
||||
break
|
||||
}
|
||||
c.Lock()
|
||||
c.checked = !c.checked
|
||||
c.Unlock()
|
||||
if c.changed != nil {
|
||||
c.changed(c.checked)
|
||||
}
|
||||
|
@ -229,7 +283,9 @@ func (c *Checkbox) MouseHandler() func(event *EventMouse) {
|
|||
return c.WrapMouseHandler(func(event *EventMouse) {
|
||||
// Process mouse event.
|
||||
if event.Action()&MouseClick != 0 {
|
||||
c.Lock()
|
||||
c.checked = !c.checked
|
||||
c.Unlock()
|
||||
if c.changed != nil {
|
||||
c.changed(c.checked)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,11 @@ the following shortcuts can be used:
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
|
@ -30,6 +34,16 @@ var app = cview.NewApplication()
|
|||
|
||||
// Starting point for the presentation.
|
||||
func main() {
|
||||
var debugPort int
|
||||
flag.IntVar(&debugPort, "debug", 0, "port to serve debug info")
|
||||
flag.Parse()
|
||||
|
||||
if debugPort > 0 {
|
||||
go func() {
|
||||
log.Println(http.ListenAndServe(fmt.Sprintf("localhost:%d", debugPort), nil))
|
||||
}()
|
||||
}
|
||||
|
||||
// The presentation slides.
|
||||
slides := []Slide{
|
||||
Cover,
|
||||
|
|
34
doc.go
34
doc.go
|
@ -34,26 +34,18 @@ primitive, Box, and thus inherit its functions. This isn't necessarily
|
|||
required, but it makes more sense than reimplementing Box's functionality in
|
||||
each widget.
|
||||
|
||||
Types
|
||||
|
||||
This package is a fork of https://github.com/rivo/tview which is based on
|
||||
https://github.com/gdamore/tcell. It uses types and constants from tcell
|
||||
(e.g. colors and keyboard values).
|
||||
|
||||
Concurrency
|
||||
|
||||
Most of cview's functions are not thread-safe. You must synchronize execution
|
||||
via Application.QueueUpdate or Application.QueueUpdateDraw (see function
|
||||
documentation for more information):
|
||||
|
||||
go func() {
|
||||
// Queue a UI change from a goroutine.
|
||||
app.QueueUpdateDraw(func() {
|
||||
// This function will execute on the main thread.
|
||||
table.SetCellSimple(0, 0, "Foo bar")
|
||||
})
|
||||
}()
|
||||
|
||||
One exception to this is the io.Writer interface implemented by TextView; you
|
||||
may safely write to a TextView from any goroutine. You may also call
|
||||
Application.Draw from any goroutine.
|
||||
|
||||
Event handlers execute on the main goroutine and thus do not require
|
||||
synchronization.
|
||||
All functions may be called concurrently (they are thread-safe). When called
|
||||
from multiple threads, functions will block until the application or widget
|
||||
becomes available. Function calls may be queued with Application.QueueUpdate to
|
||||
avoid blocking.
|
||||
|
||||
Unicode Support
|
||||
|
||||
|
@ -80,12 +72,6 @@ developers to permanently intercept mouse events.
|
|||
|
||||
Event handlers may return nil to stop propagation.
|
||||
|
||||
Types
|
||||
|
||||
This package is a fork of https://github.com/rivo/tview which is based on
|
||||
https://github.com/gdamore/tcell. It uses types and constants from tcell
|
||||
(e.g. colors and keyboard values).
|
||||
|
||||
Colors
|
||||
|
||||
Throughout this package, colors are specified using the tcell.Color type.
|
||||
|
|
92
dropdown.go
92
dropdown.go
|
@ -2,6 +2,7 @@ package cview
|
|||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
@ -79,6 +80,8 @@ type DropDown struct {
|
|||
// A callback function which is called when the user changes the drop-down's
|
||||
// selection.
|
||||
selected func(text string, index int)
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewDropDown returns a new drop-down.
|
||||
|
@ -110,20 +113,29 @@ func NewDropDown() *DropDown {
|
|||
// be a negative value to indicate that no option is currently selected. Calling
|
||||
// this function will also trigger the "selected" callback (if there is one).
|
||||
func (d *DropDown) SetCurrentOption(index int) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
if index >= 0 && index < len(d.options) {
|
||||
d.currentOption = index
|
||||
d.list.SetCurrentItem(index)
|
||||
if d.selected != nil {
|
||||
d.Unlock()
|
||||
d.selected(d.options[index].Text, index)
|
||||
d.Lock()
|
||||
}
|
||||
if d.options[index].Selected != nil {
|
||||
d.Unlock()
|
||||
d.options[index].Selected()
|
||||
d.Lock()
|
||||
}
|
||||
} else {
|
||||
d.currentOption = -1
|
||||
d.list.SetCurrentItem(0) // Set to 0 because -1 means "last item".
|
||||
if d.selected != nil {
|
||||
d.Unlock()
|
||||
d.selected("", -1)
|
||||
d.Lock()
|
||||
}
|
||||
}
|
||||
return d
|
||||
|
@ -132,6 +144,9 @@ func (d *DropDown) SetCurrentOption(index int) *DropDown {
|
|||
// GetCurrentOption returns the index of the currently selected option as well
|
||||
// as its text. If no option was selected, -1 and an empty string is returned.
|
||||
func (d *DropDown) GetCurrentOption() (int, string) {
|
||||
d.RLock()
|
||||
defer d.RUnlock()
|
||||
|
||||
var text string
|
||||
if d.currentOption >= 0 && d.currentOption < len(d.options) {
|
||||
text = d.options[d.currentOption].Text
|
||||
|
@ -145,6 +160,9 @@ func (d *DropDown) GetCurrentOption() (int, string) {
|
|||
// displayed when no option is currently selected. Per default, all of these
|
||||
// strings are empty.
|
||||
func (d *DropDown) SetTextOptions(prefix, suffix, currentPrefix, currentSuffix, noSelection string) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.currentOptionPrefix = currentPrefix
|
||||
d.currentOptionSuffix = currentSuffix
|
||||
d.noSelection = noSelection
|
||||
|
@ -158,36 +176,54 @@ func (d *DropDown) SetTextOptions(prefix, suffix, currentPrefix, currentSuffix,
|
|||
|
||||
// SetLabel sets the text to be displayed before the input area.
|
||||
func (d *DropDown) SetLabel(label string) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.label = label
|
||||
return d
|
||||
}
|
||||
|
||||
// GetLabel returns the text to be displayed before the input area.
|
||||
func (d *DropDown) GetLabel() string {
|
||||
d.RLock()
|
||||
defer d.RUnlock()
|
||||
|
||||
return d.label
|
||||
}
|
||||
|
||||
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
|
||||
// primitive to use the width of the label string.
|
||||
func (d *DropDown) SetLabelWidth(width int) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.labelWidth = width
|
||||
return d
|
||||
}
|
||||
|
||||
// SetLabelColor sets the color of the label.
|
||||
func (d *DropDown) SetLabelColor(color tcell.Color) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.labelColor = color
|
||||
return d
|
||||
}
|
||||
|
||||
// SetFieldBackgroundColor sets the background color of the options area.
|
||||
func (d *DropDown) SetFieldBackgroundColor(color tcell.Color) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.fieldBackgroundColor = color
|
||||
return d
|
||||
}
|
||||
|
||||
// SetFieldTextColor sets the text color of the options area.
|
||||
func (d *DropDown) SetFieldTextColor(color tcell.Color) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.fieldTextColor = color
|
||||
return d
|
||||
}
|
||||
|
@ -196,12 +232,18 @@ func (d *DropDown) SetFieldTextColor(color tcell.Color) *DropDown {
|
|||
// shown when the user starts typing text, which directly selects the first
|
||||
// option that starts with the typed string.
|
||||
func (d *DropDown) SetPrefixTextColor(color tcell.Color) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.prefixTextColor = color
|
||||
return d
|
||||
}
|
||||
|
||||
// SetFormAttributes sets attributes shared by all form items.
|
||||
func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.labelWidth = labelWidth
|
||||
d.labelColor = labelColor
|
||||
d.backgroundColor = bgColor
|
||||
|
@ -213,12 +255,18 @@ func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldT
|
|||
// SetFieldWidth sets the screen width of the options area. A value of 0 means
|
||||
// extend to as long as the longest option text.
|
||||
func (d *DropDown) SetFieldWidth(width int) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.fieldWidth = width
|
||||
return d
|
||||
}
|
||||
|
||||
// GetFieldWidth returns this primitive's field screen width.
|
||||
func (d *DropDown) GetFieldWidth() int {
|
||||
d.RLock()
|
||||
defer d.RUnlock()
|
||||
|
||||
if d.fieldWidth > 0 {
|
||||
return d.fieldWidth
|
||||
}
|
||||
|
@ -235,6 +283,13 @@ func (d *DropDown) GetFieldWidth() int {
|
|||
// AddOption adds a new selectable option to this drop-down. The "selected"
|
||||
// callback is called when this option was selected. It may be nil.
|
||||
func (d *DropDown) AddOption(text string, selected func()) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
return d.addOption(text, selected)
|
||||
}
|
||||
|
||||
func (d *DropDown) addOption(text string, selected func()) *DropDown {
|
||||
d.options = append(d.options, &dropDownOption{Text: text, Selected: selected})
|
||||
d.list.AddItem(d.optionPrefix+text+d.optionSuffix, "", 0, nil)
|
||||
return d
|
||||
|
@ -245,11 +300,14 @@ func (d *DropDown) AddOption(text string, selected func()) *DropDown {
|
|||
// It will be called with the option's text and its index into the options
|
||||
// slice. The "selected" parameter may be nil.
|
||||
func (d *DropDown) SetOptions(texts []string, selected func(text string, index int)) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.list.Clear()
|
||||
d.options = nil
|
||||
for index, text := range texts {
|
||||
func(t string, i int) {
|
||||
d.AddOption(text, nil)
|
||||
d.addOption(text, nil)
|
||||
}(text, index)
|
||||
}
|
||||
d.selected = selected
|
||||
|
@ -262,6 +320,9 @@ func (d *DropDown) SetOptions(texts []string, selected func(text string, index i
|
|||
// selected option's text and index. If "no option" was selected, these values
|
||||
// are an empty string and -1.
|
||||
func (d *DropDown) SetSelectedFunc(handler func(text string, index int)) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.selected = handler
|
||||
return d
|
||||
}
|
||||
|
@ -274,12 +335,18 @@ func (d *DropDown) SetSelectedFunc(handler func(text string, index int)) *DropDo
|
|||
// - KeyTab: Move to the next field.
|
||||
// - KeyBacktab: Move to the previous field.
|
||||
func (d *DropDown) SetDoneFunc(handler func(key tcell.Key)) *DropDown {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.done = handler
|
||||
return d
|
||||
}
|
||||
|
||||
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
|
||||
func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.finished = handler
|
||||
return d
|
||||
}
|
||||
|
@ -287,6 +354,10 @@ func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
|
|||
// Draw draws this primitive onto the screen.
|
||||
func (d *DropDown) Draw(screen tcell.Screen) {
|
||||
d.Box.Draw(screen)
|
||||
hasFocus := d.GetFocusable().HasFocus()
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
// Prepare.
|
||||
x, y, width, height := d.GetInnerRect()
|
||||
|
@ -338,7 +409,7 @@ func (d *DropDown) Draw(screen tcell.Screen) {
|
|||
fieldWidth = rightLimit - x
|
||||
}
|
||||
fieldStyle := tcell.StyleDefault.Background(d.fieldBackgroundColor)
|
||||
if d.GetFocusable().HasFocus() && !d.open {
|
||||
if hasFocus && !d.open {
|
||||
fieldStyle = fieldStyle.Background(d.fieldTextColor)
|
||||
}
|
||||
for index := 0; index < fieldWidth; index++ {
|
||||
|
@ -363,14 +434,14 @@ func (d *DropDown) Draw(screen tcell.Screen) {
|
|||
text = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix
|
||||
}
|
||||
// Just show the current selection.
|
||||
if d.GetFocusable().HasFocus() && !d.open {
|
||||
if hasFocus && !d.open {
|
||||
color = d.fieldBackgroundColor
|
||||
}
|
||||
Print(screen, text, x, y, fieldWidth, AlignLeft, color)
|
||||
}
|
||||
|
||||
// Draw options list.
|
||||
if d.HasFocus() && d.open {
|
||||
if hasFocus && d.open {
|
||||
// We prefer to drop down but if there is no space, maybe drop up?
|
||||
lx := x
|
||||
ly := y + 1
|
||||
|
@ -400,6 +471,9 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
|||
// Process key event.
|
||||
switch key := event.Key(); key {
|
||||
case tcell.KeyEnter, tcell.KeyRune, tcell.KeyDown:
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.prefix = ""
|
||||
|
||||
// If the first key was a letter already, it becomes part of the prefix.
|
||||
|
@ -447,10 +521,14 @@ func (d *DropDown) openList(setFocus func(Primitive), app *Application) {
|
|||
|
||||
// Trigger "selected" event.
|
||||
if d.selected != nil {
|
||||
d.Unlock()
|
||||
d.selected(d.options[d.currentOption].Text, d.currentOption)
|
||||
d.Lock()
|
||||
}
|
||||
if d.options[d.currentOption].Selected != nil {
|
||||
d.Unlock()
|
||||
d.options[d.currentOption].Selected()
|
||||
d.Lock()
|
||||
}
|
||||
}).SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyRune {
|
||||
|
@ -524,6 +602,9 @@ func (d *DropDown) Focus(delegate func(p Primitive)) {
|
|||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (d *DropDown) HasFocus() bool {
|
||||
d.RLock()
|
||||
defer d.RUnlock()
|
||||
|
||||
if d.open {
|
||||
return d.list.HasFocus()
|
||||
}
|
||||
|
@ -535,6 +616,9 @@ func (d *DropDown) MouseHandler() func(event *EventMouse) {
|
|||
return d.WrapMouseHandler(func(event *EventMouse) {
|
||||
// Process mouse event.
|
||||
if event.Action()&MouseDown != 0 && event.Buttons()&tcell.Button1 != 0 {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
//d.open = !d.open
|
||||
//event.SetFocus(d)
|
||||
if d.open {
|
||||
|
|
33
flex.go
33
flex.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -36,6 +38,8 @@ type Flex struct {
|
|||
// If set to true, Flex will use the entire screen as its available space
|
||||
// instead its box dimensions.
|
||||
fullScreen bool
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewFlex returns a new flexbox layout container with no primitives and its
|
||||
|
@ -60,6 +64,9 @@ func NewFlex() *Flex {
|
|||
// SetDirection sets the direction in which the contained primitives are
|
||||
// distributed. This can be either FlexColumn (default) or FlexRow.
|
||||
func (f *Flex) SetDirection(direction int) *Flex {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.direction = direction
|
||||
return f
|
||||
}
|
||||
|
@ -67,6 +74,9 @@ func (f *Flex) SetDirection(direction int) *Flex {
|
|||
// SetFullScreen sets the flag which, when true, causes the flex layout to use
|
||||
// the entire screen space instead of whatever size it is currently assigned to.
|
||||
func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.fullScreen = fullScreen
|
||||
return f
|
||||
}
|
||||
|
@ -86,6 +96,9 @@ func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
|
|||
// You can provide a nil value for the primitive. This will still consume screen
|
||||
// space but nothing will be drawn.
|
||||
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.items = append(f.items, &flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
|
||||
return f
|
||||
}
|
||||
|
@ -93,6 +106,9 @@ func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *F
|
|||
// RemoveItem removes all items for the given primitive from the container,
|
||||
// keeping the order of the remaining items intact.
|
||||
func (f *Flex) RemoveItem(p Primitive) *Flex {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
for index := len(f.items) - 1; index >= 0; index-- {
|
||||
if f.items[index].Item == p {
|
||||
f.items = append(f.items[:index], f.items[index+1:]...)
|
||||
|
@ -105,6 +121,9 @@ func (f *Flex) RemoveItem(p Primitive) *Flex {
|
|||
// are multiple Flex items with the same primitive, they will all receive the
|
||||
// same size. For details regarding the size parameters, see AddItem().
|
||||
func (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) *Flex {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
for _, item := range f.items {
|
||||
if item.Item == p {
|
||||
item.FixedSize = fixedSize
|
||||
|
@ -118,6 +137,9 @@ func (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) *Flex {
|
|||
func (f *Flex) Draw(screen tcell.Screen) {
|
||||
f.Box.Draw(screen)
|
||||
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
// Calculate size and position of the items.
|
||||
|
||||
// Do we use the entire screen?
|
||||
|
@ -178,16 +200,24 @@ func (f *Flex) Draw(screen tcell.Screen) {
|
|||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (f *Flex) Focus(delegate func(p Primitive)) {
|
||||
f.Lock()
|
||||
|
||||
for _, item := range f.items {
|
||||
if item.Item != nil && item.Focus {
|
||||
f.Unlock()
|
||||
delegate(item.Item)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
f.Unlock()
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (f *Flex) HasFocus() bool {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
for _, item := range f.items {
|
||||
if item.Item != nil && item.Item.GetFocusable().HasFocus() {
|
||||
return true
|
||||
|
@ -198,6 +228,9 @@ func (f *Flex) HasFocus() bool {
|
|||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (f *Flex) GetChildren() []Primitive {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
children := make([]Primitive, len(f.items))
|
||||
for i, item := range f.items {
|
||||
children[i] = item.Item
|
||||
|
|
116
form.go
116
form.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -83,6 +85,8 @@ type Form struct {
|
|||
|
||||
// An optional function which is called when the user hits Escape.
|
||||
cancel func()
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewForm returns a new form.
|
||||
|
@ -108,6 +112,9 @@ func NewForm() *Form {
|
|||
// layouts and the number of empty cells between form items for horizontal
|
||||
// layouts.
|
||||
func (f *Form) SetItemPadding(padding int) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.itemPadding = padding
|
||||
return f
|
||||
}
|
||||
|
@ -117,24 +124,36 @@ func (f *Form) SetItemPadding(padding int) *Form {
|
|||
// positioned from left to right, moving into the next row if there is not
|
||||
// enough space.
|
||||
func (f *Form) SetHorizontal(horizontal bool) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.horizontal = horizontal
|
||||
return f
|
||||
}
|
||||
|
||||
// SetLabelColor sets the color of the labels.
|
||||
func (f *Form) SetLabelColor(color tcell.Color) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.labelColor = color
|
||||
return f
|
||||
}
|
||||
|
||||
// SetFieldBackgroundColor sets the background color of the input areas.
|
||||
func (f *Form) SetFieldBackgroundColor(color tcell.Color) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.fieldBackgroundColor = color
|
||||
return f
|
||||
}
|
||||
|
||||
// SetFieldTextColor sets the text color of the input areas.
|
||||
func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.fieldTextColor = color
|
||||
return f
|
||||
}
|
||||
|
@ -142,18 +161,27 @@ func (f *Form) SetFieldTextColor(color tcell.Color) *Form {
|
|||
// SetButtonsAlign sets how the buttons align horizontally, one of AlignLeft
|
||||
// (the default), AlignCenter, and AlignRight. This is only
|
||||
func (f *Form) SetButtonsAlign(align int) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.buttonsAlign = align
|
||||
return f
|
||||
}
|
||||
|
||||
// SetButtonBackgroundColor sets the background color of the buttons.
|
||||
func (f *Form) SetButtonBackgroundColor(color tcell.Color) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.buttonBackgroundColor = color
|
||||
return f
|
||||
}
|
||||
|
||||
// SetButtonTextColor sets the color of the button texts.
|
||||
func (f *Form) SetButtonTextColor(color tcell.Color) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.buttonTextColor = color
|
||||
return f
|
||||
}
|
||||
|
@ -162,6 +190,9 @@ func (f *Form) SetButtonTextColor(color tcell.Color) *Form {
|
|||
// non-button items first and buttons last. Note that this index is only used
|
||||
// when the form itself receives focus.
|
||||
func (f *Form) SetFocus(index int) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if index < 0 {
|
||||
f.focusedElement = 0
|
||||
} else if index >= len(f.items)+len(f.buttons) {
|
||||
|
@ -178,6 +209,9 @@ func (f *Form) SetFocus(index int) *Form {
|
|||
// accept any text), and an (optional) callback function which is invoked when
|
||||
// the input field's text has changed.
|
||||
func (f *Form) AddInputField(label, value string, fieldWidth int, accept func(textToCheck string, lastChar rune) bool, changed func(text string)) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.items = append(f.items, NewInputField().
|
||||
SetLabel(label).
|
||||
SetText(value).
|
||||
|
@ -194,6 +228,9 @@ func (f *Form) AddInputField(label, value string, fieldWidth int, accept func(te
|
|||
// (optional) callback function which is invoked when the input field's text has
|
||||
// changed.
|
||||
func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune, changed func(text string)) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if mask == 0 {
|
||||
mask = '*'
|
||||
}
|
||||
|
@ -211,6 +248,9 @@ func (f *Form) AddPasswordField(label, value string, fieldWidth int, mask rune,
|
|||
// selected. The initial option may be a negative value to indicate that no
|
||||
// option is currently selected.
|
||||
func (f *Form) AddDropDown(label string, options []string, initialOption int, selected func(option string, optionIndex int)) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.items = append(f.items, NewDropDown().
|
||||
SetLabel(label).
|
||||
SetOptions(options, selected).
|
||||
|
@ -222,6 +262,9 @@ func (f *Form) AddDropDown(label string, options []string, initialOption int, se
|
|||
// initial state, and an (optional) callback function which is invoked when the
|
||||
// state of the checkbox was changed by the user.
|
||||
func (f *Form) AddCheckbox(label string, message string, checked bool, changed func(checked bool)) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.items = append(f.items, NewCheckbox().
|
||||
SetLabel(label).
|
||||
SetMessage(message).
|
||||
|
@ -233,6 +276,9 @@ func (f *Form) AddCheckbox(label string, message string, checked bool, changed f
|
|||
// AddButton adds a new button to the form. The "selected" function is called
|
||||
// when the user selects this button. It may be nil.
|
||||
func (f *Form) AddButton(label string, selected func()) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.buttons = append(f.buttons, NewButton(label).SetSelectedFunc(selected))
|
||||
return f
|
||||
}
|
||||
|
@ -241,18 +287,27 @@ func (f *Form) AddButton(label string, selected func()) *Form {
|
|||
// buttons have been specially prepared for this form and modifying some of
|
||||
// their attributes may have unintended side effects.
|
||||
func (f *Form) GetButton(index int) *Button {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
return f.buttons[index]
|
||||
}
|
||||
|
||||
// RemoveButton removes the button at the specified position, starting with 0
|
||||
// for the button that was added first.
|
||||
func (f *Form) RemoveButton(index int) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
|
||||
return f
|
||||
}
|
||||
|
||||
// GetButtonCount returns the number of buttons in this form.
|
||||
func (f *Form) GetButtonCount() int {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
return len(f.buttons)
|
||||
}
|
||||
|
||||
|
@ -260,6 +315,9 @@ func (f *Form) GetButtonCount() int {
|
|||
// with 0 for the button that was added first. If no such label was found, -1
|
||||
// is returned.
|
||||
func (f *Form) GetButtonIndex(label string) int {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
for index, button := range f.buttons {
|
||||
if button.GetLabel() == label {
|
||||
return index
|
||||
|
@ -271,6 +329,9 @@ func (f *Form) GetButtonIndex(label string) int {
|
|||
// Clear removes all input elements from the form, including the buttons if
|
||||
// specified.
|
||||
func (f *Form) Clear(includeButtons bool) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.items = nil
|
||||
if includeButtons {
|
||||
f.ClearButtons()
|
||||
|
@ -281,6 +342,9 @@ func (f *Form) Clear(includeButtons bool) *Form {
|
|||
|
||||
// ClearButtons removes all buttons from the form.
|
||||
func (f *Form) ClearButtons() *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.buttons = nil
|
||||
return f
|
||||
}
|
||||
|
@ -296,6 +360,9 @@ func (f *Form) ClearButtons() *Form {
|
|||
// - The field text color
|
||||
// - The field background color
|
||||
func (f *Form) AddFormItem(item FormItem) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.items = append(f.items, item)
|
||||
return f
|
||||
}
|
||||
|
@ -303,6 +370,9 @@ func (f *Form) AddFormItem(item FormItem) *Form {
|
|||
// GetFormItemCount returns the number of items in the form (not including the
|
||||
// buttons).
|
||||
func (f *Form) GetFormItemCount() int {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
return len(f.items)
|
||||
}
|
||||
|
||||
|
@ -310,6 +380,9 @@ func (f *Form) GetFormItemCount() int {
|
|||
// 0. Elements are referenced in the order they were added. Buttons are not
|
||||
// included.
|
||||
func (f *Form) GetFormItem(index int) FormItem {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
return f.items[index]
|
||||
}
|
||||
|
||||
|
@ -317,6 +390,9 @@ func (f *Form) GetFormItem(index int) FormItem {
|
|||
// index 0. Elements are referenced in the order they were added. Buttons are
|
||||
// not included.
|
||||
func (f *Form) RemoveFormItem(index int) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.items = append(f.items[:index], f.items[index+1:]...)
|
||||
return f
|
||||
}
|
||||
|
@ -325,6 +401,9 @@ func (f *Form) RemoveFormItem(index int) *Form {
|
|||
// no such element is found, nil is returned. Buttons are not searched and will
|
||||
// therefore not be returned.
|
||||
func (f *Form) GetFormItemByLabel(label string) FormItem {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
for _, item := range f.items {
|
||||
if item.GetLabel() == label {
|
||||
return item
|
||||
|
@ -337,6 +416,9 @@ func (f *Form) GetFormItemByLabel(label string) FormItem {
|
|||
// label. If no such element is found, -1 is returned. Buttons are not searched
|
||||
// and will therefore not be returned.
|
||||
func (f *Form) GetFormItemIndex(label string) int {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
for index, item := range f.items {
|
||||
if item.GetLabel() == label {
|
||||
return index
|
||||
|
@ -348,6 +430,9 @@ func (f *Form) GetFormItemIndex(label string) int {
|
|||
// GetFocusedItemIndex returns the indices of the form element or button which
|
||||
// currently has focus. If they don't, -1 is returned resepectively.
|
||||
func (f *Form) GetFocusedItemIndex() (formItem, button int) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
index := f.focusIndex()
|
||||
if index < 0 {
|
||||
return -1, -1
|
||||
|
@ -364,6 +449,9 @@ func (f *Form) GetFocusedItemIndex() (formItem, button int) {
|
|||
// false, the selection won't change when navigating downwards on the last item
|
||||
// or navigating upwards on the first item.
|
||||
func (f *Form) SetWrapAround(wrapAround bool) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.wrapAround = wrapAround
|
||||
return f
|
||||
}
|
||||
|
@ -371,6 +459,9 @@ func (f *Form) SetWrapAround(wrapAround bool) *Form {
|
|||
// SetCancelFunc sets a handler which is called when the user hits the Escape
|
||||
// key.
|
||||
func (f *Form) SetCancelFunc(callback func()) *Form {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.cancel = callback
|
||||
return f
|
||||
}
|
||||
|
@ -379,6 +470,9 @@ func (f *Form) SetCancelFunc(callback func()) *Form {
|
|||
func (f *Form) Draw(screen tcell.Screen) {
|
||||
f.Box.Draw(screen)
|
||||
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
// Determine the actual item that has focus.
|
||||
if index := f.focusIndex(); index >= 0 {
|
||||
f.focusedElement = index
|
||||
|
@ -565,8 +659,10 @@ func (f *Form) Draw(screen tcell.Screen) {
|
|||
|
||||
// Focus is called by the application when the primitive receives focus.
|
||||
func (f *Form) Focus(delegate func(p Primitive)) {
|
||||
f.Lock()
|
||||
if len(f.items)+len(f.buttons) == 0 {
|
||||
f.hasFocus = true
|
||||
f.Unlock()
|
||||
return
|
||||
}
|
||||
f.hasFocus = false
|
||||
|
@ -576,13 +672,17 @@ func (f *Form) Focus(delegate func(p Primitive)) {
|
|||
f.focusedElement = 0
|
||||
}
|
||||
handler := func(key tcell.Key) {
|
||||
f.Lock()
|
||||
|
||||
switch key {
|
||||
case tcell.KeyTab, tcell.KeyEnter:
|
||||
f.focusedElement++
|
||||
if !f.wrapAround && f.focusedElement >= len(f.items)+len(f.buttons) {
|
||||
f.focusedElement = (len(f.items) + len(f.buttons)) - 1
|
||||
}
|
||||
f.Unlock()
|
||||
f.Focus(delegate)
|
||||
f.Lock()
|
||||
case tcell.KeyBacktab:
|
||||
f.focusedElement--
|
||||
if f.focusedElement < 0 {
|
||||
|
@ -592,32 +692,45 @@ func (f *Form) Focus(delegate func(p Primitive)) {
|
|||
f.focusedElement = 0
|
||||
}
|
||||
}
|
||||
f.Unlock()
|
||||
f.Focus(delegate)
|
||||
f.Lock()
|
||||
case tcell.KeyEscape:
|
||||
if f.cancel != nil {
|
||||
f.Unlock()
|
||||
f.cancel()
|
||||
f.Lock()
|
||||
} else {
|
||||
f.focusedElement = 0
|
||||
f.Unlock()
|
||||
f.Focus(delegate)
|
||||
f.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
f.Unlock()
|
||||
}
|
||||
|
||||
if f.focusedElement < len(f.items) {
|
||||
// We're selecting an item.
|
||||
item := f.items[f.focusedElement]
|
||||
item.SetFinishedFunc(handler)
|
||||
f.Unlock()
|
||||
delegate(item)
|
||||
} else {
|
||||
// We're selecting a button.
|
||||
button := f.buttons[f.focusedElement-len(f.items)]
|
||||
button.SetBlurFunc(handler)
|
||||
f.Unlock()
|
||||
delegate(button)
|
||||
}
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (f *Form) HasFocus() bool {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if f.hasFocus {
|
||||
return true
|
||||
}
|
||||
|
@ -643,6 +756,9 @@ func (f *Form) focusIndex() int {
|
|||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (f *Form) GetChildren() []Primitive {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
children := make([]Primitive, len(f.items)+len(f.buttons))
|
||||
i := 0
|
||||
for _, item := range f.items {
|
||||
|
|
28
frame.go
28
frame.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -27,6 +29,8 @@ type Frame struct {
|
|||
|
||||
// Border spacing.
|
||||
top, bottom, header, footer, left, right int
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewFrame returns a new frame around the given primitive. The primitive's
|
||||
|
@ -57,6 +61,9 @@ func NewFrame(primitive Primitive) *Frame {
|
|||
// the footer are printed bottom to top. Note that long text can overlap as
|
||||
// different alignments will be placed on the same row.
|
||||
func (f *Frame) AddText(text string, header bool, align int, color tcell.Color) *Frame {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.text = append(f.text, &frameText{
|
||||
Text: text,
|
||||
Header: header,
|
||||
|
@ -68,6 +75,9 @@ func (f *Frame) AddText(text string, header bool, align int, color tcell.Color)
|
|||
|
||||
// Clear removes all text from the frame.
|
||||
func (f *Frame) Clear() *Frame {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.text = nil
|
||||
return f
|
||||
}
|
||||
|
@ -76,6 +86,9 @@ func (f *Frame) Clear() *Frame {
|
|||
// "footer", the vertical space between the header and footer text and the
|
||||
// contained primitive (does not apply if there is no text).
|
||||
func (f *Frame) SetBorders(top, bottom, header, footer, left, right int) *Frame {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.top, f.bottom, f.header, f.footer, f.left, f.right = top, bottom, header, footer, left, right
|
||||
return f
|
||||
}
|
||||
|
@ -84,6 +97,9 @@ func (f *Frame) SetBorders(top, bottom, header, footer, left, right int) *Frame
|
|||
func (f *Frame) Draw(screen tcell.Screen) {
|
||||
f.Box.Draw(screen)
|
||||
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
// Calculate start positions.
|
||||
x, top, width, height := f.GetInnerRect()
|
||||
bottom := top + height - 1
|
||||
|
@ -144,11 +160,18 @@ func (f *Frame) Draw(screen tcell.Screen) {
|
|||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (f *Frame) Focus(delegate func(p Primitive)) {
|
||||
delegate(f.primitive)
|
||||
f.Lock()
|
||||
primitive := f.primitive
|
||||
defer f.Unlock()
|
||||
|
||||
delegate(primitive)
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (f *Frame) HasFocus() bool {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
focusable, ok := f.primitive.(Focusable)
|
||||
if ok {
|
||||
return focusable.HasFocus()
|
||||
|
@ -158,5 +181,8 @@ func (f *Frame) HasFocus() bool {
|
|||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (f *Frame) GetChildren() []Primitive {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
return []Primitive{f.primitive}
|
||||
}
|
||||
|
|
64
grid.go
64
grid.go
|
@ -2,6 +2,7 @@ package cview
|
|||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
@ -56,6 +57,8 @@ type Grid struct {
|
|||
|
||||
// The color of the borders around grid items.
|
||||
bordersColor tcell.Color
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewGrid returns a new grid-based layout container with no initial primitives.
|
||||
|
@ -105,6 +108,9 @@ func NewGrid() *Grid {
|
|||
// The resulting widths would be: 30, 15, 15, 15, 20, 15, and 15 cells, a total
|
||||
// of 125 cells, 25 cells wider than the available grid width.
|
||||
func (g *Grid) SetColumns(columns ...int) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.columns = columns
|
||||
return g
|
||||
}
|
||||
|
@ -116,6 +122,9 @@ func (g *Grid) SetColumns(columns ...int) *Grid {
|
|||
// The provided values correspond to row heights, the first value defining
|
||||
// the height of the topmost row.
|
||||
func (g *Grid) SetRows(rows ...int) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.rows = rows
|
||||
return g
|
||||
}
|
||||
|
@ -123,6 +132,9 @@ func (g *Grid) SetRows(rows ...int) *Grid {
|
|||
// SetSize is a shortcut for SetRows() and SetColumns() where all row and column
|
||||
// values are set to the given size values. See SetColumns() for details on sizes.
|
||||
func (g *Grid) SetSize(numRows, numColumns, rowSize, columnSize int) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.rows = make([]int, numRows)
|
||||
for index := range g.rows {
|
||||
g.rows[index] = rowSize
|
||||
|
@ -137,6 +149,9 @@ func (g *Grid) SetSize(numRows, numColumns, rowSize, columnSize int) *Grid {
|
|||
// SetMinSize sets an absolute minimum width for rows and an absolute minimum
|
||||
// height for columns. Panics if negative values are provided.
|
||||
func (g *Grid) SetMinSize(row, column int) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
if row < 0 || column < 0 {
|
||||
panic("Invalid minimum row/column size")
|
||||
}
|
||||
|
@ -148,6 +163,9 @@ func (g *Grid) SetMinSize(row, column int) *Grid {
|
|||
// If borders are drawn (see SetBorders()), these values are ignored and a gap
|
||||
// of 1 is assumed. Panics if negative values are provided.
|
||||
func (g *Grid) SetGap(row, column int) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
if row < 0 || column < 0 {
|
||||
panic("Invalid gap size")
|
||||
}
|
||||
|
@ -159,12 +177,18 @@ func (g *Grid) SetGap(row, column int) *Grid {
|
|||
// this value to true will cause the gap values (see SetGap()) to be ignored and
|
||||
// automatically assumed to be 1 where the border graphics are drawn.
|
||||
func (g *Grid) SetBorders(borders bool) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.borders = borders
|
||||
return g
|
||||
}
|
||||
|
||||
// SetBordersColor sets the color of the item borders.
|
||||
func (g *Grid) SetBordersColor(color tcell.Color) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.bordersColor = color
|
||||
return g
|
||||
}
|
||||
|
@ -196,6 +220,9 @@ func (g *Grid) SetBordersColor(color tcell.Color) *Grid {
|
|||
// receives focus. If there are multiple items with a true focus flag, the last
|
||||
// visible one that was added will receive focus.
|
||||
func (g *Grid) AddItem(p Primitive, row, column, rowSpan, colSpan, minGridHeight, minGridWidth int, focus bool) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.items = append(g.items, &gridItem{
|
||||
Item: p,
|
||||
Row: row,
|
||||
|
@ -212,6 +239,9 @@ func (g *Grid) AddItem(p Primitive, row, column, rowSpan, colSpan, minGridHeight
|
|||
// RemoveItem removes all items for the given primitive from the grid, keeping
|
||||
// the order of the remaining items intact.
|
||||
func (g *Grid) RemoveItem(p Primitive) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
for index := len(g.items) - 1; index >= 0; index-- {
|
||||
if g.items[index].Item == p {
|
||||
g.items = append(g.items[:index], g.items[index+1:]...)
|
||||
|
@ -222,6 +252,9 @@ func (g *Grid) RemoveItem(p Primitive) *Grid {
|
|||
|
||||
// Clear removes all items from the grid.
|
||||
func (g *Grid) Clear() *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.items = nil
|
||||
return g
|
||||
}
|
||||
|
@ -232,6 +265,9 @@ func (g *Grid) Clear() *Grid {
|
|||
// the grid is drawn. The actual position of the grid may also be adjusted such
|
||||
// that contained primitives that have focus remain visible.
|
||||
func (g *Grid) SetOffset(rows, columns int) *Grid {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.rowOffset, g.columnOffset = rows, columns
|
||||
return g
|
||||
}
|
||||
|
@ -239,27 +275,43 @@ func (g *Grid) SetOffset(rows, columns int) *Grid {
|
|||
// GetOffset returns the current row and column offset (see SetOffset() for
|
||||
// details).
|
||||
func (g *Grid) GetOffset() (rows, columns int) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
return g.rowOffset, g.columnOffset
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (g *Grid) Focus(delegate func(p Primitive)) {
|
||||
for _, item := range g.items {
|
||||
g.Lock()
|
||||
items := g.items
|
||||
g.Unlock()
|
||||
|
||||
for _, item := range items {
|
||||
if item.Focus {
|
||||
delegate(item.Item)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
g.Lock()
|
||||
g.hasFocus = true
|
||||
g.Unlock()
|
||||
}
|
||||
|
||||
// Blur is called when this primitive loses focus.
|
||||
func (g *Grid) Blur() {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
g.hasFocus = false
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (g *Grid) HasFocus() bool {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
for _, item := range g.items {
|
||||
if item.visible && item.Item.GetFocusable().HasFocus() {
|
||||
return true
|
||||
|
@ -271,6 +323,9 @@ func (g *Grid) HasFocus() bool {
|
|||
// InputHandler returns the handler for this primitive.
|
||||
func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return g.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
switch event.Key() {
|
||||
case tcell.KeyRune:
|
||||
switch event.Rune() {
|
||||
|
@ -306,6 +361,10 @@ func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
// Draw draws this primitive onto the screen.
|
||||
func (g *Grid) Draw(screen tcell.Screen) {
|
||||
g.Box.Draw(screen)
|
||||
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
x, y, width, height := g.GetInnerRect()
|
||||
screenWidth, screenHeight := screen.Size()
|
||||
|
||||
|
@ -663,6 +722,9 @@ func (g *Grid) Draw(screen tcell.Screen) {
|
|||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (g *Grid) GetChildren() []Primitive {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
children := make([]Primitive, len(g.items))
|
||||
for i, item := range g.items {
|
||||
children[i] = item.Item
|
||||
|
|
103
inputfield.go
103
inputfield.go
|
@ -79,8 +79,7 @@ type InputField struct {
|
|||
|
||||
// The List object which shows the selectable autocomplete entries. If not
|
||||
// nil, the list's main texts represent the current autocomplete entries.
|
||||
autocompleteList *List
|
||||
autocompleteListMutex sync.Mutex
|
||||
autocompleteList *List
|
||||
|
||||
// An optional function which may reject the last character that was entered.
|
||||
accept func(text string, ch rune) bool
|
||||
|
@ -96,6 +95,8 @@ type InputField struct {
|
|||
// A callback function set by the Form class and called when the user leaves
|
||||
// this form item.
|
||||
finished func(tcell.Key)
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewInputField returns a new input field.
|
||||
|
@ -111,69 +112,105 @@ func NewInputField() *InputField {
|
|||
|
||||
// SetText sets the current text of the input field.
|
||||
func (i *InputField) SetText(text string) *InputField {
|
||||
i.Lock()
|
||||
|
||||
i.text = text
|
||||
i.cursorPos = len(text)
|
||||
if i.changed != nil {
|
||||
i.Unlock()
|
||||
i.changed(text)
|
||||
} else {
|
||||
i.Unlock()
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// GetText returns the current text of the input field.
|
||||
func (i *InputField) GetText() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
|
||||
return i.text
|
||||
}
|
||||
|
||||
// SetLabel sets the text to be displayed before the input area.
|
||||
func (i *InputField) SetLabel(label string) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.label = label
|
||||
return i
|
||||
}
|
||||
|
||||
// GetLabel returns the text to be displayed before the input area.
|
||||
func (i *InputField) GetLabel() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
|
||||
return i.label
|
||||
}
|
||||
|
||||
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
|
||||
// primitive to use the width of the label string.
|
||||
func (i *InputField) SetLabelWidth(width int) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.labelWidth = width
|
||||
return i
|
||||
}
|
||||
|
||||
// SetPlaceholder sets the text to be displayed when the input text is empty.
|
||||
func (i *InputField) SetPlaceholder(text string) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.placeholder = text
|
||||
return i
|
||||
}
|
||||
|
||||
// SetLabelColor sets the color of the label.
|
||||
func (i *InputField) SetLabelColor(color tcell.Color) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.labelColor = color
|
||||
return i
|
||||
}
|
||||
|
||||
// SetFieldBackgroundColor sets the background color of the input area.
|
||||
func (i *InputField) SetFieldBackgroundColor(color tcell.Color) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.fieldBackgroundColor = color
|
||||
return i
|
||||
}
|
||||
|
||||
// SetFieldTextColor sets the text color of the input area.
|
||||
func (i *InputField) SetFieldTextColor(color tcell.Color) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.fieldTextColor = color
|
||||
return i
|
||||
}
|
||||
|
||||
// SetPlaceholderTextColor sets the text color of placeholder text.
|
||||
func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.placeholderTextColor = color
|
||||
return i
|
||||
}
|
||||
|
||||
// SetFormAttributes sets attributes shared by all form items.
|
||||
func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.labelWidth = labelWidth
|
||||
i.labelColor = labelColor
|
||||
i.backgroundColor = bgColor
|
||||
|
@ -185,18 +222,27 @@ func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fiel
|
|||
// SetFieldWidth sets the screen width of the input area. A value of 0 means
|
||||
// extend as much as possible.
|
||||
func (i *InputField) SetFieldWidth(width int) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.fieldWidth = width
|
||||
return i
|
||||
}
|
||||
|
||||
// GetFieldWidth returns this primitive's field width.
|
||||
func (i *InputField) GetFieldWidth() int {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
|
||||
return i.fieldWidth
|
||||
}
|
||||
|
||||
// SetMaskCharacter sets a character that masks user input on a screen. A value
|
||||
// of 0 disables masking.
|
||||
func (i *InputField) SetMaskCharacter(mask rune) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.maskCharacter = mask
|
||||
return i
|
||||
}
|
||||
|
@ -208,7 +254,10 @@ func (i *InputField) SetMaskCharacter(mask rune) *InputField {
|
|||
// Autocomplete() is called. Entries are cleared when the user selects an entry
|
||||
// or presses Escape.
|
||||
func (i *InputField) SetAutocompleteFunc(callback func(currentText string) (entries []string)) *InputField {
|
||||
i.Lock()
|
||||
i.autocomplete = callback
|
||||
i.Unlock()
|
||||
|
||||
i.Autocomplete()
|
||||
return i
|
||||
}
|
||||
|
@ -222,20 +271,25 @@ func (i *InputField) SetAutocompleteFunc(callback func(currentText string) (entr
|
|||
// field is not redrawn automatically unless called from the main goroutine
|
||||
// (e.g. in response to events).
|
||||
func (i *InputField) Autocomplete() *InputField {
|
||||
i.autocompleteListMutex.Lock()
|
||||
defer i.autocompleteListMutex.Unlock()
|
||||
i.Lock()
|
||||
if i.autocomplete == nil {
|
||||
i.Unlock()
|
||||
return i
|
||||
}
|
||||
i.Unlock()
|
||||
|
||||
// Do we have any autocomplete entries?
|
||||
entries := i.autocomplete(i.text)
|
||||
if len(entries) == 0 {
|
||||
// No entries, no list.
|
||||
i.Lock()
|
||||
i.autocompleteList = nil
|
||||
i.Unlock()
|
||||
return i
|
||||
}
|
||||
|
||||
i.Lock()
|
||||
|
||||
// Make a list if we have none.
|
||||
if i.autocompleteList == nil {
|
||||
i.autocompleteList = NewList()
|
||||
|
@ -262,6 +316,7 @@ func (i *InputField) Autocomplete() *InputField {
|
|||
i.autocompleteList.SetCurrentItem(currentEntry)
|
||||
}
|
||||
|
||||
i.Unlock()
|
||||
return i
|
||||
}
|
||||
|
||||
|
@ -271,6 +326,9 @@ func (i *InputField) Autocomplete() *InputField {
|
|||
// This package defines a number of variables prefixed with InputField which may
|
||||
// be used for common input (e.g. numbers, maximum text length).
|
||||
func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.accept = handler
|
||||
return i
|
||||
}
|
||||
|
@ -278,6 +336,9 @@ func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar
|
|||
// SetChangedFunc sets a handler which is called whenever the text of the input
|
||||
// field has changed. It receives the current text (after the change).
|
||||
func (i *InputField) SetChangedFunc(handler func(text string)) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.changed = handler
|
||||
return i
|
||||
}
|
||||
|
@ -291,12 +352,18 @@ func (i *InputField) SetChangedFunc(handler func(text string)) *InputField {
|
|||
// - KeyTab: Move to the next field.
|
||||
// - KeyBacktab: Move to the previous field.
|
||||
func (i *InputField) SetDoneFunc(handler func(key tcell.Key)) *InputField {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.done = handler
|
||||
return i
|
||||
}
|
||||
|
||||
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
|
||||
func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
i.finished = handler
|
||||
return i
|
||||
}
|
||||
|
@ -305,6 +372,9 @@ func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
|
|||
func (i *InputField) Draw(screen tcell.Screen) {
|
||||
i.Box.Draw(screen)
|
||||
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
// Prepare
|
||||
x, y, width, height := i.GetInnerRect()
|
||||
rightLimit := x + width
|
||||
|
@ -395,8 +465,6 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Draw autocomplete list.
|
||||
i.autocompleteListMutex.Lock()
|
||||
defer i.autocompleteListMutex.Unlock()
|
||||
if i.autocompleteList != nil {
|
||||
// How much space do we need?
|
||||
lheight := i.autocompleteList.GetItemCount()
|
||||
|
@ -438,10 +506,16 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
// InputHandler returns the handler for this primitive.
|
||||
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
i.Lock()
|
||||
|
||||
// Trigger changed events.
|
||||
currentText := i.text
|
||||
defer func() {
|
||||
if i.text != currentText {
|
||||
i.Lock()
|
||||
newText := i.text
|
||||
i.Unlock()
|
||||
|
||||
if newText != currentText {
|
||||
i.Autocomplete()
|
||||
if i.changed != nil {
|
||||
i.changed(i.text)
|
||||
|
@ -494,8 +568,6 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
|||
}
|
||||
|
||||
// Process key event.
|
||||
i.autocompleteListMutex.Lock()
|
||||
defer i.autocompleteListMutex.Unlock()
|
||||
switch key := event.Key(); key {
|
||||
case tcell.KeyRune: // Regular character.
|
||||
if event.Modifiers()&tcell.ModAlt > 0 {
|
||||
|
@ -511,12 +583,14 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
|||
moveWordRight()
|
||||
default:
|
||||
if !add(event.Rune()) {
|
||||
i.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other keys are simply accepted as regular characters.
|
||||
if !add(event.Rune()) {
|
||||
i.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -563,9 +637,12 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
|||
case tcell.KeyEnter, tcell.KeyEscape: // We might be done.
|
||||
if i.autocompleteList != nil {
|
||||
i.autocompleteList = nil
|
||||
i.Unlock()
|
||||
} else {
|
||||
i.Unlock()
|
||||
finish(key)
|
||||
}
|
||||
return
|
||||
case tcell.KeyDown, tcell.KeyTab: // Autocomplete selection.
|
||||
if i.autocompleteList != nil {
|
||||
count := i.autocompleteList.GetItemCount()
|
||||
|
@ -575,10 +652,13 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
|||
}
|
||||
i.autocompleteList.SetCurrentItem(newEntry)
|
||||
currentText, _ = i.autocompleteList.GetItemText(newEntry) // Don't trigger changed function twice.
|
||||
i.Unlock()
|
||||
i.SetText(currentText)
|
||||
} else {
|
||||
i.Unlock()
|
||||
finish(key)
|
||||
}
|
||||
return
|
||||
case tcell.KeyUp, tcell.KeyBacktab: // Autocomplete selection.
|
||||
if i.autocompleteList != nil {
|
||||
newEntry := i.autocompleteList.GetCurrentItem() - 1
|
||||
|
@ -587,11 +667,16 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
|||
}
|
||||
i.autocompleteList.SetCurrentItem(newEntry)
|
||||
currentText, _ = i.autocompleteList.GetItemText(newEntry) // Don't trigger changed function twice.
|
||||
i.Unlock()
|
||||
i.SetText(currentText)
|
||||
} else {
|
||||
i.Unlock()
|
||||
finish(key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
i.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
108
list.go
108
list.go
|
@ -3,6 +3,7 @@ package cview
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
@ -73,6 +74,8 @@ type List struct {
|
|||
|
||||
// An optional function which is called when the user presses the Escape key.
|
||||
done func()
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewList returns a new form.
|
||||
|
@ -97,6 +100,8 @@ func NewList() *List {
|
|||
//
|
||||
// Calling this function triggers a "changed" event if the selection changes.
|
||||
func (l *List) SetCurrentItem(index int) *List {
|
||||
l.Lock()
|
||||
|
||||
if index < 0 {
|
||||
index = len(l.items) + index
|
||||
}
|
||||
|
@ -109,17 +114,23 @@ func (l *List) SetCurrentItem(index int) *List {
|
|||
|
||||
if index != l.currentItem && l.changed != nil {
|
||||
item := l.items[index]
|
||||
l.Unlock()
|
||||
l.changed(index, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
}
|
||||
|
||||
l.currentItem = index
|
||||
|
||||
l.Unlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// GetCurrentItem returns the index of the currently selected list item,
|
||||
// starting at 0 for the first item.
|
||||
func (l *List) GetCurrentItem() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
return l.currentItem
|
||||
}
|
||||
|
||||
|
@ -132,7 +143,10 @@ func (l *List) GetCurrentItem() int {
|
|||
// The currently selected item is shifted accordingly. If it is the one that is
|
||||
// removed, a "changed" event is fired.
|
||||
func (l *List) RemoveItem(index int) *List {
|
||||
l.Lock()
|
||||
|
||||
if len(l.items) == 0 {
|
||||
l.Unlock()
|
||||
return l
|
||||
}
|
||||
|
||||
|
@ -152,6 +166,7 @@ func (l *List) RemoveItem(index int) *List {
|
|||
|
||||
// If there is nothing left, we're done.
|
||||
if len(l.items) == 0 {
|
||||
l.Unlock()
|
||||
return l
|
||||
}
|
||||
|
||||
|
@ -164,7 +179,10 @@ func (l *List) RemoveItem(index int) *List {
|
|||
// Fire "changed" event for removed items.
|
||||
if previousCurrentItem == index && l.changed != nil {
|
||||
item := l.items[l.currentItem]
|
||||
l.Unlock()
|
||||
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
} else {
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
return l
|
||||
|
@ -172,30 +190,45 @@ func (l *List) RemoveItem(index int) *List {
|
|||
|
||||
// SetMainTextColor sets the color of the items' main text.
|
||||
func (l *List) SetMainTextColor(color tcell.Color) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.mainTextColor = color
|
||||
return l
|
||||
}
|
||||
|
||||
// SetSecondaryTextColor sets the color of the items' secondary text.
|
||||
func (l *List) SetSecondaryTextColor(color tcell.Color) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.secondaryTextColor = color
|
||||
return l
|
||||
}
|
||||
|
||||
// SetShortcutColor sets the color of the items' shortcut.
|
||||
func (l *List) SetShortcutColor(color tcell.Color) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.shortcutColor = color
|
||||
return l
|
||||
}
|
||||
|
||||
// SetSelectedTextColor sets the text color of selected items.
|
||||
func (l *List) SetSelectedTextColor(color tcell.Color) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.selectedTextColor = color
|
||||
return l
|
||||
}
|
||||
|
||||
// SetSelectedBackgroundColor sets the background color of selected items.
|
||||
func (l *List) SetSelectedBackgroundColor(color tcell.Color) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.selectedBackgroundColor = color
|
||||
return l
|
||||
}
|
||||
|
@ -204,6 +237,9 @@ func (l *List) SetSelectedBackgroundColor(color tcell.Color) *List {
|
|||
// list item is highlighted. If set to true, selected items are only highlighted
|
||||
// when the list has focus. If set to false, they are always highlighted.
|
||||
func (l *List) SetSelectedFocusOnly(focusOnly bool) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.selectedFocusOnly = focusOnly
|
||||
return l
|
||||
}
|
||||
|
@ -213,24 +249,36 @@ func (l *List) SetSelectedFocusOnly(focusOnly bool) *List {
|
|||
// true, the highlight spans the entire view. If set to false, only the text of
|
||||
// the selected item from beginning to end is highlighted.
|
||||
func (l *List) SetHighlightFullLine(highlight bool) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.highlightFullLine = highlight
|
||||
return l
|
||||
}
|
||||
|
||||
// ShowSecondaryText determines whether or not to show secondary item texts.
|
||||
func (l *List) ShowSecondaryText(show bool) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.showSecondaryText = show
|
||||
return l
|
||||
}
|
||||
|
||||
// SetScrollBarVisibility specifies the display of the scroll bar.
|
||||
func (l *List) SetScrollBarVisibility(visibility ScrollBarVisibility) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.scrollBarVisibility = visibility
|
||||
return l
|
||||
}
|
||||
|
||||
// SetScrollBarColor sets the color of the scroll bar.
|
||||
func (l *List) SetScrollBarColor(color tcell.Color) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.scrollBarColor = color
|
||||
return l
|
||||
}
|
||||
|
@ -241,6 +289,9 @@ func (l *List) SetScrollBarColor(color tcell.Color) *List {
|
|||
// false, the selection won't change when navigating downwards on the last item
|
||||
// or navigating upwards on the first item.
|
||||
func (l *List) SetWrapAround(wrapAround bool) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.wrapAround = wrapAround
|
||||
return l
|
||||
}
|
||||
|
@ -252,6 +303,9 @@ func (l *List) SetWrapAround(wrapAround bool) *List {
|
|||
// This function is also called when the first item is added or when
|
||||
// SetCurrentItem() is called.
|
||||
func (l *List) SetChangedFunc(handler func(index int, mainText string, secondaryText string, shortcut rune)) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.changed = handler
|
||||
return l
|
||||
}
|
||||
|
@ -261,6 +315,9 @@ func (l *List) SetChangedFunc(handler func(index int, mainText string, secondary
|
|||
// the item's index in the list of items (starting with 0), its main text,
|
||||
// secondary text, and its shortcut rune.
|
||||
func (l *List) SetSelectedFunc(handler func(int, string, string, rune)) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.selected = handler
|
||||
return l
|
||||
}
|
||||
|
@ -268,6 +325,9 @@ func (l *List) SetSelectedFunc(handler func(int, string, string, rune)) *List {
|
|||
// SetDoneFunc sets a function which is called when the user presses the Escape
|
||||
// key.
|
||||
func (l *List) SetDoneFunc(handler func()) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.done = handler
|
||||
return l
|
||||
}
|
||||
|
@ -301,6 +361,8 @@ func (l *List) AddItem(mainText, secondaryText string, shortcut rune, selected f
|
|||
// was previously empty, a "changed" event is fired because the new item becomes
|
||||
// selected.
|
||||
func (l *List) InsertItem(index int, mainText, secondaryText string, shortcut rune, selected func()) *List {
|
||||
l.Lock()
|
||||
|
||||
item := &listItem{
|
||||
MainText: mainText,
|
||||
SecondaryText: secondaryText,
|
||||
|
@ -333,7 +395,10 @@ func (l *List) InsertItem(index int, mainText, secondaryText string, shortcut ru
|
|||
// Fire a "change" event for the first item in the list.
|
||||
if len(l.items) == 1 && l.changed != nil {
|
||||
item := l.items[0]
|
||||
l.Unlock()
|
||||
l.changed(0, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
} else {
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
return l
|
||||
|
@ -341,18 +406,27 @@ func (l *List) InsertItem(index int, mainText, secondaryText string, shortcut ru
|
|||
|
||||
// GetItemCount returns the number of items in the list.
|
||||
func (l *List) GetItemCount() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
return len(l.items)
|
||||
}
|
||||
|
||||
// GetItemText returns an item's texts (main and secondary). Panics if the index
|
||||
// is out of range.
|
||||
func (l *List) GetItemText(index int) (main, secondary string) {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
return l.items[index].MainText, l.items[index].SecondaryText
|
||||
}
|
||||
|
||||
// SetItemText sets an item's main and secondary text. Panics if the index is
|
||||
// out of range.
|
||||
func (l *List) SetItemText(index int, main, secondary string) *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
item := l.items[index]
|
||||
item.MainText = main
|
||||
item.SecondaryText = secondary
|
||||
|
@ -370,6 +444,9 @@ func (l *List) SetItemText(index int, main, secondary string) *List {
|
|||
//
|
||||
// Set ignoreCase to true for case-insensitive search.
|
||||
func (l *List) FindItems(mainSearch, secondarySearch string, mustContainBoth, ignoreCase bool) (indices []int) {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if mainSearch == "" && secondarySearch == "" {
|
||||
return
|
||||
}
|
||||
|
@ -401,6 +478,9 @@ func (l *List) FindItems(mainSearch, secondarySearch string, mustContainBoth, ig
|
|||
|
||||
// Clear removes all items from the list.
|
||||
func (l *List) Clear() *List {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.items = nil
|
||||
l.currentItem = 0
|
||||
return l
|
||||
|
@ -410,6 +490,9 @@ func (l *List) Clear() *List {
|
|||
func (l *List) Draw(screen tcell.Screen) {
|
||||
l.Box.Draw(screen)
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
// Determine the dimensions.
|
||||
x, y, width, height := l.GetInnerRect()
|
||||
bottomLimit := y + height
|
||||
|
@ -517,12 +600,16 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
// InputHandler returns the handler for this primitive.
|
||||
func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
l.Lock()
|
||||
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
if l.done != nil {
|
||||
l.done()
|
||||
}
|
||||
l.Unlock()
|
||||
return
|
||||
} else if len(l.items) == 0 {
|
||||
l.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -545,10 +632,14 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
if l.currentItem >= 0 && l.currentItem < len(l.items) {
|
||||
item := l.items[l.currentItem]
|
||||
if item.Selected != nil {
|
||||
l.Unlock()
|
||||
item.Selected()
|
||||
l.Lock()
|
||||
}
|
||||
if l.selected != nil {
|
||||
l.Unlock()
|
||||
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
}
|
||||
}
|
||||
case tcell.KeyRune:
|
||||
|
@ -570,10 +661,14 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
}
|
||||
item := l.items[l.currentItem]
|
||||
if item.Selected != nil {
|
||||
l.Unlock()
|
||||
item.Selected()
|
||||
l.Lock()
|
||||
}
|
||||
if l.selected != nil {
|
||||
l.Unlock()
|
||||
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -593,7 +688,10 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
|
||||
if l.currentItem != previousItem && l.currentItem < len(l.items) && l.changed != nil {
|
||||
item := l.items[l.currentItem]
|
||||
l.Unlock()
|
||||
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
} else {
|
||||
l.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -621,21 +719,31 @@ func (l *List) MouseHandler() func(event *EventMouse) {
|
|||
return l.WrapMouseHandler(func(event *EventMouse) {
|
||||
// Process mouse event.
|
||||
if event.Action()&MouseClick != 0 {
|
||||
l.Lock()
|
||||
|
||||
atX, atY := event.Position()
|
||||
index := l.indexAtPoint(atX, atY)
|
||||
if index != -1 {
|
||||
item := l.items[index]
|
||||
if item.Selected != nil {
|
||||
l.Unlock()
|
||||
item.Selected()
|
||||
l.Lock()
|
||||
}
|
||||
if l.selected != nil {
|
||||
l.Unlock()
|
||||
l.selected(index, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
}
|
||||
if index != l.currentItem && l.changed != nil {
|
||||
l.Unlock()
|
||||
l.changed(index, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
}
|
||||
l.currentItem = index
|
||||
}
|
||||
|
||||
l.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
40
modal.go
40
modal.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -27,6 +29,8 @@ type Modal struct {
|
|||
// The optional callback for when the user clicked one of the buttons. It
|
||||
// receives the index of the clicked button and the button's label.
|
||||
done func(buttonIndex int, buttonLabel string)
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewModal returns a new modal message window.
|
||||
|
@ -55,6 +59,9 @@ func NewModal() *Modal {
|
|||
|
||||
// SetBackgroundColor sets the color of the modal frame background.
|
||||
func (m *Modal) SetBackgroundColor(color tcell.Color) *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.form.SetBackgroundColor(color)
|
||||
m.frame.SetBackgroundColor(color)
|
||||
return m
|
||||
|
@ -62,18 +69,27 @@ func (m *Modal) SetBackgroundColor(color tcell.Color) *Modal {
|
|||
|
||||
// SetTextColor sets the color of the message text.
|
||||
func (m *Modal) SetTextColor(color tcell.Color) *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.textColor = color
|
||||
return m
|
||||
}
|
||||
|
||||
// SetButtonBackgroundColor sets the background color of the buttons.
|
||||
func (m *Modal) SetButtonBackgroundColor(color tcell.Color) *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.form.SetButtonBackgroundColor(color)
|
||||
return m
|
||||
}
|
||||
|
||||
// SetButtonTextColor sets the color of the button texts.
|
||||
func (m *Modal) SetButtonTextColor(color tcell.Color) *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.form.SetButtonTextColor(color)
|
||||
return m
|
||||
}
|
||||
|
@ -83,6 +99,9 @@ func (m *Modal) SetButtonTextColor(color tcell.Color) *Modal {
|
|||
// handler is also called when the user presses the Escape key. The index will
|
||||
// then be negative and the label text an emptry string.
|
||||
func (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.done = handler
|
||||
return m
|
||||
}
|
||||
|
@ -91,6 +110,9 @@ func (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) *
|
|||
// breaks. Note that words are wrapped, too, based on the final size of the
|
||||
// window.
|
||||
func (m *Modal) SetText(text string) *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.text = text
|
||||
return m
|
||||
}
|
||||
|
@ -98,6 +120,9 @@ func (m *Modal) SetText(text string) *Modal {
|
|||
// AddButtons adds buttons to the window. There must be at least one button and
|
||||
// a "done" handler so the window can be closed again.
|
||||
func (m *Modal) AddButtons(labels []string) *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
for index, label := range labels {
|
||||
func(i int, l string) {
|
||||
m.form.AddButton(label, func() {
|
||||
|
@ -122,12 +147,18 @@ func (m *Modal) AddButtons(labels []string) *Modal {
|
|||
|
||||
// ClearButtons removes all buttons from the window.
|
||||
func (m *Modal) ClearButtons() *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.form.ClearButtons()
|
||||
return m
|
||||
}
|
||||
|
||||
// SetFocus shifts the focus to the button with the given index.
|
||||
func (m *Modal) SetFocus(index int) *Modal {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.form.SetFocus(index)
|
||||
return m
|
||||
}
|
||||
|
@ -139,11 +170,17 @@ func (m *Modal) Focus(delegate func(p Primitive)) {
|
|||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (m *Modal) HasFocus() bool {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return m.form.HasFocus()
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (m *Modal) Draw(screen tcell.Screen) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// Calculate the width of this modal.
|
||||
buttonsWidth := 0
|
||||
for _, button := range m.form.buttons {
|
||||
|
@ -178,5 +215,8 @@ func (m *Modal) Draw(screen tcell.Screen) {
|
|||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (m *Modal) GetChildren() []Primitive {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return []Primitive{m.frame}
|
||||
}
|
||||
|
|
104
pages.go
104
pages.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -30,6 +32,8 @@ type Pages struct {
|
|||
// An optional handler which is called whenever the visibility or the order of
|
||||
// pages changes.
|
||||
changed func()
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewPages returns a new Pages object.
|
||||
|
@ -44,12 +48,18 @@ func NewPages() *Pages {
|
|||
// 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()) *Pages {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.changed = handler
|
||||
return p
|
||||
}
|
||||
|
||||
// GetPageCount returns the number of pages currently stored in this object.
|
||||
func (p *Pages) GetPageCount() int {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
return len(p.pages)
|
||||
}
|
||||
|
||||
|
@ -64,6 +74,10 @@ func (p *Pages) GetPageCount() int {
|
|||
// the pages are drawn.
|
||||
func (p *Pages) AddPage(name string, item Primitive, resize, visible bool) *Pages {
|
||||
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:]...)
|
||||
|
@ -72,10 +86,14 @@ func (p *Pages) AddPage(name string, item Primitive, resize, visible bool) *Page
|
|||
}
|
||||
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()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
@ -91,14 +109,20 @@ func (p *Pages) AddAndSwitchToPage(name string, item Primitive, resize bool) *Pa
|
|||
// RemovePage removes the page with the given name. If that page was the only
|
||||
// visible page, visibility is assigned to the last page.
|
||||
func (p *Pages) RemovePage(name string) *Pages {
|
||||
var isVisible bool
|
||||
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
|
||||
}
|
||||
|
@ -115,13 +139,18 @@ func (p *Pages) RemovePage(name string) *Pages {
|
|||
}
|
||||
}
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// HasPage returns true if a page with the given name exists in this object.
|
||||
func (p *Pages) HasPage(name string) bool {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if page.Name == name {
|
||||
return true
|
||||
|
@ -133,34 +162,52 @@ func (p *Pages) HasPage(name string) bool {
|
|||
// ShowPage sets a page's visibility to "true" (in addition to any other pages
|
||||
// which are already visible).
|
||||
func (p *Pages) ShowPage(name string) *Pages {
|
||||
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 p.HasFocus() {
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// HidePage sets a page's visibility to "false".
|
||||
func (p *Pages) HidePage(name string) *Pages {
|
||||
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 p.HasFocus() {
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
@ -168,6 +215,11 @@ func (p *Pages) HidePage(name string) *Pages {
|
|||
// SwitchToPage sets a page's visibility to "true" and all other pages'
|
||||
// visibility to "false".
|
||||
func (p *Pages) SwitchToPage(name string) *Pages {
|
||||
hasFocus := p.HasFocus()
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if page.Name == name {
|
||||
page.Visible = true
|
||||
|
@ -176,10 +228,14 @@ func (p *Pages) SwitchToPage(name string) *Pages {
|
|||
}
|
||||
}
|
||||
if p.changed != nil {
|
||||
p.Unlock()
|
||||
p.changed()
|
||||
p.Lock()
|
||||
}
|
||||
if p.HasFocus() {
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
@ -188,19 +244,28 @@ func (p *Pages) SwitchToPage(name string) *Pages {
|
|||
// name comes last, causing it to be drawn last with the next update (if
|
||||
// visible).
|
||||
func (p *Pages) SendToFront(name string) *Pages {
|
||||
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.Lock()
|
||||
p.changed()
|
||||
p.Unlock()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.HasFocus() {
|
||||
if hasFocus {
|
||||
p.Lock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Unlock()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
@ -209,19 +274,28 @@ func (p *Pages) SendToFront(name string) *Pages {
|
|||
// name comes first, causing it to be drawn first with the next update (if
|
||||
// visible).
|
||||
func (p *Pages) SendToBack(name string) *Pages {
|
||||
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 p.HasFocus() {
|
||||
if hasFocus {
|
||||
p.Unlock()
|
||||
p.Focus(p.setFocus)
|
||||
p.Lock()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
@ -229,6 +303,9 @@ func (p *Pages) SendToBack(name string) *Pages {
|
|||
// GetFrontPage returns the front-most visible page. If there are no visible
|
||||
// pages, ("", nil) is returned.
|
||||
func (p *Pages) GetFrontPage() (name string, item Primitive) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for index := len(p.pages) - 1; index >= 0; index-- {
|
||||
if p.pages[index].Visible {
|
||||
return p.pages[index].Name, p.pages[index].Item
|
||||
|
@ -239,6 +316,9 @@ func (p *Pages) GetFrontPage() (name string, item Primitive) {
|
|||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (p *Pages) HasFocus() bool {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
for _, page := range p.pages {
|
||||
if page.Item.GetFocusable().HasFocus() {
|
||||
return true
|
||||
|
@ -249,6 +329,9 @@ func (p *Pages) HasFocus() bool {
|
|||
|
||||
// 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.
|
||||
}
|
||||
|
@ -260,13 +343,19 @@ func (p *Pages) Focus(delegate func(p Primitive)) {
|
|||
}
|
||||
}
|
||||
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
|
||||
|
@ -281,6 +370,9 @@ func (p *Pages) Draw(screen tcell.Screen) {
|
|||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (p *Pages) GetChildren() []Primitive {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
var children []Primitive
|
||||
for _, page := range p.pages {
|
||||
// Considering invisible pages as not children.
|
||||
|
|
|
@ -29,7 +29,8 @@ type ProgressBar struct {
|
|||
|
||||
max int
|
||||
progress int
|
||||
*sync.Mutex
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewProgressBar returns a new progress bar.
|
||||
|
@ -41,7 +42,6 @@ func NewProgressBar() *ProgressBar {
|
|||
FilledRune: tcell.RuneBlock,
|
||||
FilledColor: Styles.PrimaryTextColor,
|
||||
max: 100,
|
||||
Mutex: new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,11 +95,11 @@ func (p *ProgressBar) Complete() bool {
|
|||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (p *ProgressBar) Draw(screen tcell.Screen) {
|
||||
p.Box.Draw(screen)
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.Box.Draw(screen)
|
||||
|
||||
x, y, width, height := p.GetInnerRect()
|
||||
|
||||
barSize := height
|
||||
|
|
142
table.go
142
table.go
|
@ -2,6 +2,7 @@ package cview
|
|||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
colorful "github.com/lucasb-eyer/go-colorful"
|
||||
|
@ -44,6 +45,8 @@ type TableCell struct {
|
|||
|
||||
// The position and width of the cell the last time table was drawn.
|
||||
x, y, width int
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewTableCell returns a new table cell with sensible defaults. That is, left
|
||||
|
@ -60,6 +63,9 @@ func NewTableCell(text string) *TableCell {
|
|||
|
||||
// SetText sets the cell's text.
|
||||
func (c *TableCell) SetText(text string) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.Text = text
|
||||
return c
|
||||
}
|
||||
|
@ -67,6 +73,9 @@ func (c *TableCell) SetText(text string) *TableCell {
|
|||
// SetAlign sets the cell's text alignment, one of AlignLeft, AlignCenter, or
|
||||
// AlignRight.
|
||||
func (c *TableCell) SetAlign(align int) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.Align = align
|
||||
return c
|
||||
}
|
||||
|
@ -75,6 +84,9 @@ func (c *TableCell) SetAlign(align int) *TableCell {
|
|||
// give a column a maximum width. Any cell text whose screen width exceeds this
|
||||
// width is cut off. Set to 0 if there is no maximum width.
|
||||
func (c *TableCell) SetMaxWidth(maxWidth int) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.MaxWidth = maxWidth
|
||||
return c
|
||||
}
|
||||
|
@ -93,6 +105,9 @@ func (c *TableCell) SetMaxWidth(maxWidth int) *TableCell {
|
|||
//
|
||||
// This function panics if a negative value is provided.
|
||||
func (c *TableCell) SetExpansion(expansion int) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if expansion < 0 {
|
||||
panic("Table cell expansion values may not be negative")
|
||||
}
|
||||
|
@ -102,6 +117,9 @@ func (c *TableCell) SetExpansion(expansion int) *TableCell {
|
|||
|
||||
// SetTextColor sets the cell's text color.
|
||||
func (c *TableCell) SetTextColor(color tcell.Color) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.Color = color
|
||||
return c
|
||||
}
|
||||
|
@ -109,6 +127,9 @@ func (c *TableCell) SetTextColor(color tcell.Color) *TableCell {
|
|||
// SetBackgroundColor sets the cell's background color. Set to
|
||||
// tcell.ColorDefault to use the table's background color.
|
||||
func (c *TableCell) SetBackgroundColor(color tcell.Color) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.BackgroundColor = color
|
||||
return c
|
||||
}
|
||||
|
@ -118,6 +139,9 @@ func (c *TableCell) SetBackgroundColor(color tcell.Color) *TableCell {
|
|||
//
|
||||
// cell.SetAttributes(tcell.AttrUnderline | tcell.AttrBold)
|
||||
func (c *TableCell) SetAttributes(attr tcell.AttrMask) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.Attributes = attr
|
||||
return c
|
||||
}
|
||||
|
@ -125,12 +149,18 @@ func (c *TableCell) SetAttributes(attr tcell.AttrMask) *TableCell {
|
|||
// SetStyle sets the cell's style (foreground color, background color, and
|
||||
// attributes) all at once.
|
||||
func (c *TableCell) SetStyle(style tcell.Style) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.Color, c.BackgroundColor, c.Attributes = style.Decompose()
|
||||
return c
|
||||
}
|
||||
|
||||
// SetSelectable sets whether or not this cell can be selected by the user.
|
||||
func (c *TableCell) SetSelectable(selectable bool) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.NotSelectable = !selectable
|
||||
return c
|
||||
}
|
||||
|
@ -139,12 +169,18 @@ func (c *TableCell) SetSelectable(selectable bool) *TableCell {
|
|||
// will allow you to establish a mapping between the cell and your
|
||||
// actual data.
|
||||
func (c *TableCell) SetReference(reference interface{}) *TableCell {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.Reference = reference
|
||||
return c
|
||||
}
|
||||
|
||||
// GetReference returns this cell's reference object.
|
||||
func (c *TableCell) GetReference() interface{} {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.Reference
|
||||
}
|
||||
|
||||
|
@ -157,6 +193,9 @@ func (c *TableCell) GetReference() interface{} {
|
|||
// SetSelectedFunc()) or a "selectionChanged" event (see
|
||||
// SetSelectionChangedFunc()).
|
||||
func (c *TableCell) GetLastPosition() (x, y, width int) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.x, c.y, c.width
|
||||
}
|
||||
|
||||
|
@ -274,6 +313,8 @@ type Table struct {
|
|||
// An optional function which gets called when the user presses Escape, Tab,
|
||||
// or Backtab. Also when the user presses Enter if nothing is selectable.
|
||||
done func(key tcell.Key)
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewTable returns a new table.
|
||||
|
@ -290,6 +331,9 @@ func NewTable() *Table {
|
|||
|
||||
// Clear removes all table data.
|
||||
func (t *Table) Clear() *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.cells = nil
|
||||
t.lastColumn = -1
|
||||
return t
|
||||
|
@ -298,24 +342,36 @@ func (t *Table) Clear() *Table {
|
|||
// SetBorders sets whether or not each cell in the table is surrounded by a
|
||||
// border.
|
||||
func (t *Table) SetBorders(show bool) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.borders = show
|
||||
return t
|
||||
}
|
||||
|
||||
// SetBordersColor sets the color of the cell borders.
|
||||
func (t *Table) SetBordersColor(color tcell.Color) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.bordersColor = color
|
||||
return t
|
||||
}
|
||||
|
||||
// SetScrollBarVisibility specifies the display of the scroll bar.
|
||||
func (t *Table) SetScrollBarVisibility(visibility ScrollBarVisibility) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.scrollBarVisibility = visibility
|
||||
return t
|
||||
}
|
||||
|
||||
// SetScrollBarColor sets the color of the scroll bar.
|
||||
func (t *Table) SetScrollBarColor(color tcell.Color) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.scrollBarColor = color
|
||||
return t
|
||||
}
|
||||
|
@ -328,6 +384,9 @@ func (t *Table) SetScrollBarColor(color tcell.Color) *Table {
|
|||
//
|
||||
// table.SetSelectedStyle(tcell.ColorDefault, tcell.ColorDefault, 0)
|
||||
func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, attributes tcell.AttrMask) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.selectedStyle = tcell.StyleDefault.Foreground(foregroundColor).Background(backgroundColor) | tcell.Style(attributes)
|
||||
return t
|
||||
}
|
||||
|
@ -340,6 +399,9 @@ func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, a
|
|||
//
|
||||
// Separators have the same color as borders.
|
||||
func (t *Table) SetSeparator(separator rune) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.separator = separator
|
||||
return t
|
||||
}
|
||||
|
@ -348,6 +410,9 @@ func (t *Table) SetSeparator(separator rune) *Table {
|
|||
// even when the rest of the cells are scrolled out of view. Rows are always the
|
||||
// top-most ones. Columns are always the left-most ones.
|
||||
func (t *Table) SetFixed(rows, columns int) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.fixedRows, t.fixedColumns = rows, columns
|
||||
return t
|
||||
}
|
||||
|
@ -360,6 +425,9 @@ func (t *Table) SetFixed(rows, columns int) *Table {
|
|||
// - rows = false, columns = true: Columns can be selected.
|
||||
// - rows = true, columns = true: Individual cells can be selected.
|
||||
func (t *Table) SetSelectable(rows, columns bool) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.rowsSelectable, t.columnsSelectable = rows, columns
|
||||
return t
|
||||
}
|
||||
|
@ -367,6 +435,9 @@ func (t *Table) SetSelectable(rows, columns bool) *Table {
|
|||
// GetSelectable returns what can be selected in a table. Refer to
|
||||
// SetSelectable() for details.
|
||||
func (t *Table) GetSelectable() (rows, columns bool) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return t.rowsSelectable, t.columnsSelectable
|
||||
}
|
||||
|
||||
|
@ -374,6 +445,9 @@ func (t *Table) GetSelectable() (rows, columns bool) {
|
|||
// If entire rows are selected, the column index is undefined.
|
||||
// Likewise for entire columns.
|
||||
func (t *Table) GetSelection() (row, column int) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return t.selectedRow, t.selectedColumn
|
||||
}
|
||||
|
||||
|
@ -383,9 +457,14 @@ func (t *Table) GetSelection() (row, column int) {
|
|||
// is available (even if the selection ends up being the same as before, even if
|
||||
// cells are not selectable).
|
||||
func (t *Table) Select(row, column int) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.selectedRow, t.selectedColumn = row, column
|
||||
if t.selectionChanged != nil {
|
||||
t.Unlock()
|
||||
t.selectionChanged(row, column)
|
||||
t.Lock()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
@ -396,6 +475,9 @@ func (t *Table) Select(row, column int) *Table {
|
|||
//
|
||||
// Fixed rows and columns are never skipped.
|
||||
func (t *Table) SetOffset(row, column int) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.rowOffset, t.columnOffset = row, column
|
||||
t.trackEnd = false
|
||||
return t
|
||||
|
@ -404,6 +486,9 @@ func (t *Table) SetOffset(row, column int) *Table {
|
|||
// GetOffset returns the current row and column offset. This indicates how many
|
||||
// rows and columns the table is scrolled down and to the right.
|
||||
func (t *Table) GetOffset() (row, column int) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return t.rowOffset, t.columnOffset
|
||||
}
|
||||
|
||||
|
@ -414,6 +499,9 @@ func (t *Table) GetOffset() (row, column int) {
|
|||
// Set this flag to true to avoid shifting column widths when the table is
|
||||
// scrolled. (May be slower for large tables.)
|
||||
func (t *Table) SetEvaluateAllRows(all bool) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.evaluateAllRows = all
|
||||
return t
|
||||
}
|
||||
|
@ -423,6 +511,9 @@ func (t *Table) SetEvaluateAllRows(all bool) *Table {
|
|||
// the selection and its cell contents. If entire rows are selected, the column
|
||||
// index is undefined. Likewise for entire columns.
|
||||
func (t *Table) SetSelectedFunc(handler func(row, column int)) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.selected = handler
|
||||
return t
|
||||
}
|
||||
|
@ -432,6 +523,9 @@ func (t *Table) SetSelectedFunc(handler func(row, column int)) *Table {
|
|||
// If entire rows are selected, the column index is undefined. Likewise for
|
||||
// entire columns.
|
||||
func (t *Table) SetSelectionChangedFunc(handler func(row, column int)) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.selectionChanged = handler
|
||||
return t
|
||||
}
|
||||
|
@ -441,6 +535,9 @@ func (t *Table) SetSelectionChangedFunc(handler func(row, column int)) *Table {
|
|||
// user presses the Enter key (because pressing Enter on a selection triggers
|
||||
// the "selected" handler set via SetSelectedFunc()).
|
||||
func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.done = handler
|
||||
return t
|
||||
}
|
||||
|
@ -455,6 +552,9 @@ func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table {
|
|||
//
|
||||
// To avoid unnecessary garbage collection, fill columns from left to right.
|
||||
func (t *Table) SetCell(row, column int, cell *TableCell) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if row >= len(t.cells) {
|
||||
t.cells = append(t.cells, make([][]*TableCell, row-len(t.cells)+1)...)
|
||||
}
|
||||
|
@ -474,8 +574,7 @@ func (t *Table) SetCell(row, column int, cell *TableCell) *Table {
|
|||
|
||||
// SetCellSimple calls SetCell() with the given text, left-aligned, in white.
|
||||
func (t *Table) SetCellSimple(row, column int, text string) *Table {
|
||||
t.SetCell(row, column, NewTableCell(text))
|
||||
return t
|
||||
return t.SetCell(row, column, NewTableCell(text))
|
||||
}
|
||||
|
||||
// GetCell returns the contents of the cell at the specified position. A valid
|
||||
|
@ -484,6 +583,9 @@ func (t *Table) SetCellSimple(row, column int, text string) *Table {
|
|||
// be inserted. Therefore, repeated calls to this function may return different
|
||||
// pointers for uninitialized cells.
|
||||
func (t *Table) GetCell(row, column int) *TableCell {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if row >= len(t.cells) || column >= len(t.cells[row]) {
|
||||
return &TableCell{}
|
||||
}
|
||||
|
@ -493,6 +595,9 @@ func (t *Table) GetCell(row, column int) *TableCell {
|
|||
// RemoveRow removes the row at the given position from the table. If there is
|
||||
// no such row, this has no effect.
|
||||
func (t *Table) RemoveRow(row int) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if row < 0 || row >= len(t.cells) {
|
||||
return t
|
||||
}
|
||||
|
@ -505,6 +610,9 @@ func (t *Table) RemoveRow(row int) *Table {
|
|||
// RemoveColumn removes the column at the given position from the table. If
|
||||
// there is no such column, this has no effect.
|
||||
func (t *Table) RemoveColumn(column int) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for row := range t.cells {
|
||||
if column < 0 || column >= len(t.cells[row]) {
|
||||
continue
|
||||
|
@ -519,6 +627,9 @@ func (t *Table) RemoveColumn(column int) *Table {
|
|||
// given row and below will be shifted to the bottom by one row. If "row" is
|
||||
// equal or larger than the current number of rows, this function has no effect.
|
||||
func (t *Table) InsertRow(row int) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if row >= len(t.cells) {
|
||||
return t
|
||||
}
|
||||
|
@ -533,6 +644,9 @@ func (t *Table) InsertRow(row int) *Table {
|
|||
// column. Rows that have fewer initialized cells than "column" will remain
|
||||
// unchanged.
|
||||
func (t *Table) InsertColumn(column int) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for row := range t.cells {
|
||||
if column >= len(t.cells[row]) {
|
||||
continue
|
||||
|
@ -546,11 +660,17 @@ func (t *Table) InsertColumn(column int) *Table {
|
|||
|
||||
// GetRowCount returns the number of rows in the table.
|
||||
func (t *Table) GetRowCount() int {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return len(t.cells)
|
||||
}
|
||||
|
||||
// GetColumnCount returns the (maximum) number of columns in the table.
|
||||
func (t *Table) GetColumnCount() int {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if len(t.cells) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
@ -561,6 +681,9 @@ func (t *Table) GetColumnCount() int {
|
|||
// corner of the table is shown. Note that this position may be corrected if
|
||||
// there is a selection.
|
||||
func (t *Table) ScrollToBeginning() *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.trackEnd = false
|
||||
t.columnOffset = 0
|
||||
t.rowOffset = 0
|
||||
|
@ -572,6 +695,9 @@ func (t *Table) ScrollToBeginning() *Table {
|
|||
// automatically scroll with the new data. Note that this position may be
|
||||
// corrected if there is a selection.
|
||||
func (t *Table) ScrollToEnd() *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.trackEnd = true
|
||||
t.columnOffset = 0
|
||||
t.rowOffset = len(t.cells)
|
||||
|
@ -582,6 +708,9 @@ func (t *Table) ScrollToEnd() *Table {
|
|||
func (t *Table) Draw(screen tcell.Screen) {
|
||||
t.Box.Draw(screen)
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// What's our available screen space?
|
||||
x, y, width, height := t.GetInnerRect()
|
||||
if t.borders {
|
||||
|
@ -1026,6 +1155,9 @@ ColumnLoop:
|
|||
// InputHandler returns the handler for this primitive.
|
||||
func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
key := event.Key()
|
||||
|
||||
if (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) ||
|
||||
|
@ -1033,7 +1165,9 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi
|
|||
key == tcell.KeyTab ||
|
||||
key == tcell.KeyBacktab {
|
||||
if t.done != nil {
|
||||
t.Unlock()
|
||||
t.done(key)
|
||||
t.Lock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -1228,7 +1362,9 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi
|
|||
pageUp()
|
||||
case tcell.KeyEnter:
|
||||
if (t.rowsSelectable || t.columnsSelectable) && t.selected != nil {
|
||||
t.Unlock()
|
||||
t.selected(t.selectedRow, t.selectedColumn)
|
||||
t.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1236,7 +1372,9 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi
|
|||
if t.selectionChanged != nil &&
|
||||
(t.rowsSelectable && previouslySelectedRow != t.selectedRow ||
|
||||
t.columnsSelectable && previouslySelectedColumn != t.selectedColumn) {
|
||||
t.Unlock()
|
||||
t.selectionChanged(t.selectedRow, t.selectedColumn)
|
||||
t.Lock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
84
textview.go
84
textview.go
|
@ -9,8 +9,8 @@ import (
|
|||
"unicode/utf8"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
colorful "github.com/lucasb-eyer/go-colorful"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
|
@ -90,7 +90,7 @@ type textViewIndex struct {
|
|||
//
|
||||
// See https://gitlab.com/tslocum/cview/wiki/TextView for an example.
|
||||
type TextView struct {
|
||||
sync.Mutex
|
||||
sync.RWMutex
|
||||
*Box
|
||||
|
||||
// The text buffer.
|
||||
|
@ -191,6 +191,9 @@ func NewTextView() *TextView {
|
|||
// scrollable. If true, text is kept in a buffer and can be navigated. If false,
|
||||
// the last line will always be visible.
|
||||
func (t *TextView) SetScrollable(scrollable bool) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.scrollable = scrollable
|
||||
if !scrollable {
|
||||
t.trackEnd = true
|
||||
|
@ -202,6 +205,9 @@ func (t *TextView) SetScrollable(scrollable bool) *TextView {
|
|||
// available width being wrapped onto the next line. If false, any characters
|
||||
// beyond the available width are not displayed.
|
||||
func (t *TextView) SetWrap(wrap bool) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if t.wrap != wrap {
|
||||
t.index = nil
|
||||
}
|
||||
|
@ -215,6 +221,9 @@ func (t *TextView) SetWrap(wrap bool) *TextView {
|
|||
//
|
||||
// This flag is ignored if the "wrap" flag is false.
|
||||
func (t *TextView) SetWordWrap(wrapOnWords bool) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if t.wordWrap != wrapOnWords {
|
||||
t.index = nil
|
||||
}
|
||||
|
@ -225,6 +234,9 @@ func (t *TextView) SetWordWrap(wrapOnWords bool) *TextView {
|
|||
// SetTextAlign sets the text alignment within the text view. This must be
|
||||
// either AlignLeft, AlignCenter, or AlignRight.
|
||||
func (t *TextView) SetTextAlign(align int) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if t.align != align {
|
||||
t.index = nil
|
||||
}
|
||||
|
@ -236,6 +248,9 @@ func (t *TextView) SetTextAlign(align int) *TextView {
|
|||
// dynamically by sending color strings in square brackets to the text view if
|
||||
// dynamic colors are enabled).
|
||||
func (t *TextView) SetTextColor(color tcell.Color) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.textColor = color
|
||||
return t
|
||||
}
|
||||
|
@ -251,6 +266,9 @@ func (t *TextView) SetText(text string) *TextView {
|
|||
// GetText returns the current text of this text view. If "stripTags" is set
|
||||
// to true, any region/color tags are stripped from the text.
|
||||
func (t *TextView) GetText(stripTags bool) string {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
// Get the buffer.
|
||||
buffer := t.buffer
|
||||
if !stripTags {
|
||||
|
@ -279,6 +297,9 @@ func (t *TextView) GetText(stripTags bool) string {
|
|||
// SetDynamicColors sets the flag that allows the text color to be changed
|
||||
// dynamically. See class description for details.
|
||||
func (t *TextView) SetDynamicColors(dynamic bool) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if t.dynamicColors != dynamic {
|
||||
t.index = nil
|
||||
}
|
||||
|
@ -289,6 +310,9 @@ func (t *TextView) SetDynamicColors(dynamic bool) *TextView {
|
|||
// SetRegions sets the flag that allows to define regions in the text. See class
|
||||
// description for details.
|
||||
func (t *TextView) SetRegions(regions bool) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if t.regions != regions {
|
||||
t.index = nil
|
||||
}
|
||||
|
@ -313,6 +337,9 @@ func (t *TextView) SetRegions(regions bool) *TextView {
|
|||
//
|
||||
// See package description for details on dealing with concurrency.
|
||||
func (t *TextView) SetChangedFunc(handler func()) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.changed = handler
|
||||
return t
|
||||
}
|
||||
|
@ -321,12 +348,18 @@ func (t *TextView) SetChangedFunc(handler func()) *TextView {
|
|||
// following keys: Escape, Enter, Tab, Backtab. The key is passed to the
|
||||
// handler.
|
||||
func (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.done = handler
|
||||
return t
|
||||
}
|
||||
|
||||
// ScrollTo scrolls to the specified row and column (both starting with 0).
|
||||
func (t *TextView) ScrollTo(row, column int) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if !t.scrollable {
|
||||
return t
|
||||
}
|
||||
|
@ -339,6 +372,9 @@ func (t *TextView) ScrollTo(row, column int) *TextView {
|
|||
// ScrollToBeginning scrolls to the top left corner of the text if the text view
|
||||
// is scrollable.
|
||||
func (t *TextView) ScrollToBeginning() *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if !t.scrollable {
|
||||
return t
|
||||
}
|
||||
|
@ -352,6 +388,9 @@ func (t *TextView) ScrollToBeginning() *TextView {
|
|||
// is scrollable. Adding new rows to the end of the text view will cause it to
|
||||
// scroll with the new data.
|
||||
func (t *TextView) ScrollToEnd() *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if !t.scrollable {
|
||||
return t
|
||||
}
|
||||
|
@ -363,11 +402,17 @@ func (t *TextView) ScrollToEnd() *TextView {
|
|||
// GetScrollOffset returns the number of rows and columns that are skipped at
|
||||
// the top left corner when the text view has been scrolled.
|
||||
func (t *TextView) GetScrollOffset() (row, column int) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
return t.lineOffset, t.columnOffset
|
||||
}
|
||||
|
||||
// Clear removes all text from the buffer.
|
||||
func (t *TextView) Clear() *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.buffer = nil
|
||||
t.recentBytes = nil
|
||||
t.index = nil
|
||||
|
@ -383,6 +428,9 @@ func (t *TextView) Clear() *TextView {
|
|||
// Calling this function will remove any previous highlights. To remove all
|
||||
// highlights, call this function without any arguments.
|
||||
func (t *TextView) Highlight(regionIDs ...string) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.highlights = make(map[string]struct{})
|
||||
for _, id := range regionIDs {
|
||||
if id == "" {
|
||||
|
@ -396,6 +444,9 @@ func (t *TextView) Highlight(regionIDs ...string) *TextView {
|
|||
|
||||
// GetHighlights returns the IDs of all currently highlighted regions.
|
||||
func (t *TextView) GetHighlights() (regionIDs []string) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
for id := range t.highlights {
|
||||
regionIDs = append(regionIDs, id)
|
||||
}
|
||||
|
@ -411,6 +462,9 @@ func (t *TextView) GetHighlights() (regionIDs []string) {
|
|||
// Nothing happens if there are no highlighted regions or if the text view is
|
||||
// not scrollable.
|
||||
func (t *TextView) ScrollToHighlight() *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if len(t.highlights) == 0 || !t.scrollable || !t.regions {
|
||||
return t
|
||||
}
|
||||
|
@ -427,6 +481,9 @@ func (t *TextView) ScrollToHighlight() *TextView {
|
|||
// If the region does not exist or if regions are turned off, an empty string
|
||||
// is returned.
|
||||
func (t *TextView) GetRegionText(regionID string) string {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
if !t.regions || regionID == "" {
|
||||
return ""
|
||||
}
|
||||
|
@ -494,18 +551,20 @@ func (t *TextView) GetRegionText(regionID string) string {
|
|||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (t *TextView) Focus(delegate func(p Primitive)) {
|
||||
// Implemented here with locking because this is used by layout primitives.
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// Implemented here with locking because this is used by layout primitives.
|
||||
t.hasFocus = true
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
func (t *TextView) HasFocus() bool {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
// Implemented here with locking because this may be used in the "changed"
|
||||
// callback.
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
return t.hasFocus
|
||||
}
|
||||
|
||||
|
@ -513,15 +572,12 @@ func (t *TextView) HasFocus() bool {
|
|||
// replaced with TabSize space characters. A "\n" or "\r\n" will be interpreted
|
||||
// as a new line.
|
||||
func (t *TextView) Write(p []byte) (n int, err error) {
|
||||
// Notify at the end.
|
||||
t.Lock()
|
||||
changed := t.changed
|
||||
t.Unlock()
|
||||
if changed != nil {
|
||||
defer changed() // Deadlocks may occur if we lock here.
|
||||
// Notify at the end.
|
||||
defer changed()
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// Copy data over.
|
||||
|
@ -749,9 +805,10 @@ func (t *TextView) reindexBuffer(width int) {
|
|||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (t *TextView) Draw(screen tcell.Screen) {
|
||||
t.Box.Draw(screen)
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.Box.Draw(screen)
|
||||
|
||||
// Get the available size.
|
||||
x, y, width, height := t.GetInnerRect()
|
||||
|
@ -971,6 +1028,9 @@ func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
|||
return
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if !t.scrollable {
|
||||
return
|
||||
}
|
||||
|
|
161
treeview.go
161
treeview.go
|
@ -1,6 +1,8 @@
|
|||
package cview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
|
@ -49,6 +51,8 @@ type TreeNode struct {
|
|||
level int // The hierarchy level (0 for the root, 1 for its children, and so on).
|
||||
graphicsX int // The x-coordinate of the left-most graphics rune.
|
||||
textX int // The x-coordinate of the first rune of the text.
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewTreeNode returns a new tree node.
|
||||
|
@ -68,6 +72,13 @@ func NewTreeNode(text string) *TreeNode {
|
|||
// The callback returns whether traversal should continue with the traversed
|
||||
// node's child nodes (true) or not recurse any deeper (false).
|
||||
func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.walk(callback)
|
||||
}
|
||||
|
||||
func (n *TreeNode) walk(callback func(node, parent *TreeNode) bool) *TreeNode {
|
||||
n.parent = nil
|
||||
nodes := []*TreeNode{n}
|
||||
for len(nodes) > 0 {
|
||||
|
@ -93,39 +104,60 @@ func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
|
|||
// will allow you to establish a mapping between the TreeView hierarchy and your
|
||||
// internal tree structure.
|
||||
func (n *TreeNode) SetReference(reference interface{}) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.reference = reference
|
||||
return n
|
||||
}
|
||||
|
||||
// GetReference returns this node's reference object.
|
||||
func (n *TreeNode) GetReference() interface{} {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.reference
|
||||
}
|
||||
|
||||
// SetChildren sets this node's child nodes.
|
||||
func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.children = childNodes
|
||||
return n
|
||||
}
|
||||
|
||||
// GetText returns this node's text.
|
||||
func (n *TreeNode) GetText() string {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.text
|
||||
}
|
||||
|
||||
// GetChildren returns this node's children.
|
||||
func (n *TreeNode) GetChildren() []*TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.children
|
||||
}
|
||||
|
||||
// ClearChildren removes all child nodes from this node.
|
||||
func (n *TreeNode) ClearChildren() *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.children = nil
|
||||
return n
|
||||
}
|
||||
|
||||
// AddChild adds a new child node to this node.
|
||||
func (n *TreeNode) AddChild(node *TreeNode) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.children = append(n.children, node)
|
||||
return n
|
||||
}
|
||||
|
@ -133,6 +165,9 @@ func (n *TreeNode) AddChild(node *TreeNode) *TreeNode {
|
|||
// SetSelectable sets a flag indicating whether this node can be focused and
|
||||
// selected by the user.
|
||||
func (n *TreeNode) SetSelectable(selectable bool) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.selectable = selectable
|
||||
return n
|
||||
}
|
||||
|
@ -142,6 +177,9 @@ func (n *TreeNode) SetSelectable(selectable bool) *TreeNode {
|
|||
//
|
||||
// This function is also called when the user selects this node.
|
||||
func (n *TreeNode) SetFocusedFunc(handler func()) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.focused = handler
|
||||
return n
|
||||
}
|
||||
|
@ -149,24 +187,36 @@ func (n *TreeNode) SetFocusedFunc(handler func()) *TreeNode {
|
|||
// SetSelectedFunc sets a function which is called when the user selects this
|
||||
// node by hitting Enter when it is focused.
|
||||
func (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.selected = handler
|
||||
return n
|
||||
}
|
||||
|
||||
// SetExpanded sets whether or not this node's child nodes should be displayed.
|
||||
func (n *TreeNode) SetExpanded(expanded bool) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.expanded = expanded
|
||||
return n
|
||||
}
|
||||
|
||||
// Expand makes the child nodes of this node appear.
|
||||
func (n *TreeNode) Expand() *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.expanded = true
|
||||
return n
|
||||
}
|
||||
|
||||
// Collapse makes the child nodes of this node disappear.
|
||||
func (n *TreeNode) Collapse() *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.expanded = false
|
||||
return n
|
||||
}
|
||||
|
@ -191,22 +241,34 @@ func (n *TreeNode) CollapseAll() *TreeNode {
|
|||
|
||||
// IsExpanded returns whether the child nodes of this node are visible.
|
||||
func (n *TreeNode) IsExpanded() bool {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.expanded
|
||||
}
|
||||
|
||||
// SetText sets the node's text which is displayed.
|
||||
func (n *TreeNode) SetText(text string) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.text = text
|
||||
return n
|
||||
}
|
||||
|
||||
// GetColor returns the node's color.
|
||||
func (n *TreeNode) GetColor() tcell.Color {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.color
|
||||
}
|
||||
|
||||
// SetColor sets the node's text color.
|
||||
func (n *TreeNode) SetColor(color tcell.Color) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.color = color
|
||||
return n
|
||||
}
|
||||
|
@ -215,6 +277,9 @@ func (n *TreeNode) SetColor(color tcell.Color) *TreeNode {
|
|||
// keeps the text as far left as possible with a minimum of line graphics. Any
|
||||
// value greater than that moves the text to the right.
|
||||
func (n *TreeNode) SetIndent(indent int) *TreeNode {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.indent = indent
|
||||
return n
|
||||
}
|
||||
|
@ -299,6 +364,8 @@ type TreeView struct {
|
|||
|
||||
// The visible nodes, top-down, as set by process().
|
||||
nodes []*TreeNode
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewTreeView returns a new tree view.
|
||||
|
@ -314,6 +381,9 @@ func NewTreeView() *TreeView {
|
|||
|
||||
// SetRoot sets the root node of the tree.
|
||||
func (t *TreeView) SetRoot(root *TreeNode) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.root = root
|
||||
return t
|
||||
}
|
||||
|
@ -321,6 +391,9 @@ func (t *TreeView) SetRoot(root *TreeNode) *TreeView {
|
|||
// GetRoot returns the root node of the tree. If no such node was previously
|
||||
// set, nil is returned.
|
||||
func (t *TreeView) GetRoot() *TreeNode {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return t.root
|
||||
}
|
||||
|
||||
|
@ -330,9 +403,14 @@ func (t *TreeView) GetRoot() *TreeNode {
|
|||
//
|
||||
// This function does NOT trigger the "changed" callback.
|
||||
func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.currentNode = node
|
||||
if t.currentNode.focused != nil {
|
||||
t.Unlock()
|
||||
t.currentNode.focused()
|
||||
t.Lock()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
@ -340,6 +418,9 @@ func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {
|
|||
// GetCurrentNode returns the currently selected node or nil of no node is
|
||||
// currently selected.
|
||||
func (t *TreeView) GetCurrentNode() *TreeNode {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return t.currentNode
|
||||
}
|
||||
|
||||
|
@ -347,6 +428,9 @@ func (t *TreeView) GetCurrentNode() *TreeNode {
|
|||
// root, 1 to the root's child nodes, and so on. Nodes above the top level are
|
||||
// not displayed.
|
||||
func (t *TreeView) SetTopLevel(topLevel int) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.topLevel = topLevel
|
||||
return t
|
||||
}
|
||||
|
@ -361,6 +445,9 @@ func (t *TreeView) SetTopLevel(topLevel int) *TreeView {
|
|||
// treeView.SetGraphics(false).
|
||||
// SetPrefixes([]string{"* ", "- ", "x "})
|
||||
func (t *TreeView) SetPrefixes(prefixes []string) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.prefixes = prefixes
|
||||
return t
|
||||
}
|
||||
|
@ -369,6 +456,9 @@ func (t *TreeView) SetPrefixes(prefixes []string) *TreeView {
|
|||
// all texts except that of top-level nodes will be placed in the same column.
|
||||
// If set to false, they will indent with the hierarchy.
|
||||
func (t *TreeView) SetAlign(align bool) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.align = align
|
||||
return t
|
||||
}
|
||||
|
@ -376,24 +466,36 @@ func (t *TreeView) SetAlign(align bool) *TreeView {
|
|||
// SetGraphics sets a flag which determines whether or not line graphics are
|
||||
// drawn to illustrate the tree's hierarchy.
|
||||
func (t *TreeView) SetGraphics(showGraphics bool) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.graphics = showGraphics
|
||||
return t
|
||||
}
|
||||
|
||||
// SetGraphicsColor sets the colors of the lines used to draw the tree structure.
|
||||
func (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.graphicsColor = color
|
||||
return t
|
||||
}
|
||||
|
||||
// SetScrollBarVisibility specifies the display of the scroll bar.
|
||||
func (t *TreeView) SetScrollBarVisibility(visibility ScrollBarVisibility) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.scrollBarVisibility = visibility
|
||||
return t
|
||||
}
|
||||
|
||||
// SetScrollBarColor sets the color of the scroll bar.
|
||||
func (t *TreeView) SetScrollBarColor(color tcell.Color) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.scrollBarColor = color
|
||||
return t
|
||||
}
|
||||
|
@ -401,6 +503,9 @@ func (t *TreeView) SetScrollBarColor(color tcell.Color) *TreeView {
|
|||
// SetChangedFunc sets the function which is called when the user navigates to
|
||||
// a new tree node.
|
||||
func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.changed = handler
|
||||
return t
|
||||
}
|
||||
|
@ -408,6 +513,9 @@ func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {
|
|||
// SetSelectedFunc sets the function which is called when the user selects a
|
||||
// node by pressing Enter on the current selection.
|
||||
func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.selected = handler
|
||||
return t
|
||||
}
|
||||
|
@ -415,6 +523,9 @@ func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView {
|
|||
// SetDoneFunc sets a handler which is called whenever the user presses the
|
||||
// Escape, Tab, or Backtab key.
|
||||
func (t *TreeView) SetDoneFunc(handler func(key tcell.Key)) *TreeView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.done = handler
|
||||
return t
|
||||
}
|
||||
|
@ -423,6 +534,9 @@ func (t *TreeView) SetDoneFunc(handler func(key tcell.Key)) *TreeView {
|
|||
// of the tree view. Note that when the user navigates the tree view, this value
|
||||
// is only updated after the tree view has been redrawn.
|
||||
func (t *TreeView) GetScrollOffset() int {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return t.offsetY
|
||||
}
|
||||
|
||||
|
@ -431,6 +545,9 @@ func (t *TreeView) GetScrollOffset() int {
|
|||
// of collapsed nodes. Note that this value is only up to date after the tree
|
||||
// view has been drawn.
|
||||
func (t *TreeView) GetRowCount() int {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return len(t.nodes)
|
||||
}
|
||||
|
||||
|
@ -447,7 +564,7 @@ func (t *TreeView) process() {
|
|||
if t.graphics {
|
||||
graphicsOffset = 1
|
||||
}
|
||||
t.root.Walk(func(node, parent *TreeNode) bool {
|
||||
t.root.walk(func(node, parent *TreeNode) bool {
|
||||
// Set node attributes.
|
||||
node.parent = parent
|
||||
if parent == nil {
|
||||
|
@ -570,10 +687,14 @@ func (t *TreeView) process() {
|
|||
if newSelectedIndex != selectedIndex {
|
||||
t.movement = treeNone
|
||||
if t.changed != nil {
|
||||
t.Unlock()
|
||||
t.changed(t.currentNode)
|
||||
t.Lock()
|
||||
}
|
||||
if t.currentNode.focused != nil {
|
||||
t.Unlock()
|
||||
t.currentNode.focused()
|
||||
t.Lock()
|
||||
}
|
||||
}
|
||||
selectedIndex = newSelectedIndex
|
||||
|
@ -605,6 +726,10 @@ func (t *TreeView) process() {
|
|||
// Draw draws this primitive onto the screen.
|
||||
func (t *TreeView) Draw(screen tcell.Screen) {
|
||||
t.Box.Draw(screen)
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
|
@ -720,25 +845,35 @@ func (t *TreeView) Draw(screen tcell.Screen) {
|
|||
func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
selectNode := func() {
|
||||
if t.currentNode != nil {
|
||||
if t.selected != nil {
|
||||
t.selected(t.currentNode)
|
||||
}
|
||||
if t.currentNode.focused != nil {
|
||||
t.currentNode.focused()
|
||||
}
|
||||
if t.currentNode.selected != nil {
|
||||
t.currentNode.selected()
|
||||
}
|
||||
t.Lock()
|
||||
currentNode := t.currentNode
|
||||
t.Unlock()
|
||||
if currentNode == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if t.selected != nil {
|
||||
t.selected(currentNode)
|
||||
}
|
||||
if currentNode.focused != nil {
|
||||
currentNode.focused()
|
||||
}
|
||||
if currentNode.selected != nil {
|
||||
currentNode.selected()
|
||||
}
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// Because the tree is flattened into a list only at drawing time, we also
|
||||
// postpone the (selection) movement to drawing time.
|
||||
switch key := event.Key(); key {
|
||||
case tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape:
|
||||
if t.done != nil {
|
||||
t.Unlock()
|
||||
t.done(key)
|
||||
t.Lock()
|
||||
}
|
||||
case tcell.KeyDown, tcell.KeyRight:
|
||||
t.movement = treeDown
|
||||
|
@ -763,10 +898,14 @@ func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
|||
case 'k':
|
||||
t.movement = treeUp
|
||||
case ' ':
|
||||
t.Unlock()
|
||||
selectNode()
|
||||
t.Lock()
|
||||
}
|
||||
case tcell.KeyEnter:
|
||||
t.Unlock()
|
||||
selectNode()
|
||||
t.Lock()
|
||||
}
|
||||
|
||||
t.process()
|
||||
|
|
Loading…
Reference in New Issue