v0.1.0 47aaa0f

controls.md RawHistoryFolder

Controls

Import github.com/bnema/nefergui. Public Frame, Node and event types alias their implementation types, so go doc does not list their methods at the root. Inspect signatures with go doc github.com/bnema/nefergui/internal/ui.Node (or .Frame, .ButtonEvent, .EditEvent, .ChangeEvent). Applications still use the public package; do not import internal/ui.

ButtonEvent.Activated() reports activation; EditEvent.Changed() and .Submitted() report edits and submission; ChangeEvent.Changed() reports value changes. These snapshots can be queried repeatedly without consuming an event.

Text, Heading, Image, Icon, Separator, Spacer, Button, Checkbox, Radio, Slider, Input and Textarea build semantic nodes in the immediate frame. Key identifies reorderable controls; ID and Class are CSS metadata. Image accepts an existing image.Image with bounded intrinsic dimensions. Style is computed via the existing CSS cascade; the caller owns images. CSS types and default accessibility roles are assigned even though a platform accessibility adapter is not yet connected.

Button, Checkbox, Radio and Slider paint their label as text in the control's content box, using its font, color and text-align (buttons center by default).

Checkbox and radio paint a one-em indicator, centered vertically, in their left padding (UA padding-left: 1.5em). Unchecked, it is an outline in color at 45% alpha. Checked, it is filled with accent-color and carries a white mark: a rounded square for checkboxes, a dot for radios. The slider paints, in its bottom padding (UA padding-bottom: 1.25em), a rail across the content width the pointer maps to, filled with accent-color up to the value, plus a round accent-color thumb. The indicator size follows font-size; accent-color (UA default #3584e4), color, opacity and :checked let author CSS restyle them, for example checkbox:checked { accent-color: green }. The painter fixes the rest: shapes and proportions, the white mark and the 45% outline alpha. Indicators stay visible when the control clips its content. The indicator always sits at the left, including for RTL labels. A slider with no bottom padding paints no track; checkbox and radio indicators still paint at the left edge, so keep their left padding. Disabled checkboxes, radios and sliders are dimmed by UA opacity: 0.5.

The pointer cursor follows the CSS cursor of the hovered element (pointer, text, not-allowed; auto and default use the default arrow). While a primary button is held, the pressed element keeps its cursor. The UA sheet sets pointer on buttons, checkboxes, radios and sliders, text on inputs and textareas, and not-allowed on disabled controls. NeferGUI sets the shape through wp_cursor_shape_manager_v1 when the compositor offers it; otherwise the compositor's cursor stays unchanged. Placeholder paints dimmed text (half opacity) in an empty Input or Textarea until a value or preedit exists; it never enters the bound value. The label argument of Input and Textarea is not painted; place a visible label with Text.

Checkbox toggles *bool; Radio(label, option, *string, ...) selects an option in a string-valued group; Slider(label, *float64, min, max, step, ...) changes its pointer-backed value on arrow, Home/End and PageUp/Down keys. These controls never retain the model pointer. Disabled(true) blocks interaction. Input and Textarea bind *string; Password(true) masks the displayed string per Unicode grapheme and blocks copy/cut, but not paste. The editor stores cursor and anchor in UTF-8 byte offsets at extended-grapheme boundaries. Left/Right and selections are logical, not visual. Primary pointer press places the caret at the nearest shaped grapheme boundary, Shift+press extends from the existing anchor, and captured dragging extends even outside the control. A second press on the same control within 500 ms and 4 logical pixels selects exactly the word-iterator segment under that boundary; at a boundary the segment to the right wins, including whitespace or punctuation segments. An unrelated press resets this sequence. Pointer hit testing requires a measured text layout. Textarea Up/Down traverse wrapped visual lines at a retained preferred x; horizontal movement and edits reset it. The per-identity editor viewport scrolls vertically in Textarea and horizontally in unwrapped Input to keep the caret visible, clamping at the measured extent. Focused editors emit visual cluster highlight rectangles and a one-pixel caret inside the editor clip. At LTR/RTL run boundaries and soft wraps, caret painting, scrolling and preedit positioning use the preceding character's trailing edge. Up/Down navigation treats a shared soft-wrap offset as belonging to the following line. Password geometry maps offsets by grapheme, like the displayed mask. No undo/redo is implemented.

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.

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.

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.

Sliders are left-to-right: minimum is at the left edge. CSS direction is not part of the supported CSS dialect; this is an explicit limitation, not an inferred text direction. Primary-pointer press/drag/release maps the computed content-box track to the min+n*step grid, clamps at both ends (a partial final step is not a stop) and keeps capture while dragging outside the slider. Keyboard steps use the same grid; NaN/Inf values normalize to the minimum.

source
1# Controls
2
3Import `github.com/bnema/nefergui`. Public `Frame`, `Node` and event types alias their implementation types, so `go doc` does not list their methods at the root. Inspect signatures with `go doc github.com/bnema/nefergui/internal/ui.Node` (or `.Frame`, `.ButtonEvent`, `.EditEvent`, `.ChangeEvent`). Applications still use the public package; do not import `internal/ui`.
4
5`ButtonEvent.Activated()` reports activation; `EditEvent.Changed()` and `.Submitted()` report edits and submission; `ChangeEvent.Changed()` reports value changes. These snapshots can be queried repeatedly without consuming an event.
6
7`Text`, `Heading`, `Image`, `Icon`, `Separator`, `Spacer`, `Button`, `Checkbox`, `Radio`, `Slider`, `Input` and `Textarea` build semantic nodes in the immediate frame. `Key` identifies reorderable controls; `ID` and `Class` are CSS metadata. `Image` accepts an existing `image.Image` with bounded intrinsic dimensions. Style is computed via the existing CSS cascade; the caller owns images. CSS types and default accessibility roles are assigned even though a platform accessibility adapter is not yet connected.
8
9`Button`, `Checkbox`, `Radio` and `Slider` paint their label as text in the control's content box, using its font, `color` and `text-align` (buttons center by default).
10
11Checkbox and radio paint a one-em indicator, centered vertically, in their left padding (UA `padding-left: 1.5em`). Unchecked, it is an outline in `color` at 45% alpha. Checked, it is filled with `accent-color` and carries a white mark: a rounded square for checkboxes, a dot for radios. The slider paints, in its bottom padding (UA `padding-bottom: 1.25em`), a rail across the content width the pointer maps to, filled with `accent-color` up to the value, plus a round `accent-color` thumb. The indicator size follows `font-size`; `accent-color` (UA default `#3584e4`), `color`, `opacity` and `:checked` let author CSS restyle them, for example `checkbox:checked { accent-color: green }`. The painter fixes the rest: shapes and proportions, the white mark and the 45% outline alpha. Indicators stay visible when the control clips its content. The indicator always sits at the left, including for RTL labels. A slider with no bottom padding paints no track; checkbox and radio indicators still paint at the left edge, so keep their left padding. Disabled checkboxes, radios and sliders are dimmed by UA `opacity: 0.5`.
12
13The pointer cursor follows the CSS `cursor` of the hovered element (`pointer`, `text`, `not-allowed`; `auto` and `default` use the default arrow). While a primary button is held, the pressed element keeps its cursor. The UA sheet sets `pointer` on buttons, checkboxes, radios and sliders, `text` on inputs and textareas, and `not-allowed` on disabled controls. NeferGUI sets the shape through `wp_cursor_shape_manager_v1` when the compositor offers it; otherwise the compositor's cursor stays unchanged. `Placeholder` paints dimmed text (half opacity) in an empty `Input` or `Textarea` until a value or preedit exists; it never enters the bound value. The `label` argument of `Input` and `Textarea` is not painted; place a visible label with `Text`.
14
15`Checkbox` toggles `*bool`; `Radio(label, option, *string, ...)` selects an option in a string-valued group; `Slider(label, *float64, min, max, step, ...)` changes its pointer-backed value on arrow, Home/End and PageUp/Down keys. These controls never retain the model pointer. `Disabled(true)` blocks interaction. `Input` and `Textarea` bind `*string`; `Password(true)` masks the displayed string per Unicode grapheme and blocks copy/cut, but not paste. The editor stores cursor and anchor in UTF-8 byte offsets at extended-grapheme boundaries. Left/Right and selections are **logical**, not visual. Primary pointer press places the caret at the nearest shaped grapheme boundary, Shift+press extends from the existing anchor, and captured dragging extends even outside the control. A second press on the same control within 500 ms and 4 logical pixels selects exactly the word-iterator segment under that boundary; at a boundary the segment to the right wins, including whitespace or punctuation segments. An unrelated press resets this sequence. Pointer hit testing requires a measured text layout. Textarea Up/Down traverse wrapped visual lines at a retained preferred x; horizontal movement and edits reset it. The per-identity editor viewport scrolls vertically in Textarea and horizontally in unwrapped Input to keep the caret visible, clamping at the measured extent. Focused editors emit visual cluster highlight rectangles and a one-pixel caret inside the editor clip. At LTR/RTL run boundaries and soft wraps, caret painting, scrolling and preedit positioning use the preceding character's trailing edge. Up/Down navigation treats a shared soft-wrap offset as belonging to the following line. Password geometry maps offsets by grapheme, like the displayed mask. No undo/redo is implemented.
16
17`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.
18
19Controls 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.
20
21The 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.
22
23Sliders are left-to-right: minimum is at the left edge. CSS `direction` is not part of the supported CSS dialect; this is an explicit limitation, not an inferred text direction. Primary-pointer press/drag/release maps the computed content-box track to the min+n*step grid, clamps at both ends (a partial final step is not a stop) and keeps capture while dragging outside the slider. Keyboard steps use the same grid; NaN/Inf values normalize to the minimum.

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