diff --git a/docs/components/034-slider.md b/docs/components/034-slider.md index 97b36c8..129d48e 100644 --- a/docs/components/034-slider.md +++ b/docs/components/034-slider.md @@ -45,6 +45,6 @@ ui.Render(view, 400, 300, 1) ## 限制 - 基于 `ui.SliderBase`,它向辅助技术报告 Slider 角色、范围与当前值(ui/base.go:128)。 -- MyGo 只把方向键、Home、End 交给 `SliderBase` 本身(ui/base.go:118,按键路由见 ui/input.go:495),以范围的 1% 移动并把结果舍入到 1e-9(ui/widgets.go:375)。本组件让它操作副本,按方向判断按键后自己按 `Step` 移动。两种情况下按键结果无法区分:距端点不足 1% 时方向键与 Home/End 结果相同;步长细到 MyGo 舍入后不动。这时组件改看松开的是哪个键(`HandleInput` 能收到 KeyUp),在键松开时才改值;按住不放也只移动一步。 +- MyGo 只把方向键、Home、End 的按下交给 `SliderBase` 本身(ui/base.go:118,按键路由见 ui/input.go:495),以范围的 1% 移动并把结果舍入到 1e-9(ui/widgets.go:375),舍入可能让移动方向看起来相反。本组件让它操作副本,只用它判断“有按键按下”;方向与跳转取自松开的键(`HandleInput` 能收到 KeyUp),再按步长网格移动。因此键盘调整在松开按键时生效;按住不放时,松开后按 MyGo 收到的按下次数一次移动多步。 - 步长网格复用 NumberInput 的实现:值为 `Min + n·Step`,小数位取 `Min` 与 `Step` 所需,端点跳转(End/Home、另一端)先对齐网格再写值并触发事件;例如 `Max=10, Step=3` 时 End 得到 9。 - `SliderBase` 在构建时就处理指针(ui/base.go:111),禁用时外层先禁用,且只操作副本,保证值不变。 diff --git a/docs/components/035-range-slider.md b/docs/components/035-range-slider.md index 1e70940..afa352a 100644 --- a/docs/components/035-range-slider.md +++ b/docs/components/035-range-slider.md @@ -48,4 +48,4 @@ ui.Render(view, 400, 300, 1) - MyGo 自带的 `ui.RangeSlider` 使用玻璃质感滑块(ui/widgets.go:419),不符合本库风格;MyGo 也没有双滑块 Base,故每个滑块各是一个 `SliderBase`(报告 Slider 角色、范围与当前值,ui/base.go:128),拖动由外层的 `Dragged`、`PointerPosition` 处理。 - 两个滑块的范围都报告为整条轨道 `Min`–`Max`;“不能越过另一端”这一动态上下限无法告诉辅助技术。 -- 按键处理与 Slider 相同:距端点不足 1% 时等到按键松开再移动(见 034 限制)。 +- 按键处理与 Slider 相同:方向取自松开的键,在松开时移动(见 034 限制)。 diff --git a/examples/gallery/pages_choice.go b/examples/gallery/pages_choice.go index d703472..2de7d31 100644 --- a/examples/gallery/pages_choice.go +++ b/examples/gallery/pages_choice.go @@ -42,7 +42,7 @@ view := func(c *ui.Context) { view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) mujica.Slider(c, &volume, mujica.SliderOptions{Step: 5, ShowValue: true, Label: "Volume"}) -}`, Keys: "Arrows move one step; Home and End go to the ends."}, +}`, Keys: "Arrows move one step, Home and End go to the ends; the move applies when the key is released."}, {ID: "035", Name: "RangeSlider", ZhName: "范围滑块", Category: choiceCategory, Demo: rangeSliderDemo, Code: `low, high := 20.0, 80.0 view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) diff --git a/slider.go b/slider.go index 3a546c8..1163a64 100644 --- a/slider.go +++ b/slider.go @@ -2,7 +2,6 @@ package mujica import ( "log/slog" - "math" "strconv" "strings" @@ -69,11 +68,10 @@ func (g sliderGeom) move(v float64, n int) float64 { return g.grid.stepBy(v, n) } -// sliderKey is the last key released on a slider, a move waiting for it, and whether its press moved. +// sliderKey is the last key released on a slider and how many presses MyGo saw before it. type sliderKey struct { up ui.Key - pending int - acted bool + presses int } type sliderKeyID struct{} @@ -91,69 +89,32 @@ func sliderControl(c *ui.Context, v float64, g sliderGeom, from, to float64) (s ks.up = ev.Key return true }) - up, pending, acted := ks.up, ks.pending, ks.acted - ks.up, ks.pending = 0, 0 - if up != 0 { - ks.acted = false - } + up := ks.up + ks.up = 0 if _, _, dragged = s.Dragged(); dragged { + ks.presses = 0 return s, max(from, min(to, g.snap(raw))), true } - dir, jump, known := 0, false, true - switch { - case raw != v: - // MyGo moved the copy 1% (an arrow) or to an end (Home, End), rounded to 1e-9 (ui/widgets.go:375). - dir = 1 - if raw < v { - dir = -1 - } - end := g.lo - if dir > 0 { - end = g.hi - } - arrow := max(g.lo, min(g.hi, v+float64(dir)*(g.hi-g.lo)/100)) - jump = math.Abs(raw-end) < math.Abs(raw-arrow) - if math.Abs(arrow-end) <= 1e-9+math.Abs(end)*1e-12 { - // Near an end an arrow and Home/End agree: the released key tells which it was. - dir, jump, known = sliderKeyMove(up, dir) - } - if up == 0 { - ks.acted = true - } - case pending != 0: - dir, jump, known = sliderKeyMove(up, pending) - case up != 0 && !acted: - // A step finer than MyGo's rounding leaves the copy still: act on the release. - dir, jump, known = sliderKeyMove(up, 0) + if raw != v { + // A key press MyGo applied to the copy; its rounded result (ui/widgets.go:375) says nothing of the direction. + ks.presses++ } - if !known { - ks.pending = dir + if up == 0 { return s, v, false } - switch { - case dir == 0: - return s, v, false - case jump && dir < 0: - return s, g.snap(from), false - case jump: - return s, g.snap(to), false - } - return s, max(from, min(to, g.move(v, dir))), false -} - -// sliderKeyMove reads a released key as direction and jump; an unknown key keeps dir, unresolved. -func sliderKeyMove(up ui.Key, dir int) (int, bool, bool) { + n := max(1, ks.presses) + ks.presses = 0 switch up { case ui.KeyLeft, ui.KeyDown: - return -1, false, true + return s, max(from, min(to, g.move(v, -n))), false case ui.KeyRight, ui.KeyUp: - return 1, false, true + return s, max(from, min(to, g.move(v, n))), false case ui.KeyHome: - return -1, true, true + return s, g.snap(from), false case ui.KeyEnd: - return 1, true, true + return s, g.snap(to), false } - return dir, false, dir == 0 + return s, v, false } func sliderSetup(min, max, step float64, marks []SliderMark, showValue bool, c *ui.Context) (sliderGeom, float32) { diff --git a/slider_direction_test.go b/slider_direction_test.go new file mode 100644 index 0000000..c3e60e4 --- /dev/null +++ b/slider_direction_test.go @@ -0,0 +1,28 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +// MyGo rounds its 1% move to 1e-9, which can flip the apparent direction; the key decides it. +func sliderDirectionRun(t *testing.T, key ui.Key, want float64) { + t.Helper() + r := &sliderRecord{v: 6e-10, low: 6e-10, high: 8e-10} + tt := sliderGridTester(r, 0, 1e-8, 1e-10) + tt.Key(0, ui.KeyTab) + tt.Key(0, key) + if r.v != want || len(r.single) != 1 { + t.Errorf("Slider: %v, reported %v; want %v", r.v, r.single, want) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, key) + if r.low != want || len(r.lows) != 1 { + t.Errorf("RangeSlider low: %v, reported %v; want %v", r.low, r.lows, want) + } +} + +func TestSliderLeftUnderMyGoRounding(t *testing.T) { sliderDirectionRun(t, ui.KeyLeft, 5e-10) } + +func TestSliderRightUnderMyGoRounding(t *testing.T) { sliderDirectionRun(t, ui.KeyRight, 7e-10) }