From 432281f08ddc251f2be52e7a65de44a53bdaf7af Mon Sep 17 00:00:00 2001 From: Sander Schobers Date: Mon, 8 Jul 2019 19:12:10 +0200 Subject: [PATCH] Added Slider. Refactored Icon related methods & added alpha support for icon/image generation. --- ui/checkbox.go | 6 +- ui/icon.go | 53 +++++++++++++---- ui/slider.go | 153 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 ui/slider.go diff --git a/ui/checkbox.go b/ui/checkbox.go index 82ee165..5358701 100644 --- a/ui/checkbox.go +++ b/ui/checkbox.go @@ -65,7 +65,7 @@ func (c *Checkbox) checkMark() geom.PointsF32 { } } -func (c *Checkbox) hoverIcon() IconPixelTestFn { +func (c *Checkbox) hoverIcon() ImagePixelTestFn { border := c.iconBorder() check := c.checkMark() return func(pt geom.PointF32) bool { @@ -73,14 +73,14 @@ func (c *Checkbox) hoverIcon() IconPixelTestFn { } } -func (c *Checkbox) normalIcon() IconPixelTestFn { +func (c *Checkbox) normalIcon() ImagePixelTestFn { border := c.iconBorder() return func(pt geom.PointF32) bool { return pt.DistanceToPolygon(border) < 48 && !pt.InPolygon(border) } } -func (c *Checkbox) selectedIcon() IconPixelTestFn { +func (c *Checkbox) selectedIcon() ImagePixelTestFn { border := c.iconBorder() check := c.checkMark() return func(pt geom.PointF32) bool { diff --git a/ui/icon.go b/ui/icon.go index 8500a75..ff80722 100644 --- a/ui/icon.go +++ b/ui/icon.go @@ -7,23 +7,38 @@ import ( "opslag.de/schobers/geom" ) -type IconPixelTestFn func(geom.PointF32) bool +type ImagePixelTestFn func(geom.PointF32) bool +type ImageAlphaPixelTestFn func(geom.PointF32) uint8 -func IconSize() geom.Point { - return geom.Pt(448, 512) -} - -func CreateIcon(ctx Context, test IconPixelTestFn) Image { - icon := DrawIcon(test) - im, err := ctx.Renderer().CreateImage(icon) +func createImage(ctx Context, image image.Image) Image { + im, err := ctx.Renderer().CreateImage(image) if err != nil { return nil } return im } -func DrawIcon(test IconPixelTestFn) image.Image { +func CreateIcon(ctx Context, test ImagePixelTestFn) Image { + icon := DrawIcon(test) + return createImage(ctx, icon) +} + +func CreateImage(ctx Context, size geom.Point, test ImagePixelTestFn) Image { + image := DrawImage(size, test) + return createImage(ctx, image) +} + +func CreateImageAlpha(ctx Context, size geom.Point, test ImageAlphaPixelTestFn) Image { + image := DrawImageAlpha(size, test) + return createImage(ctx, image) +} + +func DrawIcon(test ImagePixelTestFn) image.Image { size := IconSize() + return DrawImage(size, test) +} + +func DrawImage(size geom.Point, test ImagePixelTestFn) image.Image { r := image.Rect(0, 0, size.X, size.Y) icon := image.NewRGBA(r) for y := 0; y < size.Y; y++ { @@ -37,7 +52,21 @@ func DrawIcon(test IconPixelTestFn) image.Image { return icon } -func GetOrCreateIcon(ctx Context, name string, testFactory func() IconPixelTestFn) Image { +func DrawImageAlpha(size geom.Point, test ImageAlphaPixelTestFn) image.Image { + r := image.Rect(0, 0, size.X, size.Y) + icon := image.NewAlpha(r) + for y := 0; y < size.Y; y++ { + for x := 0; x < size.X; x++ { + pt := geom.PtF32(float32(x), float32(y)) + if a := test(pt); a > 0 { + icon.Set(x, y, color.Alpha{A: a}) + } + } + } + return icon +} + +func GetOrCreateIcon(ctx Context, name string, testFactory func() ImagePixelTestFn) Image { im := ctx.Images().Image(name) if im != nil { return im @@ -50,3 +79,7 @@ func GetOrCreateIcon(ctx Context, name string, testFactory func() IconPixelTestF ctx.Images().AddImage(name, im) return im } + +func IconSize() geom.Point { + return geom.Pt(448, 512) +} diff --git a/ui/slider.go b/ui/slider.go new file mode 100644 index 0000000..6eb5051 --- /dev/null +++ b/ui/slider.go @@ -0,0 +1,153 @@ +package ui + +import ( + "opslag.de/schobers/geom" +) + +type Slider struct { + ControlBase + + Orientation Orientation + Minimum float32 + Maximum float32 + Value float32 + + handleWidth float32 + handle sliderHandle +} + +func BuildSlider(o Orientation, fn func(s *Slider)) *Slider { + s := &Slider{Orientation: o} + s.handle.OnDragStart(func(start geom.PointF32) { s.handleDrag(start) }) + s.handle.OnDragMove(func(_, pos geom.PointF32) { s.handleDrag(pos) }) + if fn != nil { + fn(s) + } + return s +} + +func (s *Slider) handleDrag(pos geom.PointF32) { + start, length, _ := s.offsets() + var offset float32 + if s.Orientation == OrientationHorizontal { + offset = pos.X + } else { + offset = pos.Y + } + s.Value = s.Minimum + ((offset-start)/length)*(s.Maximum-s.Minimum) + if s.Minimum < s.Maximum { + s.Value = geom.Min32(s.Maximum, geom.Max32(s.Minimum, s.Value)) + } else { + s.Value = geom.Min32(s.Minimum, geom.Max32(s.Maximum, s.Value)) + } +} + +func (s *Slider) offsets() (start, length, center float32) { + bounds := s.Bounds() + if s.Orientation == OrientationHorizontal { + start = bounds.Min.X + length = bounds.Dx() + center = .5 * (bounds.Min.Y + bounds.Max.Y) + } else { + start = bounds.Max.Y + length = -bounds.Dy() + center = .5 * (bounds.Min.X + bounds.Max.X) + } + if geom.Abs32(length) < s.handleWidth { + start += .5 * length + length = 0 + } else { + if length < 0 { + start -= .5 * s.handleWidth + length += s.handleWidth + } else { + start += .5 * s.handleWidth + length -= s.handleWidth + } + } + return +} + +func (s *Slider) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) { + s.ControlBase.Arrange(ctx, bounds, offset, parent) + + s.handleWidth = ctx.Style().Dimensions.ScrollbarWidth + start, length, center := s.offsets() + w := s.handleWidth + w05 := .5 * w + handleOffset := (s.Value - s.Minimum) / (s.Maximum - s.Minimum) + if !geom.IsNaN32(handleOffset) { + start += handleOffset * length + } + if s.Orientation == OrientationHorizontal { + s.handle.Arrange(ctx, geom.RectF32(start-w05, center-w05, start+w05, center+w05), offset, s) + } else { + s.handle.Arrange(ctx, geom.RectF32(center-w05, start-w05, center+w05, start+w05), offset, s) + } +} + +func (s *Slider) DesiredSize(ctx Context) geom.PointF32 { + w := ctx.Style().Dimensions.ScrollbarWidth + if s.Orientation == OrientationHorizontal { + return geom.PtF32(geom.NaN32(), w) + } + return geom.PtF32(w, geom.NaN32()) +} + +func (s *Slider) Handle(ctx Context, e Event) { + s.handle.Handle(ctx, e) + s.ControlBase.Handle(ctx, e) +} + +func (s *Slider) Render(ctx Context) { + s.RenderBackground(ctx) + prim := ctx.Style().Palette.Primary + start, length, center := s.offsets() + if s.Orientation == OrientationHorizontal { + ctx.Renderer().FillRectangle(geom.RectF32(start, center-1, start+length, center+1), prim) + } else { + ctx.Renderer().FillRectangle(geom.RectF32(center-1, start, center+1, start+length), prim) + } + s.handle.Render(ctx) +} + +type sliderHandle struct { + ControlBase + + handle Image +} + +func (h *sliderHandle) im(ctx Context) Image { + if h.handle != nil { + return h.handle + } + size := h.Bounds().Size() + center := geom.PtF32(.5*size.X-.5, .5*size.Y-.5) + radius := 0.5 * geom.Min32(size.X, size.Y) + h.handle = CreateImageAlpha(ctx, geom.Pt(int(size.X), int(size.Y)), func(p geom.PointF32) uint8 { + dist := center.Distance(p) + if dist < radius { + if dist < (radius - 1) { + return 255 + } + return uint8(255 * (radius - dist)) + } + return 0 + }) + return h.handle +} + +func (h *sliderHandle) Handle(ctx Context, e Event) { + h.ControlBase.Handle(ctx, e) + if h.IsOver() { + ctx.Renderer().SetMouseCursor(MouseCursorPointer) + } +} + +func (h *sliderHandle) Render(ctx Context) { + color := ctx.Style().Palette.Primary + if h.IsOver() { + color = ctx.Style().Palette.PrimaryLight + } + ctx.Renderer().DrawImageOptions(h.im(ctx), h.Bounds().Min, DrawOptions{Tint: color}) +}