Mouse input interactions (click & hover) - #42
Conversation
a0be473 to
fd2b3a0
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR introduces a complete pointer input handling system for fynix, encompassing raw input models, spatial hit-testing, gesture recognition, and semantic interactions. Core fynix gains ChangesPointer Input System
Sequence Diagram(s)sequenceDiagram
participant WinitEvent as Winit Event
participant VelloApp as VelloWinitApp
participant Recognizer as PointerRecognizer
participant Fynix as Fynix
participant Demo as FynixDemo
WinitEvent->>VelloApp: CursorMoved / MouseInput
VelloApp->>VelloApp: update cursor position
VelloApp->>VelloApp: create RawInput
VelloApp->>VelloApp: build HitTest from layout
VelloApp->>Recognizer: handle(RawInput)
Recognizer->>Fynix: dispatch_bubbling(Click/PointerEnter/Leave)
Fynix->>Demo: handler executes, queues events
VelloApp->>Demo: apply_events(events)
Demo->>Demo: drain and process UI events
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
examples/vello_winit_examples/examples/hello_world.rs (1)
145-145: ⚡ Quick winRemove stale commented-out code at Line 145.
This dead commented line adds noise in the changed path and should be removed.
🤖 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/examples/hello_world.rs` at line 145, Remove the stale commented-out call to ctx.set(path!(<Label>::font_size), 42.0) — delete that commented line (the dead code around the ctx.set(...) invocation) so the diff no longer contains noisy commented code; simply remove the line and run a quick build/format to ensure no trailing whitespace remains.
🤖 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_input/src/hit_test.rs`:
- Around line 65-97: The build() routine currently only records Entry bounds
when include(id) is true, but contains() (used by dispatch_bubbling) needs
bounds for all visited ancestors; change the visit_paint_order callback so you
always compute origin/size and always push an Entry (id, x0,y0,x1,y1) into
entries for every visited node, while only calling
tree.push_rect(Rect::new(...)) when include(id) is true -- i.e., keep the
Spatree (tree.push_rect / tree.build) filtered for query(), but populate entries
for every node so contains() and dispatch_bubbling can check ancestor bounds
correctly (refer to build(), Spatree, tree.push_rect, entries.push, contains(),
and dispatch_bubbling).
In `@crates/fynix_input/src/recognizer.rs`:
- Around line 32-34: The recognizer currently stores a single hovered:
Option<ElementId> shared across all pointers causing incorrect
PointerLeave/PointerEnter events when multiple pointers move; change the hover
state to be tracked per pointer (e.g., replace hovered with a HashMap<PointerId,
ElementId> or similar) and update the PointerMoved handling to look up and
update the entry for the specific pointer id instead of the global hovered,
emitting PointerEnter/PointerLeave with the correct pointer id; alternatively,
if hover is intended for mouse only, explicitly guard in the PointerMoved
handler (and code paths around PointerLeave/PointerEnter) to ignore non-mouse
PointerId values so only mouse pointer updates affect hover state (apply same
change to the related code around lines referenced by the comment).
- Around line 116-123: The PointerLeave dispatch currently uses a permissive
predicate (|_| true) which lets bubbling reach ancestors that still contain the
pointer; update the predicate passed to fynix.dispatch_bubbling::<PointerLeave>
so it stops bubbling at any ancestor that is still covered by the current
hit-test. Concretely, in the block that checks self.hovered and calls
fynix.dispatch_bubbling::<PointerLeave>(&left, PointerLeave { pointer }, |_|
true), replace the |_| true predicate with one that returns false for entities
contained by the current hit-test (e.g., |ent|
!self.current_hit_test.covers(ent) or equivalent method your hit-test exposes),
so PointerLeave is not delivered to ancestors that still contain the pointer.
In `@examples/vello_winit_examples/src/lib.rs`:
- Around line 371-381: Add a WindowEvent::CursorLeft handler in the event match
(near the WindowEvent::CursorMoved / MouseInput arms) that clears hover by
feeding the recognizer a synthesized off-window pointer move: update self.cursor
to an offscreen coordinate and call self.feed_input with
RawInputKind::PointerMoved (PointerId::MOUSE, x/y off-window) so hit_test.query
returns None and the recognizer emits PointerLeave; alternatively call into the
recognizer’s explicit “pointer left” path if available.
- Around line 62-69: The cursor field is currently a plain (f64, f64)
initialized to (0.0,0.0) which causes handle_mouse_input() to perform
hit-testing on PointerDown/PointerUp before any real CursorMoved has been
received; change the struct field cursor to Option<(f64, f64)> and update
initialization to None, update any assignments in the CursorMoved handler to set
Some((x,y)), and modify handle_mouse_input() to ignore MouseInput
(PointerDown/PointerUp) when cursor is None (only perform hit-test and dispatch
when cursor is Some), keeping references to PointerDown/PointerUp and
CursorMoved to locate the changes.
- Around line 241-247: The match that converts winit MouseButton to vello
PointerButton (the match on variable button) currently sends MouseButton::Back
and MouseButton::Forward through the wildcard arm which returns
PointerButton::Other(u16::MAX), causing distinct physical buttons to be
collapsed; add explicit match arms for MouseButton::Back and
MouseButton::Forward and map them to distinct PointerButton::Other(...) values
(or other distinct PointerButton variants) instead of the wildcard, leaving the
wildcard arm only for truly unknown buttons so PointerRecognizer can correctly
pair press/release by button.
---
Nitpick comments:
In `@examples/vello_winit_examples/examples/hello_world.rs`:
- Line 145: Remove the stale commented-out call to
ctx.set(path!(<Label>::font_size), 42.0) — delete that commented line (the dead
code around the ctx.set(...) invocation) so the diff no longer contains noisy
commented code; simply remove the line and run a quick build/format to ensure no
trailing whitespace remains.
🪄 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: 80fa4b00-aee7-44a6-b3de-154a3ceac1bf
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
Cargo.tomlcrates/fynix/src/ctx.rscrates/fynix/src/element/storage.rscrates/fynix/src/interaction.rscrates/fynix/src/lib.rscrates/fynix_input/Cargo.tomlcrates/fynix_input/src/hit_test.rscrates/fynix_input/src/interaction.rscrates/fynix_input/src/lib.rscrates/fynix_input/src/raw_input.rscrates/fynix_input/src/recognizer.rsexamples/vello_winit_examples/Cargo.tomlexamples/vello_winit_examples/examples/hello_world.rsexamples/vello_winit_examples/src/lib.rs
| pub fn build( | ||
| elements: &Elements, | ||
| root: &ElementId, | ||
| include: impl Fn(&ElementId) -> bool, | ||
| ) -> Self { | ||
| let mut tree = Spatree::new(); | ||
| let mut entries = Vec::new(); | ||
|
|
||
| elements.visit_paint_order(root, |id, meta| { | ||
| if !include(id) { | ||
| return; | ||
| } | ||
|
|
||
| let origin = meta.node.world_translation; | ||
| let size = meta.node.size; | ||
|
|
||
| let x0 = origin.x as f64; | ||
| let y0 = origin.y as f64; | ||
| let x1 = x0 + size.width as f64; | ||
| let y1 = y0 + size.height as f64; | ||
| tree.push_rect(Rect::new(x0, y0, x1, y1)); | ||
| entries.push(Entry { | ||
| id: *id, | ||
| x0, | ||
| y0, | ||
| x1, | ||
| y1, | ||
| }); | ||
| }); | ||
|
|
||
| tree.build(|rect| rect.center()); | ||
|
|
||
| Self { tree, entries } |
There was a problem hiding this comment.
Keep ancestor bounds outside the hit-target index.
build() only records entries for include(id) hits, but contains() is later used as the should_bubble gate for every ancestor during dispatch_bubbling. That means any layout-only wrapper between the hit node and a higher interactive ancestor becomes an unintended hard stop, because contains(wrapper, x, y) returns false and bubbling aborts before the real handler. Store bounds for all visited nodes for contains(), and keep the spatree filtered only for query().
Also applies to: 122-125
🤖 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_input/src/hit_test.rs` around lines 65 - 97, The build() routine
currently only records Entry bounds when include(id) is true, but contains()
(used by dispatch_bubbling) needs bounds for all visited ancestors; change the
visit_paint_order callback so you always compute origin/size and always push an
Entry (id, x0,y0,x1,y1) into entries for every visited node, while only calling
tree.push_rect(Rect::new(...)) when include(id) is true -- i.e., keep the
Spatree (tree.push_rect / tree.build) filtered for query(), but populate entries
for every node so contains() and dispatch_bubbling can check ancestor bounds
correctly (refer to build(), Spatree, tree.push_rect, entries.push, contains(),
and dispatch_bubbling).
| /// The topmost hit-tested element the mouse pointer is currently | ||
| /// over, tracked to emit enter/leave when it changes. | ||
| hovered: Option<ElementId>, |
There was a problem hiding this comment.
Track hover state per pointer, not globally.
hovered stores one shared element for the entire recognizer, but PointerMoved is keyed by pointer. Once two pointers move interleaved, the later move can emit PointerLeave for the earlier pointer's target with the wrong pointer id and overwrite its hover state. Keep hover state per PointerId, or explicitly ignore non-mouse pointers here if hover is intended to be mouse-only.
Also applies to: 110-133
🤖 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_input/src/recognizer.rs` around lines 32 - 34, The recognizer
currently stores a single hovered: Option<ElementId> shared across all pointers
causing incorrect PointerLeave/PointerEnter events when multiple pointers move;
change the hover state to be tracked per pointer (e.g., replace hovered with a
HashMap<PointerId, ElementId> or similar) and update the PointerMoved handling
to look up and update the entry for the specific pointer id instead of the
global hovered, emitting PointerEnter/PointerLeave with the correct pointer id;
alternatively, if hover is intended for mouse only, explicitly guard in the
PointerMoved handler (and code paths around PointerLeave/PointerEnter) to ignore
non-mouse PointerId values so only mouse pointer updates affect hover state
(apply same change to the related code around lines referenced by the comment).
| if let Some(left) = self.hovered { | ||
| // The pointer has moved off `left`, so the | ||
| // hit-test no longer covers it: always deliver. | ||
| fynix.dispatch_bubbling::<PointerLeave>( | ||
| &left, | ||
| PointerLeave { pointer }, | ||
| |_| true, | ||
| ); |
There was a problem hiding this comment.
Don't bubble PointerLeave through ancestors that still contain the pointer.
With |_| true, moving from a child to a sibling or ancestor can still deliver PointerLeave to a parent that the pointer never actually left. That happens whenever the old target has no handler and dispatch_bubbling walks up to the nearest ancestor handler. Based on PR context, dispatch_bubbling searches ancestors for the nearest handler, so this gate needs to stop at ancestors still covered by the current hit-test point. Based on learnings: none. Based on PR context: dispatch_bubbling::<I> walks up parents to the nearest handler, gated by a caller-supplied predicate.
Suggested fix.
if let Some(left) = self.hovered {
// The pointer has moved off `left`, so the
// hit-test no longer covers it: always deliver.
fynix.dispatch_bubbling::<PointerLeave>(
&left,
PointerLeave { pointer },
- |_| true,
+ |id| !hit_test.contains(id, x, y),
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if let Some(left) = self.hovered { | |
| // The pointer has moved off `left`, so the | |
| // hit-test no longer covers it: always deliver. | |
| fynix.dispatch_bubbling::<PointerLeave>( | |
| &left, | |
| PointerLeave { pointer }, | |
| |_| true, | |
| ); | |
| if let Some(left) = self.hovered { | |
| // The pointer has moved off `left`, so the | |
| // hit-test no longer covers it: always deliver. | |
| fynix.dispatch_bubbling::<PointerLeave>( | |
| &left, | |
| PointerLeave { pointer }, | |
| |id| !hit_test.contains(id, x, y), | |
| ); | |
| } |
🤖 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_input/src/recognizer.rs` around lines 116 - 123, The
PointerLeave dispatch currently uses a permissive predicate (|_| true) which
lets bubbling reach ancestors that still contain the pointer; update the
predicate passed to fynix.dispatch_bubbling::<PointerLeave> so it stops bubbling
at any ancestor that is still covered by the current hit-test. Concretely, in
the block that checks self.hovered and calls
fynix.dispatch_bubbling::<PointerLeave>(&left, PointerLeave { pointer }, |_|
true), replace the |_| true predicate with one that returns false for entities
contained by the current hit-test (e.g., |ent|
!self.current_hit_test.covers(ent) or equivalent method your hit-test exposes),
so PointerLeave is not delivered to ancestors that still contain the pointer.
| /// Start of the app, used to stamp raw input with a monotonic | ||
| /// millisecond timestamp. | ||
| start: Instant, | ||
| recognizer: PointerRecognizer, | ||
| /// Last known cursor position, in physical pixels. winit's | ||
| /// `MouseInput` carries no position, so it is paired with the | ||
| /// most recent `CursorMoved`. | ||
| cursor: (f64, f64), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="examples/vello_winit_examples/src/lib.rs"
echo "== File =="
ls -l "$FILE"
echo
echo "== Lines 40-130 =="
sed -n '40,130p' "$FILE" | cat -n
echo
echo "== Lines 200-320 =="
sed -n '200,320p' "$FILE" | cat -n
echo
echo "== Lines 340-420 =="
sed -n '340,420p' "$FILE" | cat -n
echo
echo "== Search for CursorLeft, cursor initialization, and handle_mouse_input =="
rg -n "CursorLeft|PointerLeave|handle_mouse_input|cursor:\s*\(|cursor\s*=" "$FILE" || trueRepository: voxell-tech/fynix
Length of output: 10290
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="examples/vello_winit_examples/src/lib.rs"
echo "== Cursor assignment sites =="
rg -n "self\.cursor\s*=" "$FILE"
echo
echo "== PointerMoved feed_input call sites =="
rg -n "PointerMoved|RawInputKind::PointerMoved" "$FILE"
echo
echo "== Any initialization hit-test/input before first CursorMoved? =="
rg -n "feed_input\(|recognizer\.handle\(" "$FILE"Repository: voxell-tech/fynix
Length of output: 547
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "hello_world\.rs|hello_world" -S examples/vello_winit_examples/src || true
ls -R examples/vello_winit_examples/src
echo "== Inspect hello_world.rs if present =="
if [ -f examples/vello_winit_examples/src/hello_world.rs ]; then
sed -n '1,220p' examples/vello_winit_examples/src/hello_world.rs | cat -n
elif [ -f examples/vello_winit_examples/src/examples/hello_world.rs ]; then
sed -n '1,220p' examples/vello_winit_examples/src/examples/hello_world.rs | cat -n
else
echo "hello_world.rs not found at expected locations."
fi
echo
echo "== Search for PointerLeave / hover draining =="
rg -n "PointerLeave|leave|hover|drain" examples/vello_winit_examples/src || true
echo
echo "== Check Cargo.toml for winit version =="
rg -n "winit\s*=" -S Cargo.toml examples/**/Cargo.toml || true
rg -n "name\s*=\s*\"vello_winit_examples\"|vello_winit_examples" -S examples/**/Cargo.toml Cargo.toml || true
echo
echo "== Search for MouseButton::Back / Forward usage in repo =="
rg -n "MouseButton::(Back|Forward)" examples/vello_winit_examples/src examples -S || trueRepository: voxell-tech/fynix
Length of output: 443
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate recognizer implementation and input kinds.
rg -n "struct PointerRecognizer|PointerRecognizer" -S . | head -n 50
echo
echo "== Find RawInputKind variants involving leave =="
rg -n "enum RawInputKind|RawInputKind::Pointer|PointerLeave|CursorLeft" -S . || true
echo
echo "== Search for PointerLeave emission =="
rg -n "PointerLeave" -S . || true
echo
echo "== Search for how PointerMoved/Down/Up are handled in recognizer =="
rg -n "handle\\(&mut .*PointerRecognizer|fn handle\\(|recognizer\\.handle|PointerMoved|PointerDown|PointerUp" -S . | head -n 80Repository: voxell-tech/fynix
Length of output: 5688
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show RawInputKind and recognizer hit-test usage for PointerDown/Up.
sed -n '1,120p' crates/fynix_input/src/raw_input.rs | cat -n
echo
echo "== recognizer.rs: pointer moved/leave handling around line ~90-140 =="
sed -n '1,170p' crates/fynix_input/src/recognizer.rs | cat -n
echo
echo "== recognizer.rs: handle implementation around line ~200-280 =="
sed -n '170,280p' crates/fynix_input/src/recognizer.rs | cat -n
echo
echo "== Interaction emission for PointerLeave around line ~90-140 (if present) =="
rg -n "PointerLeave" crates/fynix_input/src/recognizer.rsRepository: voxell-tech/fynix
Length of output: 12987
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find HitTest::query definition.
rg -n "struct HitTest|impl HitTest|fn query\\(" crates/fynix_input -S
# Show the query implementation.
FILE="$(rg -l "fn query\\(" crates/fynix_input -S | head -n 1)"
echo "== HitTest query file =="
echo "$FILE"
sed -n '1,220p' "$FILE" | cat -nRepository: voxell-tech/fynix
Length of output: 5292
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="examples/vello_winit_examples/examples/hello_world.rs"
echo "== File =="
ls -l "$FILE"
echo
echo "== Lines 150-280 =="
sed -n '150,280p' "$FILE" | cat -n
echo
echo "== Search for 'drain' / hover bookkeeping =="
rg -n "drain|hover|PointerEnter|PointerLeave|events\.push|events\.clear" "$FILE" || trueRepository: voxell-tech/fynix
Length of output: 6172
🌐 Web query:
winit 0.30 MouseButton enum variants Left Right Middle Back Forward Other(u16)
💡 Result:
In winit 0.30, the MouseButton enum does not include an Other(u16) variant. Instead, it explicitly defines 32 distinct mouse button variants [1][2]. The variants are: Left, Right, Middle, Back, Forward, Button6, Button7, Button8, Button9, Button10, Button11, Button12, Button13, Button14, Button15, Button16, Button17, Button18, Button19, Button20, Button21, Button22, Button23, Button24, Button25, Button26, Button27, Button28, Button29, Button30, Button31, and Button32 [1][2]. These are defined in the winit-core crate's event module [1][2]. While some older documentation or secondary sources may show an Other(u16) variant, the actual implementation in version 0.30 utilizes these numbered variants to support a broader range of hardware buttons [1][2].
Citations:
- 1: https://github.com/rust-windowing/winit/blob/da622006/winit-core/src/event.rs
- 2: https://github.com/rust-windowing/winit/blob/c4afadbf/winit-core/src/event.rs
Avoid hit-testing MouseInput with a default (0.0, 0.0) cursor position.
cursor is initialized to (0.0, 0.0) and handle_mouse_input() always uses it for PointerDown/PointerUp, even if no CursorMoved has occurred yet (or if the cached position is stale), so the first press/release can be hit-tested at the top-left and dispatch an incorrect/phantom click. Store the cursor as Option<(f64, f64)> and ignore MouseInput until a real position has been received.
🤖 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 62 - 69, The cursor
field is currently a plain (f64, f64) initialized to (0.0,0.0) which causes
handle_mouse_input() to perform hit-testing on PointerDown/PointerUp before any
real CursorMoved has been received; change the struct field cursor to
Option<(f64, f64)> and update initialization to None, update any assignments in
the CursorMoved handler to set Some((x,y)), and modify handle_mouse_input() to
ignore MouseInput (PointerDown/PointerUp) when cursor is None (only perform
hit-test and dispatch when cursor is Some), keeping references to
PointerDown/PointerUp and CursorMoved to locate the changes.
| let button = match button { | ||
| MouseButton::Left => PointerButton::Primary, | ||
| MouseButton::Right => PointerButton::Secondary, | ||
| MouseButton::Middle => PointerButton::Middle, | ||
| MouseButton::Other(n) => PointerButton::Other(n), | ||
| _ => PointerButton::Other(u16::MAX), | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="examples/vello_winit_examples/src/lib.rs"
echo "== File existence =="
ls -la "$FILE" || true
echo
echo "== Context around line 241 =="
nl -ba "$FILE" | sed -n '220,280p'
echo
echo "== Find PointerButton mapping/usages =="
rg -n "PointerButton" "$FILE"
echo
echo "== Find click/press/release handling around mapping =="
rg -n "MouseButton|press|release|Input|pointer.*button|click" "$FILE"Repository: voxell-tech/fynix
Length of output: 266
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="examples/vello_winit_examples/src/lib.rs"
echo "== Context around lines 220-280 =="
awk 'NR>=220 && NR<=280 {printf "%5d %s\n", NR, $0}' "$FILE"
echo
echo "== PointerButton mapping/usages (in this file) =="
rg -n "PointerButton" "$FILE"
echo
echo "== MouseButton and press/release matching (in this file) =="
rg -n "MouseButton|press|release|pointer.*button|Input|click" "$FILE"Repository: voxell-tech/fynix
Length of output: 4019
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="examples/vello_winit_examples/src/lib.rs"
echo "== PointerRecognizer usage (in this file) =="
rg -n "PointerRecognizer|recognizer|feed_input|PointerDown|PointerUp|RawInputKind::Pointer" "$FILE"
echo
echo "== Context around feed_input/recognizer dispatch =="
awk 'NR>=260 && NR<=420 {printf "%5d %s\n", NR, $0}' "$FILE"Repository: voxell-tech/fynix
Length of output: 5735
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Find PointerButton / PointerRecognizer definitions in the repo
rg -n "struct PointerRecognizer|enum PointerButton|PointerRecognizer" . -S
# 2) Find where winit version is pinned
fd -a "Cargo.toml" -S . | head -n 50
# Try common locations first
rg -n "winit\s*=" -S Cargo.toml **/Cargo.toml 2>/dev/null || true
rg -n "\"winit\"" -S Cargo.toml **/Cargo.toml 2>/dev/null || true
# 3) Look for MouseButton mapping elsewhere (might exist in other example files)
rg -n "MouseButton::(Back|Forward)|MouseButton::Left|MouseButton::Right|MouseButton::Middle|MouseButton::Other" examples -SRepository: voxell-tech/fynix
Length of output: 1239
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# List Cargo.toml files and show the one(s) likely defining winit
echo "== Cargo.toml files (top 20) =="
fd -e toml Cargo.toml -p . 2>/dev/null | head -n 20 || true
# Some fd versions use different flags; fallback:
fd -e toml Cargo.toml . | head -n 20 || true
echo
echo "== Search for winit version pins in workspace Cargo.toml(s) =="
rg -n "winit\s*=" -S . --glob "Cargo.toml" || true
rg -n "winit" -S Cargo.toml **/Cargo.toml 2>/dev/null || true
echo
echo "== Inspect PointerButton enum =="
FILE="crates/fynix_input/src/raw_input.rs"
rg -n "pub enum PointerButton" "$FILE"
awk 'NR>=1 && NR<=140 {printf "%5d %s\n", NR, $0}' "$FILE" | sed -n '1,140p'
echo
echo "== Inspect PointerRecognizer button handling =="
FILE="crates/fynix_input/src/recognizer.rs"
rg -n "PointerButton|button|Mouse|PointerDown|PointerUp" "$FILE"Repository: voxell-tech/fynix
Length of output: 3732
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="crates/fynix_input/src/recognizer.rs"
echo "== Pointer press tracking structures =="
rg -n "in_flight|presses|held|take_press|struct Press|Vec|HashMap|HashSet" "$FILE"
echo
echo "== take_press and matching logic (around take_press) =="
rg -n "fn take_press" "$FILE"
START=$(rg -n "fn take_press" "$FILE" | head -n 1 | cut -d: -f1)
awk -v s="$START" 'NR>=s-40 && NR<=s+80 {printf "%5d %s\n", NR, $0}' "$FILE"
echo
echo "== Any normalization/collapsing of buttons in recognizer =="
rg -n "PointerButton::Other|Other\\(|match .*PointerButton|u16::MAX" "$FILE"Repository: voxell-tech/fynix
Length of output: 5461
🌐 Web query:
winit 0.30 enum MouseButton variants Back Forward Other(u16)
💡 Result:
In winit 0.30, the MouseButton enum does not contain an Other(u16) variant [1][2][3]. Instead, it defines explicit variants for buttons up to 32 [1][2]. The MouseButton enum variants are [1][2]: - Left (assigned 0) - Right (assigned 1) - Middle (assigned 2) - Back (assigned 3) - Forward (assigned 4) - Button6 through Button32 (assigned 5 through 31, respectively) The Back and Forward variants are intended to represent the common side buttons on a mouse [1][2][3]. While some older or external documentation might reflect an Other(u16) variant (often found in different libraries or older versions), the current winit 0.30 implementation uses a fixed set of variants to represent mouse buttons [1][2]. Supporting specific buttons beyond these is not handled via an Other variant but is constrained to this explicit range [1][2][3].
Citations:
- 1: https://rust-windowing.github.io/winit/winit/event/enum.MouseButton.html
- 2: https://github.com/rust-windowing/winit/blob/da622006/winit-core/src/event.rs
- 3: https://github.com/rust-windowing/winit/blob/c4afadbf/winit-core/src/event.rs
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== winit version from Cargo.lock =="
if [ -f Cargo.lock ]; then
rg -n 'name = "winit"|version = ' Cargo.lock | head -n 50
rg -n 'name = "winit"' -n Cargo.lock -n && true
else
echo "No Cargo.lock found"
fi
echo
echo "== Search for MouseButton variants in local cargo registry (if present) =="
REG="${CARGO_HOME:-$HOME/.cargo}/registry/src"
if [ -d "$REG" ]; then
rg -n "enum MouseButton|pub enum MouseButton" "$REG" -S --glob "*event.rs" | head -n 50
rg -n "MouseButton::Other\\b|Other\\(u16\\)" "$REG" -S --glob "*event.rs" | head -n 50
rg -n "MouseButton::Back\\b|Back\\b" "$REG" -S --glob "*event.rs" | head -n 50
else
echo "No cargo registry cache at: $REG"
fi
echo
echo "== Search intent/docs for PointerButton::Other in repo =="
rg -n "PointerButton::Other|enum PointerButton|Other\\(u16\\)|u16::MAX|Back|Forward|Button[0-9]+" crates/fynix_input examples/vello_winit_examples/src/lib.rs -SRepository: voxell-tech/fynix
Length of output: 1740
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="crates/fynix_input/src/recognizer.rs"
awk 'NR>=1 && NR<=200 {printf "%5d %s\n", NR, $0}' "$FILE" | sed -n '1,200p'Repository: voxell-tech/fynix
Length of output: 7610
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check winit dependency versions per package
echo "== winit deps =="
rg -n "winit\s*=\s*\"|winit\s*=" -S Cargo.toml crates/**/Cargo.toml examples/**/Cargo.toml
echo
echo "== Any other MouseButton mappings elsewhere =="
rg -n "MouseButton::(Back|Forward|Button|Other\()|MouseButton::Other" -S examples crates || true
echo
echo "== Show current mapping snippet for context =="
awk 'NR>=230 && NR<=260 {printf "%5d %s\n", NR, $0}' examples/vello_winit_examples/src/lib.rsRepository: voxell-tech/fynix
Length of output: 1562
🌐 Web query:
winit 0.30 MouseButton Other(u16) Back Forward Button32
💡 Result:
In winit version 0.30, the MouseButton enum is defined as follows: pub enum MouseButton { Left, Right, Middle, Back, Forward, Other(u16), } The Back and Forward variants were introduced to provide a cross-platform way to handle common side buttons found on many mice [1]. The Other(u16) variant is used for mouse buttons that do not map to the standard Left, Right, Middle, Back, or Forward variants [2][3]. Regarding "Button32": In winit 0.30, there is no explicit variant named Button32 within the MouseButton enum [2][4][3]. While newer developments and discussions in the winit repository have explored revising the MouseButton type to better support high-count button mice—including proposed support for buttons up to Button32—these changes are not part of the standard MouseButton enum definition in the 0.30 stable release [5][6]. In version 0.30, any button beyond the standard set is typically handled via the Other(u16) variant, where the u16 value represents the raw button code provided by the underlying operating system [2][3].
Citations:
- 1: Add MouseButton::{Back, Forward} to mouse input events on Wayland, X11, Windows, macOS and Web rust-windowing/winit#2770
- 2: https://docs.rs/winit/latest/x86_64-apple-darwin/winit/event/enum.MouseButton.html
- 3: https://docs.rs/winit/latest/winit/event/enum.MouseButton.html
- 4: https://docs.rs/winit/0.30.12/winit/event/index.html
- 5: Revise MouseButton type rust-windowing/winit#4324
- 6: https://github.com/rust-windowing/winit/blob/da622006/winit-core/src/event.rs
🌐 Web query:
winit 0.30 MouseButton enum definition "Other(u16)" "Back" "Forward"
💡 Result:
In winit 0.30, the MouseButton enum is used to represent buttons on a mouse controller [1][2]. Its definition is as follows: pub enum MouseButton { Left, Right, Middle, Back, Forward, Other(u16), } Explanation of variants: - Left, Right, Middle: Represent the standard primary, secondary, and tertiary mouse buttons [1][3]. - Back, Forward: Represent the common side buttons often found on modern mice [1][4]. These were added to support cross-platform mouse input, though platform-specific limitations may exist (e.g., support on macOS may depend on hardware and driver implementation) [4][5][6]. - Other(u16): This variant is used for any mouse buttons that do not correspond to the named variants [1][5]. The u16 value allows for the representation of additional buttons, accommodating different platform conventions for button numbering [1][7]. Historical context: The Back and Forward variants were introduced to standardize mouse input across major platforms, including Wayland, X11, Windows, macOS, and Web [4]. Previously, these buttons were often inconsistently mapped or handled via the Other variant [4][7]. The Other(u16) variant itself has evolved; for instance, some earlier versions used a u8 for this field, but it was updated to u16 to better accommodate the wider range of button IDs supported by various operating systems [3][4][7].
Citations:
- 1: https://docs.rs/winit/latest/x86_64-apple-darwin/winit/event/enum.MouseButton.html
- 2: https://docs.rs/winit/0.30.12/winit/event/index.html
- 3: https://github.com/rust-windowing/winit/blob/c4afadbf/winit-core/src/event.rs
- 4: Add MouseButton::{Back, Forward} to mouse input events on Wayland, X11, Windows, macOS and Web rust-windowing/winit#2770
- 5: https://doc.servo.org/winit/event/enum.MouseButton.html
- 6: https://smithay.github.io/smithay/winit/event/enum.MouseButton.html
- 7: Revise MouseButton type rust-windowing/winit#4324
Preserve distinct non-primary mouse buttons.
MouseButton::Back and MouseButton::Forward fall into the wildcard arm and are both mapped to PointerButton::Other(u16::MAX), so PointerRecognizer can pair a down event with the wrong physical button on release (press.button == button). Match Back/Forward explicitly and map them to distinct PointerButton::Other(..) codes (or add dedicated PointerButton variants).
🤖 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 241 - 247, The match
that converts winit MouseButton to vello PointerButton (the match on variable
button) currently sends MouseButton::Back and MouseButton::Forward through the
wildcard arm which returns PointerButton::Other(u16::MAX), causing distinct
physical buttons to be collapsed; add explicit match arms for MouseButton::Back
and MouseButton::Forward and map them to distinct PointerButton::Other(...)
values (or other distinct PointerButton variants) instead of the wildcard,
leaving the wildcard arm only for truly unknown buttons so PointerRecognizer can
correctly pair press/release by button.
| WindowEvent::CursorMoved { position, .. } => { | ||
| self.cursor = (position.x, position.y); | ||
| self.feed_input(RawInputKind::PointerMoved { | ||
| pointer: PointerId::MOUSE, | ||
| x: position.x, | ||
| y: position.y, | ||
| }); | ||
| } | ||
| WindowEvent::MouseInput { state, button, .. } => { | ||
| self.handle_mouse_input(state, button); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the referenced section
sed -n '330,430p' examples/vello_winit_examples/src/lib.rs | nl -ba | sed -n '1,140p'
# 2) Search for cursor/leave handling in this file
rg -n --no-heading "CursorLeft|PointerLeave|RawInputKind::.*Leave|hover|leave" examples/vello_winit_examples/src/lib.rs
# 3) Search repo-wide for how PointerLeave / cursor-left is handled or defined
rg -n --no-heading "CursorLeft|PointerLeave|RawInputKind::.*Leave" -S .
# 4) Find where hover state is stored/cleared and how it’s triggered
rg -n --no-heading "hover" examples/vello_winit_examples/src/lib.rs
rg -n --no-heading "cursor.*left|leave.*cursor|Pointer.*Leave" -S .Repository: voxell-tech/fynix
Length of output: 104
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the referenced section with line numbers
sed -n '330,430p' examples/vello_winit_examples/src/lib.rs | cat -n
# 2) Search for cursor/leave handling in this file
rg -n --no-heading "CursorLeft|PointerLeave|RawInputKind::.*Leave|hover|leave" examples/vello_winit_examples/src/lib.rs || true
# 3) Search repo-wide for how PointerLeave / cursor-left is handled or defined
rg -n --no-heading "CursorLeft|PointerLeave|RawInputKind::.*Leave" -S . || true
# 4) Find where hover state is stored/cleared and how it’s triggered
rg -n --no-heading "hover|cursor.*left|leave.*cursor|Pointer.*Leave" -S . || trueRepository: voxell-tech/fynix
Length of output: 6116
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for CursorLeft handling in the example file
rg -n "CursorLeft" examples/vello_winit_examples/src/lib.rs || true
# Show all RawInputKind variants used by the example
rg -n "RawInputKind::" examples/vello_winit_examples/src/lib.rs
# Locate and show feed_input implementation in this crate example
rg -n "fn feed_input|feed_input\(" examples/vello_winit_examples/src/lib.rs
# Inspect recognizer hover/leave logic
sed -n '1,220p' crates/fynix_input/src/recognizer.rs | cat -n
echo "----"
sed -n '220,380p' crates/fynix_input/src/recognizer.rs | cat -n
# Definitions of PointerLeave / related interactions
sed -n '1,120p' crates/fynix_input/src/interaction.rs | cat -n
# Show how recognizer triggers PointerLeave on hover changes
rg -n "hovered: Option|PointerLeave|PointerEnter|target" crates/fynix_input/src/recognizer.rsRepository: voxell-tech/fynix
Length of output: 15709
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check example event match for CursorLeft
rg -n "CursorLeft" examples/vello_winit_examples/src/lib.rs || true
# Check the whole repo for CursorLeft / pointer-left mapping
rg -n "CursorLeft" . || true
rg -n "PointerLeft|RawInputKind::.*Left|PointerLeave" crates/fynix_input . || true
# Inspect raw_input kind variants
rg -n "enum RawInputKind" -S crates/fynix_input/src || true
rg -n "RawInputKind::" crates/fynix_input/src/raw_input.rs crates/fynix_input/src -S || true
# Inspect feed_input call site and surrounding code in the example for cursor->raw mapping
sed -n '200,330p' examples/vello_winit_examples/src/lib.rs | cat -nRepository: voxell-tech/fynix
Length of output: 7855
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect RawInputKind definition
sed -n '1,120p' crates/fynix_input/src/raw_input.rs | cat -n
# Search for CursorLeft usage in the repo (more targeted)
rg -n "CursorLeft" -S . || true
# Confirm whether any window event maps to PointerMoved with "off-window" coords
rg -n "PointerMoved\\s*\\{[^}]*x|PointerMoved\\s*\\{" examples/vello_winit_examples/src/lib.rs -n || true
# Show full match arms around window_event for CursorLeft-related events
sed -n '320,420p' examples/vello_winit_examples/src/lib.rs | cat -nRepository: voxell-tech/fynix
Length of output: 4822
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "WindowEvent::CursorMoved" examples/vello_winit_examples/src/lib.rs
rg -n "WindowEvent::CursorLeft" examples/vello_winit_examples/src/lib.rs || true
rg -n "match event \\{" -n examples/vello_winit_examples/src/lib.rsRepository: voxell-tech/fynix
Length of output: 150
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "impl PointerRecognizer|pub fn|hovered" crates/fynix_input/src/recognizer.rsRepository: voxell-tech/fynix
Length of output: 322
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the exact window_event match arms with line numbers
sed -n '330,410p' examples/vello_winit_examples/src/lib.rs | cat -n
# Repo-wide check for CursorLeft handling
rg -n "CursorLeft" . || trueRepository: voxell-tech/fynix
Length of output: 2085
Fix stuck hover when the cursor leaves the window.
PointerLeave in fynix_input is emitted only when the recognizer processes RawInputKind::PointerMoved and the new hit-test target differs from the stored hovered element. In examples/vello_winit_examples/src/lib.rs, hover updates come only from WindowEvent::CursorMoved, and there is no WindowEvent::CursorLeft handling, so when the cursor exits without more CursorMoved events the hovered element never receives PointerLeave.
Add a WindowEvent::CursorLeft arm (around lines 371-381) that clears the recognizer’s hover by dispatching PointerLeave immediately, e.g. by feeding a synthesized PointerMoved with off-window coordinates so hit_test.query returns None (or by extending the recognizer with an explicit “pointer left/offscreen” path).
🤖 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 371 - 381, Add a
WindowEvent::CursorLeft handler in the event match (near the
WindowEvent::CursorMoved / MouseInput arms) that clears hover by feeding the
recognizer a synthesized off-window pointer move: update self.cursor to an
offscreen coordinate and call self.feed_input with RawInputKind::PointerMoved
(PointerId::MOUSE, x/y off-window) so hit_test.query returns None and the
recognizer emits PointerLeave; alternatively call into the recognizer’s explicit
“pointer left” path if available.
5cae53f to
6426057
Compare
Introduce a `fynix_input` crate for the stateful input layer, keeping fynix-core free of spatial-indexing concerns. Core (fynix): - `Elements::visit_paint_order` exposes a paint-order traversal so an external index can read each element's absolute rect. - `Fynix::dispatch_bubbling::<I>` walks up parents to the nearest handler, gated by a caller-supplied `should_bubble` predicate (the element tree alone cannot express the stop condition). - `Interactions::contains::<I>` probes for a handler without running it. - `FynixCtx::compose`/`compose_with` now return an `ElementHandle`, so `.on` chains directly onto composed elements. fynix_input: - `RawInput` (backend-facing) + a unified pointer model. - `HitTest` builds a spatree over only the elements an `include` predicate accepts (`is_hit_target`), so non-interactive elements stay out of the index. - `PointerRecognizer` turns raw pointer events into `Click`, `PointerEnter`, and `PointerLeave`, dispatched via bubbling with a hit-test gate. spatree now re-exports kurbo; workspace points rectree/spatree at the local checkout. hello_world: clickable counter and a hover label driven by the new input layer.
Replace the RawInput-based recognizer with a generic PointerRecognizer<Id, Btn> driven by semantic methods (pointer_down/up/moved/cancel) instead of a raw-input envelope. Backends classify their native button into a ButtonRole via the ClassifyButton trait, so the recognizer picks Click vs SecondaryClick without the native type leaking into interaction handlers. Interaction payloads drop their pointer/button fields and stay concrete, keeping UI code backend-agnostic. Move the recognizer into pointer.rs alongside ButtonRole and ClassifyButton, leaving room for future key/touch modules. The demo gains a thin WinitInput shim that wraps winit events (mouse + touch) onto the recognizer, the shape a future fynix_winit crate would take.
|
Superseded by #56 |
Screen.Recording.2026-06-10.at.10.59.13.PM.mov