detached 84c1f24

refactor(ui): move private implementation out of the root package

commit
84c1f246107febed0d595f21fc0901d2285ee23c
author
brice · · 2026-09-30 03:50 UTC
parent
068ba85
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
84 files · +228 -113
D doc.go +0 -13
@@ -1,13 +0,0 @@
1-// Package nefergui provides a pure-Go immediate-mode GUI for Wayland, rendered with
2-// Vulkan and styled with a bounded CSS dialect (see docs/css.md).
3-//
4-// Run opens a window and calls the view function to build each frame. The view
5-// receives a Frame; Frame and its Node handles are valid only during that call.
6-// Key identifies a child among its siblings across frames; without a key,
7-// position and type determine identity. ID is CSS metadata, not frame identity.
8-// Options such as Title, Size, Styles and Transparent configure the window.
9-//
10-// Builds support CGO_ENABLED=0. At runtime, NeferGUI requires libvulkan.so.1,
11-// libxkbcommon.so.0 and a Wayland compositor supporting linux-dmabuf and
12-// linux-drm-syncobj. See docs/runtime.md and docs/troubleshooting.md.
13-package nefergui
@@ -1,13 +0,0 @@
1-// Package nefergui provides a pure-Go immediate-mode GUI for Wayland, rendered with
2-// Vulkan and styled with a bounded CSS dialect (see docs/css.md).
3-//
4-// Run opens a window and calls the view function to build each frame. The view
5-// receives a Frame; Frame and its Node handles are valid only during that call.
6-// Key identifies a child among its siblings across frames; without a key,
7-// position and type determine identity. ID is CSS metadata, not frame identity.
8-// Options such as Title, Size, Styles and Transparent configure the window.
9-//
10-// Builds support CGO_ENABLED=0. At runtime, NeferGUI requires libvulkan.so.1,
11-// libxkbcommon.so.0 and a Wayland compositor supporting linux-dmabuf and
12-// linux-drm-syncobj. See docs/runtime.md and docs/troubleshooting.md.
13-package nefergui
M docs/controls.md +1 -1
@@ -12,7 +12,7 @@ The pointer cursor follows the CSS `cursor` of the hovered element (`pointer`, `
1212
1313 `internal/edit` supplies the headless clipboard (`ReadText(limit)`, `WriteText`) and IME (`Enable`, `Disable`, `Surrounding`, `CursorRect`, `ContentType`) ports. A read is limited to 1 MiB and rejected unless UTF-8. IME batches represent preedit, commit and delete-surrounding relative to the UTF-8 cursor, processed at done. The preedit is shaped inline at the model cursor with an underline and its own cursor commands, without changing the model or its normal text run until commit. Password preedit is masked per grapheme before shaping or entering the display list. Ordinary keyboard text is inserted while an IME port is active, except while a preedit is shown; an input method that grabs the keyboard receives its keys instead of the editor. The ports do not negotiate MIME types, own FDs, or provide a Wayland adapter.
1414
15-Display-list JSON snapshots under `testdata/control-display` cover every control's normal and hover states, interactive focus-visible and disabled states, checkbox/radio checked states, and Input LTR/Arabic selections and preedit. They are deterministic command snapshots, not raster screenshots. Platform `Run` uses the same control tree and display list.
15+Controls are implemented in `internal/ui`; the root package re-exports their types. Display-list JSON snapshots under `internal/ui/testdata/control-display` cover every control's normal and hover states, interactive focus-visible and disabled states, checkbox/radio checked states, and Input LTR/Arabic selections and preedit. They are deterministic command snapshots, not raster screenshots. Platform `Run` uses the same control tree and display list.
1616
1717 The additive UA rules for `checkbox`, `radio`, `slider`, `separator`, `spacer`, and `icon` live in `internal/css/ua.css`. Their dimensions and colors are regular computed CSS and can be overridden by author styles. The control helpers hard-code no paint values; only the indicator painter fixes the shape details listed above.
1818
@@ -12,7 +12,7 @@ The pointer cursor follows the CSS `cursor` of the hovered element (`pointer`, `
12 12
13 `internal/edit` supplies the headless clipboard (`ReadText(limit)`, `WriteText`) and IME (`Enable`, `Disable`, `Surrounding`, `CursorRect`, `ContentType`) ports. A read is limited to 1 MiB and rejected unless UTF-8. IME batches represent preedit, commit and delete-surrounding relative to the UTF-8 cursor, processed at done. The preedit is shaped inline at the model cursor with an underline and its own cursor commands, without changing the model or its normal text run until commit. Password preedit is masked per grapheme before shaping or entering the display list. Ordinary keyboard text is inserted while an IME port is active, except while a preedit is shown; an input method that grabs the keyboard receives its keys instead of the editor. The ports do not negotiate MIME types, own FDs, or provide a Wayland adapter.13 `internal/edit` supplies the headless clipboard (`ReadText(limit)`, `WriteText`) and IME (`Enable`, `Disable`, `Surrounding`, `CursorRect`, `ContentType`) ports. A read is limited to 1 MiB and rejected unless UTF-8. IME batches represent preedit, commit and delete-surrounding relative to the UTF-8 cursor, processed at done. The preedit is shaped inline at the model cursor with an underline and its own cursor commands, without changing the model or its normal text run until commit. Password preedit is masked per grapheme before shaping or entering the display list. Ordinary keyboard text is inserted while an IME port is active, except while a preedit is shown; an input method that grabs the keyboard receives its keys instead of the editor. The ports do not negotiate MIME types, own FDs, or provide a Wayland adapter.
14 14
15-Display-list JSON snapshots under `testdata/control-display` cover every control's normal and hover states, interactive focus-visible and disabled states, checkbox/radio checked states, and Input LTR/Arabic selections and preedit. They are deterministic command snapshots, not raster screenshots. Platform `Run` uses the same control tree and display list.15+Controls are implemented in `internal/ui`; the root package re-exports their types. Display-list JSON snapshots under `internal/ui/testdata/control-display` cover every control's normal and hover states, interactive focus-visible and disabled states, checkbox/radio checked states, and Input LTR/Arabic selections and preedit. They are deterministic command snapshots, not raster screenshots. Platform `Run` uses the same control tree and display list.
16 16
17 The additive UA rules for `checkbox`, `radio`, `slider`, `separator`, `spacer`, and `icon` live in `internal/css/ua.css`. Their dimensions and colors are regular computed CSS and can be overridden by author styles. The control helpers hard-code no paint values; only the indicator painter fixes the shape details listed above.17 The additive UA rules for `checkbox`, `radio`, `slider`, `separator`, `spacer`, and `icon` live in `internal/css/ua.css`. Their dimensions and colors are regular computed CSS and can be overridden by author styles. The control helpers hard-code no paint values; only the indicator painter fixes the shape details listed above.
18 18
M docs/performance.md +23 -3
@@ -1,4 +1,6 @@
1-# CPU performance budgets
1+# Performance
2+
3+## CPU budgets
24
35 AMD Ryzen 9 7900X3D 12-Core Processor; `CGO_ENABLED=0 GOWORK=off go test -run '^$' -bench . -benchmem -count=5` in the indicated packages. Entries are medians of five runs. Budgets are approximately twice the measured medians (zero allocations remain zero). CPU numbers vary with machine load. The demo-frame benchmark builds a headless home-view tree, computes layout and produces a display list after warmup; Prepare uses a representative warm text/box command mix and excludes GPU submission.
46
@@ -24,11 +26,29 @@ Frame construction stays low-allocation because:
2426 - `text.Engine.Measure` caches results by text, request and width; entries unused for two frames are evicted and the cache holds at most 4,096 entries (callers treat results as immutable and use `Layout.Clone` before adjusting positions);
2527 - identity path keys are computed once per element.
2628
27-`Run` builds a frame only on input or a redraw request, so an idle window allocates nothing. Profile with:
29+`Run` builds a frame only on input or a redraw request. Idle frame construction stops, but platform release-wait polling can still allocate small amounts. Profile with:
2830
2931 ```sh
30-go test -run '^$' -bench BenchmarkDemoFrame -benchmem -memprofile mem.out -memprofilerate=1 .
32+go test -run '^$' -bench BenchmarkDemoFrame -benchmem -memprofile mem.out -memprofilerate=1 ./internal/ui
3133 go tool pprof -sample_index=alloc_objects -top -cum mem.out
3234 ```
3335
3436 A 400-frame demo harness run (960×640, scale 1, input: name Ada then continue) recorded `submit_to_commit_ns` median **184,545 ns** and nearest-rank p95 **481,550 ns** in `frame-timings.jsonl`. This is host-side submission-to-Wayland-commit latency, not GPU execution time. `atlas_pages` stayed at **1** allocated 1024×1024 grayscale page (maximum 4) for all 400 frames. These figures depend on the compositor, driver and machine load.
37+
38+## Memory as a dependency
39+
40+An external Go module with a minimal counter view, a 960×640 window, and a private headless NeferWL compositor was profiled on AMD/RADV. Debug readbacks were disabled. Live Go heap was sampled after forced GC; RSS includes shared driver libraries, while PSS apportions shared pages.
41+
42+| Measurement | Minimal view | Demo view |
43+|---|---:|---:|
44+| Live Go heap after 1,000 frames | 5.3 MiB | 5.4 MiB |
45+| Process RSS / PSS after 1,000 frames | 60 / 43 MiB | 64 / 47 MiB |
46+| Estimated temporary allocations per rendered frame, sampler excluded | about 65 KiB | about 230 KiB |
47+
48+The minimal view starts at about 0.75 MiB live Go heap, reaches 5.2 MiB after the first frame, and returns to about 0.9 MiB after closing. No sustained live-heap growth was observed over 1,000–3,000 frames. Driver mappings and Go heap capacity can remain resident after closing; RSS is not a leak measurement on its own.
49+
50+DRM counters reported about 41 MiB VRAM and 4 MiB GTT once all three presentation buffers were active. Window dimensions, scale, fonts and driver affect these figures. These are measurements on one stack, not portable limits.
51+
52+The largest frame-allocation sources are prepared quads, render batches, and layout painting. Release-wait polling allocated about 150 KiB over a 38-second idle interval. `NEFERGUI_DEBUG_DIR` enables readbacks and PNG work and raised observed peak RSS to about 113 MiB; leave it unset when measuring ordinary application cost.
53+
54+Use `pprof`'s `alloc_space` to find cumulative allocation churn and `inuse_space` after GC to inspect retained Go memory. Use `/proc/<pid>/smaps_rollup` for RSS/PSS and DRM fdinfo for GPU counters; heap profiles do not include driver or GPU memory.
@@ -1,4 +1,6 @@
1-# CPU performance budgets1+# Performance
2+
3+## CPU budgets
2 4
3 AMD Ryzen 9 7900X3D 12-Core Processor; `CGO_ENABLED=0 GOWORK=off go test -run '^$' -bench . -benchmem -count=5` in the indicated packages. Entries are medians of five runs. Budgets are approximately twice the measured medians (zero allocations remain zero). CPU numbers vary with machine load. The demo-frame benchmark builds a headless home-view tree, computes layout and produces a display list after warmup; Prepare uses a representative warm text/box command mix and excludes GPU submission.5 AMD Ryzen 9 7900X3D 12-Core Processor; `CGO_ENABLED=0 GOWORK=off go test -run '^$' -bench . -benchmem -count=5` in the indicated packages. Entries are medians of five runs. Budgets are approximately twice the measured medians (zero allocations remain zero). CPU numbers vary with machine load. The demo-frame benchmark builds a headless home-view tree, computes layout and produces a display list after warmup; Prepare uses a representative warm text/box command mix and excludes GPU submission.
4 6
@@ -24,11 +26,29 @@ Frame construction stays low-allocation because:
24 - `text.Engine.Measure` caches results by text, request and width; entries unused for two frames are evicted and the cache holds at most 4,096 entries (callers treat results as immutable and use `Layout.Clone` before adjusting positions);26 - `text.Engine.Measure` caches results by text, request and width; entries unused for two frames are evicted and the cache holds at most 4,096 entries (callers treat results as immutable and use `Layout.Clone` before adjusting positions);
25 - identity path keys are computed once per element.27 - identity path keys are computed once per element.
26 28
27-`Run` builds a frame only on input or a redraw request, so an idle window allocates nothing. Profile with:29+`Run` builds a frame only on input or a redraw request. Idle frame construction stops, but platform release-wait polling can still allocate small amounts. Profile with:
28 30
29 ```sh31 ```sh
30-go test -run '^$' -bench BenchmarkDemoFrame -benchmem -memprofile mem.out -memprofilerate=1 .32+go test -run '^$' -bench BenchmarkDemoFrame -benchmem -memprofile mem.out -memprofilerate=1 ./internal/ui
31 go tool pprof -sample_index=alloc_objects -top -cum mem.out33 go tool pprof -sample_index=alloc_objects -top -cum mem.out
32 ```34 ```
33 35
34 A 400-frame demo harness run (960×640, scale 1, input: name Ada then continue) recorded `submit_to_commit_ns` median **184,545 ns** and nearest-rank p95 **481,550 ns** in `frame-timings.jsonl`. This is host-side submission-to-Wayland-commit latency, not GPU execution time. `atlas_pages` stayed at **1** allocated 1024×1024 grayscale page (maximum 4) for all 400 frames. These figures depend on the compositor, driver and machine load.36 A 400-frame demo harness run (960×640, scale 1, input: name Ada then continue) recorded `submit_to_commit_ns` median **184,545 ns** and nearest-rank p95 **481,550 ns** in `frame-timings.jsonl`. This is host-side submission-to-Wayland-commit latency, not GPU execution time. `atlas_pages` stayed at **1** allocated 1024×1024 grayscale page (maximum 4) for all 400 frames. These figures depend on the compositor, driver and machine load.
37+
38+## Memory as a dependency
39+
40+An external Go module with a minimal counter view, a 960×640 window, and a private headless NeferWL compositor was profiled on AMD/RADV. Debug readbacks were disabled. Live Go heap was sampled after forced GC; RSS includes shared driver libraries, while PSS apportions shared pages.
41+
42+| Measurement | Minimal view | Demo view |
43+|---|---:|---:|
44+| Live Go heap after 1,000 frames | 5.3 MiB | 5.4 MiB |
45+| Process RSS / PSS after 1,000 frames | 60 / 43 MiB | 64 / 47 MiB |
46+| Estimated temporary allocations per rendered frame, sampler excluded | about 65 KiB | about 230 KiB |
47+
48+The minimal view starts at about 0.75 MiB live Go heap, reaches 5.2 MiB after the first frame, and returns to about 0.9 MiB after closing. No sustained live-heap growth was observed over 1,000–3,000 frames. Driver mappings and Go heap capacity can remain resident after closing; RSS is not a leak measurement on its own.
49+
50+DRM counters reported about 41 MiB VRAM and 4 MiB GTT once all three presentation buffers were active. Window dimensions, scale, fonts and driver affect these figures. These are measurements on one stack, not portable limits.
51+
52+The largest frame-allocation sources are prepared quads, render batches, and layout painting. Release-wait polling allocated about 150 KiB over a 38-second idle interval. `NEFERGUI_DEBUG_DIR` enables readbacks and PNG work and raised observed peak RSS to about 113 MiB; leave it unset when measuring ordinary application cost.
53+
54+Use `pprof`'s `alloc_space` to find cumulative allocation churn and `inuse_space` after GC to inspect retained Go memory. Use `/proc/<pid>/smaps_rollup` for RSS/PSS and DRM fdinfo for GPU counters; heap profiles do not include driver or GPU memory.
M example_test.go +20 -0
@@ -2,10 +2,30 @@ package nefergui_test
22
33 import (
44 "context"
5+ "fmt"
6+ "log"
57
68 "github.com/bnema/nefergui"
79 )
810
11+// The README counter. Run needs a Wayland session, so this example is
12+// compiled but not executed by go test.
13+func Example_counter() {
14+ count := 0
15+ view := func(f *nefergui.Frame, count *int) {
16+ root := f.Root().Column()
17+ root.Text("Hello, Wayland!")
18+ if root.Button("Count", nefergui.Key("count")).Activated() {
19+ *count++
20+ }
21+ root.Text(fmt.Sprintf("Clicked %d times", *count))
22+ }
23+ if err := nefergui.Run(context.Background(), &count, view,
24+ nefergui.Title("Hello"), nefergui.Size(320, 200)); err != nil {
25+ log.Fatal(err)
26+ }
27+}
28+
929 // The product-design example. Run needs a Wayland session, so this example is
1030 // compiled but not executed by go test; examples/demo runs it.
1131 func Example_compileOnly() {
@@ -2,10 +2,30 @@ package nefergui_test
2 2
3 import (3 import (
4 "context"4 "context"
5+ "fmt"
6+ "log"
5 7
6 "github.com/bnema/nefergui"8 "github.com/bnema/nefergui"
7 )9 )
8 10
11+// The README counter. Run needs a Wayland session, so this example is
12+// compiled but not executed by go test.
13+func Example_counter() {
14+ count := 0
15+ view := func(f *nefergui.Frame, count *int) {
16+ root := f.Root().Column()
17+ root.Text("Hello, Wayland!")
18+ if root.Button("Count", nefergui.Key("count")).Activated() {
19+ *count++
20+ }
21+ root.Text(fmt.Sprintf("Clicked %d times", *count))
22+ }
23+ if err := nefergui.Run(context.Background(), &count, view,
24+ nefergui.Title("Hello"), nefergui.Size(320, 200)); err != nil {
25+ log.Fatal(err)
26+ }
27+}
28+
9 // The product-design example. Run needs a Wayland session, so this example is29 // The product-design example. Run needs a Wayland session, so this example is
10 // compiled but not executed by go test; examples/demo runs it.30 // compiled but not executed by go test; examples/demo runs it.
11 func Example_compileOnly() {31 func Example_compileOnly() {
R controls.go → internal/ui/controls.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "fmt"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "fmt"4 "fmt"
R controls_extra.go → internal/ui/controls_extra.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 vi "github.com/bnema/nefergui/internal/image"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 vi "github.com/bnema/nefergui/internal/image"4 vi "github.com/bnema/nefergui/internal/image"
R controls_extra_test.go → internal/ui/controls_extra_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "github.com/bnema/nefergui/internal/css"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "github.com/bnema/nefergui/internal/css"4 "github.com/bnema/nefergui/internal/css"
R controls_label_test.go → internal/ui/controls_label_test.go +3 -3 renamed, 96% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "testing"
@@ -12,7 +12,7 @@ import (
1212 // glyphs. Snapshots alone once froze labelless controls; this invariant does not
1313 // depend on a golden file.
1414 func TestDeclaredTextIsPainted(t *testing.T) {
15- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
15+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
1616 if err != nil {
1717 t.Fatal(err)
1818 }
@@ -75,7 +75,7 @@ func TestDeclaredTextIsPainted(t *testing.T) {
7575 // Placeholders follow editor text geometry: text-align and RTL position,
7676 // letter-spacing, and textarea wrapping.
7777 func TestPlaceholderGeometry(t *testing.T) {
78- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
78+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
7979 if err != nil {
8080 t.Fatal(err)
8181 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "testing"4 "testing"
@@ -12,7 +12,7 @@ import (
12 // glyphs. Snapshots alone once froze labelless controls; this invariant does not12 // glyphs. Snapshots alone once froze labelless controls; this invariant does not
13 // depend on a golden file.13 // depend on a golden file.
14 func TestDeclaredTextIsPainted(t *testing.T) {14 func TestDeclaredTextIsPainted(t *testing.T) {
15- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))15+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
16 if err != nil {16 if err != nil {
17 t.Fatal(err)17 t.Fatal(err)
18 }18 }
@@ -75,7 +75,7 @@ func TestDeclaredTextIsPainted(t *testing.T) {
75 // Placeholders follow editor text geometry: text-align and RTL position,75 // Placeholders follow editor text geometry: text-align and RTL position,
76 // letter-spacing, and textarea wrapping.76 // letter-spacing, and textarea wrapping.
77 func TestPlaceholderGeometry(t *testing.T) {77 func TestPlaceholderGeometry(t *testing.T) {
78- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))78+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
79 if err != nil {79 if err != nil {
80 t.Fatal(err)80 t.Fatal(err)
81 }81 }
R controls_snapshot_test.go → internal/ui/controls_snapshot_test.go +2 -2 renamed, 98% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "bytes"
@@ -17,7 +17,7 @@ import (
1717 // The golden is the serialized display list, not a rendered image; each case
1818 // still checks the intended pseudo-state/event before comparing exact JSON.
1919 func TestControlDisplaySnapshots(t *testing.T) {
20- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
20+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
2121 if err != nil {
2222 t.Fatal(err)
2323 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "bytes"4 "bytes"
@@ -17,7 +17,7 @@ import (
17 // The golden is the serialized display list, not a rendered image; each case17 // The golden is the serialized display list, not a rendered image; each case
18 // still checks the intended pseudo-state/event before comparing exact JSON.18 // still checks the intended pseudo-state/event before comparing exact JSON.
19 func TestControlDisplaySnapshots(t *testing.T) {19 func TestControlDisplaySnapshots(t *testing.T) {
20- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))20+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
21 if err != nil {21 if err != nil {
22 t.Fatal(err)22 t.Fatal(err)
23 }23 }
R controls_visual_test.go → internal/ui/controls_visual_test.go +1 -1 renamed, 98% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "encoding/json"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "encoding/json"4 "encoding/json"
R debug_off.go → internal/ui/debug_off.go +1 -1 renamed, 77% similar
@@ -1,5 +1,5 @@
11 //go:build !nefergui_debug
22
3-package nefergui
3+package ui
44
55 const debugDiagnostics = false
@@ -1,5 +1,5 @@
1 //go:build !nefergui_debug1 //go:build !nefergui_debug
2 2
3-package nefergui3+package ui
4 4
5 const debugDiagnostics = false5 const debugDiagnostics = false
R debug_on.go → internal/ui/debug_on.go +1 -1 renamed, 77% similar
@@ -1,5 +1,5 @@
11 //go:build nefergui_debug
22
3-package nefergui
3+package ui
44
55 const debugDiagnostics = true
@@ -1,5 +1,5 @@
1 //go:build nefergui_debug1 //go:build nefergui_debug
2 2
3-package nefergui3+package ui
4 4
5 const debugDiagnostics = true5 const debugDiagnostics = true
R edit_geometry.go → internal/ui/edit_geometry.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R edit_geometry_review_test.go → internal/ui/edit_geometry_review_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R edit_geometry_test.go → internal/ui/edit_geometry_test.go +2 -2 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "bytes"
@@ -17,7 +17,7 @@ import (
1717
1818 func geometryRuntime(t *testing.T, sheet string) *runtime {
1919 t.Helper()
20- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
20+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
2121 if err != nil {
2222 t.Fatal(err)
2323 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "bytes"4 "bytes"
@@ -17,7 +17,7 @@ import (
17 17
18 func geometryRuntime(t *testing.T, sheet string) *runtime {18 func geometryRuntime(t *testing.T, sheet string) *runtime {
19 t.Helper()19 t.Helper()
20- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))20+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
21 if err != nil {21 if err != nil {
22 t.Fatal(err)22 t.Fatal(err)
23 }23 }
R edit_mixed_direction_test.go → internal/ui/edit_mixed_direction_test.go +2 -2 renamed, 96% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -11,7 +11,7 @@ import (
1111 )
1212
1313 func TestMixedDirectionSelectionVisualRects(t *testing.T) {
14- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
14+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
1515 if err != nil {
1616 t.Fatal(err)
1717 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
@@ -11,7 +11,7 @@ import (
11 )11 )
12 12
13 func TestMixedDirectionSelectionVisualRects(t *testing.T) {13 func TestMixedDirectionSelectionVisualRects(t *testing.T) {
14- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))14+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
15 if err != nil {15 if err != nil {
16 t.Fatal(err)16 t.Fatal(err)
17 }17 }
R edit_navigation.go → internal/ui/edit_navigation.go +1 -1 renamed, 98% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R edit_navigation_test.go → internal/ui/edit_navigation_test.go +2 -2 renamed, 95% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "github.com/bnema/nefergui/internal/css"
@@ -7,7 +7,7 @@ import (
77 )
88
99 func TestWrappedNavigationAndScroll(t *testing.T) {
10- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
10+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
1111 if err != nil {
1212 t.Fatal(err)
1313 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "github.com/bnema/nefergui/internal/css"4 "github.com/bnema/nefergui/internal/css"
@@ -7,7 +7,7 @@ import (
7 )7 )
8 8
9 func TestWrappedNavigationAndScroll(t *testing.T) {9 func TestWrappedNavigationAndScroll(t *testing.T) {
10- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))10+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
11 if err != nil {11 if err != nil {
12 t.Fatal(err)12 t.Fatal(err)
13 }13 }
R edit_paint.go → internal/ui/edit_paint.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R edit_paint_test.go → internal/ui/edit_paint_test.go +2 -2 renamed, 95% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "github.com/bnema/nefergui/internal/css"
@@ -8,7 +8,7 @@ import (
88 )
99
1010 func TestEditorVisualSelectionAndCaret(t *testing.T) {
11- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
11+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
1212 if err != nil {
1313 t.Fatal(err)
1414 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "github.com/bnema/nefergui/internal/css"4 "github.com/bnema/nefergui/internal/css"
@@ -8,7 +8,7 @@ import (
8 )8 )
9 9
10 func TestEditorVisualSelectionAndCaret(t *testing.T) {10 func TestEditorVisualSelectionAndCaret(t *testing.T) {
11- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))11+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
12 if err != nil {12 if err != nil {
13 t.Fatal(err)13 t.Fatal(err)
14 }14 }
R edit_pointer_test.go → internal/ui/edit_pointer_test.go +2 -2 renamed, 96% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "testing"
@@ -9,7 +9,7 @@ import (
99 )
1010
1111 func TestPointerTextSelection(t *testing.T) {
12- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
12+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
1313 if err != nil {
1414 t.Fatal(err)
1515 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "testing"4 "testing"
@@ -9,7 +9,7 @@ import (
9 )9 )
10 10
11 func TestPointerTextSelection(t *testing.T) {11 func TestPointerTextSelection(t *testing.T) {
12- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))12+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
13 if err != nil {13 if err != nil {
14 t.Fatal(err)14 t.Fatal(err)
15 }15 }
R edit_preedit.go → internal/ui/edit_preedit.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R edit_preedit_password_test.go → internal/ui/edit_preedit_password_test.go +2 -2 renamed, 93% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "bytes"
@@ -11,7 +11,7 @@ import (
1111 )
1212
1313 func TestPasswordPreeditNeverLeaksIntoDisplay(t *testing.T) {
14- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
14+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
1515 if err != nil {
1616 t.Fatal(err)
1717 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "bytes"4 "bytes"
@@ -11,7 +11,7 @@ import (
11 )11 )
12 12
13 func TestPasswordPreeditNeverLeaksIntoDisplay(t *testing.T) {13 func TestPasswordPreeditNeverLeaksIntoDisplay(t *testing.T) {
14- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))14+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
15 if err != nil {15 if err != nil {
16 t.Fatal(err)16 t.Fatal(err)
17 }17 }
R edit_preedit_test.go → internal/ui/edit_preedit_test.go +2 -2 renamed, 94% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "github.com/bnema/nefergui/internal/css"
@@ -8,7 +8,7 @@ import (
88 )
99
1010 func TestPreeditInlineWithoutModelMutation(t *testing.T) {
11- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
11+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
1212 if err != nil {
1313 t.Fatal(err)
1414 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "github.com/bnema/nefergui/internal/css"4 "github.com/bnema/nefergui/internal/css"
@@ -8,7 +8,7 @@ import (
8 )8 )
9 9
10 func TestPreeditInlineWithoutModelMutation(t *testing.T) {10 func TestPreeditInlineWithoutModelMutation(t *testing.T) {
11- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))11+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
12 if err != nil {12 if err != nil {
13 t.Fatal(err)13 t.Fatal(err)
14 }14 }
R edit_scroll.go → internal/ui/edit_scroll.go +1 -1 renamed, 98% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R ime_focus_test.go → internal/ui/ime_focus_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "reflect"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "reflect"4 "reflect"
R ime_return_test.go → internal/ui/ime_return_test.go +1 -1 renamed, 98% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "testing"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "testing"4 "testing"
R indicator_paint.go → internal/ui/indicator_paint.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R indicator_paint_test.go → internal/ui/indicator_paint_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R interaction.go → internal/ui/interaction.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R interaction_test.go → internal/ui/interaction_test.go +2 -2 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "encoding/json"
@@ -27,7 +27,7 @@ func shell(r *runtime, activated *int) func(*Frame) {
2727 }
2828 func TestShellStyleAndFocus(t *testing.T) {
2929 r := newRuntime()
30- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
30+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
3131 if err != nil {
3232 t.Fatal(err)
3333 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "encoding/json"4 "encoding/json"
@@ -27,7 +27,7 @@ func shell(r *runtime, activated *int) func(*Frame) {
27 }27 }
28 func TestShellStyleAndFocus(t *testing.T) {28 func TestShellStyleAndFocus(t *testing.T) {
29 r := newRuntime()29 r := newRuntime()
30- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))30+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
31 if err != nil {31 if err != nil {
32 t.Fatal(err)32 t.Fatal(err)
33 }33 }
R options.go → internal/ui/options.go +26 -22 renamed, 51% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import "github.com/bnema/nefergui/internal/css"
44
@@ -9,21 +9,24 @@ type EditOption interface{ edit(*element) }
99
1010 // ValueOption accepts CSS options and Disabled for pointer-backed controls.
1111 type ValueOption interface{ valueOption(*element) }
12-type commonOption struct{ apply func(*element) }
13-
14-func (o commonOption) container(e *element) { o.apply(e) }
15-func (o commonOption) button(e *element) { o.apply(e) }
16-func (o commonOption) edit(e *element) { o.apply(e) }
17-func (o commonOption) valueOption(e *element) { o.apply(e) }
18-func Key(key string) commonOption {
19- return commonOption{func(e *element) { e.identity = &identity{key: key} }}
12+
13+// CommonOption is the concrete type of Key, ID, Class and Inline; it belongs
14+// to every option family. It is exported only so the root facade can return it.
15+type CommonOption struct{ apply func(*element) }
16+
17+func (o CommonOption) container(e *element) { o.apply(e) }
18+func (o CommonOption) button(e *element) { o.apply(e) }
19+func (o CommonOption) edit(e *element) { o.apply(e) }
20+func (o CommonOption) valueOption(e *element) { o.apply(e) }
21+func Key(key string) CommonOption {
22+ return CommonOption{func(e *element) { e.identity = &identity{key: key} }}
2023 }
21-func ID(id string) commonOption { return commonOption{func(e *element) { e.id = id }} }
22-func Class(class string) commonOption {
23- return commonOption{func(e *element) { e.classes = append(e.classes, class) }}
24+func ID(id string) CommonOption { return CommonOption{func(e *element) { e.id = id }} }
25+func Class(class string) CommonOption {
26+ return CommonOption{func(e *element) { e.classes = append(e.classes, class) }}
2427 }
25-func Inline(src string) commonOption {
26- return commonOption{func(e *element) { e.inline, _ = css.ParseInline(src) }}
28+func Inline(src string) CommonOption {
29+ return CommonOption{func(e *element) { e.inline, _ = css.ParseInline(src) }}
2730 }
2831 func applyContainer(e *element, opts []ContainerOption) {
2932 for _, o := range opts {
@@ -31,21 +34,22 @@ func applyContainer(e *element, opts []ContainerOption) {
3134 }
3235 }
3336
34-// disabledOption applies to every interactive control (buttons, value controls
35-// and editors) but not to containers or headings.
36-type disabledOption struct{ disabled bool }
37+// DisabledOption is the concrete type of Disabled. It applies to every
38+// interactive control (buttons, value controls and editors) but not to
39+// containers or headings. Exported only so the root facade can return it.
40+type DisabledOption struct{ disabled bool }
3741
38-func (o disabledOption) button(e *element) { e.disabled = o.disabled }
39-func (o disabledOption) edit(e *element) { e.disabled = o.disabled }
40-func (o disabledOption) valueOption(e *element) { e.disabled = o.disabled }
42+func (o DisabledOption) button(e *element) { e.disabled = o.disabled }
43+func (o DisabledOption) edit(e *element) { e.disabled = o.disabled }
44+func (o DisabledOption) valueOption(e *element) { e.disabled = o.disabled }
4145
4246 // Disabled blocks focus and interaction on an interactive control.
43-func Disabled(v bool) disabledOption { return disabledOption{v} }
47+func Disabled(v bool) DisabledOption { return DisabledOption{v} }
4448
4549 // HeadingOption accepts common CSS options and Level, but not control options.
4650 type HeadingOption interface{ heading(*element) }
4751
48-func (o commonOption) heading(e *element) { o.apply(e) }
52+func (o CommonOption) heading(e *element) { o.apply(e) }
4953
5054 type headingLevel int
5155
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import "github.com/bnema/nefergui/internal/css"3 import "github.com/bnema/nefergui/internal/css"
4 4
@@ -9,21 +9,24 @@ type EditOption interface{ edit(*element) }
9 9
10 // ValueOption accepts CSS options and Disabled for pointer-backed controls.10 // ValueOption accepts CSS options and Disabled for pointer-backed controls.
11 type ValueOption interface{ valueOption(*element) }11 type ValueOption interface{ valueOption(*element) }
12-type commonOption struct{ apply func(*element) }12+
13-13+// CommonOption is the concrete type of Key, ID, Class and Inline; it belongs
14-func (o commonOption) container(e *element) { o.apply(e) }14+// to every option family. It is exported only so the root facade can return it.
15-func (o commonOption) button(e *element) { o.apply(e) }15+type CommonOption struct{ apply func(*element) }
16-func (o commonOption) edit(e *element) { o.apply(e) }16+
17-func (o commonOption) valueOption(e *element) { o.apply(e) }17+func (o CommonOption) container(e *element) { o.apply(e) }
18-func Key(key string) commonOption {18+func (o CommonOption) button(e *element) { o.apply(e) }
19- return commonOption{func(e *element) { e.identity = &identity{key: key} }}19+func (o CommonOption) edit(e *element) { o.apply(e) }
20+func (o CommonOption) valueOption(e *element) { o.apply(e) }
21+func Key(key string) CommonOption {
22+ return CommonOption{func(e *element) { e.identity = &identity{key: key} }}
20 }23 }
21-func ID(id string) commonOption { return commonOption{func(e *element) { e.id = id }} }24+func ID(id string) CommonOption { return CommonOption{func(e *element) { e.id = id }} }
22-func Class(class string) commonOption {25+func Class(class string) CommonOption {
23- return commonOption{func(e *element) { e.classes = append(e.classes, class) }}26+ return CommonOption{func(e *element) { e.classes = append(e.classes, class) }}
24 }27 }
25-func Inline(src string) commonOption {28+func Inline(src string) CommonOption {
26- return commonOption{func(e *element) { e.inline, _ = css.ParseInline(src) }}29+ return CommonOption{func(e *element) { e.inline, _ = css.ParseInline(src) }}
27 }30 }
28 func applyContainer(e *element, opts []ContainerOption) {31 func applyContainer(e *element, opts []ContainerOption) {
29 for _, o := range opts {32 for _, o := range opts {
@@ -31,21 +34,22 @@ func applyContainer(e *element, opts []ContainerOption) {
31 }34 }
32 }35 }
33 36
34-// disabledOption applies to every interactive control (buttons, value controls37+// DisabledOption is the concrete type of Disabled. It applies to every
35-// and editors) but not to containers or headings.38+// interactive control (buttons, value controls and editors) but not to
36-type disabledOption struct{ disabled bool }39+// containers or headings. Exported only so the root facade can return it.
40+type DisabledOption struct{ disabled bool }
37 41
38-func (o disabledOption) button(e *element) { e.disabled = o.disabled }42+func (o DisabledOption) button(e *element) { e.disabled = o.disabled }
39-func (o disabledOption) edit(e *element) { e.disabled = o.disabled }43+func (o DisabledOption) edit(e *element) { e.disabled = o.disabled }
40-func (o disabledOption) valueOption(e *element) { e.disabled = o.disabled }44+func (o DisabledOption) valueOption(e *element) { e.disabled = o.disabled }
41 45
42 // Disabled blocks focus and interaction on an interactive control.46 // Disabled blocks focus and interaction on an interactive control.
43-func Disabled(v bool) disabledOption { return disabledOption{v} }47+func Disabled(v bool) DisabledOption { return DisabledOption{v} }
44 48
45 // HeadingOption accepts common CSS options and Level, but not control options.49 // HeadingOption accepts common CSS options and Level, but not control options.
46 type HeadingOption interface{ heading(*element) }50 type HeadingOption interface{ heading(*element) }
47 51
48-func (o commonOption) heading(e *element) { o.apply(e) }52+func (o CommonOption) heading(e *element) { o.apply(e) }
49 53
50 type headingLevel int54 type headingLevel int
51 55
R paste.go → internal/ui/paste.go +1 -1 renamed, 98% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "context"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "context"4 "context"
R paste_test.go → internal/ui/paste_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "context"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "context"4 "context"
R performance_test.go → internal/ui/performance_test.go +2 -2 renamed, 95% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "testing"
@@ -28,7 +28,7 @@ func demoFrame(f *Frame) {
2828
2929 func demoRuntime(tb testing.TB) *runtime {
3030 tb.Helper()
31- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))
31+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
3232 if err != nil {
3333 tb.Fatal(err)
3434 }
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "testing"4 "testing"
@@ -28,7 +28,7 @@ func demoFrame(f *Frame) {
28 28
29 func demoRuntime(tb testing.TB) *runtime {29 func demoRuntime(tb testing.TB) *runtime {
30 tb.Helper()30 tb.Helper()
31- catalog, err := text.Load(text.DirectorySource("testdata/fonts"))31+ catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
32 if err != nil {32 if err != nil {
33 tb.Fatal(err)33 tb.Fatal(err)
34 }34 }
R platform.go → internal/ui/platform.go +1 -1 renamed, 98% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "github.com/bnema/nefergui/internal/css"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "github.com/bnema/nefergui/internal/css"4 "github.com/bnema/nefergui/internal/css"
R runtime.go → internal/ui/runtime.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "context"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "context"4 "context"
R runtime_test.go → internal/ui/runtime_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "context"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "context"4 "context"
R slider_grid_test.go → internal/ui/slider_grid_test.go +1 -1 renamed, 99% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "math"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "math"4 "math"
R slider_pointer_test.go → internal/ui/slider_pointer_test.go +1 -1 renamed, 98% similar
@@ -1,4 +1,4 @@
1-package nefergui
1+package ui
22
33 import (
44 "github.com/bnema/nefergui/internal/css"
@@ -1,4 +1,4 @@
1-package nefergui1+package ui
2 2
3 import (3 import (
4 "github.com/bnema/nefergui/internal/css"4 "github.com/bnema/nefergui/internal/css"
R testdata/control-display/button_disabled.json → internal/ui/testdata/control-display/button_disabled.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/button_focus-visible.json → internal/ui/testdata/control-display/button_focus-visible.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/button_hover.json → internal/ui/testdata/control-display/button_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/button_normal.json → internal/ui/testdata/control-display/button_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/checkbox_checked.json → internal/ui/testdata/control-display/checkbox_checked.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/checkbox_disabled.json → internal/ui/testdata/control-display/checkbox_disabled.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/checkbox_focus-visible.json → internal/ui/testdata/control-display/checkbox_focus-visible.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/checkbox_hover.json → internal/ui/testdata/control-display/checkbox_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/checkbox_normal.json → internal/ui/testdata/control-display/checkbox_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/heading_hover.json → internal/ui/testdata/control-display/heading_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/heading_normal.json → internal/ui/testdata/control-display/heading_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/icon_hover.json → internal/ui/testdata/control-display/icon_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/icon_normal.json → internal/ui/testdata/control-display/icon_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/image_hover.json → internal/ui/testdata/control-display/image_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/image_normal.json → internal/ui/testdata/control-display/image_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/input_disabled.json → internal/ui/testdata/control-display/input_disabled.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/input_focus-visible.json → internal/ui/testdata/control-display/input_focus-visible.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/input_hover.json → internal/ui/testdata/control-display/input_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/input_normal.json → internal/ui/testdata/control-display/input_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/input_preedit.json → internal/ui/testdata/control-display/input_preedit.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/input_selection-arabic.json → internal/ui/testdata/control-display/input_selection-arabic.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/input_selection-ltr.json → internal/ui/testdata/control-display/input_selection-ltr.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/radio_checked.json → internal/ui/testdata/control-display/radio_checked.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/radio_disabled.json → internal/ui/testdata/control-display/radio_disabled.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/radio_focus-visible.json → internal/ui/testdata/control-display/radio_focus-visible.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/radio_hover.json → internal/ui/testdata/control-display/radio_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/radio_normal.json → internal/ui/testdata/control-display/radio_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/separator_hover.json → internal/ui/testdata/control-display/separator_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/separator_normal.json → internal/ui/testdata/control-display/separator_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/slider_disabled.json → internal/ui/testdata/control-display/slider_disabled.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/slider_focus-visible.json → internal/ui/testdata/control-display/slider_focus-visible.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/slider_hover.json → internal/ui/testdata/control-display/slider_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/slider_normal.json → internal/ui/testdata/control-display/slider_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/spacer_hover.json → internal/ui/testdata/control-display/spacer_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/spacer_normal.json → internal/ui/testdata/control-display/spacer_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/text_hover.json → internal/ui/testdata/control-display/text_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/text_normal.json → internal/ui/testdata/control-display/text_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/textarea_disabled.json → internal/ui/testdata/control-display/textarea_disabled.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/textarea_focus-visible.json → internal/ui/testdata/control-display/textarea_focus-visible.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/textarea_hover.json → internal/ui/testdata/control-display/textarea_hover.json +0 -0 renamed, 100% similar

renamed, 100% similar

R testdata/control-display/textarea_normal.json → internal/ui/testdata/control-display/textarea_normal.json +0 -0 renamed, 100% similar

renamed, 100% similar

R tree.go → internal/ui/tree.go +3 -1 renamed, 97% similar
@@ -1,4 +1,6 @@
1-package nefergui
1+// Package ui owns the immediate-mode frame tree, controls, editors, input
2+// routing and the Wayland run loop behind the public nefergui facade.
3+package ui
24
35 import (
46 "fmt"
@@ -1,4 +1,6 @@
1-package nefergui1+// Package ui owns the immediate-mode frame tree, controls, editors, input
2+// routing and the Wayland run loop behind the public nefergui facade.
3+package ui
2 4
3 import (5 import (
4 "fmt"6 "fmt"
A nefergui.go +108 -0
@@ -0,0 +1,108 @@
1+// Package nefergui provides a pure-Go immediate-mode GUI for Wayland, rendered with
2+// Vulkan and styled with a bounded CSS dialect (see docs/css.md).
3+//
4+// Run opens a window and calls the view function to build each frame. The view
5+// receives a Frame; Frame and its Node handles are valid only during that call.
6+// Key identifies a child among its siblings across frames; without a key,
7+// position and type determine identity. ID is CSS metadata, not frame identity.
8+// Options such as Title, Size, Styles and Transparent configure the window.
9+//
10+// The frame tree, controls, editors and input routing are implemented in
11+// internal/ui; this package is the public facade and re-exports those types.
12+//
13+// Builds support CGO_ENABLED=0. At runtime, NeferGUI requires libvulkan.so.1,
14+// libxkbcommon.so.0 and a Wayland compositor supporting linux-dmabuf and
15+// linux-drm-syncobj. See docs/runtime.md and docs/troubleshooting.md.
16+package nefergui
17+
18+import (
19+ "context"
20+
21+ "github.com/bnema/nefergui/internal/ui"
22+)
23+
24+// Frame is valid only during one call to the view function. Root builds the
25+// application element; Diagnostics reports identity problems in debug builds.
26+type Frame = ui.Frame
27+
28+// Node is an ephemeral handle into the current frame. Do not retain it between
29+// frames. Container methods (Box, Row, Column, Stack, Scroll, Header, Nav,
30+// Main, Section, Aside, Footer, Element) return child nodes; control methods
31+// (Button, Checkbox, Radio, Slider, Input, Textarea, Text, Heading, Image,
32+// Icon, Separator, Spacer) declare controls. Interactive controls return event
33+// snapshots.
34+type Node = ui.Node
35+
36+// Control event values are immutable snapshots; querying them never consumes events.
37+type (
38+ ButtonEvent = ui.ButtonEvent
39+ EditEvent = ui.EditEvent
40+ ChangeEvent = ui.ChangeEvent
41+)
42+
43+// Option families are sealed: ContainerOption, ButtonOption, EditOption,
44+// ValueOption and HeadingOption accept the common CSS options (Key, ID,
45+// Class, Inline) plus the control-specific options listed on each function.
46+type (
47+ ContainerOption = ui.ContainerOption
48+ ButtonOption = ui.ButtonOption
49+ EditOption = ui.EditOption
50+ ValueOption = ui.ValueOption
51+ HeadingOption = ui.HeadingOption
52+ WindowOption = ui.WindowOption
53+)
54+
55+// commonOption belongs to every option family; disabledOption to every
56+// interactive control family. Both are concrete internal types, aliased so
57+// the constructors below can return them without a second implementation.
58+type (
59+ commonOption = ui.CommonOption
60+ disabledOption = ui.DisabledOption
61+)
62+
63+// Key identifies an element among its siblings across frames.
64+func Key(key string) commonOption { return ui.Key(key) }
65+
66+// ID sets the CSS ID; it does not affect frame identity.
67+func ID(id string) commonOption { return ui.ID(id) }
68+
69+// Class adds a CSS class.
70+func Class(class string) commonOption { return ui.Class(class) }
71+
72+// Inline applies inline CSS declarations.
73+func Inline(src string) commonOption { return ui.Inline(src) }
74+
75+// Disabled blocks focus and interaction on an interactive control.
76+func Disabled(v bool) disabledOption { return ui.Disabled(v) }
77+
78+// Level sets the accessibility heading level (1 through 6).
79+func Level(level int) HeadingOption { return ui.Level(level) }
80+
81+// Placeholder sets the text shown in an empty editor.
82+func Placeholder(v string) EditOption { return ui.Placeholder(v) }
83+
84+// Password masks displayed editor text and blocks copy and cut.
85+func Password(v bool) EditOption { return ui.Password(v) }
86+
87+// Title sets the window title.
88+func Title(s string) WindowOption { return ui.Title(s) }
89+
90+// Size sets the initial logical window size.
91+func Size(w, h int) WindowOption { return ui.Size(w, h) }
92+
93+// Styles loads an author stylesheet from a file path.
94+func Styles(s string) WindowOption { return ui.Styles(s) }
95+
96+// Transparent requests an alpha-capable Wayland surface (opaque by default).
97+func Transparent() WindowOption { return ui.Transparent() }
98+
99+// Run builds and presents an immediate view on the Wayland session owner loop.
100+func Run[T any](ctx context.Context, model *T, view func(*Frame, *T), options ...WindowOption) error {
101+ return ui.Run(ctx, model, view, options...)
102+}
103+
104+// RunFrames is a deterministic harness entry: each commit requests a redraw
105+// and the loop stops after the given number of committed frames.
106+func RunFrames[T any](ctx context.Context, frames int, model *T, view func(*Frame, *T), committed func(uint64) error, options ...WindowOption) error {
107+ return ui.RunFrames(ctx, frames, model, view, committed, options...)
108+}
@@ -0,0 +1,108 @@
1+// Package nefergui provides a pure-Go immediate-mode GUI for Wayland, rendered with
2+// Vulkan and styled with a bounded CSS dialect (see docs/css.md).
3+//
4+// Run opens a window and calls the view function to build each frame. The view
5+// receives a Frame; Frame and its Node handles are valid only during that call.
6+// Key identifies a child among its siblings across frames; without a key,
7+// position and type determine identity. ID is CSS metadata, not frame identity.
8+// Options such as Title, Size, Styles and Transparent configure the window.
9+//
10+// The frame tree, controls, editors and input routing are implemented in
11+// internal/ui; this package is the public facade and re-exports those types.
12+//
13+// Builds support CGO_ENABLED=0. At runtime, NeferGUI requires libvulkan.so.1,
14+// libxkbcommon.so.0 and a Wayland compositor supporting linux-dmabuf and
15+// linux-drm-syncobj. See docs/runtime.md and docs/troubleshooting.md.
16+package nefergui
17+
18+import (
19+ "context"
20+
21+ "github.com/bnema/nefergui/internal/ui"
22+)
23+
24+// Frame is valid only during one call to the view function. Root builds the
25+// application element; Diagnostics reports identity problems in debug builds.
26+type Frame = ui.Frame
27+
28+// Node is an ephemeral handle into the current frame. Do not retain it between
29+// frames. Container methods (Box, Row, Column, Stack, Scroll, Header, Nav,
30+// Main, Section, Aside, Footer, Element) return child nodes; control methods
31+// (Button, Checkbox, Radio, Slider, Input, Textarea, Text, Heading, Image,
32+// Icon, Separator, Spacer) declare controls. Interactive controls return event
33+// snapshots.
34+type Node = ui.Node
35+
36+// Control event values are immutable snapshots; querying them never consumes events.
37+type (
38+ ButtonEvent = ui.ButtonEvent
39+ EditEvent = ui.EditEvent
40+ ChangeEvent = ui.ChangeEvent
41+)
42+
43+// Option families are sealed: ContainerOption, ButtonOption, EditOption,
44+// ValueOption and HeadingOption accept the common CSS options (Key, ID,
45+// Class, Inline) plus the control-specific options listed on each function.
46+type (
47+ ContainerOption = ui.ContainerOption
48+ ButtonOption = ui.ButtonOption
49+ EditOption = ui.EditOption
50+ ValueOption = ui.ValueOption
51+ HeadingOption = ui.HeadingOption
52+ WindowOption = ui.WindowOption
53+)
54+
55+// commonOption belongs to every option family; disabledOption to every
56+// interactive control family. Both are concrete internal types, aliased so
57+// the constructors below can return them without a second implementation.
58+type (
59+ commonOption = ui.CommonOption
60+ disabledOption = ui.DisabledOption
61+)
62+
63+// Key identifies an element among its siblings across frames.
64+func Key(key string) commonOption { return ui.Key(key) }
65+
66+// ID sets the CSS ID; it does not affect frame identity.
67+func ID(id string) commonOption { return ui.ID(id) }
68+
69+// Class adds a CSS class.
70+func Class(class string) commonOption { return ui.Class(class) }
71+
72+// Inline applies inline CSS declarations.
73+func Inline(src string) commonOption { return ui.Inline(src) }
74+
75+// Disabled blocks focus and interaction on an interactive control.
76+func Disabled(v bool) disabledOption { return ui.Disabled(v) }
77+
78+// Level sets the accessibility heading level (1 through 6).
79+func Level(level int) HeadingOption { return ui.Level(level) }
80+
81+// Placeholder sets the text shown in an empty editor.
82+func Placeholder(v string) EditOption { return ui.Placeholder(v) }
83+
84+// Password masks displayed editor text and blocks copy and cut.
85+func Password(v bool) EditOption { return ui.Password(v) }
86+
87+// Title sets the window title.
88+func Title(s string) WindowOption { return ui.Title(s) }
89+
90+// Size sets the initial logical window size.
91+func Size(w, h int) WindowOption { return ui.Size(w, h) }
92+
93+// Styles loads an author stylesheet from a file path.
94+func Styles(s string) WindowOption { return ui.Styles(s) }
95+
96+// Transparent requests an alpha-capable Wayland surface (opaque by default).
97+func Transparent() WindowOption { return ui.Transparent() }
98+
99+// Run builds and presents an immediate view on the Wayland session owner loop.
100+func Run[T any](ctx context.Context, model *T, view func(*Frame, *T), options ...WindowOption) error {
101+ return ui.Run(ctx, model, view, options...)
102+}
103+
104+// RunFrames is a deterministic harness entry: each commit requests a redraw
105+// and the loop stops after the given number of committed frames.
106+func RunFrames[T any](ctx context.Context, frames int, model *T, view func(*Frame, *T), committed func(uint64) error, options ...WindowOption) error {
107+ return ui.RunFrames(ctx, frames, model, view, committed, options...)
108+}
D readme_example_test.go +0 -26
@@ -1,26 +0,0 @@
1-package nefergui_test
2-
3-import (
4- "context"
5- "fmt"
6- "log"
7-
8- "github.com/bnema/nefergui"
9-)
10-
11-// Compile the README example without opening a window during tests.
12-func Example_counter() {
13- count := 0
14- view := func(f *nefergui.Frame, count *int) {
15- root := f.Root().Column()
16- root.Text("Hello, Wayland!")
17- if root.Button("Count", nefergui.Key("count")).Activated() {
18- *count++
19- }
20- root.Text(fmt.Sprintf("Clicked %d times", *count))
21- }
22- if err := nefergui.Run(context.Background(), &count, view,
23- nefergui.Title("Hello"), nefergui.Size(320, 200)); err != nil {
24- log.Fatal(err)
25- }
26-}
@@ -1,26 +0,0 @@
1-package nefergui_test
2-
3-import (
4- "context"
5- "fmt"
6- "log"
7-
8- "github.com/bnema/nefergui"
9-)
10-
11-// Compile the README example without opening a window during tests.
12-func Example_counter() {
13- count := 0
14- view := func(f *nefergui.Frame, count *int) {
15- root := f.Root().Column()
16- root.Text("Hello, Wayland!")
17- if root.Button("Count", nefergui.Key("count")).Activated() {
18- *count++
19- }
20- root.Text(fmt.Sprintf("Clicked %d times", *count))
21- }
22- if err := nefergui.Run(context.Background(), &count, view,
23- nefergui.Title("Hello"), nefergui.Size(320, 200)); err != nil {
24- log.Fatal(err)
25- }
26-}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 12

↑↓ moveenter openesc close