Move every shape gizmo onto a registry-driven generic system - #4479
Move every shape gizmo onto a registry-driven generic system#4479Ayush2k02 wants to merge 40 commits into
Conversation
… grid drawing a stray handle
There was a problem hiding this comment.
1 issue found and verified against the latest diff
Confidence score: 3/5
editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs: mixed selections such as Star and Polygon are incorrectly grouped because Generic handlers all reportkind()as"generic", risking incorrect gizmo handle behavior; make the grouping key distinguish the concrete shape type.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs:35">
P1: When multiple shapes of different types are selected (e.g., Star and Polygon), all Generic handlers report the same `kind()` ("generic"), so the grouping logic incorrectly combines them. This causes one shape's handler to draw overlays for a different shape type, because `GenericGizmoManager::overlays` ignores the `selected_shape_layers` parameter and uses only the layer it was constructed with. Either track the actual shape type in `kind()` to prevent cross-shape grouping, or make `GenericGizmoManager` respect the layer parameter passed to `overlays`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
6 issues found across 28 files
Confidence score: 3/5
editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rsgroups different selected shapes under the sharedkind()valuegeneric, so mixed selections such as stars and polygons can receive the wrong overlay rendering and altered grouping behavior inhandle_actions— preserve distinct grouping identities for shapes or update the grouping logic.editor/src/messages/tool/common_functionality/shapes/grid_shape.rscontains misleading panic messages inrect()anddirection(), which can slow diagnosis when invalid grid data is encountered — correct the copied concept names.- The
Genericenum documentation ineditor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rsis stale because migrated shapes including arcs, grids, spirals, and hearts now use it — update the documented variants to match the routing. - The registry length assertion in
editor/src/messages/tool/common_functionality/gizmos/gizmo_registry.rsis coupled to the current node count, whilecheck_if_over_gizmoingrid_shape.rsis unused and duplicates hit-testing logic elsewhere — assert specific registrations and remove or integrate the dead helper.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/tool/common_functionality/shapes/grid_shape.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/shapes/grid_shape.rs:196">
P3: `check_if_over_gizmo` is defined but never called anywhere in the crate. The grid gizmo's real hit-testing lives in `gizmo_behaviors.rs::grid_edge_distances`, which re-implements the same `rect().contains()` + `line().nearest()` proximity check, so this new function is dead code. Remove it (along with any helpers it alone uses) or wire it into the gizmo path.</violation>
<violation number="2" location="editor/src/messages/tool/common_functionality/shapes/grid_shape.rs:400">
P3: Four panic messages have copy-paste errors that name the wrong concept. In `rect()`, the message says "does not have opposite" instead of "does not have a rect". In `direction()`, it says "does not have a line" instead of "does not have a direction". In `initial_dimension()` and `spacing()`, both say "does not have a mouse_icon" when they should describe the method's actual return value. These misleading messages will confuse debugging if the panics are ever triggered.</violation>
</file>
<file name="editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs:19">
P3: The enum doc comment claims only "(polygon, circle, heart, star)" use the `Generic` variant, but this diff routes every shape (arc, grid, spiral, and the newly added heart) through `Generic`. The list is stale as of this change — the migration covered all seven shapes. Update the comment to reflect that all shape gizmos now go through `Generic`.</violation>
<violation number="2" location="editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs:35">
P2: When multiple shapes of different types are selected (e.g., a star and a polygon), the grouping logic groups them together because all generic shapes return the same `kind()`. The overlay rendering then calls one shape's GenericGizmoManager to render overlays for a different shape's layer, which fails silently because GenericGizmoManager only contains gizmos for the layer it was created with. Either make GenericGizmoManager return a shape-specific kind ("generic-star", "generic-polygon", etc.) so different shapes remain in separate groups, or redesign the grouping to account for layer-specific handler instances.</violation>
<violation number="3" location="editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs:35">
P2: Collapsing `kind()` to a single `"generic"` value for every migrated shape changes the grouping behavior in `handle_actions`. The `layers_handlers` grouping groups selected layers by `kind()`; previously each shape type had its own kind ("star", "polygon", ...), so a concurrently selected circle and star stayed in separate groups and each handler drew its own overlay. Now they share `"generic"` and are merged into one group, and since `GenericGizmoManager::overlays` ignores the `_selected_shape_layers` argument (it draws only its own bound node's gizmos), the first group's gizmo manager renders its gizmos once per layer in the merged group — showing circle gizmos on a star layer and duplicating overlays. This regresses multi-shape simultaneous-selection overlays versus the old per-kind grouping.</violation>
</file>
<file name="editor/src/messages/tool/common_functionality/gizmos/gizmo_registry.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/gizmos/gizmo_registry.rs:490">
P3: The test asserts a hardcoded `len() == 7`, but the registry is meant to grow as nodes are registered, so adding/removing a shape breaks this test for no behavioral reason. Assert against the specific expected node identifiers instead, so the test verifies content rather than an arbitrary count.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
2 issues found across 12 files (changes from recent commits).
Confidence score: 3/5
- In
editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/mod.rs, drawing a new circle can leaveGizmoManagerusing cached handlers for the previous selection, causing the newly created layer to render incorrectly; preserve lazy selected-layer detection or refresh the handlers here. - In
editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/generic_slider_gizmo.rs,GizmoInfo::position_hintandPositionHintare now unused after their final runtime consumer was removed, leaving dead API/code that should be removed or intentionally restored.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/mod.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/mod.rs:235">
P2: While drawing a new circle, `GizmoManager` does not refresh its cached handlers, so this method renders the previous selection instead of the newly created layer. Preserve selected-layer lazy detection here or refresh handlers when the drawing layer is created, otherwise the circle radius gizmo disappears during creation.</violation>
</file>
<file name="editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/generic_slider_gizmo.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/generic_slider_gizmo.rs:156">
P3: The `GizmoInfo::position_hint` field and the whole `PositionHint` enum are now never read by runtime code. This delta removed the last consumer: `GenericSliderGizmo::handle_positions` previously matched on `self.info.position_hint` (with both arms returning the same value), and it has collapsed to `None => vec![DVec2::new(value.abs(), 0.)]`. Every registry entry still writes `position_hint` (including `BoundingBoxCenter`/`BoundingBoxCorner`), but nothing ever reads it, so those hints are silently inert — a dead field that invites future nodes to set it expecting behavior that never happens.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| } | ||
| } | ||
|
|
||
| fn overlays(&self, document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext) { |
There was a problem hiding this comment.
P2: While drawing a new circle, GizmoManager does not refresh its cached handlers, so this method renders the previous selection instead of the newly created layer. Preserve selected-layer lazy detection here or refresh handlers when the drawing layer is created, otherwise the circle radius gizmo disappears during creation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/mod.rs, line 235:
<comment>While drawing a new circle, `GizmoManager` does not refresh its cached handlers, so this method renders the previous selection instead of the newly created layer. Preserve selected-layer lazy detection here or refresh handlers when the drawing layer is created, otherwise the circle radius gizmo disappears during creation.</comment>
<file context>
@@ -256,30 +232,7 @@ impl ShapeGizmoHandler for GenericGizmoManager {
- return;
- }
-
+ fn overlays(&self, document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext) {
for gizmo in &self.gizmos {
gizmo.overlays(document, mouse_position, Some(shape_editor), overlay_context);
</file context>
| fn handle_positions(&self, document: &DocumentMessageHandler, value: f64) -> Vec<DVec2> { | ||
| match self.info.behavior.handle_positions { | ||
| Some(positions) => positions(&self.context(document, DVec2::ZERO, None), value), | ||
| None => vec![DVec2::new(value.abs(), 0.)], |
There was a problem hiding this comment.
P3: The GizmoInfo::position_hint field and the whole PositionHint enum are now never read by runtime code. This delta removed the last consumer: GenericSliderGizmo::handle_positions previously matched on self.info.position_hint (with both arms returning the same value), and it has collapsed to None => vec![DVec2::new(value.abs(), 0.)]. Every registry entry still writes position_hint (including BoundingBoxCenter/BoundingBoxCorner), but nothing ever reads it, so those hints are silently inert — a dead field that invites future nodes to set it expecting behavior that never happens.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/generic_slider_gizmo.rs, line 156:
<comment>The `GizmoInfo::position_hint` field and the whole `PositionHint` enum are now never read by runtime code. This delta removed the last consumer: `GenericSliderGizmo::handle_positions` previously matched on `self.info.position_hint` (with both arms returning the same value), and it has collapsed to `None => vec![DVec2::new(value.abs(), 0.)]`. Every registry entry still writes `position_hint` (including `BoundingBoxCenter`/`BoundingBoxCorner`), but nothing ever reads it, so those hints are silently inert — a dead field that invites future nodes to set it expecting behavior that never happens.</comment>
<file context>
@@ -151,18 +147,13 @@ impl GenericSliderGizmo {
- // hints are refined as more node types adopt the slider.
- _ => DVec2::new(value.abs(), 0.),
- }],
+ None => vec![DVec2::new(value.abs(), 0.)],
}
}
</file context>
There was a problem hiding this comment.
1 issue found across 12 files (changes from recent commits).
Confidence score: 5/5
- In
editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/generic_slider_gizmo.rs, a comment still refers to removedPositionHint/position_hintbehavior, which could mislead future maintenance but does not indicate a functional regression — update or remove the stale comment.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/generic_slider_gizmo.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/generic_slider_gizmo.rs:151">
P3: This comment still describes "bounding-box position hints" falling through to the default handle, but `PositionHint`/`position_hint` was dropped entirely in this PR (commit "Drop the position hint"), and the `handle_positions` `None` branch no longer consults any hint. The comment now points readers at a mechanism that no longer exists. Drop the trailing sentence and just document that the default single handle sits `value` out along the local +X axis.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| /// Every point in the layer's local space where this parameter can be grabbed. | ||
| /// | ||
| /// A shape that puts its handles on its own geometry supplies them. Everything else gets the single | ||
| /// default handle, `value` out along the local +X axis. The bounding-box position hints fall |
There was a problem hiding this comment.
P3: This comment still describes "bounding-box position hints" falling through to the default handle, but PositionHint/position_hint was dropped entirely in this PR (commit "Drop the position hint"), and the handle_positions None branch no longer consults any hint. The comment now points readers at a mechanism that no longer exists. Drop the trailing sentence and just document that the default single handle sits value out along the local +X axis.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/tool/common_functionality/gizmos/generic_gizmos/generic_slider_gizmo.rs, line 151:
<comment>This comment still describes "bounding-box position hints" falling through to the default handle, but `PositionHint`/`position_hint` was dropped entirely in this PR (commit "Drop the position hint"), and the `handle_positions` `None` branch no longer consults any hint. The comment now points readers at a mechanism that no longer exists. Drop the trailing sentence and just document that the default single handle sits `value` out along the local +X axis.</comment>
<file context>
@@ -151,18 +147,13 @@ impl GenericSliderGizmo {
- /// Shapes that place their handles on their own geometry supply them; everything else gets the single
- /// default handle, sitting `value` out along the local +X axis.
+ /// A shape that puts its handles on its own geometry supplies them. Everything else gets the single
+ /// default handle, `value` out along the local +X axis. The bounding-box position hints fall
+ /// through to that same default until a node needs them.
fn handle_positions(&self, document: &DocumentMessageHandler, value: f64) -> Vec<DVec2> {
</file context>
Every shape declares its gizmos in the registry now, so the seven per-shape branches in `detect_shape_handler` all returned the same thing, and `ShapeGizmoHandlers` wrapped a single variant. Its `kind()` grouping collapsed into one bucket, which is why overlays re-detected a layer's gizmos every frame; holding one handler per layer drops that. The grid's `RowColumnGizmoType::None` had no construction site and eight panicking accessors, four with another method's message. Delete it along with `check_if_over_gizmo`, `opposite_gizmo_type`, `mouse_icon` and `all()`, which were dead with it. Fix a doc comment sitting on the wrong function, a match whose two arms were identical, a link to a file that does not exist, and a README snippet with the wrong signature. Give the `GizmoBehavior` constants a `..NONE` tail so each shows only what is unusual about it.
Every declaration set `position_hint` and nothing read it: the only match on it returned the same expression from both arms, so the bounding-box variants never anchored anything. Shapes that need a handle elsewhere already use `handle_positions`. Also replace the registry test's `len() == 7` assertion, which checks the length of a fixed-size array and so cannot fail, with the seven identifiers it meant to check.
The layer being dragged out is created after the last `handle_actions` pass, which only runs while the tool is ready, so the overlay drawn during a circle drag showed the previous selection.
70af8df to
65119e7
Compare
There was a problem hiding this comment.
2 issues found across 12 files (changes from recent commits).
Confidence score: 3/5
- In
editor/src/messages/tool/tool_messages/shape_tool.rs, Circle drawing can rebuild an inactive handler without running hover arbitration, causingcircular_radius_overlayto render nothing; run the handler state pass before rendering. - In
editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs,refresh_handlerscan leave a stale active handler rendering while the refreshed selection is skipped, producing incorrect gizmo overlays; clearactive_shape_handlerbefore rebuilding or re-arbitrate afterward.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/messages/tool/tool_messages/shape_tool.rs">
<violation number="1" location="editor/src/messages/tool/tool_messages/shape_tool.rs:954">
P2: While drawing a Circle, this rebuilds an inactive handler but never runs hover arbitration, so `circular_radius_overlay` returns without drawing anything. Run the handler state pass before rendering so the new Circle handler can become hovered and display its radius overlay.</violation>
</file>
<file name="editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs">
<violation number="1" location="editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs:32">
P2: When `refresh_handlers` runs with an existing active handler, `overlays` continues rendering that stale handler and skips the refreshed selection. Clear `active_shape_handler` when rebuilding handlers, or re-arbitrate it against the new selection.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| if tool_options.shape_type == ShapeType::Circle { | ||
| // The layer being dragged out was created after the last `handle_actions` pass, which only | ||
| // runs while the tool is ready, so its handler has to be built here. | ||
| tool_data.gizmo_manager.refresh_handlers(document); |
There was a problem hiding this comment.
P2: While drawing a Circle, this rebuilds an inactive handler but never runs hover arbitration, so circular_radius_overlay returns without drawing anything. Run the handler state pass before rendering so the new Circle handler can become hovered and display its radius overlay.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/tool/tool_messages/shape_tool.rs, line 954:
<comment>While drawing a Circle, this rebuilds an inactive handler but never runs hover arbitration, so `circular_radius_overlay` returns without drawing anything. Run the handler state pass before rendering so the new Circle handler can become hovered and display its radius overlay.</comment>
<file context>
@@ -953,6 +949,9 @@ impl Fsm for ShapeToolFsmState {
if tool_options.shape_type == ShapeType::Circle {
+ // The layer being dragged out was created after the last `handle_actions` pass, which only
+ // runs while the tool is ready, so its handler has to be built here.
+ tool_data.gizmo_manager.refresh_handlers(document);
tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
}
</file context>
| /// [`handle_actions`](Self::handle_actions) does this as part of its pass. A caller that draws overlays | ||
| /// from a state where it does not run, such as while a shape is still being dragged out, has to ask for | ||
| /// it here or it draws the previous selection. | ||
| pub fn refresh_handlers(&mut self, document: &DocumentMessageHandler) { |
There was a problem hiding this comment.
P2: When refresh_handlers runs with an existing active handler, overlays continues rendering that stale handler and skips the refreshed selection. Clear active_shape_handler when rebuilding handlers, or re-arbitrate it against the new selection.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs, line 32:
<comment>When `refresh_handlers` runs with an existing active handler, `overlays` continues rendering that stale handler and skips the refreshed selection. Clear `active_shape_handler` when rebuilding handlers, or re-arbitrate it against the new selection.</comment>
<file context>
@@ -1,212 +1,58 @@
+ /// [`handle_actions`](Self::handle_actions) does this as part of its pass. A caller that draws overlays
+ /// from a state where it does not run, such as while a shape is still being dragged out, has to ask for
+ /// it here or it draws the previous selection.
+ pub fn refresh_handlers(&mut self, document: &DocumentMessageHandler) {
+ self.layer_handlers = document
+ .network_interface
</file context>
| pub fn refresh_handlers(&mut self, document: &DocumentMessageHandler) { | |
| pub fn refresh_handlers(&mut self, document: &DocumentMessageHandler) { | |
| self.active_shape_handler = None; |
Replaces the per-shape gizmo handlers with a registry that nodes declare into.
gizmo_registry.rs: node type to the parameters worth exposing. Each entry has theparameter index, control type, name, bounds and position hint.
generic_slider_gizmo.rs/generic_dial_gizmo.rs: the two controls. State machine,hit-testing, hover arbitration, cursor and graph write live here.
gizmo_behaviors.rs: per-shape overrides for grab points, hover distance, drag,snapping and overlays. Eleven definitions cover seven shapes.
README.md: how to add a gizmo to a node.Migration:
unchanged behaviour.
shape_gizmos/deleted: roughly 2K lines across six handlers.ShapeGizmoHandlersgoes from seven variants to two.New:
Fixes, found by hand rather than by the test suite:
off-centre, and moved on a click that never moved. Now reads horizontally.
Includes the Heart node commits as a base.
1-polygon-sides-dial.mp4
2-star-radius-snapping-and-points-dial.mp4
3-circle-radius.mp4
4-arc-radius.mp4
5-arc-sweep-endpoints.mp4
6-spiral-turns.mp4
7-grid-rows.mp4
Regression from master ( CIrcle and Arc radius bug )
Screen.Recording.2026-08-26.at.6.53.34.PM.mov