Repository navigation
(0.10.x) - docs(surface): tree, canvas pointer, menus, child windows, dialogs and focus for v0.10.5 #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(0.10.x) - docs(surface): tree, canvas pointer, menus, child windows, dialogs and focus for v0.10.5 #33
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| # View mail | ||
|
|
||
| Mail primitive natives post - clicks, text, toggles, values, selections, dates, rows, resizes, video state. | ||
| Mail primitive natives post - clicks, canvas pointer and wheel, text, toggles, values, selections, dates, rows, resizes, video state. | ||
|
|
||
| ## Schema | ||
|
|
||
|
|
@@ -10,6 +10,12 @@ All readonly `NamedSignal` + `PrimitiveMail` (`window()`, `path()`, `uuid()`); b | |
| |---|---|---|---| | ||
| | `ButtonClicked` | — | `view.clicked.<window>.<path>` | `TKButton` | | ||
| | `CanvasClicked` | x, y (where the press landed, points from the canvas's top-left) | `view.clicked.<window>.<path>` | canvas: primary press + release on it, not moved past the drag distance (a tap too); not Ctrl-click on macOS, not a hold past the long-press time on Linux (right clicks); none when it or an ancestor is disabled | | ||
| | `CanvasPressed` | x, y, `MouseButton` button, int clicks, `Modifiers` modifiers | `view.pressed.<window>.<path>` | canvas after `watchPointer()`: a button down on it, which then owns the press (other buttons post nothing until it ends); clicks counts same-button presses within the toolkit's double-click time and distance, so a triple click is 3 | | ||
| | `CanvasDragged` | x, y, button, modifiers | `view.dragged.<window>.<path>` | the owning button held and the point changed; keeps posting outside the canvas and the window (implicit capture), x / y then negative or past its size | | ||
| | `CanvasReleased` | x, y, button, modifiers | `view.released.<window>.<path>` | exactly one per `CanvasPressed`: the owning button's release, or at once at the last point posted when the toolkit ends the press (context menu, GTK grab broken, touch cancelled) | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Document the code-cancellation exception for
🤖 Prompt for AI Agents
|
||
| | `CanvasWheeled` | dx, dy, bool precise, modifiers | `view.wheeled.<window>.<path>` | a wheel over a watching, enabled canvas; kept from an enclosing scroll view. precise = trackpad points, else notches (one notch = 1.0); positive dx / dy go toward the content's right / bottom | | ||
| | `FocusGained` | — | `view.focus-gained.<window>.<path>` | any primitive: the user moved keyboard focus to its native (click, Tab, the toolkit's own move); never from `focus()` or `setInitialFocus()` | | ||
| | `FocusLost` | — | `view.focus-lost.<window>.<path>` | any primitive: the user moved keyboard focus away from its native | | ||
| | `RightClicked` | x, y (in the primitive) | `view.right-clicked.<window>.<path>` | any primitive: the innermost under a right click (secondary click, Ctrl-click on macOS, a primary press held still on Linux); none when it or an ancestor is disabled | | ||
| | `TextChanged` | value | `view.text-changed.<window>.<path>` | `TKTextInput`, `TKTextArea` | | ||
| | `TextSubmitted` | value | `view.text-submitted.<window>.<path>` | `TKTextInput` (return) | | ||
|
|
@@ -22,6 +28,9 @@ All readonly `NamedSignal` + `PrimitiveMail` (`window()`, `path()`, `uuid()`); b | |
| | `ViewScrolled` | position (ScrollPosition) | `view.scrolled.<window>.<path>` | `TKTextArea` while watched; latest-only | | ||
| | `SplitMoved` | positions (list<float>) | `view.split-moved.<window>.<path>` | `TKSplitView`: once a user divider drag ends with positions changed; not window resizes or `setPositions()` | | ||
| | `TabSelected` | page (name) | `view.tab-selected.<window>.<path>` | `TKTabView`: user tab switch; not `select()`, `addPage()` or removing a page | | ||
| | `NodeSelected` | ?id | `view.node-selected.<window>.<path>` | `TKTree`: user selection (null = cleared); not `selectNode()` or `setNodes()` | | ||
| | `NodeActivated` | id | `view.node-activated.<window>.<path>` | `TKTree`: double click, Enter or Return | | ||
| | `NodeExpanded` | id, expanded | `view.node-expanded.<window>.<path>` | `TKTree`: user expand / collapse; not `expand()`, `selectNode()` or `setNodes()` | | ||
| | `VideoPlaying` / `VideoPaused` / `VideoEnded` | — | `view.video-playing.<window>.<path>` etc. | `TKVideo` | | ||
| | `VideoFailed` | reason | `view.video-failed.<window>.<path>` | `TKVideo` (bad file, codec) | | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,13 @@ All readonly, all `Voyager\Contracts\Signals\NamedSignal` (`name()` for Signals | |
| | `WindowClosed` | window | `window.closed.<window>` | window closed (button or `close()`), once | | ||
| | `WindowFocused` | window | `window.focused.<window>` | window became active / key | | ||
| | `WindowRightClicked` | window, x, y | `window.right-clicked.<window>` | right click on the window's bare content (no primitive under it); x, y in the content area | | ||
| | `WindowKeyPressed` | window, `Key` key, character, `Modifiers` modifiers, repeat | `window.key.<window>` | a key press no focused native, menu accelerator, default/cancel button or Tab move took; key is physical, character the layout's text (`''` for none) | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Clarify when Line 14 contains “a key press no focused native,” which is not grammatical and does not state that the focused native control failed to handle the key. Describe the event as posting only after the accelerator, default/cancel, Tab, and focused-native checks do not handle the key. 🤖 Prompt for AI Agents
|
||
| | `MenuActivated` | window, item | `menu.activated.<window>.<item>` | plain item chosen | | ||
| | `MenuToggled` | window, item, on | `menu.toggled.<window>.<item>` | toggle chosen by the user; `on` = new state | | ||
| | `QuitRequested` | ?window | `quit.requested` | Quit item chosen | | ||
| | `WindowResized` | window, width, height | `window.resized.<window>` | content area resized; via `postLatest`, one per pump, last size | | ||
| | `Services\AlertAnswered` | id, button | `dialog.alert.<id>` | alert answered: button index; Esc and the alert's close give its cancel index; `-1` when the parent closed first | | ||
| | `Services\FilesChosen` | id, paths | `dialog.files.<id>` | file panel answered: absolute paths; `[]` when cancelled, dismissed or the parent closed first | | ||
|
|
||
| * Default bar items (macOS, no window) post `window` = `''`; Quit posts `window` = `null`. | ||
| * `setToggle()` from code posts nothing. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ Primitive = native view in a `ToolkitWindow`. Toolkit owns layout: PHP declares | |
|
|
||
| | Layer | Where | Holds | | ||
| |---|---|---| | ||
| | Contracts | `Surface\Contracts\Windows\Primitives` | `TK*` interfaces, `Align`, `ImageScaling`, `TableColumn`, `Placement`, `PrimitiveRegistry`, `PrimitiveFactory`, `HasEnabledState` | | ||
| | Contracts | `Surface\Contracts\Windows\Primitives` | `TK*` interfaces, `Align`, `ImageScaling`, `TableColumn`, `TreeNode`, `Placement`, `PrimitiveRegistry`, `PrimitiveFactory`, `HasEnabledState` | | ||
| | Abstracts | `Surface\Windows\Primitives` | `TK*` abstracts (state + guards + `apply*` hooks), `HostsPrimitives` | | ||
| | Concretes | `Jovian\Toolkits\<Kit>\Primitives` | `<Kit><Name> extends TK<Name>`: native build, hooks, mail | | ||
|
|
||
|
|
@@ -66,7 +66,7 @@ Every primitive: `setVisible`/`show`/`hide`, `setEnabled`/`enable`/`disable` (`H | |
| |---|---|---|---| | ||
| | `TKLabel` | text | `applyText`, `applyWrap`, `applyAlignment`, `applyFont`, `applyTextColor` | — | | ||
| | `TKLink` | text, url | `applyText`, `applyUrl`, `applyAlignment`, `applyFont` | — | | ||
| | `TKButton` | label | `applyLabel`, `applyFont`, `applyTextColor` | — (driver posts `ButtonClicked`) | | ||
| | `TKButton` | label | `applyLabel`, `applyFont`, `applyTextColor`, `applyRole` | — (driver posts `ButtonClicked`) | | ||
| | `TKImage` | ?file | `applyFile`, `applyScaling` | — | | ||
| | `TKCanvas` | — | `nativeScale`, `applyPixels(rgba8, w, h, damage)`, `applyAddress(addr, w, h, stride, damage)` | — | | ||
| | `TKSeparator` | horizontal | — (`isHorizontal()`) | — | | ||
|
|
@@ -81,13 +81,15 @@ Every primitive: `setVisible`/`show`/`hide`, `setEnabled`/`enable`/`disable` (`H | |
| | `TKDropdown` | list<string> options, selected | `applyOptions` then `applySelected` | `nativeSelected(int)` | | ||
| | `TKDatepicker` | ?date | `applyDate` (null = native default, `date()` stays null) | `nativeDateChanged(DateTimeImmutable)` | | ||
| | `TKTable` | list<TableColumn>, rows | `applyRows(list<list<string>>)` then `applySelectedRow(?int)` | `nativeRowSelected(?int)` | | ||
| | `TKTree` | list<TableColumn>, list<TreeNode> | `applyNodes`, `applyExpanded(id, bool)` (ancestors all expanded), `applySelected(?id)`, `nativeSelected()` | `nativeNodeExpanded(id, bool)` → changed | | ||
| | `TKVideo` | ?file | `applyFile`, `applyPlay`, `applyPause`, `applySeek`, `applyMuted`, `applyLoop`; reads `nativePosition()`, `nativeDuration()` | `nativeStateChanged(bool playing)` | | ||
|
|
||
| A link's URL needs a scheme (WindowException otherwise, before the native exists or changes); the toolkit opens it on a click and nothing posts. | ||
|
|
||
| * Guards (`WindowException`): NAN/INF in a progress fraction, slider range or value, or `seek()`; fraction outside 0..1, slider `min >= max`, dropdown index out of range / options not a list of strings, table row out of range / cell not scalar|null / column ids not unique, `seek()` < 0. | ||
| * Dropdown: `selected` = -1 exactly when no options; `setOptions` keeps an in-range selection else 0. | ||
| * Table: rows normalised to every column in column order, missing key `''`; `setRows`/`clearRows` clear selection; `appendRow` keeps it; every reload re-applies the selection. | ||
| * Tree: ids unique across the forest (`WindowException`, nothing changed). Expansion recorded in Surface, nodes with children only; kept while an ancestor is collapsed, re-applied by `reopen()` when it opens. `expand()` on a leaf = no-op. `setNodes` keeps expansion and selection for ids still present; a kept selection now under a collapsed node opens its ancestors, as `selectNode()` does. Selection read from the native. | ||
| * Video: `isPlaying()` = engine's last report, not the last call. | ||
|
|
||
|
|
@@ -117,6 +119,15 @@ $view->present(); // on screen | |
| * Driver from the app's `FramebufferManager` (`WindowsServiceProvider` sets `TKCanvas::resolveFramebuffersUsing()`); with none set, the canvas builds `native` / `extended` itself. | ||
| * A toolkit's canvas implements `nativeScale()` + `applyPixels()` + `applyAddress()`; the toolkit copies what it needs before returning. | ||
|
|
||
| Focus: every primitive has `focus()` (throws `WindowException` when removed, hidden or disabled itself or through a container, or when the native declines; the native is not asked in those cases) and `hasFocus()`, through the abstract hooks `applyFocus(): bool` and `nativeHasFocus()`; the driver focuses the native's focus target (the inner view of a scroll-wrapped native). A canvas takes no focus until `setFocusable(bool $on = true, bool $takesTab = false)`; `takesTab` is false whenever focusable is, and a change reaches the driver's `applyFocusable($on, $takesTab)` once. `FocusGained` / `FocusLost` post only for the user's focus changes. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Complete the The sentence ends at “whenever focusable is”. State the missing condition explicitly, for example: “ 🤖 Prompt for AI Agents
|
||
|
|
||
| Pointer: `watchPointer(bool $on = true)` / `isWatchingPointer()`. Off by default; the flag lives in the abstract and nothing is written to the native (as a context menu is not) — the window's pointer source reads it. `watchPointer()` throws `WindowException` once the canvas is removed. While watching, the canvas posts `CanvasPressed`, `CanvasDragged`, `CanvasReleased` and `CanvasWheeled` ([view mail](/docs/{{version}}/surface/api/view-mail)) in points from its top-left, as `CanvasClicked`'s. `CanvasClicked` and the right-click rules post either way, so a sketch that wants only clicks never watches. | ||
|
|
||
| * One press at a time per window: the first button down on a watching, enabled canvas owns it; other buttons' presses and releases post nothing until it ends. `CanvasDragged` posts per move while that button is held, only when the point changed, and goes on outside the canvas and the window (implicit capture). | ||
| * Exactly one `CanvasReleased` per `CanvasPressed`: at the owning button's release, or at once at the last point posted when the toolkit ends the press without one (a context menu opens, GTK's grab breaks, a touch is cancelled). A press ended from code — `watchPointer(false)`, the canvas or a group above it disabled, `remove()`, the window closed — drops silently, as no mail comes from code. | ||
| * A toolkit-emulated touch is the left button. `clicks` counts presses of the same button on the same canvas within the toolkit's double-click time and distance, so a triple click is 3 everywhere. | ||
| * A wheel over a watching, enabled canvas is the canvas's: posted and not passed on, so an enclosing scroll view stays put. Not watching, it passes as before. | ||
|
|
||
| Surface lending: `TKCanvas` is a `WindowOutput` (`Surface\Contracts\Drawing`). A GPU engine borrows a native surface inside the canvas instead of drawing into its framebuffer. | ||
|
|
||
| * `surfaces()`: `[]` until a toolkit's canvas overrides it, with `makeSurface(kind, handles): array` (the surface's handles by name, always `kind->handle()`) and `removeSurface(kind)`. Both have default bodies. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: VenusianPHP/docs
Length of output: 21024
Exclude edit roles from the general accelerator rule.
GTK and Qt edit roles use the focused widget's action and have no accelerator. AppKit edit roles use the standard key equivalents. Update the final sentence so it applies only to non-edit accelerators.
Suggested clarification
🤖 Prompt for AI Agents