Let the application handle selection state updates - #5
Merged
Conversation
The editor kept its own selection (two id arrays, a version counter and two is-*-selected computation callbacks) while every embedder kept a second copy — two sources of truth kept in step by hand-written sync callbacks. Now the editor keeps none. It reports gestures — node-selected, select-link, selection-cleared and the new box-selection-committed, which hands over the marquee rectangle in world coordinates plus the shift state captured at press — and renders the `selected` flag it finds on the rows: a new LinkData field, and whatever field the embedder's node model already binds to BaseNode.selected. Apply an intent synchronously and the click/drag logic reads the new flags back within the same event, as it always did. Rust side: project_selection writes a SelectionManager into model rows (diff-based, so untouched rows don't re-render), extend_selection covers shift+marquee, and wire_node_selection! closes the loop for the common case. NodeEditorSetup's private selection mirror is gone — hand it the application's selection with with_selection() to keep multi-node drag. Gone with it: selected-node-ids, selected-link-ids, selection-version, selection-changed, sync-selection-to-nodes/links, is-node-selected, is-link-selected, compute-box-selection, compute-link-box-selection, SelectionManager::sync_to_model/sync_from_model, and a dead handle-link-click.
The editor already held no selection state, but the host still had to keep a SelectionManager beside the rows and remember to project one into the other. Two records of one fact, kept in step by hand — and `GraphLogic::commit_drag` read the wrong one: it walked the model asking an external set about rows that carry `selected` themselves, so a drag's visuals and its commit could disagree. Now the rows are the only record. `selection::resolve_click` / `resolve_box` hold the policy — pure, generic over the id type, absolute sets in and out, order-stable — and `project_selection` / `selected_rows` move a set between a set and the rows. `apply_click` / `apply_box` / `clear_selection` compose the three, which is all `wire_selection!` needs; it stores nothing. A host can keep selection elsewhere — in its own document or session state — and project after every write, or let the rows be the store, as the examples now do. `commit_drag` reads `selected` off the row — the same data the editor renders, so the two cannot disagree — and moves the dragged node plus anything else selected. `end-node-drag` carries the dragged id to make that self-contained. `MovableNode` gains `selected()`: a drag that commits the co-selected set has to know the co-selected set, and the row already carries it. That kills the synchrony requirement. The only live read-back of `selected` after an emitted intent was `start-node-drag`'s `already-selected`; it now comes from press-time latches (`was-selected-on-press || selection-intent-sent`), so a late-applied intent costs at most a frame of sibling lag instead of silently replacing a shift-toggle in flight. Gone: SelectionManager and NodeEditorSetup::with_selection (the double handoff that let the two sources diverge), wire_node_selection!, and the editor's unreachable drag API — start/update/end-node-drag as component functions plus node-drag-started/-ended, which nothing has ever invoked: BaseNode talks straight to NodeEditorInternalCallbacks and nothing routed back. Grid snapping goes with it. Its only reader sat inside that unreachable end-node-drag, so snap-on-drag has never once run, default-true, with a checkbox in the advanced example bound to it. Deleting it is the one behavior change here, and it is example-only: node creation in `advanced` no longer snaps. Position policy belongs to the host at commit time; it needs no library surface. wire_selection! takes two arms, nodes or nodes-and-links, so LinkData.selected finally has a blessed and tested path. level4 now drives real pointer gestures as well as intents — including the shift-marquee, which pins down that shift is captured at press and replayed at commit, since the release carries no modifiers. That test caught a real asymmetry: clicking a link dropped the node selection, but clicking a node left links selected.
A shift-click on a node cleared the link selection and vice versa, so no gesture could ever build a mixed set — an artifact of where the shift flag flowed (into the click resolver, never into the cross-kind decision). Meanwhile a shift-marquee extends both kinds at once and a delete acts on whatever is selected, of either kind. So the cross-kind clear now happens only for a plain click. Shift leaves the other model alone, in the two-model macro arm and in the advanced example, which hand-wires the same policy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rather than the node editor directly updating its own state (requiring a two-way sync to application state), only notify of selection change events and let the application decide whether/how to handle them.
wire_selection! provided for examples and simple cases where an application doesn't care to own the selection model.