More text improvements, performance enhancements, input handling, text selection, wl cursors

This commit is contained in:
2026-03-20 22:24:29 -04:00
parent d79a3bb728
commit 423df4ae1f
15 changed files with 2458 additions and 265 deletions

View File

@@ -50,7 +50,7 @@ pub struct RoutedPointerEvent {
#[derive(Default)]
pub struct PointerRouter {
hovered: Option<HitTarget>,
hovered: Vec<HitTarget>,
pressed: Option<HitTarget>,
}
@@ -60,7 +60,11 @@ impl PointerRouter {
}
pub fn hovered_target(&self) -> Option<&HitTarget> {
self.hovered.as_ref()
self.hovered.last()
}
pub fn hovered_targets(&self) -> &[HitTarget] {
&self.hovered
}
pub fn pressed_target(&self) -> Option<&HitTarget> {
@@ -72,14 +76,21 @@ impl PointerRouter {
interaction_tree: &InteractionTree,
event: PointerEvent,
) -> Vec<RoutedPointerEvent> {
let hit_target = match event.kind {
PointerEventKind::LeaveWindow => None,
_ => interaction_tree.hit_test(event.position),
let hovered_targets = match event.kind {
PointerEventKind::LeaveWindow => Vec::new(),
_ => interaction_tree.hit_path(event.position),
};
let hit_target = hovered_targets.last().cloned();
let mut routed = Vec::new();
if self.hovered != hit_target {
if let Some(previous) = self.hovered.take() {
let shared_depth = self
.hovered
.iter()
.zip(hovered_targets.iter())
.take_while(|(previous, next)| previous == next)
.count();
if shared_depth != self.hovered.len() || shared_depth != hovered_targets.len() {
for previous in self.hovered[shared_depth..].iter().rev().cloned() {
routed.push(RoutedPointerEvent {
kind: RoutedPointerEventKind::Leave,
target: previous,
@@ -87,20 +98,20 @@ impl PointerRouter {
position: event.position,
});
}
if let Some(target) = hit_target.clone() {
for target in hovered_targets[shared_depth..].iter().cloned() {
routed.push(RoutedPointerEvent {
kind: RoutedPointerEventKind::Enter,
target: target.clone(),
target,
pointer_id: event.pointer_id,
position: event.position,
});
self.hovered = Some(target);
}
self.hovered = hovered_targets;
}
match event.kind {
PointerEventKind::Move => {
if let Some(target) = hit_target {
if let Some(target) = hit_target.clone() {
routed.push(RoutedPointerEvent {
kind: RoutedPointerEventKind::Move,
target,
@@ -144,7 +155,7 @@ mod tests {
};
use crate::layout::{InteractionTree, LayoutNode, LayoutPath};
use crate::scene::{Point, Rect};
use crate::tree::ElementId;
use crate::tree::{CursorIcon, ElementId};
fn interaction_tree() -> InteractionTree {
InteractionTree {
@@ -153,12 +164,16 @@ mod tests {
element_id: None,
rect: Rect::new(0.0, 0.0, 200.0, 120.0),
pointer_events: false,
cursor: CursorIcon::Default,
prepared_text: None,
children: vec![
LayoutNode {
path: LayoutPath::root().child(0),
element_id: Some(ElementId::new(1)),
rect: Rect::new(0.0, 0.0, 120.0, 120.0),
pointer_events: true,
cursor: CursorIcon::Default,
prepared_text: None,
children: Vec::new(),
},
LayoutNode {
@@ -166,6 +181,8 @@ mod tests {
element_id: Some(ElementId::new(2)),
rect: Rect::new(80.0, 0.0, 120.0, 120.0),
pointer_events: true,
cursor: CursorIcon::Default,
prepared_text: None,
children: Vec::new(),
},
],
@@ -173,6 +190,36 @@ mod tests {
}
}
fn nested_interaction_tree() -> InteractionTree {
InteractionTree {
root: LayoutNode {
path: LayoutPath::root(),
element_id: None,
rect: Rect::new(0.0, 0.0, 200.0, 120.0),
pointer_events: false,
cursor: CursorIcon::Default,
prepared_text: None,
children: vec![LayoutNode {
path: LayoutPath::root().child(0),
element_id: Some(ElementId::new(1)),
rect: Rect::new(0.0, 0.0, 160.0, 120.0),
pointer_events: true,
cursor: CursorIcon::Default,
prepared_text: None,
children: vec![LayoutNode {
path: LayoutPath::root().child(0).child(0),
element_id: Some(ElementId::new(2)),
rect: Rect::new(16.0, 16.0, 80.0, 40.0),
pointer_events: true,
cursor: CursorIcon::Default,
prepared_text: None,
children: Vec::new(),
}],
}],
},
}
}
#[test]
fn router_emits_enter_and_move_for_new_hover_target() {
let mut router = PointerRouter::new();
@@ -223,4 +270,66 @@ mod tests {
Some(ElementId::new(1))
);
}
#[test]
fn router_tracks_hovered_ancestors_for_nested_hits() {
let mut router = PointerRouter::new();
let routed = router.route(
&nested_interaction_tree(),
PointerEvent::new(0, Point::new(24.0, 24.0), PointerEventKind::Move),
);
assert_eq!(
routed
.iter()
.filter(|event| event.kind == RoutedPointerEventKind::Enter)
.map(|event| event.target.element_id)
.collect::<Vec<_>>(),
vec![Some(ElementId::new(1)), Some(ElementId::new(2))]
);
assert_eq!(
router
.hovered_targets()
.iter()
.map(|target| target.element_id)
.collect::<Vec<_>>(),
vec![Some(ElementId::new(1)), Some(ElementId::new(2))]
);
assert_eq!(
router.hovered_target().unwrap().element_id,
Some(ElementId::new(2))
);
}
#[test]
fn router_leaves_only_nested_child_when_pointer_moves_to_parent() {
let mut router = PointerRouter::new();
let tree = nested_interaction_tree();
let _ = router.route(
&tree,
PointerEvent::new(0, Point::new(24.0, 24.0), PointerEventKind::Move),
);
let routed = router.route(
&tree,
PointerEvent::new(0, Point::new(140.0, 24.0), PointerEventKind::Move),
);
assert_eq!(
routed
.iter()
.filter(|event| event.kind == RoutedPointerEventKind::Leave)
.map(|event| event.target.element_id)
.collect::<Vec<_>>(),
vec![Some(ElementId::new(2))]
);
assert_eq!(
router
.hovered_targets()
.iter()
.map(|target| target.element_id)
.collect::<Vec<_>>(),
vec![Some(ElementId::new(1))]
);
}
}