Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Dated delivery history and release records. Each release is a `## vX.Y.Z — YYY

## Unreleased

- Added pointer scrubbing for every `NumberInput` and `VectorInput` component through the public [`NumberDragDispatcher`](docs/guides/text-number-input.md#scrub-with-the-pointer) and `number_drag_target`, without new parts, roles or document vocabulary. A primary press on an input's label, field or padding captures the pointer and focuses the input; after a 3-unit horizontal threshold, moves and the release request whole steps of 4 units from the press, truncated toward zero, through the input's own command whenever the count changes. `started` marks the origin for the host, Escape or Cancel while scrubbing requests 0 steps and reports `canceled`, `PointerCancel` keeps the last value, other key, text and logical input end capture unconsumed, and replacement keeps a compatible capture by author ID. Both distances are configurable. The [Inspector](docs/guides/inspector-hierarchy.md#scrub-numeric-fields) scrubs Scale and Position X/Y/Z from their parsed drafts, rejects invalid drafts with its usual feedback, shows the active control and a Windows horizontal resize cursor, and is checked by window-free and injected native smokes.

- Added a controlled public [`authoring::TextEditor`](docs/guides/text-editor.md) for multiple-line plain text. `TextEditState` gains a fixed `TextLines` mode: multiple-line buffers accept LF and reject CR with `carriage_return`, while single-line buffers keep rejecting both. The editor shares TextInput's label, value and selection parts, wraps its rows to the field width and projects the additive `text_editor` role through UI JSON v1, semantics, Semantic JSON v1, Replay v1/v2 and inspection/invariant targets; line-mode mismatches reject at lowering with `edit_lines`. Core `layout_text_carets` derives visual rows, caret stops, caret rectangles and nearest-stop lookups from a shaped run. The Inspector adds per-item Notes with Enter line breaks, row-aware Home/End and Up/Down with a retained column, normalized paste, CRLF clipboard writes, IME composition and candidate placement on the caret's row, with window-free and injected native smokes.

- Added a controlled public [`authoring::ColorPicker`](docs/guides/color-picker.md) for opaque colors. Core `HsvColor` keeps hue, saturation and value of the sRGB components, with `to_color` and `format_hex_color`. The picker returns an inline trigger with a swatch and `#RRGGBB` value and a root-sibling panel holding a saturation/value area and a hue strip, drawn with ordinary gradients and input-transparent thumbs. The additive `color_picker` role, named by its label with the hex `value` and `expanded` state, joins UI JSON v1, semantics, Semantic JSON v1, Replay v1/v2 and inspection/invariant targets; the panel is a `group`. `ColorPickerDispatcher` opens at the area, captures primary presses on either control, maps arrows, Home/End and gamepad directions to configurable steps, rounds requested values, moves between the controls with Tab and dismisses like ComboBox, plus Enter. The [Inspector](docs/guides/inspector-hierarchy.md#display-color-picker) gains a per-item Display color on the Display tab, restored by Reset and checked by window-free keyboard/pointer and injected native smokes. Hex or channel entry, alpha, revert and numeric range semantics remain out of scope.
Expand Down
1 change: 1 addition & 0 deletions cmake/TesseraCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ add_library(tessera_core
src/input/scrollbar.cpp
src/input/split_pane.cpp
src/input/popup.cpp
src/input/number_drag.cpp
src/input/modal.cpp
src/commands/commands.cpp
src/commands/execution.cpp
Expand Down
1 change: 1 addition & 0 deletions cmake/TesseraTesting.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ set_tests_properties(async_allocation PROPERTIES TIMEOUT 30)
tessera_add_contract_test(split_pane tests/input/split_pane_tests.cpp)
tessera_add_contract_test(scrollbar tests/input/scrollbar_tests.cpp)
tessera_add_contract_test(popup tests/input/popup_tests.cpp)
tessera_add_contract_test(number_drag tests/input/number_drag_tests.cpp)
tessera_add_contract_test(modal tests/input/modal_tests.cpp)
tessera_add_contract_test(collection tests/input/collection_tests.cpp)
tessera_add_contract_test(menu tests/input/menu_tests.cpp)
Expand Down
10 changes: 10 additions & 0 deletions docs/architecture/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ Use the [portal model](../proposals/overlays-and-portals.md#proposed-overlays-an

A modal establishes a bounded focus scope for sequential and directional keyboard/gamepad navigation and remembers a restoration target by logical identity. Dismissal restores it only if it is eligible in the accepted generation, otherwise applying a declared recovery policy. Opening or closing a dropdown/modal uses ordinary actions and command scopes under the logical component owner. Verify that relocated paint, clips, targeting, focus recovery, inspection, source mapping, and replay agree for the same portal generation.

## Implemented number scrubbing

[number_drag.hpp](../../include/tessera/input/number_drag.hpp) adds horizontal pointer scrubbing to every [NumberInput and VectorInput component](../guides/text-number-input.md#scrub-with-the-pointer) without a new widget, role or document vocabulary. `number_drag_target(input, position)` returns the eligible, focusable `number_input` owner when the ordinary topmost hit is that owner or lies in its generated `<id>-label` or `<id>-field`; step parts, other controls and misses return none.

`NumberDragDispatcher(NumberDragOptions)` is host-owned capture for one pointer, dispatched before ordinary pointer routing. A primary press on a target captures the pointer and returns focus without a command. Once the pointer has moved `threshold` (default 3) logical units horizontally from the press, matching moves and the primary release return whole steps of `distance` (default 4) units from the press position, truncated toward zero, with the owner's ordinary `activate` request, only when the step count changes. The first such event reports `started`, so the host records its origin value; the dispatcher never sees a number. Shorter presses end as a click. Steps use root-logical pointer travel, so scrolling or replacement during the scrub does not move them.

Capture retains the pointer, the owner's author ID and command and the press position, never tree handles. `refresh` at every accepted replacement keeps a compatible scrub and reports `ended` when the owner disappears, stops being an eligible `number_input` or changes command. While scrubbing, unmodified Escape or logical `Cancel` returns `canceled` with a 0-step request when needed, restoring the origin. Matching `PointerCancel` ends capture keeping the last request. Other key, text and logical events end capture unconsumed before ordinary routing; Escape before scrubbing does too, so it keeps reverting the draft. KeyUp, Scroll, foreign pointers and secondary buttons pass through. Invalid options or input leave capture unchanged.

Each request is an ordinary command. A host with [edit transactions](commands.md#implemented-edit-gesture-boundary) can join a scrub as one gesture, beginning at `started`, committing at `ended` and rolling back on `canceled`. OS capture and resize cursors remain host adapters. The [Inspector](../guides/inspector-hierarchy.md#scrub-numeric-fields) wires Scale and Position this way; [owner checks](../../tests/input/number_drag_tests.cpp) cover targets, the threshold, truncation, replacement, cancellation and rejection.

## Implemented logical string editing

[text_edit.hpp](../../include/tessera/input/text_edit.hpp) defines owned `TextEditState`, revision-addressed `TextEditRequest`, selection/insertion/erase/move operations and `TextEditPolicy`; [text_edit.cpp](../../src/input/text_edit.cpp) implements pure candidate transforms. The host owns accepted observations, publication, history and lifetime allocation. `make_text_edit_state` requires a nonzero host owner that the host never reuses, starts revision one, and places anchor and caret at the buffer end. `edit_text` returns a complete candidate without mutating its input or invoking application code; hosts publish it only through their ordinary eligibility and command/update boundary.
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Declare both axes, at most 256 tracks each. Children occupy cells in declared or
Set `grid_column_span` or `grid_row_span` on a child to occupy consecutive tracks. A full-width note below a property row spans both columns:

```cpp
ui::Text("Enter applies, Esc reverts, Up/Down step.",
ui::Text("Enter applies, Esc reverts, Up/Down or drag step.",
{.id = "scale-hint", .classes = {"muted"}, .style = {.grid_column_span = std::uint16_t{2}}})
```

Expand Down
20 changes: 16 additions & 4 deletions docs/guides/inspector-hierarchy.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Use `Release` for the other configuration. Without arguments the executable runs
- With Display name focused, type Japanese through the Windows IME. Preedit appears inline in the editor, the candidate window opens at the preedit caret, Enter commits and Escape cancels; see [Japanese IME composition](#japanese-ime-composition).
- Rejected name input, including typing beyond the 4096-byte limit, keeps the accepted text, selection and focus. Windows shows name-edit and clipboard failures in the window title, with a short reason for text validation failures. A successful name edit or clipboard operation clears the notice; navigation alone retains it. Stderr records the operation, original target item and located diagnostics. Presentation rejection also preserves the accepted inspection and view, so retry can use the retained revision.
- Activate Enabled or adjust Amount with Left/Down, Right/Up, Home/End or a pointer drag. Amount has integer stops from 0 to 100 in steps of 5.
- Tab to Scale, a [NumberInput](text-number-input.md) from 0 to 10. Type a decimal and press Enter to apply it, or Escape to restore the accepted value; Up/Down or the `-`/`+` parts step by 0.1. See [Scale number input](#scale-number-input).
- Tab on to Position, a [VectorInput](vector-input.md) whose X, Y and Z are separate Tab stops from -1000 to 1000. Each edits, applies, reverts and steps by 0.1 like Scale. See [Position vector input](#position-vector-input).
- Tab to Scale, a [NumberInput](text-number-input.md) from 0 to 10. Type a decimal and press Enter to apply it, or Escape to restore the accepted value; Up/Down or the `-`/`+` parts step by 0.1. Drag its label or field sideways to scrub; see [Scrub numeric fields](#scrub-numeric-fields).
- Tab on to Position, a [VectorInput](vector-input.md) whose X, Y and Z are separate Tab stops from -1000 to 1000. Each edits, applies, reverts, steps by 0.1 and scrubs like Scale. See [Position vector input](#position-vector-input).
- Tab on to Notes, a multiple-line [TextEditor](text-editor.md). Enter starts a line, Up/Down move by row, Home/End move within the row and Control+Home/End to the ends; copy, cut, paste and the IME work as for Display name. See [Notes text editor](#notes-text-editor).
- On the Display tab, Tab to Visibility, which enters at the checked choice. Arrows move between Inherited, Visible and Hidden, wrapping, and select the focused choice; a pointer press selects directly. See [Visibility choice](#visibility-choice).
- Tab to Cast shadows and press Enter/Space, or click its track, thumb or label, to toggle the [Switch](checkbox-switch.md). See [Shadows switch](#shadows-switch).
Expand Down Expand Up @@ -64,7 +64,7 @@ The window-free smoke checks the toolbar role and name, single-stop entry/exit,

## Scale hint and Modified badge

Below Scale and Position, the General section's `properties` Grid has a sixth row holding the `scale-hint` text, "Enter applies, Esc reverts, Up/Down step.", with `grid_column_span` 2. It starts in the label column and ends with the control column, so it uses the full card width instead of wrapping inside the 72-unit label column, and its row keeps the shared 12-unit row gap. The bottom help no longer repeats it.
Below Scale and Position, the General section's `properties` Grid has a sixth row holding the `scale-hint` text, "Enter applies, Esc reverts, Up/Down or drag step.", with `grid_column_span` 2. It starts in the label column and ends with the control column, so it uses the full card width instead of wrapping inside the 72-unit label column, and its row keeps the shared 12-unit row gap. The bottom help no longer repeats it.

When the selected item differs from the values Reset restores (Enabled on, Amount 50, Scale 1 and Position 0, 0, 0 with no pending drafts, Visibility Inherited, Cast shadows on, Purpose Default, Display color 50% gray), the card's last child is a `modified-badge` Text with `position: absolute`, `top: -9` and `right: 12`. It straddles the card's top border against the card's padding box, painted after and therefore above the card's own content, without changing the card's size or moving Selected, the tabs or the Grid. It is static text: it requests no command and carries no focus stop. The badge follows the selected item's own values and survives section changes.

Expand All @@ -83,6 +83,18 @@ An invalid draft, such as `2,5` or `12`, rejects apply and step without publishi

The window-free smoke checks the `number_input` role, name and value, Tab arrival after Amount, steps without binary noise, draft editing, apply/revert, rejected syntax and range for both apply and step, step-part presses, field focus without a command, clamping and per-item retention of the accepted value and pending draft. The injected native smoke types characters, applies with Return, steps with Up, rejects an invalid draft with caption feedback, reverts with Escape and presses a step part. On Windows the field takes committed `WM_CHAR` text and Return like the name editor, but the IME attaches only to the name editor.

## Scrub numeric fields

Scale and each Position component can be scrubbed with the pointer through one [`NumberDragDispatcher`](text-number-input.md#scrub-with-the-pointer), routed after the popup dispatchers and before the splitter. A primary press on an input's label (`Scale`, `X`, `Y`, `Z`), field or padding focuses it without a command, as a click did before; the Scale step parts keep stepping once per press.

Once the pointer has moved 3 units sideways, every 4 units from the press is one 0.1 step. The host parses the draft when the scrub starts and runs the field's own `item.scale` or `item.position.<axis>` command with a `NumberScrub` request whenever the step count reaches a new value, accepting `step_number(range, start, steps)` and replacing the draft with canonical text. Clamped steps that repeat the accepted value run nothing. The pressed input keeps the `control` active background while captured, and Windows shows the horizontal resize cursor over scrubbable inputs and during the scrub.

- Escape while scrubbing restores the start value through one more command; release, `PointerCancel` and Tab keep the last value. Tab and other keys then continue with their usual meaning.
- A valid pending draft, such as typed `2.5`, is the start value. An invalid draft, such as `2,5`, rejects the scrub with the usual `Scale could not finish...` feedback and stays as typed; Escape still reverts it.
- Each changed value is a separate accepted command, like the Display color drag; the Inspector keeps no history.

The window-free smoke checks the cursor targets, focus and active feedback without a command, the threshold, one command per changed step, clamping without repeated commands, return through the start value, Escape restoration without a later release command, release steps, a click, a pending draft as the start, `PointerCancel`, invalid-draft rejection, a Position component through its own command, Tab ending the scrub, unchanged step parts and per-item retention. The injected native smoke drags the X label with Windows mouse messages under OS capture and checks the cursor request.

## Position vector input

The fifth General row is an ordinary [VectorInput](vector-input.md) with stable ID `position` and components `position-x`, `position-y` and `position-z`, initially `0` for every item. Each item keeps an accepted `position` array and one `TextEditState` draft per component; draft owners differ from every name and Scale owner. The host treats Scale and the three components as one table of numeric fields, each with its own `item.scale` or `item.position.<axis>` command, range and reset value, and routes committed text, editing keys, Enter, Escape and Up/Down to whichever field holds focus. Position uses the range -1000 to 1000 with step 0.1 and has no step parts.
Expand Down Expand Up @@ -248,4 +260,4 @@ Name and notes rejection returns to the host instead of throwing out of the mess

Windows hosting reuses the Settings starter's [optional host](../../examples/settings-starter/native_win32.hpp): pointer capture/cancellation, logical coordinate mapping, keyboard/text messages, GDI glyph coverage and CPU presentation. The application-local [clipboard adapter](../../examples/inspector-hierarchy/clipboard_win32.hpp) uses `CF_UNICODETEXT`, strict UTF-16/UTF-8 conversion, bounded reads and explicit Windows memory ownership. Open/format/lock/write failures return diagnostics without throwing through the message loop.

The window-free checks cover rejected committed name text and text/deletion/selection presentation staging, unchanged items/focus/inspection, reversed Unicode selection and a successful retry. Clipboard checks use injected services for reverse Unicode selections, partial replacement/deletion, modifier/repeat/focus guards, retained independent items, service/validation/presentation failures, retry and reentrant target changes. The injected native smoke checks arrow/Activate selection, text/Checkbox/Slider/Scale/Visibility/Switch/Purpose/Display color edits, Display tab selection, toolbar arrows and Reset, pointer select/return after wheeling the Hierarchy back into view (each smoke click requires the pointer to reach its control), right-click Rename, splitter capture and window resize, plus Ctrl+C/X/V routing, suppression of translated control characters/repeats, Unicode cut/paste, rejection recovery and strict Unicode conversion. It also verifies caption feedback and recovery after rejected typing, a full Unicode name buffer, invalid paste and an over-budget ASCII glyph atlas. It substitutes an in-memory clipboard, leaving the user's OS clipboard untouched. Live OS clipboard exchange, physical devices and IME sessions, real text shaping, GPU rendering, native editable accessibility and persistence remain unvalidated. Capability claims and validation limits live in [support](../reference/support-matrix.md).
The window-free checks cover rejected committed name text and text/deletion/selection presentation staging, unchanged items/focus/inspection, reversed Unicode selection and a successful retry. Clipboard checks use injected services for reverse Unicode selections, partial replacement/deletion, modifier/repeat/focus guards, retained independent items, service/validation/presentation failures, retry and reentrant target changes. The injected native smoke checks arrow/Activate selection, text/Checkbox/Slider/Scale/Position scrub/Visibility/Switch/Purpose/Display color edits, Display tab selection, toolbar arrows and Reset, pointer select/return after wheeling the Hierarchy back into view (each smoke click requires the pointer to reach its control), right-click Rename, splitter capture and window resize, plus Ctrl+C/X/V routing, suppression of translated control characters/repeats, Unicode cut/paste, rejection recovery and strict Unicode conversion. It also verifies caption feedback and recovery after rejected typing, a full Unicode name buffer, invalid paste and an over-budget ASCII glyph atlas. It substitutes an in-memory clipboard, leaving the user's OS clipboard untouched. Live OS clipboard exchange, physical devices and IME sessions, real text shaping, GPU rendering, native editable accessibility and persistence remain unvalidated. Capability claims and validation limits live in [support](../reference/support-matrix.md).
Loading