This commit is contained in:
pj 2018-01-26 18:15:43 +11:00
commit ae7a061585
5 changed files with 118 additions and 45 deletions

View File

@ -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 method to set and read visible buttons in title bar
[+] Added feature: Window manual resizing and moving can be disabled [+] Added feature: Window manual resizing and moving can be disabled
[*] Fix TableView scrollbars: both scrollbars did not response mouse clicks [*] Fix TableView scrollbars: both scrollbars did not response mouse clicks

View File

@ -2,6 +2,7 @@ package clui
import ( import (
term "github.com/nsf/termbox-go" term "github.com/nsf/termbox-go"
"sync"
) )
// Composer is a service object that manages Views and console, processes // Composer is a service object that manages Views and console, processes
@ -22,6 +23,8 @@ type Composer struct {
lastX, lastY int lastX, lastY int
// Type of dragging // Type of dragging
dragType DragType dragType DragType
// For safe Window manipulations
mtx sync.RWMutex
} }
var ( var (
@ -66,16 +69,21 @@ func termboxEventToLocal(ev term.Event) Event {
// Repaints everything on the screen // Repaints everything on the screen
func RefreshScreen() { func RefreshScreen() {
comp.BeginUpdate()
term.Clear(ColorWhite, ColorBlack) term.Clear(ColorWhite, ColorBlack)
comp.EndUpdate()
for _, wnd := range comp.windows { windows := comp.getWindowList()
v := comp.topWindow().(*Window) for _, wnd := range windows {
v := wnd.(*Window)
if v.Visible() { if v.Visible() {
wnd.Draw() wnd.Draw()
} }
} }
comp.BeginUpdate()
term.Flush() term.Flush()
comp.EndUpdate()
} }
// AddWindow constucts a new Window, adds it to the composer automatically, // 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 { func AddWindow(posX, posY, width, height int, title string) *Window {
window := CreateWindow(posX, posY, width, height, title) window := CreateWindow(posX, posY, width, height, title)
comp.BeginUpdate()
comp.windows = append(comp.windows, window) comp.windows = append(comp.windows, window)
comp.EndUpdate()
window.Draw() window.Draw()
comp.activateWindow(window) comp.activateWindow(window)
@ -96,13 +106,41 @@ func AddWindow(posX, posY, width, height int, title string) *Window {
return 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) { func (c *Composer) checkWindowUnderMouse(screenX, screenY int) (Control, HitResult) {
if len(c.windows) == 0 { windows := c.getWindowList()
if len(windows) == 0 {
return nil, HitOutside return nil, HitOutside
} }
for i := len(c.windows) - 1; i >= 0; i-- { for i := len(windows) - 1; i >= 0; i-- {
window := c.windows[i] window := windows[i]
hit := window.HitTest(screenX, screenY) hit := window.HitTest(screenX, screenY)
if hit != HitOutside { if hit != HitOutside {
return window, hit return window, hit
@ -113,8 +151,9 @@ func (c *Composer) checkWindowUnderMouse(screenX, screenY int) (Control, HitResu
} }
func (c *Composer) activateWindow(window Control) bool { func (c *Composer) activateWindow(window Control) bool {
windows := c.getWindowList()
if c.topWindow() == window { if c.topWindow() == window {
for _, v := range c.windows { for _, v := range windows {
v.SetActive(false) v.SetActive(false)
} }
window.SetActive(true) window.SetActive(true)
@ -124,7 +163,7 @@ func (c *Composer) activateWindow(window Control) bool {
var wList []Control var wList []Control
found := false found := false
for _, v := range c.windows { for _, v := range windows {
if v != window { if v != window {
v.SetActive(false) v.SetActive(false)
wList = append(wList, v) wList = append(wList, v)
@ -138,12 +177,15 @@ func (c *Composer) activateWindow(window Control) bool {
} }
window.SetActive(true) window.SetActive(true)
c.BeginUpdate()
defer c.EndUpdate()
c.windows = append(wList, window) c.windows = append(wList, window)
return true return true
} }
func (c *Composer) moveActiveWindowToBottom() bool { func (c *Composer) moveActiveWindowToBottom() bool {
if len(c.windows) < 2 { windows := c.getWindowList()
if len(windows) < 2 {
return false return false
} }
@ -152,7 +194,7 @@ func (c *Composer) moveActiveWindowToBottom() bool {
} }
anyVisible := false anyVisible := false
for _, w := range c.windows { for _, w := range windows {
v := w.(*Window) v := w.(*Window)
if v.Visible() { if v.Visible() {
anyVisible = true anyVisible = true
@ -168,10 +210,12 @@ func (c *Composer) moveActiveWindowToBottom() bool {
for { for {
last := c.topWindow() last := c.topWindow()
c.BeginUpdate()
for i := len(c.windows) - 1; i > 0; i-- { for i := len(c.windows) - 1; i > 0; i-- {
c.windows[i] = c.windows[i-1] c.windows[i] = c.windows[i-1]
} }
c.windows[0] = last c.windows[0] = last
c.EndUpdate()
v := c.topWindow().(*Window) v := c.topWindow().(*Window)
if v.Visible() { if v.Visible() {
@ -200,11 +244,13 @@ func (c *Composer) sendEventToActiveWindow(ev Event) bool {
} }
func (c *Composer) topWindow() Control { func (c *Composer) topWindow() Control {
if len(c.windows) == 0 { windows := c.getWindowList()
if len(windows) == 0 {
return nil return nil
} }
return c.windows[len(c.windows)-1] return windows[len(windows)-1]
} }
func (c *Composer) resizeTopWindow(ev Event) bool { 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 { func (c *Composer) moveTopWindow(ev Event) bool {
if len(c.windows) > 0 { view := c.topWindow()
view := c.topWindow() if view != nil {
if view != nil { topwindow, ok := view.(*Window)
topwindow, ok := view.(*Window) if ok && !topwindow.Movable() {
if ok && !topwindow.Movable() { return false
return false }
}
x, y := view.Pos() x, y := view.Pos()
w, h := view.Size() w, h := view.Size()
x1, y1 := x, y x1, y1 := x, y
cx, cy := term.Size() cx, cy := term.Size()
if ev.Key == term.KeyArrowUp && y > 0 { if ev.Key == term.KeyArrowUp && y > 0 {
y-- y--
} else if ev.Key == term.KeyArrowDown && y+h < cy { } else if ev.Key == term.KeyArrowDown && y+h < cy {
y++ y++
} else if ev.Key == term.KeyArrowLeft && x > 0 { } else if ev.Key == term.KeyArrowLeft && x > 0 {
x-- x--
} else if ev.Key == term.KeyArrowRight && x+w < cx { } else if ev.Key == term.KeyArrowRight && x+w < cx {
x++ x++
} }
if x1 != x || y1 != y { if x1 != x || y1 != y {
view.SetPos(x, y) view.SetPos(x, y)
event := Event{Type: EventMove, X: x, Y: y} event := Event{Type: EventMove, X: x, Y: y}
c.sendEventToActiveWindow(event) c.sendEventToActiveWindow(event)
RefreshScreen() RefreshScreen()
}
} }
return true return true
} }
@ -525,13 +569,16 @@ func (c *Composer) DestroyWindow(view Control) {
ev := Event{Type: EventClose} ev := Event{Type: EventClose}
c.sendEventToActiveWindow(ev) c.sendEventToActiveWindow(ev)
windows := c.getWindowList()
var newOrder []Control var newOrder []Control
for i := 0; i < len(c.windows); i++ { for i := 0; i < len(windows); i++ {
if c.windows[i] != view { if windows[i] != view {
newOrder = append(newOrder, c.windows[i]) newOrder = append(newOrder, windows[i])
} }
} }
c.BeginUpdate()
c.windows = newOrder c.windows = newOrder
c.EndUpdate()
c.activateWindow(c.topWindow()) c.activateWindow(c.topWindow())
} }

View File

@ -59,6 +59,8 @@ func CreateConfirmationDialog(title, question string, buttons []string, defaultB
cw, ch := term.Size() cw, ch := term.Size()
dlg.View = AddWindow(cw/2-12, ch/2-8, 30, 3, title) dlg.View = AddWindow(cw/2-12, ch/2-8, 30, 3, title)
WindowManager().BeginUpdate()
defer WindowManager().EndUpdate()
dlg.View.SetConstraints(30, 3) dlg.View.SetConstraints(30, 3)
dlg.View.SetModal(true) dlg.View.SetModal(true)
dlg.View.SetPack(Vertical) dlg.View.SetPack(Vertical)
@ -78,9 +80,13 @@ func CreateConfirmationDialog(title, question string, buttons []string, defaultB
btn1 := CreateButton(frm1, AutoSize, AutoSize, bText, Fixed) btn1 := CreateButton(frm1, AutoSize, AutoSize, bText, Fixed)
btn1.OnClick(func(ev Event) { btn1.OnClick(func(ev Event) {
dlg.result = DialogButton1 dlg.result = DialogButton1
WindowManager().DestroyWindow(dlg.View) WindowManager().DestroyWindow(dlg.View)
if dlg.onClose != nil { WindowManager().BeginUpdate()
go dlg.onClose() closeFunc := dlg.onClose
WindowManager().EndUpdate()
if closeFunc != nil {
go closeFunc()
} }
}) })
var btn2, btn3 *Button 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 // OnClose sets the callback that is called when the
// dialog is closed // dialog is closed
func (d *ConfirmationDialog) OnClose(fn func()) { func (d *ConfirmationDialog) OnClose(fn func()) {
WindowManager().BeginUpdate()
defer WindowManager().EndUpdate()
d.onClose = fn d.onClose = fn
} }
@ -170,6 +178,8 @@ func CreateSelectDialog(title string, items []string, selectedItem int, typ Sele
dlg.typ = typ dlg.typ = typ
dlg.View = AddWindow(cw/2-12, ch/2-8, 20, 10, title) dlg.View = AddWindow(cw/2-12, ch/2-8, 20, 10, title)
WindowManager().BeginUpdate()
defer WindowManager().EndUpdate()
dlg.View.SetModal(true) dlg.View.SetModal(true)
dlg.View.SetPack(Vertical) 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 // OnClose sets the callback that is called when the
// dialog is closed // dialog is closed
func (d *SelectDialog) OnClose(fn func()) { func (d *SelectDialog) OnClose(fn func()) {
WindowManager().BeginUpdate()
defer WindowManager().EndUpdate()
d.onClose = fn d.onClose = fn
} }

View File

@ -253,9 +253,12 @@ func (l *ListBox) processMouseClick(ev Event) bool {
} }
l.SelectItem(l.topLine + dy) 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()} ev := Event{Y: l.topLine + dy, Msg: l.SelectedItemText()}
go l.onSelectItem(ev) go onSelFunc(ev)
} }
return true return true

View File

@ -125,6 +125,8 @@ func (wnd *Window) drawButtons() {
} }
func (wnd *Window) Draw() { func (wnd *Window) Draw() {
WindowManager().BeginUpdate()
defer WindowManager().EndUpdate()
PushAttributes() PushAttributes()
defer PopAttributes() defer PopAttributes()