From d299bf4f90985de260b6c51409ba3d9099b5f005 Mon Sep 17 00:00:00 2001 From: Graham Clark Date: Tue, 17 Dec 2019 22:31:22 -0500 Subject: [PATCH] Allow the scrollbar to hide if the data fits in the space available. This is a new option provided to the scrollbar widget. If enabled, the scrollbar will simply render its data widget if the data available would fit in the space provided to the scrollbar. Note that this adjusts the focus path of certain elements in the UI... This is fragile. I really need a better way e.g. labelling widgets within containers like columns or piles, rather than using a numeric index. --- ui/ui.go | 14 ++-- widgets/withscrollbar/withscrollbar.go | 68 ++++++++++++++--- widgets/withscrollbar/withscrollbar_test.go | 82 +++++++++++++++++++++ 3 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 widgets/withscrollbar/withscrollbar_test.go diff --git a/ui/ui.go b/ui/ui.go index a8e78a6..46d39b4 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -2825,19 +2825,19 @@ func Build() (*gowid.App, error) { maxViewPath = []interface{}{2, 0} // list, structure or hex - whichever one is selected mainviewPaths = [][]interface{}{ - {2, 0}, // packet list - {4}, // packet structure - {6}, // packet hex + {2}, // packet list + {4}, // packet structure + {6}, // packet hex } altview1Paths = [][]interface{}{ - {2, 0, 0, 0}, // packet list - {2, 0, 2}, // packet structure - {2, 2}, // packet hex + {2, 0, 0}, // packet list + {2, 0, 2}, // packet structure + {2, 2}, // packet hex } altview2Paths = [][]interface{}{ - {2, 0, 0}, // packet list + {2, 0}, // packet list {2, 2, 0}, // packet structure {2, 2, 2}, // packet hex } diff --git a/widgets/withscrollbar/withscrollbar.go b/widgets/withscrollbar/withscrollbar.go index 596e599..1447fe9 100644 --- a/widgets/withscrollbar/withscrollbar.go +++ b/widgets/withscrollbar/withscrollbar.go @@ -15,11 +15,18 @@ import ( //====================================================================== type Widget struct { - *columns.Widget + always *columns.Widget // use if scrollbar is to be shown w IScrollSubWidget sb *vscroll.Widget goUpDown int // positive means down pgUpDown int // positive means down + opt Options +} + +var _ gowid.IWidget = (*Widget)(nil) + +type Options struct { + HideIfContentFits bool } type IScrollValues interface { @@ -27,19 +34,33 @@ type IScrollValues interface { ScrollLength() int } -type IScrollSubWidget interface { - gowid.IWidget - IScrollValues +// Implemented by widgets that can scroll +type IScrollOneLine interface { Up(lines int, size gowid.IRenderSize, app gowid.IApp) Down(lines int, size gowid.IRenderSize, app gowid.IApp) +} + +type IScrollOnePage interface { UpPage(num int, size gowid.IRenderSize, app gowid.IApp) DownPage(num int, size gowid.IRenderSize, app gowid.IApp) } -func New(w IScrollSubWidget) *Widget { +type IScrollSubWidget interface { + gowid.IWidget + IScrollValues + IScrollOneLine + IScrollOnePage +} + +func New(w IScrollSubWidget, opts ...Options) *Widget { + var opt Options + if len(opts) > 0 { + opt = opts[0] + } + sb := vscroll.NewExt(vscroll.VerticalScrollbarUnicodeRunes) res := &Widget{ - Widget: columns.New([]gowid.IContainerWidget{ + always: columns.New([]gowid.IContainerWidget{ &gowid.ContainerWidget{ IWidget: w, D: gowid.RenderWithWeight{W: 1}, @@ -55,6 +76,7 @@ func New(w IScrollSubWidget) *Widget { sb: sb, goUpDown: 0, pgUpDown: 0, + opt: opt, } sb.OnClickAbove(gowid.MakeWidgetCallback("cb", res.clickUp)) sb.OnClickBelow(gowid.MakeWidgetCallback("cb", res.clickDown)) @@ -84,7 +106,19 @@ func CalculateMenuRows(vals IScrollValues, rows int, focus gowid.Selector, app g return vals.ScrollPosition(), 1, vals.ScrollLength() - (vals.ScrollPosition() + 1) } +func (w *Widget) contentFits(size gowid.IRenderSize) bool { + res := true + if rower, ok := size.(gowid.IRows); ok { + res = (w.w.ScrollLength() <= rower.Rows()) + } + return res +} + func (w *Widget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { + if w.opt.HideIfContentFits && w.contentFits(size) { + return w.w.UserInput(ev, size, focus, app) + } + box, ok := size.(gowid.IRenderBox) if !ok { panic(gowid.WidgetSizeError{Widget: w, Size: size, Required: "gowid.IRenderBox"}) @@ -96,14 +130,18 @@ func (w *Widget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.S w.sb.Middle = y w.sb.Bottom = z - res := w.Widget.UserInput(ev, size, focus, app) + res := w.always.UserInput(ev, size, focus, app) if res { - w.Widget.SetFocus(app, 0) + w.always.SetFocus(app, 0) } return res } func (w *Widget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas { + if w.opt.HideIfContentFits && w.contentFits(size) { + return w.w.Render(size, focus, app) + } + box, ok := size.(gowid.IRenderBox) if !ok { panic(gowid.WidgetSizeError{Widget: w, Size: size, Required: "gowid.IRenderBox"}) @@ -136,11 +174,23 @@ func (w *Widget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid. w.sb.Middle = y w.sb.Bottom = z - canvas := w.Widget.Render(size, focus, app) + canvas := w.always.Render(size, focus, app) return canvas } +func (w *Widget) RenderSize(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.IRenderBox { + if w.opt.HideIfContentFits && w.contentFits(size) { + return w.w.RenderSize(size, focus, app) + } + + return w.always.RenderSize(size, focus, app) +} + +func (w *Widget) Selectable() bool { + return w.w.Selectable() +} + //====================================================================== // Local Variables: // mode: Go diff --git a/widgets/withscrollbar/withscrollbar_test.go b/widgets/withscrollbar/withscrollbar_test.go new file mode 100644 index 0000000..aa7ec2b --- /dev/null +++ b/widgets/withscrollbar/withscrollbar_test.go @@ -0,0 +1,82 @@ +package withscrollbar + +import ( + "fmt" + "strings" + "testing" + + "github.com/gcla/gowid" + "github.com/gcla/gowid/gwtest" + "github.com/gcla/gowid/widgets/button" + "github.com/gcla/gowid/widgets/list" + "github.com/gcla/gowid/widgets/text" + "github.com/stretchr/testify/assert" +) + +type scrollingListBox struct { + *list.Widget +} + +func (t *scrollingListBox) Up(lines int, size gowid.IRenderSize, app gowid.IApp) {} +func (t *scrollingListBox) Down(lines int, size gowid.IRenderSize, app gowid.IApp) {} +func (t *scrollingListBox) UpPage(num int, size gowid.IRenderSize, app gowid.IApp) {} +func (t *scrollingListBox) DownPage(num int, size gowid.IRenderSize, app gowid.IApp) {} + +func (t *scrollingListBox) ScrollLength() int { + return 8 +} + +func (t *scrollingListBox) ScrollPosition() int { + return 0 +} + +func Test1(t *testing.T) { + bws := make([]gowid.IWidget, 8) + for i := 0; i < len(bws); i++ { + bws[i] = button.NewBare(text.New(fmt.Sprintf("%03d", i))) + } + + walker := list.NewSimpleListWalker(bws) + lbox := &scrollingListBox{Widget: list.New(walker)} + sbox := New(lbox) + + canvas1 := sbox.Render(gowid.MakeRenderBox(4, 8), gowid.NotSelected, gwtest.D) + res := strings.Join([]string{ + "000▲", + "001█", + "002 ", + "003 ", + "004 ", + "005 ", + "006 ", + "007▼", + }, "\n") + assert.Equal(t, res, canvas1.String()) + + sbox = New(lbox, Options{ + HideIfContentFits: true, + }) + + canvas1 = sbox.Render(gowid.MakeRenderBox(4, 8), gowid.NotSelected, gwtest.D) + res = strings.Join([]string{ + "000 ", + "001 ", + "002 ", + "003 ", + "004 ", + "005 ", + "006 ", + "007 ", + }, "\n") + assert.Equal(t, res, canvas1.String()) + + canvas1 = sbox.Render(gowid.MakeRenderBox(4, 5), gowid.NotSelected, gwtest.D) + res = strings.Join([]string{ + "000▲", + "001█", + "002 ", + "003 ", + "004▼", + }, "\n") + assert.Equal(t, res, canvas1.String()) +}