diff --git a/barchart.go b/barchart.go index 8a5f426..b04e3fa 100644 --- a/barchart.go +++ b/barchart.go @@ -106,6 +106,10 @@ func CreateBarChart(parent Control, w, h int, scale int) *BarChart { // Repaint draws the control on its View surface func (b *BarChart) Draw() { + if b.hidden { + return + } + b.mtx.RLock() defer b.mtx.RUnlock() diff --git a/base_control.go b/base_control.go index 1671b81..f070cbc 100644 --- a/base_control.go +++ b/base_control.go @@ -19,6 +19,7 @@ type BaseControl struct { bgActive term.Attribute tabSkip bool disabled bool + hidden bool align Align parent Control inactive bool @@ -118,6 +119,34 @@ func (c *BaseControl) SetEnabled(enabled bool) { c.disabled = !enabled } +func (c *BaseControl) Visible() bool { + c.mtx.RLock() + defer c.mtx.RUnlock() + + return !c.hidden +} + +func (c *BaseControl) SetVisible(visible bool) { + c.mtx.Lock() + defer c.mtx.Unlock() + + if visible == !c.hidden { + return + } + + c.hidden = !visible + if c.parent == nil { + return + } + + p := c.Parent() + for p.Parent() != nil { + p = p.Parent() + } + + go PutEvent(Event{Type: EventLayout, Target: p}) +} + func (c *BaseControl) Parent() Control { return c.parent } @@ -204,23 +233,39 @@ func (c *BaseControl) SetBackColor(clr term.Attribute) { c.bg = clr } +func (c *BaseControl) childCount() int { + cnt := 0 + for _, child := range c.children { + if child.Visible() { + cnt++ + } + } + + return cnt +} + func (c *BaseControl) ResizeChildren() { - if len(c.children) == 0 { + children := c.childCount() + if children == 0 { return } fullWidth := c.width - 2*c.padX fullHeight := c.height - 2*c.padY if c.pack == Horizontal { - fullWidth -= (len(c.children) - 1) * c.gapX + fullWidth -= (children - 1) * c.gapX } else { - fullHeight -= (len(c.children) - 1) * c.gapY + fullHeight -= (children - 1) * c.gapY } totalSc := c.ChildrenScale() minWidth := 0 minHeight := 0 for _, child := range c.children { + if !child.Visible() { + continue + } + cw, ch := child.MinimalSize() if c.pack == Horizontal { minWidth += cw @@ -239,6 +284,10 @@ func (c *BaseControl) ResizeChildren() { } for _, ctrl := range c.children { + if !ctrl.Visible() { + continue + } + tw, th := ctrl.MinimalSize() sc := ctrl.Scale() d := int(ctrl.Scale() * aStep) @@ -332,20 +381,23 @@ func (c *BaseControl) ChildExists(control Control) bool { } func (c *BaseControl) ChildrenScale() int { - if len(c.children) == 0 { + if c.childCount() == 0 { return c.scale } total := 0 for _, ctrl := range c.children { - total += ctrl.Scale() + if ctrl.Visible() { + total += ctrl.Scale() + } } return total } func (c *BaseControl) MinimalSize() (w int, h int) { - if len(c.children) == 0 { + children := c.childCount() + if children == 0 { return c.minW, c.minH } @@ -353,12 +405,15 @@ func (c *BaseControl) MinimalSize() (w int, h int) { totalY := 2 * c.padY if c.pack == Vertical { - totalY += (len(c.children) - 1) * c.gapY + totalY += (children - 1) * c.gapY } else { - totalX += (len(c.children) - 1) * c.gapX + totalX += (children - 1) * c.gapX } for _, ctrl := range c.children { + if !ctrl.Visible() { + continue + } ww, hh := ctrl.MinimalSize() if c.pack == Vertical { totalY += hh @@ -388,6 +443,10 @@ func (c *BaseControl) Draw() { } func (c *BaseControl) DrawChildren() { + if c.hidden { + return + } + PushClip() defer PopClip() @@ -422,14 +481,18 @@ func (c *BaseControl) ProcessEvent(ev Event) bool { } func (c *BaseControl) PlaceChildren() { - if c.children == nil || len(c.children) == 0 { + children := c.childCount() + if c.children == nil || children == 0 { return } xx, yy := c.x+c.padX, c.y+c.padY for _, ctrl := range c.children { - ctrl.SetPos(xx, yy) + if !ctrl.Visible() { + continue + } + ctrl.SetPos(xx, yy) ww, hh := ctrl.Size() if c.pack == Vertical { yy += c.gapY + hh diff --git a/button.go b/button.go index 02870c1..dbae036 100644 --- a/button.go +++ b/button.go @@ -3,8 +3,8 @@ package clui import ( xs "github.com/huandu/xstrings" term "github.com/nsf/termbox-go" + "sync/atomic" "time" - "sync/atomic" ) /* @@ -63,6 +63,10 @@ func CreateButton(parent Control, width, height int, title string, scale int) *B // Repaint draws the control on its View surface func (b *Button) Draw() { + if b.hidden { + return + } + b.mtx.RLock() defer b.mtx.RUnlock() PushAttributes() @@ -122,7 +126,7 @@ func (b *Button) ProcessEvent(event Event) bool { ev := Event{Type: EventRedraw} go func() { - PutEvent(ev) + PutEvent(ev) time.Sleep(100 * time.Millisecond) b.setPressed(0) PutEvent(ev) diff --git a/changelog b/changelog index d3ca627..9a55e88 100644 --- a/changelog +++ b/changelog @@ -1,4 +1,15 @@ -2018-01-26 - version 0.7.0 +2018-03-13 - version 0.8.0-RC1 +[+] Added new property for all controls: Visible. It makes possible to show + and hide any control with its children(if there are any). + New Control interface methods: Visible and SetVisible +[+] A new event to support hiding/displaying controls: EventLayout with one + argument - Control that should handle the event. On receiving the event + the Control must recalculate and reposition all its children. + At this moment only Windows handle this event. Other kinds of Control + never receieves the event +[+] Add a new simple demo to play with Control visiblity: demos/visible + +2018-01-26 - version 0.7.0 [+] Added new event handler for Window: set a callback OnScreenResize if you want to handle terminal resize event diff --git a/checkbox.go b/checkbox.go index 26618be..4f4c7e2 100644 --- a/checkbox.go +++ b/checkbox.go @@ -52,6 +52,10 @@ func CreateCheckBox(parent Control, width int, title string, scale int) *CheckBo // Repaint draws the control on its View surface func (c *CheckBox) Draw() { + if c.hidden { + return + } + c.mtx.RLock() defer c.mtx.RUnlock() diff --git a/composer.go b/composer.go index 9d530a9..2259756 100644 --- a/composer.go +++ b/composer.go @@ -696,5 +696,13 @@ func ProcessEvent(ev Event) { comp.processKey(ev) case EventMouse: comp.processMouse(ev) + case EventLayout: + for _, c := range comp.windows { + if c == ev.Target { + c.ResizeChildren() + c.PlaceChildren() + break + } + } } } diff --git a/consts.go b/consts.go index daa5c37..b3ea5c9 100644 --- a/consts.go +++ b/consts.go @@ -85,6 +85,7 @@ type Event struct { // For resize event - new terminal size Width int Height int + Target Control } // BorderStyle constants @@ -286,8 +287,10 @@ const ( EventDialogClose // Close application EventQuit - // Close top window - or application is there is only one window - EventCloseWindow + // Close top window - or application is there is only one window + EventCloseWindow + // Make a control (Target field of Event structure) to recalculate and reposition all its children + EventLayout ) // ConfirmationDialog and SelectDialog exit codes diff --git a/control_intf.go b/control_intf.go index ab6c9df..96125d3 100644 --- a/control_intf.go +++ b/control_intf.go @@ -38,6 +38,9 @@ type Control interface { // Enable return if a control can process keyboard and mouse events Enabled() bool SetEnabled(enabled bool) + // Visible return if a control is visible + Visible() bool + SetVisible(enabled bool) // Parent return control's container or nil if there is no parent container // that is true for Windows Parent() Control diff --git a/ctrlutil.go b/ctrlutil.go index 67e0ce1..48e6398 100644 --- a/ctrlutil.go +++ b/ctrlutil.go @@ -211,7 +211,7 @@ func _nextControl(parent Control, curr, prev Control, foundPrev, next bool) (boo } } - if ctrl.Enabled() && ctrl.TabStop() { + if ctrl.Enabled() && ctrl.TabStop() && ctrl.Visible() { if found { return found, ctrl } else if !next { @@ -233,7 +233,7 @@ func _nextControl(parent Control, curr, prev Control, foundPrev, next bool) (boo // that has tab-stop feature on. Used by library when processing TAB key func NextControl(parent Control, curr Control, next bool) Control { fnTab := func(c Control) bool { - return c.TabStop() + return c.TabStop() && c.Visible() } var defControl Control diff --git a/demos/visible/visible.go b/demos/visible/visible.go new file mode 100644 index 0000000..5d38dc1 --- /dev/null +++ b/demos/visible/visible.go @@ -0,0 +1,62 @@ +package main + +import ( + ui "github.com/VladimirMarkelov/clui" +) + +func main() { + ui.InitLibrary() + defer ui.DeinitLibrary() + + view := ui.AddWindow(0, 0, 10, 7, "Hello World!") + view.SetPack(ui.Vertical) + + frmResize := ui.CreateFrame(view, 8, 6, ui.BorderNone, ui.Fixed) + frmResize.SetTitle("FrameTop") + frmResize.SetPack(ui.Horizontal) + btn1 := ui.CreateButton(frmResize, 8, 5, "Button 1", 1) + btn2 := ui.CreateButton(frmResize, 8, 5, "Button 2", 1) + btn3 := ui.CreateButton(frmResize, 8, 5, "Button 3", 1) + + frmBtns := ui.CreateFrame(view, 8, 5, ui.BorderNone, ui.Fixed) + frmBtns.SetPack(ui.Horizontal) + frmBtns.SetTitle("FrameBottom") + + btnHide1 := ui.CreateButton(frmBtns, 8, 4, "Hide 1", 1) + btnHide1.OnClick(func(ev ui.Event) { + if btn1.Visible() { + btnHide1.SetTitle("Show 1") + btn1.SetVisible(false) + } else { + btnHide1.SetTitle("Hide 1") + btn1.SetVisible(true) + } + }) + btnHide2 := ui.CreateButton(frmBtns, 8, 4, "Hide 2", 1) + btnHide2.OnClick(func(ev ui.Event) { + if btn2.Visible() { + btnHide2.SetTitle("Show 2") + btn2.SetVisible(false) + } else { + btnHide2.SetTitle("Hide 2") + btn2.SetVisible(true) + } + }) + btnHide3 := ui.CreateButton(frmBtns, 8, 4, "Hide 3", 1) + btnHide3.OnClick(func(ev ui.Event) { + if btn3.Visible() { + btnHide3.SetTitle("Show 3") + btn3.SetVisible(false) + } else { + btnHide3.SetTitle("Hide 3") + btn3.SetVisible(true) + } + }) + + btnQuit := ui.CreateButton(frmBtns, 8, 4, "Quit", 1) + btnQuit.OnClick(func(ev ui.Event) { + go ui.Stop() + }) + + ui.MainLoop() +} diff --git a/edit.go b/edit.go index 06746d7..f91b189 100644 --- a/edit.go +++ b/edit.go @@ -32,6 +32,10 @@ func (e *EditField) SetTitle(title string) { // Repaint draws the control on its View surface func (e *EditField) Draw() { + if e.hidden { + return + } + PushAttributes() defer PopAttributes() diff --git a/frame.go b/frame.go index a55fcf8..6f13964 100644 --- a/frame.go +++ b/frame.go @@ -59,6 +59,10 @@ func CreateFrame(parent Control, width, height int, bs BorderStyle, scale int) * // Repaint draws the control on its View surface func (f *Frame) Draw() { + if f.hidden { + return + } + PushAttributes() defer PopAttributes() diff --git a/label.go b/label.go index fcffb44..77b46da 100644 --- a/label.go +++ b/label.go @@ -63,6 +63,10 @@ func (l *Label) SetDirection(dir Direction) { } func (l *Label) Draw() { + if l.hidden { + return + } + PushAttributes() defer PopAttributes() diff --git a/listbox.go b/listbox.go index 03d36e9..d24f28a 100644 --- a/listbox.go +++ b/listbox.go @@ -111,6 +111,10 @@ func (l *ListBox) drawItems() { // Repaint draws the control on its View surface func (l *ListBox) Draw() { + if l.hidden { + return + } + PushAttributes() defer PopAttributes() diff --git a/progressbar.go b/progressbar.go index 8b9f0db..b1e7632 100644 --- a/progressbar.go +++ b/progressbar.go @@ -71,8 +71,12 @@ func CreateProgressBar(parent Control, width, height int, scale int) *ProgressBa // pb.SetTitle("{{value}} of {{max}}") // pb.SetTitle("{{percent}}%") func (b *ProgressBar) Draw() { - b.mtx.RLock() - defer b.mtx.RUnlock() + if b.hidden { + return + } + + b.mtx.RLock() + defer b.mtx.RUnlock() if b.max <= b.min { return } @@ -164,8 +168,8 @@ func (b *ProgressBar) Draw() { // SetValue sets new progress value. If value exceeds ProgressBar // limits then the limit value is used func (b *ProgressBar) SetValue(pos int) { - b.mtx.Lock() - defer b.mtx.Unlock() + b.mtx.Lock() + defer b.mtx.Unlock() if pos < b.min { b.value = b.min } else if pos > b.max { @@ -177,8 +181,8 @@ func (b *ProgressBar) SetValue(pos int) { // Value returns the current ProgressBar value func (b *ProgressBar) Value() int { - b.mtx.RLock() - defer b.mtx.RUnlock() + b.mtx.RLock() + defer b.mtx.RUnlock() return b.value } @@ -204,8 +208,8 @@ func (b *ProgressBar) SetLimits(min, max int) { // Step increases ProgressBar value by 1 if the value is less // than ProgressBar high limit func (b *ProgressBar) Step() int { - b.mtx.Lock() - defer b.mtx.Unlock() + b.mtx.Lock() + defer b.mtx.Unlock() b.value++ if b.value > b.max { diff --git a/radio.go b/radio.go index 1177abf..1333d33 100644 --- a/radio.go +++ b/radio.go @@ -48,6 +48,10 @@ func CreateRadio(parent Control, width int, title string, scale int) *Radio { // Repaint draws the control on its View surface func (c *Radio) Draw() { + if c.hidden { + return + } + PushAttributes() defer PopAttributes() diff --git a/sparkchart.go b/sparkchart.go index 670ca73..0ee051e 100644 --- a/sparkchart.go +++ b/sparkchart.go @@ -72,6 +72,10 @@ func CreateSparkChart(parent Control, w, h int, scale int) *SparkChart { // Repaint draws the control on its View surface func (b *SparkChart) Draw() { + if b.hidden { + return + } + b.mtx.RLock() defer b.mtx.RUnlock() diff --git a/tableview.go b/tableview.go index f9af4c4..2e68e49 100644 --- a/tableview.go +++ b/tableview.go @@ -348,6 +348,10 @@ func (l *TableView) drawCells() { // Repaint draws the control on its View surface func (l *TableView) Draw() { + if l.hidden { + return + } + l.mtx.RLock() defer l.mtx.RUnlock() PushAttributes() diff --git a/textreader.go b/textreader.go index ca8a2e0..33b01f0 100644 --- a/textreader.go +++ b/textreader.go @@ -80,6 +80,10 @@ func (l *TextReader) drawText() { // Repaint draws the control on its View surface func (l *TextReader) Draw() { + if l.hidden { + return + } + PushAttributes() defer PopAttributes() diff --git a/textview.go b/textview.go index 3bbaa9d..78be754 100644 --- a/textview.go +++ b/textview.go @@ -166,6 +166,10 @@ func (l *TextView) drawText() { // Repaint draws the control on its View surface func (l *TextView) Draw() { + if l.hidden { + return + } + PushAttributes() defer PopAttributes()