mirror of
https://github.com/VladimirMarkelov/clui.git
synced 2025-04-26 13:49:01 +08:00
Merge branch 'master' of https://github.com/VladimirMarkelov/clui
This commit is contained in:
commit
ae7a061585
11
changelog
11
changelog
@ -1,4 +1,13 @@
|
||||
2018-01-23 - version 0.7.0
|
||||
2018-01-25 - version 0.7.0
|
||||
[+] Added new methods to Composer: BeginUpdate and EndUpdate to use in
|
||||
multithreading application. Call BeginUpdate before creating a new Window or
|
||||
manipulating Composer Windows if the code runs in separate thread. And
|
||||
call EndUpdate right after all changes are done. Do not lock for a long
|
||||
time because while lock is on the screen is not updated.
|
||||
Another usage: create new Window inside varios handlers (e.g, OnSelectItem
|
||||
of ListBox)
|
||||
|
||||
2018-01-23 - version 0.7.0
|
||||
[+] Added feature: Window method to set and read visible buttons in title bar
|
||||
[+] Added feature: Window manual resizing and moving can be disabled
|
||||
[*] Fix TableView scrollbars: both scrollbars did not response mouse clicks
|
||||
|
127
composer.go
127
composer.go
@ -2,6 +2,7 @@ package clui
|
||||
|
||||
import (
|
||||
term "github.com/nsf/termbox-go"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Composer is a service object that manages Views and console, processes
|
||||
@ -22,6 +23,8 @@ type Composer struct {
|
||||
lastX, lastY int
|
||||
// Type of dragging
|
||||
dragType DragType
|
||||
// For safe Window manipulations
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
var (
|
||||
@ -66,16 +69,21 @@ func termboxEventToLocal(ev term.Event) Event {
|
||||
|
||||
// Repaints everything on the screen
|
||||
func RefreshScreen() {
|
||||
comp.BeginUpdate()
|
||||
term.Clear(ColorWhite, ColorBlack)
|
||||
comp.EndUpdate()
|
||||
|
||||
for _, wnd := range comp.windows {
|
||||
v := comp.topWindow().(*Window)
|
||||
windows := comp.getWindowList()
|
||||
for _, wnd := range windows {
|
||||
v := wnd.(*Window)
|
||||
if v.Visible() {
|
||||
wnd.Draw()
|
||||
}
|
||||
}
|
||||
|
||||
comp.BeginUpdate()
|
||||
term.Flush()
|
||||
comp.EndUpdate()
|
||||
}
|
||||
|
||||
// AddWindow constucts a new Window, adds it to the composer automatically,
|
||||
@ -86,7 +94,9 @@ func RefreshScreen() {
|
||||
func AddWindow(posX, posY, width, height int, title string) *Window {
|
||||
window := CreateWindow(posX, posY, width, height, title)
|
||||
|
||||
comp.BeginUpdate()
|
||||
comp.windows = append(comp.windows, window)
|
||||
comp.EndUpdate()
|
||||
window.Draw()
|
||||
|
||||
comp.activateWindow(window)
|
||||
@ -96,13 +106,41 @@ func AddWindow(posX, posY, width, height int, title string) *Window {
|
||||
return window
|
||||
}
|
||||
|
||||
// BeginUpdate locks any screen update until EndUpdate is called.
|
||||
// Useful only in multithreading application if you create a new Window in
|
||||
// some thread that is not main one (e.g, create new Window inside
|
||||
// OnSelectItem handler of ListBox)
|
||||
// Note: Do not lock for a long time because while the lock is on the screen is
|
||||
// not updated
|
||||
func (c *Composer) BeginUpdate() {
|
||||
c.mtx.Lock()
|
||||
}
|
||||
|
||||
// EndUpdate unlocks the screen for any manipulations.
|
||||
// Useful only in multithreading application if you create a new Window in
|
||||
// some thread that is not main one (e.g, create new Window inside
|
||||
// OnSelectItem handler of ListBox)
|
||||
func (c *Composer) EndUpdate() {
|
||||
c.mtx.Unlock()
|
||||
}
|
||||
|
||||
func (c *Composer) getWindowList() []Control {
|
||||
c.mtx.RLock()
|
||||
defer c.mtx.RUnlock()
|
||||
|
||||
arr_copy := make([]Control, len(c.windows))
|
||||
copy(arr_copy, c.windows)
|
||||
return arr_copy
|
||||
}
|
||||
|
||||
func (c *Composer) checkWindowUnderMouse(screenX, screenY int) (Control, HitResult) {
|
||||
if len(c.windows) == 0 {
|
||||
windows := c.getWindowList()
|
||||
if len(windows) == 0 {
|
||||
return nil, HitOutside
|
||||
}
|
||||
|
||||
for i := len(c.windows) - 1; i >= 0; i-- {
|
||||
window := c.windows[i]
|
||||
for i := len(windows) - 1; i >= 0; i-- {
|
||||
window := windows[i]
|
||||
hit := window.HitTest(screenX, screenY)
|
||||
if hit != HitOutside {
|
||||
return window, hit
|
||||
@ -113,8 +151,9 @@ func (c *Composer) checkWindowUnderMouse(screenX, screenY int) (Control, HitResu
|
||||
}
|
||||
|
||||
func (c *Composer) activateWindow(window Control) bool {
|
||||
windows := c.getWindowList()
|
||||
if c.topWindow() == window {
|
||||
for _, v := range c.windows {
|
||||
for _, v := range windows {
|
||||
v.SetActive(false)
|
||||
}
|
||||
window.SetActive(true)
|
||||
@ -124,7 +163,7 @@ func (c *Composer) activateWindow(window Control) bool {
|
||||
var wList []Control
|
||||
found := false
|
||||
|
||||
for _, v := range c.windows {
|
||||
for _, v := range windows {
|
||||
if v != window {
|
||||
v.SetActive(false)
|
||||
wList = append(wList, v)
|
||||
@ -138,12 +177,15 @@ func (c *Composer) activateWindow(window Control) bool {
|
||||
}
|
||||
|
||||
window.SetActive(true)
|
||||
c.BeginUpdate()
|
||||
defer c.EndUpdate()
|
||||
c.windows = append(wList, window)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Composer) moveActiveWindowToBottom() bool {
|
||||
if len(c.windows) < 2 {
|
||||
windows := c.getWindowList()
|
||||
if len(windows) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -152,7 +194,7 @@ func (c *Composer) moveActiveWindowToBottom() bool {
|
||||
}
|
||||
|
||||
anyVisible := false
|
||||
for _, w := range c.windows {
|
||||
for _, w := range windows {
|
||||
v := w.(*Window)
|
||||
if v.Visible() {
|
||||
anyVisible = true
|
||||
@ -168,10 +210,12 @@ func (c *Composer) moveActiveWindowToBottom() bool {
|
||||
|
||||
for {
|
||||
last := c.topWindow()
|
||||
c.BeginUpdate()
|
||||
for i := len(c.windows) - 1; i > 0; i-- {
|
||||
c.windows[i] = c.windows[i-1]
|
||||
}
|
||||
c.windows[0] = last
|
||||
c.EndUpdate()
|
||||
|
||||
v := c.topWindow().(*Window)
|
||||
if v.Visible() {
|
||||
@ -200,11 +244,13 @@ func (c *Composer) sendEventToActiveWindow(ev Event) bool {
|
||||
}
|
||||
|
||||
func (c *Composer) topWindow() Control {
|
||||
if len(c.windows) == 0 {
|
||||
windows := c.getWindowList()
|
||||
|
||||
if len(windows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.windows[len(c.windows)-1]
|
||||
return windows[len(windows)-1]
|
||||
}
|
||||
|
||||
func (c *Composer) resizeTopWindow(ev Event) bool {
|
||||
@ -242,34 +288,32 @@ func (c *Composer) resizeTopWindow(ev Event) bool {
|
||||
}
|
||||
|
||||
func (c *Composer) moveTopWindow(ev Event) bool {
|
||||
if len(c.windows) > 0 {
|
||||
view := c.topWindow()
|
||||
if view != nil {
|
||||
topwindow, ok := view.(*Window)
|
||||
if ok && !topwindow.Movable() {
|
||||
return false
|
||||
}
|
||||
view := c.topWindow()
|
||||
if view != nil {
|
||||
topwindow, ok := view.(*Window)
|
||||
if ok && !topwindow.Movable() {
|
||||
return false
|
||||
}
|
||||
|
||||
x, y := view.Pos()
|
||||
w, h := view.Size()
|
||||
x1, y1 := x, y
|
||||
cx, cy := term.Size()
|
||||
if ev.Key == term.KeyArrowUp && y > 0 {
|
||||
y--
|
||||
} else if ev.Key == term.KeyArrowDown && y+h < cy {
|
||||
y++
|
||||
} else if ev.Key == term.KeyArrowLeft && x > 0 {
|
||||
x--
|
||||
} else if ev.Key == term.KeyArrowRight && x+w < cx {
|
||||
x++
|
||||
}
|
||||
x, y := view.Pos()
|
||||
w, h := view.Size()
|
||||
x1, y1 := x, y
|
||||
cx, cy := term.Size()
|
||||
if ev.Key == term.KeyArrowUp && y > 0 {
|
||||
y--
|
||||
} else if ev.Key == term.KeyArrowDown && y+h < cy {
|
||||
y++
|
||||
} else if ev.Key == term.KeyArrowLeft && x > 0 {
|
||||
x--
|
||||
} else if ev.Key == term.KeyArrowRight && x+w < cx {
|
||||
x++
|
||||
}
|
||||
|
||||
if x1 != x || y1 != y {
|
||||
view.SetPos(x, y)
|
||||
event := Event{Type: EventMove, X: x, Y: y}
|
||||
c.sendEventToActiveWindow(event)
|
||||
RefreshScreen()
|
||||
}
|
||||
if x1 != x || y1 != y {
|
||||
view.SetPos(x, y)
|
||||
event := Event{Type: EventMove, X: x, Y: y}
|
||||
c.sendEventToActiveWindow(event)
|
||||
RefreshScreen()
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -525,13 +569,16 @@ func (c *Composer) DestroyWindow(view Control) {
|
||||
ev := Event{Type: EventClose}
|
||||
c.sendEventToActiveWindow(ev)
|
||||
|
||||
windows := c.getWindowList()
|
||||
var newOrder []Control
|
||||
for i := 0; i < len(c.windows); i++ {
|
||||
if c.windows[i] != view {
|
||||
newOrder = append(newOrder, c.windows[i])
|
||||
for i := 0; i < len(windows); i++ {
|
||||
if windows[i] != view {
|
||||
newOrder = append(newOrder, windows[i])
|
||||
}
|
||||
}
|
||||
c.BeginUpdate()
|
||||
c.windows = newOrder
|
||||
c.EndUpdate()
|
||||
c.activateWindow(c.topWindow())
|
||||
}
|
||||
|
||||
|
16
dialog.go
16
dialog.go
@ -59,6 +59,8 @@ func CreateConfirmationDialog(title, question string, buttons []string, defaultB
|
||||
cw, ch := term.Size()
|
||||
|
||||
dlg.View = AddWindow(cw/2-12, ch/2-8, 30, 3, title)
|
||||
WindowManager().BeginUpdate()
|
||||
defer WindowManager().EndUpdate()
|
||||
dlg.View.SetConstraints(30, 3)
|
||||
dlg.View.SetModal(true)
|
||||
dlg.View.SetPack(Vertical)
|
||||
@ -78,9 +80,13 @@ func CreateConfirmationDialog(title, question string, buttons []string, defaultB
|
||||
btn1 := CreateButton(frm1, AutoSize, AutoSize, bText, Fixed)
|
||||
btn1.OnClick(func(ev Event) {
|
||||
dlg.result = DialogButton1
|
||||
|
||||
WindowManager().DestroyWindow(dlg.View)
|
||||
if dlg.onClose != nil {
|
||||
go dlg.onClose()
|
||||
WindowManager().BeginUpdate()
|
||||
closeFunc := dlg.onClose
|
||||
WindowManager().EndUpdate()
|
||||
if closeFunc != nil {
|
||||
go closeFunc()
|
||||
}
|
||||
})
|
||||
var btn2, btn3 *Button
|
||||
@ -137,6 +143,8 @@ func CreateConfirmationDialog(title, question string, buttons []string, defaultB
|
||||
// OnClose sets the callback that is called when the
|
||||
// dialog is closed
|
||||
func (d *ConfirmationDialog) OnClose(fn func()) {
|
||||
WindowManager().BeginUpdate()
|
||||
defer WindowManager().EndUpdate()
|
||||
d.onClose = fn
|
||||
}
|
||||
|
||||
@ -170,6 +178,8 @@ func CreateSelectDialog(title string, items []string, selectedItem int, typ Sele
|
||||
|
||||
dlg.typ = typ
|
||||
dlg.View = AddWindow(cw/2-12, ch/2-8, 20, 10, title)
|
||||
WindowManager().BeginUpdate()
|
||||
defer WindowManager().EndUpdate()
|
||||
dlg.View.SetModal(true)
|
||||
dlg.View.SetPack(Vertical)
|
||||
|
||||
@ -248,6 +258,8 @@ func CreateSelectDialog(title string, items []string, selectedItem int, typ Sele
|
||||
// OnClose sets the callback that is called when the
|
||||
// dialog is closed
|
||||
func (d *SelectDialog) OnClose(fn func()) {
|
||||
WindowManager().BeginUpdate()
|
||||
defer WindowManager().EndUpdate()
|
||||
d.onClose = fn
|
||||
}
|
||||
|
||||
|
@ -253,9 +253,12 @@ func (l *ListBox) processMouseClick(ev Event) bool {
|
||||
}
|
||||
|
||||
l.SelectItem(l.topLine + dy)
|
||||
if l.onSelectItem != nil {
|
||||
WindowManager().BeginUpdate()
|
||||
onSelFunc := l.onSelectItem
|
||||
WindowManager().EndUpdate()
|
||||
if onSelFunc != nil {
|
||||
ev := Event{Y: l.topLine + dy, Msg: l.SelectedItemText()}
|
||||
go l.onSelectItem(ev)
|
||||
go onSelFunc(ev)
|
||||
}
|
||||
|
||||
return true
|
||||
|
Loading…
x
Reference in New Issue
Block a user