Make cview thread-safe
This commit is contained in:
parent
d045073571
commit
e29d4b73b9
22 changed files with 1291 additions and 87 deletions
|
@ -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