From d0413e0a13dd89fbae52ab0f2f10a66cf8056fd4 Mon Sep 17 00:00:00 2001 From: "5684185+vsariola@users.noreply.github.com" <5684185+vsariola@users.noreply.github.com> Date: Sun, 27 Apr 2025 09:00:13 +0300 Subject: [PATCH] feat(tracker/gioui): rewrite the numeric updown, with new appearance --- CHANGELOG.md | 1 + tracker/gioui/numericupdown.go | 200 +++++++++++++-------------------- tracker/gioui/theme.go | 2 + 3 files changed, 83 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa4188..bc05689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ([#156][i156]) ### Changed +- The numeric updown widget has a new appearance. - The draggable UI splitters snap more controllably to the window edges. - New & better presets, organized by their type to subfolders (thanks Reaby!) ([#136][i136]) diff --git a/tracker/gioui/numericupdown.go b/tracker/gioui/numericupdown.go index 3b788bc..2fa9540 100644 --- a/tracker/gioui/numericupdown.go +++ b/tracker/gioui/numericupdown.go @@ -1,14 +1,15 @@ package gioui import ( - "fmt" "image" "image/color" + "strconv" "github.com/vsariola/sointu/tracker" "golang.org/x/exp/shiny/materialdesign/icons" "gioui.org/font" + "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/widget" @@ -18,7 +19,6 @@ import ( "gioui.org/io/event" "gioui.org/io/pointer" "gioui.org/layout" - "gioui.org/op" "gioui.org/text" "gioui.org/unit" "gioui.org/widget/material" @@ -56,16 +56,11 @@ func NewNumberInput(v tracker.Int) *NumberInput { } func NumericUpDown(th *material.Theme, number *NumberInput, tooltip string) NumericUpDownStyle { - bgColor := th.Palette.Fg - bgColor.R /= 4 - bgColor.G /= 4 - bgColor.B /= 4 return NumericUpDownStyle{ NumberInput: number, Color: white, - BorderColor: th.Palette.Fg, - IconColor: th.Palette.ContrastFg, - BackgroundColor: bgColor, + IconColor: th.Palette.Fg, + BackgroundColor: numberInputBgColor, CornerRadius: unit.Dp(4), ButtonWidth: unit.Dp(16), Border: unit.Dp(1), @@ -78,6 +73,43 @@ func NumericUpDown(th *material.Theme, number *NumberInput, tooltip string) Nume } } +func (s *NumericUpDownStyle) Update(gtx layout.Context) { + // handle dragging + pxPerStep := float32(gtx.Dp(s.UnitsPerStep)) + for { + ev, ok := gtx.Event(pointer.Filter{ + Target: s.NumberInput, + Kinds: pointer.Press | pointer.Drag | pointer.Release, + }) + if !ok { + break + } + if e, ok := ev.(pointer.Event); ok { + switch e.Kind { + case pointer.Press: + s.NumberInput.dragStartValue = s.NumberInput.Int.Value() + s.NumberInput.dragStartXY = e.Position.X - e.Position.Y + case pointer.Drag: + var deltaCoord float32 + deltaCoord = e.Position.X - e.Position.Y - s.NumberInput.dragStartXY + s.NumberInput.Int.Set(s.NumberInput.dragStartValue + int(deltaCoord/pxPerStep+0.5)) + } + } + } + // handle decrease clicks + for ev, ok := s.NumberInput.clickDecrease.Update(gtx.Source); ok; ev, ok = s.NumberInput.clickDecrease.Update(gtx.Source) { + if ev.Kind == gesture.KindClick { + s.NumberInput.Int.Add(-1) + } + } + // handle increase clicks + for ev, ok := s.NumberInput.clickIncrease.Update(gtx.Source); ok; ev, ok = s.NumberInput.clickIncrease.Update(gtx.Source) { + if ev.Kind == gesture.KindClick { + s.NumberInput.Int.Add(1) + } + } +} + func (s *NumericUpDownStyle) Layout(gtx C) D { if s.Tooltip.Text.Text != "" { return s.NumberInput.tipArea.Layout(gtx, s.Tooltip, s.actualLayout) @@ -85,119 +117,47 @@ func (s *NumericUpDownStyle) Layout(gtx C) D { return s.actualLayout(gtx) } -func (s *NumericUpDownStyle) actualLayout(gtx C) D { - size := image.Pt(gtx.Dp(s.Width), gtx.Dp(s.Height)) - gtx.Constraints.Min = size - rr := gtx.Dp(s.CornerRadius) - border := gtx.Dp(s.Border) - c := clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops) - paint.Fill(gtx.Ops, s.BorderColor) - c.Pop() - off := op.Offset(image.Pt(border, border)).Push(gtx.Ops) - c2 := clip.UniformRRect(image.Rectangle{Max: image.Pt( - gtx.Constraints.Min.X-border*2, - gtx.Constraints.Min.Y-border*2, - )}, rr-border).Push(gtx.Ops) - gtx.Constraints.Min.X -= int(border * 2) - gtx.Constraints.Min.Y -= int(border * 2) - gtx.Constraints.Max = gtx.Constraints.Min - layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(s.button(gtx.Constraints.Max.Y, widgetForIcon(icons.NavigationArrowBack), -1, &s.NumberInput.clickDecrease)), - layout.Flexed(1, s.layoutText), - layout.Rigid(s.button(gtx.Constraints.Max.Y, widgetForIcon(icons.NavigationArrowForward), 1, &s.NumberInput.clickIncrease)), - ) - off.Pop() - c2.Pop() - return layout.Dimensions{Size: size} -} - -func (s *NumericUpDownStyle) button(height int, icon *widget.Icon, delta int, click *gesture.Click) layout.Widget { - return func(gtx C) D { - width := gtx.Dp(s.ButtonWidth) - return layout.Background{}.Layout(gtx, - func(gtx C) D { - if icon != nil { - return icon.Layout(gtx, s.IconColor) - } - return layout.Dimensions{Size: image.Point{X: width, Y: height}} - }, - func(gtx C) D { - gtx.Constraints = layout.Exact(image.Pt(width, height)) - return s.layoutClick(gtx, delta, click) - }) - } -} - -func (s *NumericUpDownStyle) layoutText(gtx C) D { +func (s NumericUpDownStyle) actualLayout(gtx C) D { + s.Update(gtx) + gtx.Constraints = layout.Exact(image.Pt(gtx.Dp(s.Width), gtx.Dp(s.Height))) + width := gtx.Dp(s.ButtonWidth) + height := gtx.Dp(s.Height) return layout.Background{}.Layout(gtx, func(gtx C) D { - paint.FillShape(gtx.Ops, s.BackgroundColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op()) - paint.ColorOp{Color: s.Color}.Add(gtx.Ops) - return widget.Label{Alignment: text.Middle}.Layout(gtx, &s.shaper, s.Font, s.TextSize, fmt.Sprintf("%v", s.NumberInput.Int.Value()), op.CallOp{}) + defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, gtx.Dp(s.CornerRadius)).Push(gtx.Ops).Pop() + paint.Fill(gtx.Ops, s.BackgroundColor) + event.Op(gtx.Ops, s.NumberInput) // register drag inputs, if not hitting the clicks + return D{Size: gtx.Constraints.Min} }, func(gtx C) D { - gtx.Constraints.Min = gtx.Constraints.Max - return s.layoutDrag(gtx) - }) -} - -func (s *NumericUpDownStyle) layoutDrag(gtx layout.Context) layout.Dimensions { - { // handle dragging - pxPerStep := float32(gtx.Dp(s.UnitsPerStep)) - for { - ev, ok := gtx.Event(pointer.Filter{ - Target: s.NumberInput, - Kinds: pointer.Press | pointer.Drag | pointer.Release, - }) - if !ok { - break - } - if e, ok := ev.(pointer.Event); ok { - switch e.Kind { - case pointer.Press: - s.NumberInput.dragStartValue = s.NumberInput.Int.Value() - s.NumberInput.dragStartXY = e.Position.X - e.Position.Y - - case pointer.Drag: - var deltaCoord float32 - deltaCoord = e.Position.X - e.Position.Y - s.NumberInput.dragStartXY - s.NumberInput.Int.Set(s.NumberInput.dragStartValue + int(deltaCoord/pxPerStep+0.5)) - } - } - } - - // Avoid affecting the input tree with pointer events. - stack := op.Offset(image.Point{}).Push(gtx.Ops) - // register for input - dragRect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y) - area := clip.Rect(dragRect).Push(gtx.Ops) - event.Op(gtx.Ops, s.NumberInput) - area.Pop() - stack.Pop() - } - return layout.Dimensions{Size: gtx.Constraints.Min} -} - -func (s *NumericUpDownStyle) layoutClick(gtx layout.Context, delta int, click *gesture.Click) layout.Dimensions { - // handle clicking - for { - ev, ok := click.Update(gtx.Source) - if !ok { - break - } - switch ev.Kind { - case gesture.KindClick: - s.NumberInput.Int.Add(delta) - } - } - // Avoid affecting the input tree with pointer events. - stack := op.Offset(image.Point{}).Push(gtx.Ops) - - // register for input - clickRect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y) - area := clip.Rect(clickRect).Push(gtx.Ops) - click.Add(gtx.Ops) - area.Pop() - stack.Pop() - return layout.Dimensions{Size: gtx.Constraints.Min} + return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + gtx.Constraints = layout.Exact(image.Pt(width, height)) + return layout.Background{}.Layout(gtx, + func(gtx C) D { + defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop() + s.NumberInput.clickDecrease.Add(gtx.Ops) + return D{Size: gtx.Constraints.Min} + }, + func(gtx C) D { return widgetForIcon(icons.ContentRemove).Layout(gtx, s.IconColor) }, + ) + }), + layout.Flexed(1, func(gtx C) D { + paint.ColorOp{Color: s.Color}.Add(gtx.Ops) + return widget.Label{Alignment: text.Middle}.Layout(gtx, &s.shaper, s.Font, s.TextSize, strconv.Itoa(s.NumberInput.Int.Value()), op.CallOp{}) + }), + layout.Rigid(func(gtx C) D { + gtx.Constraints = layout.Exact(image.Pt(width, height)) + return layout.Background{}.Layout(gtx, + func(gtx C) D { + defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop() + s.NumberInput.clickIncrease.Add(gtx.Ops) + return D{Size: gtx.Constraints.Min} + }, + func(gtx C) D { return widgetForIcon(icons.ContentAdd).Layout(gtx, s.IconColor) }, + ) + }), + ) + }, + ) } diff --git a/tracker/gioui/theme.go b/tracker/gioui/theme.go index 1df08c7..063414d 100644 --- a/tracker/gioui/theme.go +++ b/tracker/gioui/theme.go @@ -57,6 +57,8 @@ var dragListHoverColor = color.NRGBA{R: 42, G: 45, B: 61, A: 255} var inactiveLightSurfaceColor = color.NRGBA{R: 37, G: 37, B: 38, A: 255} var activeLightSurfaceColor = color.NRGBA{R: 45, G: 45, B: 45, A: 255} +var numberInputBgColor = color.NRGBA{R: 255, G: 255, B: 255, A: 3} + var cursorColor = color.NRGBA{R: 100, G: 140, B: 255, A: 48} var selectionColor = color.NRGBA{R: 100, G: 140, B: 255, A: 12} var inactiveSelectionColor = color.NRGBA{R: 140, G: 140, B: 140, A: 16}