Add pointer interaction via fynix_interaction crate - #56
Conversation
`Handler`s now take a `Response` giving mutable world access (via `Deref`/`DerefMut`) and propagation control, instead of returning a value.
- Propagation { Stop, Continue } (default Stop): handlers consume by default; call Response::propagate() to bubble to the next ancestor.
- HandlerFn<I, W> = fn(I, &mut Response<W>): a non-capturing closure coerces directly, no wrapper/Box/unsafe needed.
- dispatch builds a Response; dispatch_bubbling walks ancestors, stopping at the first that consumes.
A component can derive others: registering on_insert::<C> / on_remove::<C> runs a callback when a C is inserted/removed on any element, handed the table and that element's id. - typarena: add TypeTable::remove_by_column (by-ColumnId counterpart to remove / insert_by_column). - element/observer.rs: Observers<W> holding per-column insert/remove ObserverFn<W> = fn(&mut ElementTable<W>, ElementId). - ElementTable routes every typed column write through private insert_component_by_column / remove_component_by_column, the single point where observers fire; node/scene/style/watcher/binding and arbitrary components all go through them, so every insert is observed (remove_row teardown is bulk and does not fire on_remove).
Introduce `fynix_interaction`, turning backend pointer input into the semantic interactions handlers consume (clicks, drags, hover). A `PointerRecognizer` runs the per-pointer gesture state machine and an `Interactor` owns the hit-test, feeding events through `down`/`up`/`moved`/`cancel`. Hit targets are marked by observers that `pointer::init` registers, so the spatial index holds only interactive elements. Handlers become a `Handler<I, W>` enum: a non-capturing `fn` pointer (`interact`) or a boxed closure (`interact_with`), both attached via the shared `interact_raw`, and invoked through `Handler::call`. Add `ElementTable::contains` and `Fynix::elements`/`on_insert`/ `on_remove` to support hit-target marking, and wire winit pointer input into the vello example with an interactive counter and hover demo.
|
Warning Review limit reached
More reviews will be available in 55 minutes and 12 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughIntroduces the ChangesPointer Interaction Layer
Sequence Diagram(s)sequenceDiagram
participant winit as winit EventLoop
participant VelloWinitApp
participant Interactor
participant PointerRecognizer
participant HitTest
participant Fynix
participant Handler
rect rgba(100, 149, 237, 0.5)
Note over VelloWinitApp,HitTest: Layout invalidation
VelloWinitApp->>Interactor: invalidate()
Note over Interactor: clears cached HitTest
end
rect rgba(144, 238, 144, 0.5)
Note over winit,Handler: Pointer moved
winit->>VelloWinitApp: CursorMoved(pos)
VelloWinitApp->>Interactor: moved(fynix, world, MOUSE, pos)
Interactor->>HitTest: ensure_hit_test (filter HitTarget elements)
Interactor->>HitTest: query(pos) → hit
Interactor->>PointerRecognizer: moved(id, pos, hit, fynix, world, hit_test)
PointerRecognizer->>Fynix: dispatch_bubbling(PointerLeave/PointerEnter/Hover)
Fynix->>Handler: call(interaction, &mut Response)
Handler-->>Fynix: response.consumed() → stop or continue
end
rect rgba(255, 165, 100, 0.5)
Note over winit,Handler: Mouse button pressed/released
winit->>VelloWinitApp: MouseInput(button, state)
VelloWinitApp->>Interactor: down / up (fynix, world, MOUSE, pos)
Interactor->>PointerRecognizer: down / up (id, role, pos, hit)
PointerRecognizer->>Fynix: dispatch_bubbling(RawClick / PrimaryClick / DragEnd …)
Fynix->>Handler: call(interaction, &mut Response)
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
44a1c40 to
c6943ec
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
examples/vello_winit_examples/src/lib.rs (1)
283-331:⚠️ Potential issue | 🟠 MajorHandle pointer cancellation on focus loss or cursor exit.
The event loop never calls
Interactor::cancel, so a missed release (e.g., button down then focus loss or cursor exit) can leave pressed/drag state stuck across later events.Suggested patch
@@ WindowEvent::MouseInput { state, button, .. } => { match state { ElementState::Pressed => self.interactor.down( &mut self.fynix, MOUSE, button, self.cursor, ), ElementState::Released => self.interactor.up( &mut self.fynix, &mut self.world, MOUSE, self.cursor, ), } } + WindowEvent::CursorLeft { .. } + | WindowEvent::Focused(false) => { + self.interactor.cancel( + &mut self.fynix, + &mut self.world, + MOUSE, + ); + } _ => {}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/vello_winit_examples/src/lib.rs` around lines 283 - 331, The window_event method does not handle focus loss or cursor exit events, which can leave the pointer in a stuck pressed or drag state. Add event handlers for WindowEvent::Focused(false) and WindowEvent::CursorLeft to call self.interactor.cancel() with appropriate parameters to clear any lingering interaction state when these events occur, similar to how MouseInput events are handled in the existing match statement.
🧹 Nitpick comments (6)
crates/fynix_interaction/src/key.rs (1)
3-8: ⚡ Quick winKeep continuous module docs unbroken and rely on rustfmt wrapping.
This module doc prose is manually wrapped line-by-line; please keep continuous text unbroken so nightly rustfmt handles wrapping consistently.
As per coding guidelines, "Write doc comments without linebreaking when continuous and allow
cargo +nightly fmtto wrap comments."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/fynix_interaction/src/key.rs` around lines 3 - 8, The module-level documentation comment for keyboard input handling is manually wrapped with intentional line breaks, which prevents rustfmt from handling the formatting consistently. Combine all the documentation text into a single continuous comment block without manual line breaks, keeping all the content as one flowing paragraph within the //! documentation comment lines, and then run cargo +nightly fmt to allow rustfmt to wrap the comment appropriately according to the project's formatting standards.Source: Coding guidelines
crates/fynix_interaction/src/pointer/identity.rs (1)
1-8: ⚡ Quick winUse unbroken continuous doc comments and let rustfmt wrap them.
The docs are manually hard-wrapped across multiple
///lines for continuous prose; this repo’s guideline requires writing continuous doc text without manual linebreaking.As per coding guidelines, "Write doc comments without linebreaking when continuous and allow
cargo +nightly fmtto wrap comments."Also applies to: 32-34
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/fynix_interaction/src/pointer/identity.rs` around lines 1 - 8, The documentation comment starting with "A pointer's full identity" is manually hard-wrapped across multiple `///` lines, which violates the repository guideline to use unbroken continuous doc comments and let rustfmt handle wrapping. Rewrite this doc comment block to use continuous prose without manual linebreaking—combine the text into fewer, unbroken lines within the `///` comment block—and allow cargo fmt to wrap the lines automatically. Apply the same change to the doc comments also mentioned around lines 32-34.Source: Coding guidelines
crates/fynix_interaction/src/pointer.rs (1)
1-9: ⚡ Quick winKeep module docs continuous and let formatter wrap.
The top-level doc block is manually wrapped line-by-line; please keep continuous prose and let nightly rustfmt wrap it.
As per coding guidelines, "Write doc comments without linebreaking when continuous and allow
cargo +nightly fmtto wrap comments."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/fynix_interaction/src/pointer.rs` around lines 1 - 9, The module-level documentation at the top of the pointer.rs file is manually wrapped across multiple lines with unnecessary line breaks. Consolidate the doc comment block that describes the Pointer interaction module, Interactor, PointerRecognizer, and related functionality into continuous prose without manual line breaks, then allow nightly rustfmt to handle the line wrapping automatically when formatting the code.Source: Coding guidelines
crates/fynix_interaction/src/pointer/interactor.rs (1)
13-18: ⚡ Quick winAvoid manual hard-wraps in continuous doc comments.
Please keep continuous doc prose unwrapped and let nightly rustfmt handle wrapping.
As per coding guidelines, "Write doc comments without linebreaking when continuous and allow
cargo +nightly fmtto wrap comments."Also applies to: 110-111
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/fynix_interaction/src/pointer/interactor.rs` around lines 13 - 18, The doc comment for the pointer interactor struct contains manual hard-wraps across multiple lines that should be removed to follow Rust formatting standards. Unwrap the continuous prose in the doc comment blocks (lines 13-18 and also at lines 110-111) by removing the manual line breaks within each paragraph, keeping the overall structure but allowing the text to flow continuously on longer lines. Let the nightly rustfmt tool handle automatic wrapping when cargo +nightly fmt is run, rather than maintaining manual line breaks in the documentation.Source: Coding guidelines
crates/fynix_interaction/src/pointer/recognizer.rs (1)
15-23: ⚡ Quick winUse continuous doc comments instead of manual wrapping.
There are several manually wrapped continuous doc blocks. Keep them continuous and let formatter wrapping handle line length.
As per coding guidelines, "Write doc comments without linebreaking when continuous and allow
cargo +nightly fmtto wrap comments."Also applies to: 60-67, 111-114
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/fynix_interaction/src/pointer/recognizer.rs` around lines 15 - 23, The doc comments in the Config struct and other locations are manually wrapped across multiple lines with separate /// markers. Combine each multi-line doc comment into a single continuous line for the click_slop field, drag_threshold field, and the other instances mentioned at lines 60-67 and 111-114. Keep all comment text on one line per field/item and allow the cargo formatter to handle line wrapping automatically.Source: Coding guidelines
crates/fynix_interaction/src/pointer/interaction.rs (1)
3-8: ⚡ Quick winStop manually hard-wrapping continuous doc comments.
Several continuous doc blocks are line-broken manually. Keep the sentence continuous and let
cargo +nightly fmtwrap it.As per coding guidelines, "Write doc comments without linebreaking when continuous and allow
cargo +nightly fmtto wrap comments."Also applies to: 50-51, 65-66
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/fynix_interaction/src/pointer/interaction.rs` around lines 3 - 8, The doc comments in the interaction module contain manually hard-wrapped lines where continuous prose is broken across multiple lines (for example, "A press-then-release on the same target within click slop, for any button." is split across two lines). Remove the manual line breaks and keep continuous sentence prose on single lines, allowing cargo +nightly fmt to handle the wrapping automatically. This applies to the doc comment block starting at line 3 as well as the similar issues noted at lines 50-51 and 65-66.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/fynix_interaction/src/lib.rs`:
- Around line 8-9: The `#![no_std]` attribute in the lib.rs file is missing the
required `extern crate alloc;` declaration that must follow it according to
no_std crate guidelines. Add a new line immediately after the `#![no_std]`
attribute declaration to include the `extern crate alloc;` statement, ensuring
the crate properly declares its dependency on the alloc crate for heap
allocation support in the no_std environment.
- Around line 5-10: The crate-level documentation comment references a `key`
module via the intra-doc link `[`key`]`, but this module is not exported from
the crate (there is no `pub mod key;` declaration and no corresponding `key.rs`
file). Fix this by either adding the missing `pub mod key;` export statement
after the existing `pub mod pointer;` line to make the keyboard input module
accessible, or by removing the reference to the non-existent `key` module from
the documentation comment at the top of the file.
In `@crates/fynix_interaction/src/pointer.rs`:
- Around line 59-67: The code registers on_insert observers for all pointer
handler types (RawClick, PrimaryClick, SecondaryClick, PointerEnter,
PointerLeave, Hover, DragStart, Drag, DragEnd) to mark elements with HitTarget,
but does not register corresponding on_remove observers to clean up the
HitTarget component when handlers are removed. This causes HitTarget to persist
even after all handler components are removed, allowing events to be delivered
to non-interactive elements. Add an on_remove observer for each pointer handler
type that removes the HitTarget component, but only when the element has no
remaining handler components of any type. This can be accomplished by checking
if any handler components exist before removing HitTarget.
In `@examples/vello_winit_examples/examples/hello_world.rs`:
- Around line 154-155: The hover state management in the on_enter and on_leave
callbacks can become stale because it relies exclusively on PointerEnter and
PointerLeave events, but pointer interactions like press-drag-release can change
the hover target without dispatching a corresponding leave event. To fix this,
update the hover state not only in the on_enter and on_leave callbacks, but also
check the actual cursor position and interaction state in other pointer event
handlers (such as on_move or during press/release) to ensure the hovered field
is cleared when the cursor moves away from the target during a drag operation or
when the interaction state changes without a leave event being dispatched.
In `@examples/vello_winit_examples/src/lib.rs`:
- Around line 73-74: Change the `cursor` field from type `Point` to
`Option<Point>` and initialize it to `None` instead of `Point::ZERO`. Then
update the `MouseInput` event handler to check if `cursor` is `Some` before
performing hit-testing, and only proceed with the hit-test if a valid cursor
position has been received. Additionally, update the `CursorMoved` event handler
to set `cursor` to `Some(point)` when the cursor position is updated. This
ensures hit-testing only occurs when a valid cursor position has actually been
provided, preventing false hits at (0,0).
---
Outside diff comments:
In `@examples/vello_winit_examples/src/lib.rs`:
- Around line 283-331: The window_event method does not handle focus loss or
cursor exit events, which can leave the pointer in a stuck pressed or drag
state. Add event handlers for WindowEvent::Focused(false) and
WindowEvent::CursorLeft to call self.interactor.cancel() with appropriate
parameters to clear any lingering interaction state when these events occur,
similar to how MouseInput events are handled in the existing match statement.
---
Nitpick comments:
In `@crates/fynix_interaction/src/key.rs`:
- Around line 3-8: The module-level documentation comment for keyboard input
handling is manually wrapped with intentional line breaks, which prevents
rustfmt from handling the formatting consistently. Combine all the documentation
text into a single continuous comment block without manual line breaks, keeping
all the content as one flowing paragraph within the //! documentation comment
lines, and then run cargo +nightly fmt to allow rustfmt to wrap the comment
appropriately according to the project's formatting standards.
In `@crates/fynix_interaction/src/pointer.rs`:
- Around line 1-9: The module-level documentation at the top of the pointer.rs
file is manually wrapped across multiple lines with unnecessary line breaks.
Consolidate the doc comment block that describes the Pointer interaction module,
Interactor, PointerRecognizer, and related functionality into continuous prose
without manual line breaks, then allow nightly rustfmt to handle the line
wrapping automatically when formatting the code.
In `@crates/fynix_interaction/src/pointer/identity.rs`:
- Around line 1-8: The documentation comment starting with "A pointer's full
identity" is manually hard-wrapped across multiple `///` lines, which violates
the repository guideline to use unbroken continuous doc comments and let rustfmt
handle wrapping. Rewrite this doc comment block to use continuous prose without
manual linebreaking—combine the text into fewer, unbroken lines within the `///`
comment block—and allow cargo fmt to wrap the lines automatically. Apply the
same change to the doc comments also mentioned around lines 32-34.
In `@crates/fynix_interaction/src/pointer/interaction.rs`:
- Around line 3-8: The doc comments in the interaction module contain manually
hard-wrapped lines where continuous prose is broken across multiple lines (for
example, "A press-then-release on the same target within click slop, for any
button." is split across two lines). Remove the manual line breaks and keep
continuous sentence prose on single lines, allowing cargo +nightly fmt to handle
the wrapping automatically. This applies to the doc comment block starting at
line 3 as well as the similar issues noted at lines 50-51 and 65-66.
In `@crates/fynix_interaction/src/pointer/interactor.rs`:
- Around line 13-18: The doc comment for the pointer interactor struct contains
manual hard-wraps across multiple lines that should be removed to follow Rust
formatting standards. Unwrap the continuous prose in the doc comment blocks
(lines 13-18 and also at lines 110-111) by removing the manual line breaks
within each paragraph, keeping the overall structure but allowing the text to
flow continuously on longer lines. Let the nightly rustfmt tool handle automatic
wrapping when cargo +nightly fmt is run, rather than maintaining manual line
breaks in the documentation.
In `@crates/fynix_interaction/src/pointer/recognizer.rs`:
- Around line 15-23: The doc comments in the Config struct and other locations
are manually wrapped across multiple lines with separate /// markers. Combine
each multi-line doc comment into a single continuous line for the click_slop
field, drag_threshold field, and the other instances mentioned at lines 60-67
and 111-114. Keep all comment text on one line per field/item and allow the
cargo formatter to handle line wrapping automatically.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0dc5d6d3-5746-4be1-93ee-dccfa3d2cb0e
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (20)
Cargo.tomlcrates/fynix/src/ctx.rscrates/fynix/src/element.rscrates/fynix/src/element/observer.rscrates/fynix/src/element/table.rscrates/fynix/src/interaction.rscrates/fynix/src/lib.rscrates/fynix_interaction/Cargo.tomlcrates/fynix_interaction/src/key.rscrates/fynix_interaction/src/lib.rscrates/fynix_interaction/src/pointer.rscrates/fynix_interaction/src/pointer/identity.rscrates/fynix_interaction/src/pointer/interaction.rscrates/fynix_interaction/src/pointer/interactor.rscrates/fynix_interaction/src/pointer/recognizer.rscrates/typarena/src/type_table.rsdocs/PLANS.mdexamples/vello_winit_examples/Cargo.tomlexamples/vello_winit_examples/examples/hello_world.rsexamples/vello_winit_examples/src/lib.rs
Screen.Recording.2026-06-18.at.4.16.35.PM.mov