Migrate all shape gizmos to the registry-driven generic system - #4471
Draft
Ayush2k02 wants to merge 18 commits into
Draft
Migrate all shape gizmos to the registry-driven generic system#4471Ayush2k02 wants to merge 18 commits into
Ayush2k02 wants to merge 18 commits into
Conversation
…ter APIs The stack was written against the 2026-07-23 fork point and does not build on current master. Two of master's changes land on it directly: - GraphiteEditor#4457 deleted the legacy `subpath` module. The Heart node was the only remaining caller of `ManipulatorGroup`/`Subpath::new`/`Vector::from_subpath`. Its anchor math moves to a `heart_bezpath` constructor in `shapes.rs`, alongside the other generators, so the node body stays as thin as its neighbors and the load-bearing winding order lives with the rest of the geometry. - GraphiteEditor#4387 replaced raw input indices with parameter symbols, removing the `NodeInputDecleration` trait and `grid::SpacingInput`'s type parameter. The registry's remaining `parameter_index: 1` literals become the generated symbols, and the generic gizmos pair their runtime-chosen index back with their node identifier through `ParameterRef` rather than passing a bare `usize` to `InputConnector::node`. No behavior change. `cargo test -p graphite-editor -p vector-nodes` passes (225 tests), including the six registry tests.
The port from the deleted subpath API to BezPath was a manual translation of anchor and handle positions, and nothing else in the tree exercises the heart's geometry. These pin the properties the translation could plausibly have broken: segment count and closure, mirror symmetry about the vertical axis, linear scaling with radius, and honoring the center argument.
Every parametric shape's gizmo shares the same mechanics -- hit-test a handle, run a hover/drag state machine, draw the handle, write the input -- which is what the generic gizmos already replace. But three behaviors in the hand-written handlers genuinely depend on a node's geometry and cannot be expressed as registry data: - A star's snap radii fall out of its side count and its *other* radius. - A spiral's turns and outer radius have to move together, or the spiral changes tightness as you drag it. - A star previews its outline and spokes while dragging, not just the handle. `GizmoBehavior` carries those as optional functions supplied by the shape, so the registry stays a declarative table and the geometry-specific math stays with the geometry. All fifteen existing declarations opt out via `GizmoBehavior::NONE`; nothing changes for them. Snap resolution is extracted as `nearest_snap_target` so it can be tested without a live document. Targets are captured when the gizmo is first hovered rather than recomputed per frame, since they depend on parameters that are themselves in flux during a drag.
…gration The hand-written `NumberOfPointsDial` drew a spoke per side and the shape's outline: the spokes at rest as a hint that the dial exists at all, and both while dragging so the count being edited stays legible. Moving the polygon to the generic dial dropped all of it, leaving a bare ring with no indication of what it controls. The overlay hook now runs in every state, including Inactive, since a resting affordance is exactly the case the shape wants to draw for. `GizmoContext` grows the cursor position, the interaction state, and the path editor's state, which between them are what the ported drawing code needs -- including standing down near an editable segment, so the hint never competes with path editing. `gizmo_behaviors` is the new home for this shape-specific half. It also carries the star's declarations, which are correct but not yet reachable: the star's radius handles sit on its vertices rather than at a single point on the +X axis, so they need a gizmo type the generic layer does not have yet.
The star's radius is not a slider. Its handles sit on the shape's own points -- `radius_1` grabbable at every outer vertex, `radius_2` at every inner one -- and the drag runs out along whichever point the user took hold of, not along a fixed axis. The generic slider assumed a single handle on the local +X axis, which is why the star could not migrate with the circle and the polygon. So the slider now asks the shape where its parameter can be grabbed. The default is unchanged and still a single handle `value` out along +X, which is what a circle, a heart, and a spiral all want. A shape that puts handles on its geometry returns the whole set, and the drag projects onto the ray through the one that was grabbed. `handle_index` records which, so the overlay can draw the right ray. With that, the star's remaining behavior is expressible through the hooks: snapping to the radii where its points line up, ticks marking them, the ray extended across the viewport, and the outline of the shape being reshaped. Deletes `PointRadiusHandle` and `NumberOfPointsDial`, whose last user this was, along with the `ShapeGizmoHandlers::Star` variant. Two deliberate differences from the hand-written version: the drag projects in the layer's local space rather than mixing local deltas with viewport-space directions, which is what it meant to do and now also holds under rotation; and the red alignment guides drawn at the moment of snapping are not reproduced -- the ticks still mark every snap radius.
A spiral's control neither extends nor steps: it winds. Dragging an endpoint sweeps around the origin, and the sweep has to keep counting past a full turn, which the angle between two points cannot express. Turns alone would also change the spiral's tightness as it grows, so the outer radius has to move with it. So the generic layer grows two things. `drag` lets a shape convert cursor motion into node inputs itself, returning every input the motion implies rather than the single value the default projection produces. And the slider now accumulates the angle swept about the layer's origin frame by frame, since that is bookkeeping no stateless hook can do for itself -- with a half-degree deadzone, because near the origin the angle between successive cursor positions is mostly noise. Because a multi-parameter drag is the only thing that knows how its parameters constrain each other, supplying `drag` also bypasses clamping and snapping. The registry entry drops to the turns control alone. The inner and outer radius sliders it used to declare were never reachable, and a handle for either would land at an arbitrary point on a curve that is nowhere near circular -- while winding the spiral from its own endpoints reads immediately. Both are still adjustable from the Properties panel. Deletes `SpiralTurns` and `SpiralGizmoHandler`.
An arc's sweep is held to a single turn and never runs backwards, and its start angle is kept inside [-180°, 180°]. All three limits get reached by *continuing* a drag rather than by ending it, so the gesture does not stop at them -- dragging the start endpoint past a full sweep hands over to the end endpoint and carries on from there. That means a drag has to be able to rewrite the baseline the rest of the gesture is measured against, so `drag` now takes its `DragInput` mutably and the slider carries whatever the shape leaves behind into the next frame. Re-anchoring is the one thing a drag genuinely owns about itself. `GizmoType::Angle` now builds a gizmo instead of being silently skipped. It runs on the same handle machinery as a slider, since with a drag hook the two differ only in what the default would have done -- and for an angle the default, a distance along a ray, is meaningless. `angle_deadzone` moves onto the behavior for the same reason: the spiral wants its jitter guard, the arc does not. One entry covers the sweep rather than two. Dragging either endpoint can move the start angle and the sweep together, so a separate start-angle gizmo would be a second control over one gesture. Deletes `SweepAngleGizmo`, `RadiusHandle`, and `ArcGizmoHandler`. The arc's radius now uses the plain slider, as the circle's already did. Not reproduced: the red guide lines drawn at the moment a sweep snaps. The sweep still snaps to each eighth of a turn, and the angle readout still tracks it.
A grid's rows and columns are not grabbed at a point: any spot along an edge will do, and the band the edge occupies counts too. So a shape can now supply `hover_distances` in place of measuring to its handle positions, which is the same escape hatch the other hooks are -- the generic layer keeps arbitrating between overlapping gizmos, it just no longer assumes the thing being aimed at is a point. Dragging the top edge upward also has to move the layer. The node builds its grid downward from the origin, so without a matching translation the new rows would appear at the bottom and the edge would slide out from under the cursor. `drag` therefore returns a `DragWrites` rather than a bare list of inputs, carrying an optional transform alongside them. Dragging past the last row does not stop at one either -- the grid turns inside out and the opposite edge takes over -- so the gesture re-anchors its origin the same way the arc re-anchors its baseline. Rows and columns are declared as dials, but a declaration that brings its own drag is hosted by the general handle gizmo rather than the dial: the dial is the narrow one, a count stepped by horizontal drag, and none of that applies here. The grid's edge geometry -- four edges across two layouts, rectangular and isometric -- moves to `grid_shape.rs`. It is the grid's own geometry rather than gizmo machinery, and it is the same shape of split as the star's snap radii. The spacing gizmo the registry used to declare is dropped. It was a position gizmo that was never built, and spacing is a two-axis value with no obvious handle on the shape. With this the `shape_gizmos` module is empty and deleted. All seven shapes now run on the registry.
Six function-pointer fields spelled out inline made the struct hard to read and tripped clippy's complex-type lint. The aliases also give the hooks somewhere to be documented individually.
… grid drawing a stray handle Auditing the migrated shapes against the handlers they replaced turned up three regressions, two of them shipped and one of them mine. A circle's radius was never grabbed at a point. The hand-written handler took the whole circumference, through a band that widened with the stroke so a thick outline stayed grabbable at its edge, and narrowed for a circle small on screen so the band could not swallow the shape. It drew that band as a pair of dashed ellipses rather than a handle. Moving the circle to the plain slider replaced all of it with a single dot on the +X axis; the arc's radius lost the same when it followed. `CIRCULAR_RADIUS` puts it back for both. It reports the radial distance from the circumference rather than a flat yes, so an arc's endpoints still win the cursor where the two overlap -- which the hand-written version could not do, since its two gizmos hovered independently. The grid, meanwhile, was drawing a handle dot and a line to it at local `(row_count, 0)`: a point with no meaning, left over from the default handle position, sitting inside the grid. `draws_own_handle` marks the shapes whose overlay already draws the thing being grabbed. `hover_distances` now also owns its own range. A shape that answers the hover question knows how far is too far, and the point threshold that suits a handle is wrong for a band or an edge.
Two differences from the hand-written dial, both dating from the original migration. It stepped every 25 pixels of drag, not 20, so the generic one turned a quarter faster than the shape it replaced. And it disappeared once the shape was small enough on screen that the dial would cover it -- the generic one's check compared a single local unit against nothing meaningful and never fired, so the dial stayed live on a shape a few pixels across and swallowed presses meant for the layer. The replacement measures the layer's bounding box, which is what "too small to sit around" actually means and does not depend on the shape's own parameters.
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.
No description provided.