diff --git a/widgets/number/number.go b/widgets/number/number.go new file mode 100644 index 0000000..a890ae6 --- /dev/null +++ b/widgets/number/number.go @@ -0,0 +1,141 @@ +// Copyright 2019-2021 Graham Clark. All rights reserved. Use of this source +// code is governed by the MIT license that can be found in the LICENSE +// file. + +// Package hexdumper provides a numeric widget with a couple of buttons that increase or decrease its value. +package number + +import ( + "fmt" + + "github.com/gcla/gowid" + "github.com/gcla/gowid/gwutil" + "github.com/gcla/gowid/widgets/button" + "github.com/gcla/gowid/widgets/columns" + "github.com/gcla/gowid/widgets/fill" + "github.com/gcla/gowid/widgets/holder" + "github.com/gcla/gowid/widgets/hpadding" + "github.com/gcla/gowid/widgets/text" +) + +//====================================================================== + +type Options struct { + Value int + Max gwutil.IntOption + Min gwutil.IntOption + Styler func(gowid.IWidget) gowid.IWidget +} + +type Widget struct { + gowid.IWidget + up *button.Widget + down *button.Widget + valHolder *holder.Widget + Value int + Opt Options +} + +var _ gowid.IWidget = (*Widget)(nil) + +var blank *hpadding.Widget +var upArrow *text.Widget +var downArrow *text.Widget +var leftbr *text.Widget +var rightbr *text.Widget + +func init() { + blank = hpadding.New( + fill.New(' '), + gowid.HAlignLeft{}, + gowid.RenderWithUnits{U: 1}, + ) + + leftbr = text.New("[") + rightbr = text.New("]") + upArrow = text.New("^") + downArrow = text.New("v") +} + +func New(opts ...Options) *Widget { + + var opt Options + if len(opts) > 0 { + opt = opts[0] + } + + res := &Widget{ + valHolder: holder.New(text.New(fmt.Sprintf("%d", opt.Value))), + Value: opt.Value, + } + + up := button.NewBare(upArrow) + + up.OnClick(gowid.MakeWidgetCallback("cb", func(app gowid.IApp, widget gowid.IWidget) { + if !res.Opt.Max.IsNone() && res.Value >= res.Opt.Max.Val() { + res.Value = res.Opt.Max.Val() + } else { + res.Value += 1 + } + res.valHolder.SetSubWidget(text.New(fmt.Sprintf("%d", res.Value)), app) + })) + + down := button.NewBare(downArrow) + + down.OnClick(gowid.MakeWidgetCallback("cb", func(app gowid.IApp, widget gowid.IWidget) { + if !res.Opt.Min.IsNone() && res.Value <= res.Opt.Min.Val() { + res.Value = res.Opt.Min.Val() + } else { + res.Value -= 1 + } + res.valHolder.SetSubWidget(text.New(fmt.Sprintf("%d", res.Value)), app) + })) + + styler := opt.Styler + if styler == nil { + styler = func(w gowid.IWidget) gowid.IWidget { + return w + } + } + + cols := columns.New([]gowid.IContainerWidget{ + &gowid.ContainerWidget{ + IWidget: res.valHolder, + D: gowid.RenderFixed{}, + //D: gowid.RenderWithWeight{W: 1}, + }, + &gowid.ContainerWidget{ + IWidget: blank, + D: gowid.RenderWithUnits{U: 1}, + }, + &gowid.ContainerWidget{ + IWidget: leftbr, + D: gowid.RenderFixed{}, + }, + &gowid.ContainerWidget{ + IWidget: styler(up), + D: gowid.RenderFixed{}, + }, + &gowid.ContainerWidget{ + IWidget: styler(down), + D: gowid.RenderFixed{}, + }, + &gowid.ContainerWidget{ + IWidget: rightbr, + D: gowid.RenderFixed{}, + }, + }) + + res.IWidget = cols + res.Opt = opt + res.up = up + res.down = down + + return res +} + +//====================================================================== +// Local Variables: +// mode: Go +// fill-column: 110 +// End: diff --git a/widgets/number/number_test.go b/widgets/number/number_test.go new file mode 100644 index 0000000..a0541e7 --- /dev/null +++ b/widgets/number/number_test.go @@ -0,0 +1,61 @@ +// Copyright 2019-2021 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license +// that can be found in the LICENSE file. + +package number + +import ( + "testing" + + "github.com/gcla/gowid" + "github.com/gcla/gowid/gwtest" + "github.com/gdamore/tcell" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +//====================================================================== + +func evclick(x, y int) *tcell.EventMouse { + return tcell.NewEventMouse(x, y, tcell.Button1, 0) +} + +func evunclick(x, y int) *tcell.EventMouse { + return tcell.NewEventMouse(x, y, tcell.ButtonNone, 0) +} + +func TestNumber1(t *testing.T) { + v := 2 + + w := New(Options{ + Value: v, + }) + sz := gowid.RenderFixed{} + + c1 := w.Render(sz, gowid.NotSelected, gwtest.D) + log.Infof("Canvas is %s", c1.String()) + // "0 [^v]" + assert.Equal(t, 1, c1.BoxRows()) + + clickat := func(x, y int) { + w.UserInput(evclick(x, y), sz, gowid.Focused, gwtest.D) + gwtest.D.SetLastMouseState(gowid.MouseState{true, false, false}) + w.UserInput(evunclick(x, y), sz, gowid.Focused, gwtest.D) + gwtest.D.SetLastMouseState(gowid.MouseState{false, false, false}) + } + + clickat(2, 0) + assert.Equal(t, v, w.Value) + + clickat(3, 0) + assert.Equal(t, v+1, w.Value) + + clickat(4, 0) + clickat(4, 0) + assert.Equal(t, v+1-2, w.Value) +} + +//====================================================================== +// Local Variables: +// mode: Go +// fill-column: 110 +// End: