From 239c24533d3e0c00404b2987080b65cdc5b1c574 Mon Sep 17 00:00:00 2001 From: Sander Schobers Date: Tue, 7 Aug 2018 07:03:52 +0200 Subject: [PATCH] Some changes to UI elements. - Added colors to palette and renamed white/black to lightest/darkest. - Extracted drawing code. - Restyled checkbox, spinner and scrollbar. --- ui/checkbox.go | 89 ++++++++++++++---------------------- ui/context.go | 2 +- ui/dimensions.go | 4 +- ui/draw.go | 38 ++++++++++++++++ ui/label.go | 2 +- ui/palette.go | 40 ++++++++++++----- ui/scrollbar.go | 114 ++++++++++++++++++++++++++++++----------------- ui/spinner.go | 82 +++++++++++++++++----------------- ui/statusbar.go | 5 ++- 9 files changed, 223 insertions(+), 153 deletions(-) create mode 100644 ui/draw.go diff --git a/ui/checkbox.go b/ui/checkbox.go index c92acf8..e8790b6 100644 --- a/ui/checkbox.go +++ b/ui/checkbox.go @@ -2,7 +2,6 @@ package ui import ( "fmt" - "image" "image/color" "github.com/llgcode/draw2d/draw2dimg" @@ -13,50 +12,34 @@ import ( type CheckboxValueChangedFn func(bool) -func createOnBitmap(fill, stroke color.Color) *allegro5.Bitmap { - var sz = float64(checkboxSize) - on := image.NewRGBA(image.Rect(0, 0, checkboxSize, checkboxSize)) +func drawCheckedBitmap(fill, stroke color.Color) *allegro5.Bitmap { + return drawBitmap(checkboxSize, checkboxSize, func(gc *draw2dimg.GraphicContext) { + var size = float64(checkboxSize) + var margin = float64(checkboxMargin) - gc := draw2dimg.NewGraphicContext(on) - gc.SetFillColor(color.Transparent) - gc.Clear() - gc.SetFillColor(fill) - draw2dkit.RoundedRectangle(gc, 0, 0, sz, sz, 3, 3) - gc.Fill() + gc.SetFillColor(fill) + draw2dkit.RoundedRectangle(gc, margin, margin, size-margin, size-margin, margin, margin) + gc.Fill() - gc.SetStrokeColor(stroke) - gc.SetLineWidth(2) - gc.MoveTo(2.5, 5.5) - gc.LineTo(5.5, 8.5) - gc.LineTo(10, 3.5) - gc.Stroke() - - bmp, err := allegro5.NewBitmapFromImage(on, false) - if nil != err { - return nil - } - return bmp + gc.SetStrokeColor(stroke) + gc.SetLineWidth(1.5) + gc.MoveTo(7, 12) + gc.LineTo(10.5, 15.5) + gc.LineTo(17, 9) + gc.Stroke() + }) } -func createOffBitmap(fill, stroke color.Color) *allegro5.Bitmap { - var sz = float64(checkboxSize) - off := image.NewRGBA(image.Rect(0, 0, checkboxSize, checkboxSize)) +func drawUncheckedBitmap(fill, stroke color.Color) *allegro5.Bitmap { + return drawBitmap(checkboxSize, checkboxSize, func(gc *draw2dimg.GraphicContext) { + var size = float64(checkboxSize) + var margin = float64(checkboxMargin) - gc := draw2dimg.NewGraphicContext(off) - gc.SetFillColor(color.Transparent) - gc.Clear() - gc.SetFillColor(stroke) - draw2dkit.RoundedRectangle(gc, 0, 0, sz, sz, 4, 4) - gc.Fill() - gc.SetFillColor(fill) - draw2dkit.RoundedRectangle(gc, 1, 1, sz-1, sz-1, 3, 3) - gc.Fill() - - bmp, err := allegro5.NewBitmapFromImage(off, false) - if nil != err { - return nil - } - return bmp + gc.SetLineWidth(2) + gc.SetStrokeColor(stroke) + draw2dkit.RoundedRectangle(gc, margin+1, margin+1, size-margin-1, size-margin-1, margin-1, margin-1) + gc.Stroke() + }) } type Checkbox struct { @@ -64,8 +47,8 @@ type Checkbox struct { Value bool Text string OnChanged CheckboxValueChangedFn - on *allegro5.Bitmap - off *allegro5.Bitmap + checked *allegro5.Bitmap + unchecked *allegro5.Bitmap } func (c *Checkbox) Created(ctx Context, p Container) error { @@ -74,9 +57,9 @@ func (c *Checkbox) Created(ctx Context, p Container) error { return err } var plt = ctx.Palette() - c.on = createOnBitmap(plt.Primary(), plt.White()) - c.off = createOffBitmap(plt.White(), plt.Black()) - if nil == c.on || nil == c.off { + c.checked = drawCheckedBitmap(plt.Primary(), plt.Lightest()) + c.unchecked = drawUncheckedBitmap(plt.Lightest(), plt.Darkest()) + if nil == c.checked || nil == c.unchecked { return fmt.Errorf("error creating checkboxes") } return nil @@ -101,32 +84,28 @@ func (c *Checkbox) DesiredSize(ctx Context) geom.PointF { var fonts = ctx.Fonts() var fnt = fonts.Get("default") var w = fnt.TextWidth(c.Text) - return geom.PtF(float64(w+2*leftMargin+checkboxSize), 24) + return geom.PtF(float64(w+checkboxSize+leftMargin), checkboxSize) } func (c *Checkbox) box() *allegro5.Bitmap { if c.Value { - return c.on + return c.checked } - return c.off + return c.unchecked } func (c *Checkbox) Render(ctx Context) { var fonts = ctx.Fonts() var min = c.Bounds.Min.To32() - min = geom.PointF32{X: min.X + leftMargin, Y: min.Y + topMargin} - var fnt = fonts.Get("default") - var _, textY, _, textH = fnt.TextDims(c.Text) - fnt.Draw(min.X+leftMargin+checkboxSize, min.Y-textY, ctx.Palette().Black(), allegro5.AlignLeft, c.Text) - var checkboxTop = min.Y + textH - checkboxSize + fnt.Draw(min.X+checkboxSize, min.Y-.67*fnt.Ascent()+.5*checkboxSize, ctx.Palette().Darkest(), allegro5.AlignLeft, c.Text) if c.Disabled { var disabled = ctx.Palette().Disabled() - c.box().DrawOptions(min.X, checkboxTop, allegro5.DrawOptions{Tint: &disabled}) + c.box().DrawOptions(min.X, min.Y, allegro5.DrawOptions{Tint: &disabled}) } else { - c.box().Draw(min.X, checkboxTop) + c.box().Draw(min.X, min.Y) } c.ControlBase.Render(ctx) diff --git a/ui/context.go b/ui/context.go index 095c917..6bf0d1e 100644 --- a/ui/context.go +++ b/ui/context.go @@ -32,7 +32,7 @@ type debug struct { func rainbow() []allegro5.Color { var colors = make([]allegro5.Color, len(Colors500)) for i, c := range Colors500 { - colors[i] = NewColorAlpha(c, 0xbf) + colors[i] = NewColorAlpha(c, 0x7f) } return colors } diff --git a/ui/dimensions.go b/ui/dimensions.go index d492ad6..30f7444 100644 --- a/ui/dimensions.go +++ b/ui/dimensions.go @@ -2,4 +2,6 @@ package ui const topMargin = 4 const leftMargin = 8 -const checkboxSize = 12 +const lineHeight = 16 +const checkboxMargin = 4 +const checkboxSize = 24 diff --git a/ui/draw.go b/ui/draw.go new file mode 100644 index 0000000..c022d29 --- /dev/null +++ b/ui/draw.go @@ -0,0 +1,38 @@ +package ui + +import ( + "image" + "image/color" + "math" + + "github.com/llgcode/draw2d/draw2dimg" + "opslag.de/schobers/galleg/allegro5" +) + +func drawBitmap(w, h int, draw func(*draw2dimg.GraphicContext)) *allegro5.Bitmap { + dest := image.NewRGBA(image.Rect(0, 0, w, h)) + gc := draw2dimg.NewGraphicContext(dest) + gc.SetFillColor(color.Transparent) + gc.Clear() + draw(gc) + bmp, err := allegro5.NewBitmapFromImage(dest, false) + if nil != err { + return nil + } + return bmp +} + +func drawCircle(r, w int, startAngle, a float64, c color.Color) *allegro5.Bitmap { + var width = 2*r + w + return drawBitmap(width, width, func(gc *draw2dimg.GraphicContext) { + gc.SetFillColor(c) + gc.SetStrokeColor(c) + gc.SetLineWidth(float64(w)) + var rad = float64(r) + var cnt = float64(width) * .5 + var dx1, dy1 = cnt + math.Cos(startAngle)*rad, cnt + math.Sin(startAngle)*rad + gc.MoveTo(dx1, dy1) + gc.ArcTo(cnt, cnt, rad, rad, startAngle, a) + gc.Stroke() + }) +} diff --git a/ui/label.go b/ui/label.go index 3852fc8..ef55689 100644 --- a/ui/label.go +++ b/ui/label.go @@ -13,7 +13,7 @@ func (l *Label) Render(ctx Context) { var min = l.Bounds.Min.To32() var fnt = fonts.Get("default") - fnt.Draw(min.X+leftMargin, min.Y+topMargin, ctx.Palette().Black(), allegro5.AlignLeft, l.Text) + fnt.Draw(min.X+leftMargin, min.Y+lineHeight-fnt.Ascent(), ctx.Palette().Darkest(), allegro5.AlignLeft, l.Text) l.ControlBase.Render(ctx) } diff --git a/ui/palette.go b/ui/palette.go index e0bd471..cf3131a 100644 --- a/ui/palette.go +++ b/ui/palette.go @@ -8,27 +8,42 @@ import ( type Palette interface { Primary() allegro5.Color - White() allegro5.Color - Black() allegro5.Color + PrimaryTransparent() allegro5.Color + Lightest() allegro5.Color + Darker() allegro5.Color + Darkest() allegro5.Color Disabled() allegro5.Color } type palette struct { primary allegro5.Color - white allegro5.Color - black allegro5.Color + primaryT allegro5.Color + lightest allegro5.Color + darker allegro5.Color + darkest allegro5.Color disabled allegro5.Color } func (p *palette) Primary() allegro5.Color { return p.primary } -func (p *palette) White() allegro5.Color { - return p.white + +func (p *palette) PrimaryTransparent() allegro5.Color { + return p.primaryT } -func (p *palette) Black() allegro5.Color { - return p.black + +func (p *palette) Lightest() allegro5.Color { + return p.lightest } + +func (p *palette) Darker() allegro5.Color { + return p.darker +} + +func (p *palette) Darkest() allegro5.Color { + return p.darkest +} + func (p *palette) Disabled() allegro5.Color { return p.disabled } @@ -42,10 +57,13 @@ func NewColorAlpha(c *color.RGBA, a uint8) allegro5.Color { } func DefaultPalette() Palette { + var primary = Blue500 return &palette{ - primary: NewColor(Blue500), - white: allegro5.NewColor(0xff, 0xff, 0xff), - black: allegro5.NewColor(0, 0, 0), + primary: NewColor(primary), + primaryT: NewColorAlpha(primary, 96), + lightest: allegro5.NewColor(0xff, 0xff, 0xff), + darker: allegro5.NewColorAlpha(0, 0, 0, 188), + darkest: allegro5.NewColorAlpha(0, 0, 0, 222), disabled: allegro5.NewColorAlpha(0x1f, 0x1f, 0x1f, 0x1f), } } diff --git a/ui/scrollbar.go b/ui/scrollbar.go index f0abb70..8170939 100644 --- a/ui/scrollbar.go +++ b/ui/scrollbar.go @@ -3,13 +3,15 @@ package ui import ( "math" + "github.com/llgcode/draw2d/draw2dimg" + "opslag.de/schobers/galleg/allegro5" "opslag.de/schobers/geom" ) var _ Control = &Scrollbar{} -const ScrollbarWidth = 26 +const ScrollbarWidth = 12 const ScrollbarHandleThickness = 4 const ScrollbarHandlePadding = 1 @@ -23,27 +25,57 @@ type Scrollbar struct { Orientation Orientation OnChanged ScrollbarValueChangedFn handle *ControlBase - handles []*allegro5.Bitmap + normal *allegro5.Bitmap + hover *allegro5.Bitmap + pressed *allegro5.Bitmap } func (s *Scrollbar) Created(ctx Context, p Container) error { s.ControlBase.Created(ctx, p) - s.handles = []*allegro5.Bitmap{ - createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue500), - createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue300), - createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue700), - } + var center = float64(ScrollbarWidth) * 1.5 + var rad = float64(ScrollbarWidth) * .5 + s.normal = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) { + gc.SetFillColor(ctx.Palette().Primary()) + gc.MoveTo(center, center) + gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi) + gc.Fill() + }) + s.hover = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) { + gc.SetFillColor(ctx.Palette().PrimaryTransparent()) + gc.MoveTo(center, center) + gc.ArcTo(center, center, center, center, 0, 2*math.Pi) + gc.Fill() + gc.SetFillColor(ctx.Palette().Primary()) + gc.MoveTo(center, center) + gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi) + gc.Fill() + }) + s.pressed = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) { + gc.SetFillColor(ctx.Palette().PrimaryTransparent()) + for i := 0; i < 2; i++ { + gc.MoveTo(center, center) + gc.ArcTo(center, center, center, center, 0, 2*math.Pi) + gc.Fill() + } + gc.SetFillColor(ctx.Palette().Primary()) + gc.MoveTo(center, center) + gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi) + gc.Fill() + }) s.handle = &ControlBase{} s.handle.Created(ctx, nil) return nil } func (s *Scrollbar) Destroyed(ctx Context) { - if nil != s.handles { - for _, h := range s.handles { - h.Destroy() + var d = func(b *allegro5.Bitmap) { + if nil != b { + b.Destroy() } } + d(s.normal) + d(s.hover) + d(s.pressed) s.handle.Destroyed(ctx) } @@ -52,12 +84,12 @@ func (s *Scrollbar) barViewRange() (float64, float64) { var min, max float64 switch s.Orientation { case OrientationHorizontal: - min = s.Bounds.Min.X + ScrollbarWidth*.5 - max = s.Bounds.Max.X - ScrollbarWidth*.5 + min = s.Bounds.Min.X + ScrollbarWidth + max = s.Bounds.Max.X - ScrollbarWidth small = min > max default: - min = s.Bounds.Max.Y - ScrollbarWidth*.5 - max = s.Bounds.Min.Y + ScrollbarWidth*.5 + min = s.Bounds.Max.Y - ScrollbarWidth + max = s.Bounds.Min.Y + ScrollbarWidth small = max > min } if small { @@ -143,22 +175,24 @@ func (s *Scrollbar) Handle(ctx Context, ev allegro5.Event) { } func (s *Scrollbar) DesiredSize(Context) geom.PointF { + var width = float64(2 * ScrollbarWidth) switch s.Orientation { case OrientationHorizontal: - return geom.PtF(math.NaN(), ScrollbarWidth) + return geom.PtF(math.NaN(), width) } - return geom.PtF(ScrollbarWidth, math.NaN()) + return geom.PtF(width, math.NaN()) } func (s *Scrollbar) SetRect(rect geom.RectangleF) { + var width = float64(2 * ScrollbarWidth) switch s.Orientation { case OrientationHorizontal: - if rect.Dy() > ScrollbarWidth { - rect.Min.Y = rect.Max.Y - ScrollbarWidth + if rect.Dy() > width { + rect.Min.Y = rect.Max.Y - width } default: - if rect.Dx() > ScrollbarWidth { - rect.Min.X = rect.Max.X - ScrollbarWidth + if rect.Dx() > width { + rect.Min.X = rect.Max.X - width } } s.ControlBase.SetRect(rect) @@ -166,7 +200,7 @@ func (s *Scrollbar) SetRect(rect geom.RectangleF) { var min, max = s.barViewRange() var off = float64(s.Value-s.Minimum) / float64(s.Maximum-s.Minimum) var centerH = min + (max-min)*off - var r = 0.5 * float64(s.handles[0].Width()) + var r = 0.5 * float64(s.normal.Width()) var center = s.barViewCenter() switch s.Orientation { @@ -182,32 +216,28 @@ func (s *Scrollbar) Render(ctx Context) { var min64, max64 = s.barViewRange() var min, max = float32(min64), float32(max64) - var minH = s.handle.Bounds.Min.To32() - var stateH = 0 - if s.handle.IsOver { - stateH = 1 - if s.handle.IsPressed { - stateH = 2 - } - } - s.handles[stateH].Draw(minH.X, minH.Y) - var maxH = s.handle.Bounds.Max.To32() + var centerH = s.handle.Bounds.Center().To32() switch s.Orientation { case OrientationHorizontal: - if min < minH.X-1 { // Top line - allegro5.DrawLine(min, center, minH.X-1, center, ctx.Palette().Black(), 1) - } - if max > maxH.X+1 { // Bottom line - allegro5.DrawLine(maxH.X+1, center, max, center, ctx.Palette().Black(), 1) - } + // Left line + allegro5.DrawLine(min, center, centerH.X, center, ctx.Palette().Primary(), 2) + allegro5.DrawLine(centerH.X, center, max, center, ctx.Palette().PrimaryTransparent(), 2) default: - if max < minH.Y-1 { // Top line - allegro5.DrawLine(center, max, center, minH.Y-1, ctx.Palette().Black(), 1) - } - if min > maxH.Y+1 { // Bottom line - allegro5.DrawLine(center, maxH.Y+1, center, min, ctx.Palette().Black(), 1) + allegro5.DrawLine(center, max, center, centerH.Y, ctx.Palette().PrimaryTransparent(), 2) + allegro5.DrawLine(center, centerH.Y, center, min, ctx.Palette().Primary(), 2) + } + + var minH = s.handle.Bounds.Min.To32() + var state = s.normal + if s.handle.IsOver { + if s.handle.IsPressed { + state = s.pressed + } else { + state = s.hover } } + state.Draw(minH.X, minH.Y) + s.ControlBase.Render(ctx) } diff --git a/ui/spinner.go b/ui/spinner.go index b4fa8b5..486e3cf 100644 --- a/ui/spinner.go +++ b/ui/spinner.go @@ -1,12 +1,11 @@ package ui import ( - "image" - "image/color" "math" "time" "github.com/llgcode/draw2d/draw2dimg" + "opslag.de/schobers/galleg/allegro5" ) @@ -16,53 +15,56 @@ type Spinner struct { ControlBase Text string spin float32 - circs []*allegro5.Bitmap -} - -func createCirle(r, w int, startAngle, a float64, c color.Color) *allegro5.Bitmap { - var width = 2*r + w - dest := image.NewRGBA(image.Rect(0, 0, width, width)) - - gc := draw2dimg.NewGraphicContext(dest) - gc.SetFillColor(color.Transparent) - gc.Clear() - gc.SetFillColor(c) - gc.SetStrokeColor(c) - gc.SetLineWidth(float64(w)) - var rad = float64(r) - var cnt = float64(width) * .5 - var dx1, dy1 = cnt + math.Cos(startAngle)*rad, cnt + math.Sin(startAngle)*rad - gc.MoveTo(dx1, dy1) - gc.ArcTo(cnt, cnt, rad, rad, startAngle, a) - gc.Stroke() - - bmp, err := allegro5.NewBitmapFromImage(dest, false) - if nil != err { - return nil - } - return bmp + circs *allegro5.Bitmap } func (s *Spinner) Created(ctx Context, p Container) error { s.ControlBase.Created(ctx, p) - const numCircs = 32 - s.circs = make([]*allegro5.Bitmap, numCircs) - var am = math.Pi * 2 / float64(numCircs) - for i := range s.circs { - s.circs[i] = createCirle(8, 3, float64(i)*am, math.Pi, Blue500) + const row = 6 + const numCircs = row * row + const width = 48 + const center = width * .5 + const full = 2 * math.Pi + s.circs = drawBitmap(row*width, row*width, func(gc *draw2dimg.GraphicContext) { + var start, a float64 = 0, .2 * math.Pi + var inc = true + gc.SetLineWidth(4) + gc.SetStrokeColor(ctx.Palette().Primary()) + for i := 0; i < numCircs; i++ { + var left = float64(i%row) * width + var top = float64(i/row) * width + gc.ArcTo(left+center, top+center, center-8, center-8, start, a) + gc.Stroke() + if inc { + start += full / numCircs + a += 1.90 * full / numCircs + if a >= full { + inc = false + } + } else { + start += 2.90 * full / numCircs + a -= 1.90 * full / numCircs + // if a <= .1*math.Pi { + // inc = true + // } + } + } + }) + for i := 0; i < numCircs; i++ { + var left = (i % row) * width + var top = (i / row) * width + s.circs.Sub(left, top, width, width) } return nil } func (s *Spinner) Destroyed(ctx Context) { - for _, circ := range s.circs { - circ.Destroy() - } + s.circs.Destroy() } func (s *Spinner) Update(ctx Context, dt time.Duration) { var spin = float64(s.spin) - spin += dt.Seconds() + spin += dt.Seconds() * .5 for spin > 1. { spin -= 1. } @@ -85,13 +87,13 @@ func (s *Spinner) Render(ctx Context) { const textH float32 = 12 var rectW, rectH float32 = textW + 2*marginH, 3*marginV + textH + 32 var rectX, rectY = (width - rectW) * .5, (height - rectH) * .5 - allegro5.DrawFilledRectangle(rectX, rectY, rectX+rectW, rectY+rectH, ctx.Palette().White()) + allegro5.DrawFilledRectangle(rectX, rectY, rectX+rectW, rectY+rectH, ctx.Palette().Lightest()) DropShadow(rectX, rectY, rectX+rectW, rectY+rectH) - fnt.Draw(rectX+marginH, rectY+marginV, ctx.Palette().Black(), allegro5.AlignLeft, s.Text) + fnt.Draw(rectX+marginH, rectY+marginV, ctx.Palette().Darkest(), allegro5.AlignLeft, s.Text) - const numCircs = 32 + const numCircs = 36 var i = int(math.Floor(float64(s.spin) * numCircs)) - s.circs[i].DrawOptions(width*.5, rectY+2*marginV+2*textH, allegro5.DrawOptions{Center: true}) + s.circs.Subs()[i].DrawOptions(width*.5, rectY+2*marginV+2*textH, allegro5.DrawOptions{Center: true}) s.ControlBase.Render(ctx) } diff --git a/ui/statusbar.go b/ui/statusbar.go index 90e2224..9cca90d 100644 --- a/ui/statusbar.go +++ b/ui/statusbar.go @@ -29,8 +29,9 @@ func (b *StatusBar) Render(ctx Context) { allegro5.DrawFilledRectangle(min.X, min.Y, max.X, max.Y, ctx.Palette().Primary()) var fnt = fonts.Get("default") - fnt.Draw(min.X+leftMargin, min.Y+topMargin, ctx.Palette().White(), allegro5.AlignLeft, b.Text) - fnt.Draw(max.X-leftMargin, min.Y+topMargin, ctx.Palette().White(), allegro5.AlignRight, b.RightText) + var y = min.Y + float32(.5*b.Bounds.Dy()) - .67*fnt.Ascent() + fnt.Draw(min.X+leftMargin, y, ctx.Palette().Lightest(), allegro5.AlignLeft, b.Text) + fnt.Draw(max.X-leftMargin, y, ctx.Palette().Lightest(), allegro5.AlignRight, b.RightText) b.ControlBase.Render(ctx) }