From debb914dba8931c1d6e297c6a2cf6038f07ec185 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Fri, 7 Aug 2026 10:51:20 +0300 Subject: [PATCH] feat: add sounds example + update CHANGELOG with TextField fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit examples/sounds/ — system sounds demo with checkbox, radio, slider, buttons. CHANGELOG updated with SceneCache, TextField cursor/scroll fixes, gg v0.50.13. --- CHANGELOG.md | 13 ++++- examples/sounds/main.go | 117 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 examples/sounds/main.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 725a87c..f165c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **examples/sounds** — system sounds demo with checkbox, radio, slider, and button interactions using platform-native `gogpu/sound`. Demonstrates `sound.Play(sound.Click)` for UI feedback. + +### Fixed + +- **fix(textfield): cursor rounding** ([#211](https://github.com/gogpu/ui/issues/211)) — `CursorX` base X now rounded with `math.Round` to match `DrawText`'s pixel-grid rounding. Eliminates constant cursor-to-text offset (up to 0.5px). +- **fix(textfield): horizontal scroll** ([#212](https://github.com/gogpu/ui/issues/212)) — TextField scrolls text horizontally when content exceeds field width. `ensureCursorVisible()` keeps cursor within visible area (Flutter `_showCaretOnScreen` pattern). `TextRect` in PaintState separates text drawing origin from clip rect. +- **fix(textfield): cursor-scroll sync** — cursor position computed in text-local coordinates, `scrollOffsetX` applied uniformly to cursor/selection/text. Matches Flutter `_paintOffset` and Qt `topLeft` patterns. Fixes cursor drifting leftward during scroll. + ### Changed - **refactor(widget): decouple widget/ from gg/scene** (ADR-036 Phase 1) — replaced `*scene.Scene` with `SceneCache` interface in widget package. Third-party widget authors importing `widget/` + `geometry/` + `event/` now compile **71 packages instead of 124** (zero gg/wgpu/naga in the dependency chain). `SceneFactory` registration pattern follows existing `SceneRecorder` DI. @@ -15,7 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed: `WidgetBase.CachedScene()` / `SetCachedScene()` types → `SceneCache` - Changed: `SceneRecorder` function signature uses `SceneCache` - 92 files changed, rendering layer uses type assertions where concrete `*scene.Scene` needed -- **deps:** gg v0.50.11 → v0.50.12, gogpu v0.48.5 → v0.50.0, wgpu v0.30.35 → v0.30.36 +- **deps:** gg v0.50.11 → v0.50.13, gogpu v0.48.5 → v0.50.0, wgpu v0.30.35 → v0.30.36 + - **gg v0.50.13:** `Face.Advance()` returns hinted advances matching `drawGlyphs` — fixes cursor drift on wide Cyrillic glyphs (gg#479). - **gogpu v0.50.0:** outgoing drag-and-drop, per-pixel-alpha transparency, window Show/Hide/SetPosition/SetSize API, macOS menu Role+Action fix. - **wgpu v0.30.36:** Vulkan present semaphore fix. diff --git a/examples/sounds/main.go b/examples/sounds/main.go new file mode 100644 index 0000000..2391587 --- /dev/null +++ b/examples/sounds/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "fmt" + "log" + + _ "github.com/gogpu/gg/gpu" + + "github.com/gogpu/gogpu" + "github.com/gogpu/gogpu/sound" + "github.com/gogpu/ui/app" + "github.com/gogpu/ui/core/button" + "github.com/gogpu/ui/core/checkbox" + "github.com/gogpu/ui/core/radio" + "github.com/gogpu/ui/core/slider" + "github.com/gogpu/ui/desktop" + "github.com/gogpu/ui/primitives" + "github.com/gogpu/ui/theme/material3" + "github.com/gogpu/ui/widget" +) + +func main() { + sound.SetEnabled(true) + + m3 := material3.New(widget.Hex(0x1565C0)) + bp := material3.ButtonPainter{Theme: m3} + cp := material3.CheckboxPainter{Theme: m3} + rp := material3.RadioPainter{Theme: m3} + sp := material3.SliderPainter{Theme: m3} + + root := primitives.VBox( + primitives.Text("System Sounds Demo").FontSize(22).Bold(), + primitives.Text("Every interaction plays a platform system sound"). + FontSize(13).Color(widget.RGBA(0.5, 0.5, 0.5, 1)), + + primitives.Text("Checkboxes").FontSize(16).Bold(), + checkbox.New( + checkbox.LabelOpt("Enable notifications"), + checkbox.OnToggle(func(checked bool) { + sound.Play(sound.Click) + fmt.Println("notifications:", checked) + }), + checkbox.PainterOpt(cp), + ), + checkbox.New( + checkbox.LabelOpt("Dark mode"), + checkbox.OnToggle(func(checked bool) { + sound.Play(sound.Click) + fmt.Println("dark mode:", checked) + }), + checkbox.PainterOpt(cp), + ), + + primitives.Text("Theme").FontSize(16).Bold(), + radio.NewGroup( + radio.Items( + radio.ItemDef{Value: "light", Label: "Light"}, + radio.ItemDef{Value: "dark", Label: "Dark"}, + radio.ItemDef{Value: "system", Label: "System"}, + ), + radio.Selected("light"), + radio.OnChange(func(v string) { + sound.Play(sound.Click) + fmt.Println("theme:", v) + }), + radio.GroupPainter(rp), + ), + + primitives.Text("Volume").FontSize(16).Bold(), + slider.New( + slider.Min(0), + slider.Max(100), + slider.Value(50), + slider.OnChange(func(v float32) { + fmt.Printf("volume: %.0f%%\n", v) + }), + slider.PainterOpt(sp), + ), + + primitives.HBox( + button.New( + button.Text("Success"), + button.OnClick(func() { sound.Play(sound.Success) }), + button.PainterOpt(bp), + button.VariantOpt(button.Filled), + ), + button.New( + button.Text("Warning"), + button.OnClick(func() { sound.Play(sound.Warning) }), + button.PainterOpt(bp), + button.VariantOpt(button.Tonal), + ), + button.New( + button.Text("Error"), + button.OnClick(func() { sound.Play(sound.Error) }), + button.PainterOpt(bp), + button.VariantOpt(button.Outlined), + ), + ).Gap(8), + ).Padding(24).Gap(12) + + gogpuApp := gogpu.NewApp(gogpu.DefaultConfig(). + WithTitle("gogpu/ui — System Sounds"). + WithSize(400, 520)) + + uiApp := app.New( + app.WithWindowProvider(gogpuApp), + app.WithPlatformProvider(gogpuApp), + app.WithEventSource(gogpuApp.EventSource()), + app.WithTheme(m3.AsTheme()), + ) + uiApp.SetRoot(root) + + if err := desktop.Run(gogpuApp, uiApp); err != nil { + log.Fatal(err) + } +}