use cranpose_ui::FocusDirection;
use super::*;
impl<R> SurfaceMut<'_, R>
where
R: Renderer,
R::Error: Debug,
{
fn pointer_event(
&self,
kind: PointerEventKind,
position: Point,
global_position: Point,
event_time: PointerEventTime,
) -> PointerEvent {
let screen_position = self.surface().screen_origin.map(|origin| Point {
x: origin.x + global_position.x,
y: origin.y + global_position.y,
});
let mut event = PointerEvent::new(kind, position, global_position)
.with_time_ms(event_time.platform_time_ms)
.with_animation_time_nanos(event_time.animation_time_nanos)
.with_screen_position(screen_position);
event.modifiers = self.shell.app.modifiers;
event
}
fn resolve_gesture_targets(
&self,
pointer: PointerId,
) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
self.resolve_hit_path(pointer)
}
fn resolve_hit_path(
&self,
pointer: PointerId,
) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
let Some(node_ids) = self.surface().hit_path_tracker.dispatch_order(pointer) else {
return Vec::new();
};
let scene = self.surface().renderer.scene();
let targets: Vec<_> = node_ids
.iter()
.filter_map(|&id| scene.find_target(id))
.collect();
log::trace!(
target: "cranpose::input",
"resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
targets.len()
);
targets
}
fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
where
I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
{
let mut applier = self.shell.app.composition.applier_mut();
for target in targets {
let node_id = target.node_id();
target.dispatch_with_applier(&mut applier, event.clone());
log::trace!(
target: "cranpose::input",
"dispatch {:?} node={} consumed={} stop_on_consume={}",
event.kind,
node_id,
event.is_consumed(),
stop_on_consume,
);
if stop_on_consume && event.is_consumed() {
break;
}
}
event.finish_post_dispatch();
}
fn dispatch_to_hits(
&mut self,
hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
event: PointerEvent,
) -> bool {
let capture_paths = hits
.iter()
.map(|hit| hit.capture_path())
.collect::<Vec<_>>();
let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
.into_iter()
.filter_map(|node_id| self.surface().renderer.scene().find_target(node_id))
.collect::<Vec<_>>();
self.dispatch_targets(targets, event.clone(), true);
event.is_consumed()
}
pub fn set_pointer_source(&mut self, source: PointerSource) {
self.surface_mut().pointer_source = source;
}
pub fn pointer_source(&self) -> PointerSource {
self.surface().pointer_source
}
pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
self.set_cursor_at_time(x, y, None)
}
pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
self.set_cursor_at_event_time(x, y, event_time)
}
pub fn set_cursor_at_event_time(
&mut self,
x: f32,
y: f32,
event_time: PointerEventTime,
) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let result = app_context.enter(|| {
run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
});
self.route_drag_and_drop();
if result {
self.mark_dirty();
}
log::trace!(
target: "cranpose::input",
"set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
event_time.platform_time_ms,
event_time.animation_time_nanos,
);
result
}
fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
self.surface_mut().cursor = (x, y);
if self.surface().buttons_pressed != PointerButtons::NONE {
if self.surface().hit_path_tracker.has_path(PointerId::PRIMARY) {
let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
if !targets.is_empty() {
let event = self
.pointer_event(
PointerEventKind::Move,
Point { x, y },
Point { x, y },
event_time,
)
.with_buttons(self.surface().buttons_pressed)
.with_source(self.surface().pointer_source);
self.dispatch_targets(targets, event, false);
return true;
}
return false;
}
return false;
}
let hits = self.surface().renderer.scene().hit_test(x, y);
let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
let pos = Point { x, y };
let previously_hovered = self.surface().hovered_nodes.clone();
for old_id in previously_hovered {
if !new_ids.contains(&old_id)
&& let Some(target) = self.surface().renderer.scene().find_target(old_id)
{
let exit_event = self
.pointer_event(PointerEventKind::Exit, pos, pos, event_time)
.with_buttons(self.surface().buttons_pressed)
.with_source(self.surface().pointer_source);
self.dispatch_targets(std::iter::once(target), exit_event, false);
}
}
for hit in &hits {
if !self.surface().hovered_nodes.contains(&hit.node_id()) {
let enter_event = self
.pointer_event(PointerEventKind::Enter, pos, pos, event_time)
.with_buttons(self.surface().buttons_pressed)
.with_source(self.surface().pointer_source);
self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
}
}
self.surface_mut().hovered_nodes = new_ids;
self.apply_hovered_pointer_icon(&hits);
if !hits.is_empty() {
let event = self
.pointer_event(PointerEventKind::Move, pos, pos, event_time)
.with_buttons(self.surface().buttons_pressed)
.with_source(self.surface().pointer_source);
self.dispatch_targets(hits, event, true);
true
} else {
false
}
}
pub fn pointer_pressed(&mut self) -> bool {
self.pointer_pressed_at_time(None)
}
pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
self.pointer_pressed_at_event_time(event_time)
}
pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
let (cursor_x, cursor_y) = self.surface().cursor;
if self.dev_overlay_press(cursor_x, cursor_y) {
return true;
}
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let result = app_context.enter(|| {
run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
});
if result {
self.mark_dirty();
}
log::trace!(
target: "cranpose::input",
"pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
event_time.platform_time_ms,
event_time.animation_time_nanos,
);
result
}
fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
self.activate();
self.note_focus_moved_by_keyboard(false);
self.surface_mut()
.buttons_pressed
.insert(PointerButton::Primary);
let (cursor_x, cursor_y) = self.surface().cursor;
let mut hits = self.surface().renderer.scene().hit_test(cursor_x, cursor_y);
if self.surface().pointer_source.is_touch_like()
&& let Some(near) = self
.surface()
.renderer
.scene()
.hit_test_near(cursor_x, cursor_y)
&& hits.iter().all(|hit| {
hit.node_id() != near.node_id() && near.capture_path().contains(&hit.node_id())
})
{
hits.insert(0, near);
}
if hits.is_empty() {
self.surface_mut()
.hit_path_tracker
.remove_path(PointerId::PRIMARY);
false
} else {
let event = self
.pointer_event(
PointerEventKind::Down,
Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
},
Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
},
event_time,
)
.with_buttons(self.surface().buttons_pressed)
.with_source(self.surface().pointer_source);
let mut delivered_capture_paths = Vec::new();
let (app, surface) = self.parts();
let mut applier = app.composition.applier_mut();
for hit in hits {
let node_id = hit.node_id();
delivered_capture_paths.push(hit.capture_path());
hit.dispatch_with_applier(&mut applier, event.clone());
log::trace!(
target: "cranpose::input",
"dispatch {:?} node={} consumed={} stop_on_consume=true",
event.kind,
node_id,
event.is_consumed(),
);
if event.is_consumed() {
break;
}
}
surface
.hit_path_tracker
.add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
log::trace!(
target: "cranpose::input",
"pointer_pressed_inner cached_hit_path={:?}",
surface.hit_path_tracker.get_path(PointerId::PRIMARY),
);
true
}
}
pub fn pointer_released(&mut self) -> bool {
self.pointer_released_at_time(None)
}
pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
self.pointer_released_at_position_time(x, y, None)
}
pub fn pointer_released_at_position_time(
&mut self,
x: f32,
y: f32,
time_ms: Option<i64>,
) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
self.pointer_released_at_position_event_time(x, y, event_time)
}
pub fn pointer_released_at_position_event_time(
&mut self,
x: f32,
y: f32,
event_time: PointerEventTime,
) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let result = app_context.enter(|| {
run_in_mutable_snapshot(|| {
self.surface_mut().cursor = (x, y);
self.pointer_released_inner(event_time)
})
.unwrap_or(false)
});
self.route_drag_and_drop();
if result {
self.mark_dirty();
}
log::trace!(
target: "cranpose::input",
"pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
event_time.platform_time_ms,
event_time.animation_time_nanos,
);
result
}
pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
self.pointer_released_at_event_time(event_time)
}
pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let result = app_context.enter(|| {
run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
});
self.route_drag_and_drop();
if result {
self.mark_dirty();
}
log::trace!(
target: "cranpose::input",
"pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
event_time.platform_time_ms,
event_time.animation_time_nanos,
);
result
}
fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
self.surface_mut()
.buttons_pressed
.remove(PointerButton::Primary);
let corrected_buttons = self.surface().buttons_pressed;
let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
self.surface_mut()
.hit_path_tracker
.remove_path(PointerId::PRIMARY);
if !targets.is_empty() {
let event = self
.pointer_event(
PointerEventKind::Up,
Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
},
Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
},
event_time,
)
.with_buttons(corrected_buttons)
.with_source(self.surface().pointer_source);
self.dispatch_targets(targets, event, false);
true
} else {
false
}
}
pub fn secondary_pointer_pressed(
&mut self,
pointer_id: u64,
x: f32,
y: f32,
time_ms: Option<i64>,
) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
}
pub fn secondary_pointer_moved(
&mut self,
pointer_id: u64,
x: f32,
y: f32,
time_ms: Option<i64>,
) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
}
pub fn secondary_pointer_released(
&mut self,
pointer_id: u64,
x: f32,
y: f32,
time_ms: Option<i64>,
) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
}
fn dispatch_secondary_pointer(
&mut self,
kind: PointerEventKind,
pointer_id: u64,
x: f32,
y: f32,
event_time: PointerEventTime,
) -> bool {
if pointer_id == 0 {
log::warn!(
target: "cranpose::input",
"secondary pointer dispatch called with the primary pointer id"
);
return false;
}
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let result = app_context.enter(|| {
run_in_mutable_snapshot(|| {
if !self.surface().hit_path_tracker.has_path(PointerId::PRIMARY) {
return false;
}
let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
if targets.is_empty() {
return false;
}
let pos = Point { x, y };
let event = self
.pointer_event(kind, pos, pos, event_time)
.with_buttons(self.surface().buttons_pressed)
.with_id(pointer_id)
.with_source(self.surface().pointer_source);
self.dispatch_targets(targets, event, false);
true
})
.unwrap_or(false)
});
if result {
self.mark_dirty();
}
log::trace!(
target: "cranpose::input",
"secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
event_time.platform_time_ms,
event_time.animation_time_nanos,
);
result
}
pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(None);
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let result = app_context.enter(|| {
run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
.unwrap_or(false)
});
if result {
self.mark_dirty();
}
log::trace!(
target: "cranpose::input",
"pointer_zoomed factor={zoom_factor:.4} -> {result}"
);
result
}
fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
return false;
}
let hits = self
.surface()
.renderer
.scene()
.hit_test(self.surface().cursor.0, self.surface().cursor.1);
if hits.is_empty() {
return false;
}
let pos = Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
};
let event = self
.pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
.with_buttons(self.surface().buttons_pressed)
.with_zoom_delta(zoom_factor)
.with_source(self.surface().pointer_source);
self.dispatch_to_hits(&hits, event)
}
pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
if wheel.is_zoom() {
let zoom_factor = wheel.zoom_factor();
log::trace!(
target: "cranpose::input",
"wheel zoom factor={zoom_factor:.4}"
);
return self.pointer_zoomed(zoom_factor);
}
let rotary =
RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
if self.rotary_scrolled(rotary) {
return true;
}
let delta = wheel.scroll_delta();
log::trace!(
target: "cranpose::input",
"wheel delta ({:.2},{:.2}) alt={}",
delta.x,
delta.y,
wheel.modifiers.alt
);
self.pointer_scrolled(delta.x, delta.y)
}
pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
let event_time = self.shell.app.realtime_pointer_event_time(None);
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let result = app_context.enter(|| {
run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
.unwrap_or(false)
});
if result {
self.mark_dirty();
}
log::trace!(
target: "cranpose::input",
"pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
);
result
}
fn pointer_scrolled_inner(
&mut self,
delta_x: f32,
delta_y: f32,
event_time: PointerEventTime,
) -> bool {
if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
return false;
}
let hits = self
.surface()
.renderer
.scene()
.hit_test(self.surface().cursor.0, self.surface().cursor.1);
if hits.is_empty() {
return false;
}
let event = self
.pointer_event(
PointerEventKind::Scroll,
Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
},
Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
},
event_time,
)
.with_buttons(self.surface().buttons_pressed)
.with_scroll_delta(Point {
x: delta_x,
y: delta_y,
})
.with_source(self.surface().pointer_source);
self.dispatch_to_hits(&hits, event)
}
pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
where
F: Fn(RotaryScrollEvent) -> bool + 'static,
{
self.surface_mut().on_rotary_scroll = Some(Rc::new(handler));
}
pub fn clear_on_rotary_scroll(&mut self) {
self.surface_mut().on_rotary_scroll = None;
}
pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
let factor = self.shell.app.rotary_scroll_factor;
self.rotary_scrolled(RotaryScrollEvent::from_detents(
detents,
factor,
factor,
uptime_millis,
))
}
pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let result = app_context.enter(|| {
run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
});
if result {
self.mark_dirty();
}
log::trace!(
target: "cranpose::input",
"rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
event.vertical_scroll_pixels,
event.horizontal_scroll_pixels,
event.uptime_millis,
);
result
}
fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
if rotary.is_empty() {
return false;
}
let bubble_order = self.rotary_dispatch_order();
let position = Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
};
if !bubble_order.is_empty() {
let capture_targets = bubble_order
.iter()
.rev()
.filter_map(|&node_id| self.surface().renderer.scene().find_target(node_id))
.collect::<Vec<_>>();
let mut capture_event =
PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
capture_event.modifiers = self.shell.app.modifiers;
self.dispatch_targets(capture_targets, capture_event.clone(), true);
if capture_event.is_consumed() {
return true;
}
let bubble_targets = bubble_order
.iter()
.filter_map(|&node_id| self.surface().renderer.scene().find_target(node_id))
.collect::<Vec<_>>();
let mut bubble_event =
PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
bubble_event.modifiers = self.shell.app.modifiers;
self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
if bubble_event.is_consumed() {
return true;
}
}
if let Some(handler) = self.surface().on_rotary_scroll.clone() {
return handler(rotary);
}
false
}
fn rotary_dispatch_order(&self) -> Vec<NodeId> {
if let Some(focused) = cranpose_ui::active_focus_target()
&& let Some(target) = self.surface().renderer.scene().find_target(focused)
{
let path = target.capture_path();
if !path.is_empty() {
return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
}
}
let hits = self
.surface()
.renderer
.scene()
.hit_test(self.surface().cursor.0, self.surface().cursor.1);
if hits.is_empty() {
return Vec::new();
}
let capture_paths = hits
.iter()
.map(|hit| hit.capture_path())
.collect::<Vec<_>>();
crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
}
pub fn cancel_gesture(&mut self) {
let event_time = self.shell.app.realtime_pointer_event_time(None);
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
let _ = app_context.enter(|| {
run_in_mutable_snapshot(|| {
self.cancel_gesture_inner(event_time);
})
});
self.route_drag_and_drop();
}
fn route_drag_and_drop(&mut self) {
let app_context = Rc::clone(&self.shell.app.app_context);
let source = self.index;
let shell = &mut *self.shell;
let routed = app_context.enter(|| {
run_in_mutable_snapshot(|| {
app_context
.drag_and_drop()
.route(|point| shell.drag_and_drop_target_at(point, source))
})
.unwrap_or(false)
});
if routed {
shell.mark_all_dirty();
}
}
pub fn cancel_gesture_unless_pressed(&mut self) {
if self.surface().buttons_pressed != PointerButtons::NONE {
return;
}
self.cancel_gesture();
}
fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
self.surface_mut().hit_path_tracker.clear();
self.surface_mut().buttons_pressed = PointerButtons::NONE;
if !targets.is_empty() {
let event = self
.pointer_event(
PointerEventKind::Cancel,
Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
},
Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
},
event_time,
)
.with_source(self.surface().pointer_source);
self.dispatch_targets(targets, event, false);
}
let pos = Point {
x: self.surface().cursor.0,
y: self.surface().cursor.1,
};
let hovered_nodes = self.surface().hovered_nodes.clone();
for node_id in hovered_nodes {
if let Some(target) = self.surface().renderer.scene().find_target(node_id) {
let exit_event = self
.pointer_event(PointerEventKind::Exit, pos, pos, event_time)
.with_source(self.surface().pointer_source);
self.dispatch_targets(std::iter::once(target), exit_event, false);
}
}
self.surface_mut().hovered_nodes.clear();
self.surface().pointer_icon.set(PointerIcon::DEFAULT);
}
fn apply_hovered_pointer_icon(
&self,
hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
) {
let icon = hits
.iter()
.find_map(HitTestTarget::pointer_icon)
.unwrap_or(PointerIcon::DEFAULT);
self.surface().pointer_icon.set(icon);
}
fn on_focus_key(&mut self, event: &KeyEvent) -> bool {
if !plain_key_down(event) || event.key_code != KeyCode::Tab {
return false;
}
let direction = if event.modifiers.shift {
FocusDirection::Previous
} else {
FocusDirection::Next
};
self.move_focus_in_context(direction)
}
fn on_activation_key(&mut self, event: &KeyEvent) -> bool {
if !plain_key_down(event)
|| !matches!(event.key_code, KeyCode::Enter | KeyCode::Space)
|| cranpose_ui::text_field_focus::has_focused_field()
{
return false;
}
let Some(focused) = cranpose_ui::active_focus_target() else {
return false;
};
let center = self.with_layout_tree(|layout_tree| {
layout_tree
.map(cranpose_ui::collect_focus_order)
.unwrap_or_default()
.iter()
.find(|entry| entry.node_id == focused)
.map(cranpose_ui::FocusEntry::center)
});
let Some((x, y)) = center else {
return false;
};
self.set_cursor(x, y);
let pressed = self.pointer_pressed();
let released = self.pointer_released_at_position(x, y);
self.note_focus_moved_by_keyboard(true);
pressed || released
}
fn on_arrow_key(&mut self, event: &KeyEvent) -> bool {
if !plain_key_down(event) || cranpose_ui::text_field_focus::has_focused_field() {
return false;
}
let direction = match event.key_code {
KeyCode::ArrowLeft => FocusDirection::Left,
KeyCode::ArrowRight => FocusDirection::Right,
KeyCode::ArrowUp => FocusDirection::Up,
KeyCode::ArrowDown => FocusDirection::Down,
_ => return false,
};
let Some(focused) = cranpose_ui::active_focus_target() else {
return false;
};
let order = self.with_layout_tree(|layout_tree| {
let layout_tree = layout_tree?;
let group = cranpose_ui::selectable_group_of(layout_tree, focused)?;
Some(cranpose_ui::collect_focus_order_under(layout_tree, group))
});
order.is_some_and(|order| self.move_focus_in_order(order, direction))
}
fn note_focus_moved_by_keyboard(&mut self, keyboard: bool) {
if cranpose_ui::set_keyboard_focus_visible(keyboard) {
cranpose_ui::request_render_invalidation();
self.mark_dirty();
}
}
fn on_escape_key(&mut self, event: &KeyEvent) -> bool {
if event.event_type != KeyEventType::KeyDown || event.key_code != KeyCode::Escape {
return false;
}
self.dismiss_top_modal_in_context()
}
pub fn dismiss_top_modal(&mut self) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| self.dismiss_top_modal_in_context())
}
fn dismiss_top_modal_in_context(&mut self) -> bool {
let closed = run_in_mutable_snapshot(|| {
if cranpose_ui::modal_depth() > 0 {
cranpose_ui::dispatch_modal_back()
} else {
cranpose_ui::dismiss_top_popup()
}
})
.unwrap_or(false);
if closed {
self.mark_dirty();
}
closed
}
pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
let order = self.with_layout_tree(|layout_tree| {
layout_tree
.map(cranpose_ui::collect_focus_order)
.unwrap_or_default()
});
self.move_focus_in_order(order, direction)
}
fn move_focus_in_order(
&mut self,
order: Vec<cranpose_ui::FocusEntry>,
direction: FocusDirection,
) -> bool {
cranpose_ui::set_focus_order(order);
let moved = run_in_mutable_snapshot(|| cranpose_ui::FocusManager.move_focus(direction))
.unwrap_or(false);
if moved {
self.mark_dirty();
self.note_focus_moved_by_keyboard(true);
}
moved
}
pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| self.on_key_event_inner(event))
}
fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
use KeyEventType::KeyDown;
if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
#[cfg(all(
feature = "clipboard-native",
not(target_arch = "wasm32"),
not(target_os = "android"),
not(target_os = "ios")
))]
{
match event.key_code {
KeyCode::C => {
if let Some(text) = self.on_copy_inner() {
cranpose_ui::clipboard_session::clipboard_write_text(&text);
return true;
}
}
KeyCode::X => {
if let Some(text) = self.on_cut_inner() {
cranpose_ui::clipboard_session::clipboard_write_text(&text);
self.mark_dirty();
self.shell.app.request_layout_pass();
return true;
}
}
KeyCode::V => {
if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
&& self.on_paste_inner(&text)
{
return true;
}
}
_ => {}
}
}
}
if self.on_focus_key(event) {
return true;
}
if self.on_escape_key(event) {
return true;
}
if self.on_activation_key(event) || self.on_arrow_key(event) {
return true;
}
if !cranpose_ui::text_field_focus::has_focused_field() {
return false;
}
let handled =
run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_key_event(event))
.unwrap_or(false);
if handled {
self.mark_dirty();
self.shell.app.request_layout_pass();
}
handled
}
pub fn on_paste(&mut self, text: &str) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| self.on_paste_inner(text))
}
fn on_paste_inner(&mut self, text: &str) -> bool {
let handled =
run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
.unwrap_or(false);
if handled {
self.mark_dirty();
self.shell.app.request_layout_pass();
}
handled
}
pub fn on_copy(&mut self) -> Option<String> {
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| self.on_copy_inner())
}
fn on_copy_inner(&mut self) -> Option<String> {
cranpose_ui::text_field_focus::dispatch_copy()
}
pub fn on_cut(&mut self) -> Option<String> {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| self.on_cut_inner())
}
fn on_cut_inner(&mut self) -> Option<String> {
let text =
run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
if text.is_some() {
self.mark_dirty();
self.shell.app.request_layout_pass();
}
text
}
pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
}
fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
let handled = run_in_mutable_snapshot(|| {
cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
})
.unwrap_or(false);
if handled {
self.mark_dirty();
self.shell.app.request_layout_pass();
}
handled
}
pub fn on_ime_finish_composing(&mut self) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| self.on_ime_finish_composing_inner())
}
fn on_ime_finish_composing_inner(&mut self) -> bool {
let handled =
run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
.unwrap_or(false);
if handled {
self.mark_dirty();
self.shell.app.request_layout_pass();
}
handled
}
pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| {
let handled = run_in_mutable_snapshot(|| {
cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
start_bytes,
end_bytes,
)
})
.unwrap_or(false);
if handled {
self.mark_dirty();
self.shell.app.request_layout_pass();
}
handled
})
}
pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| {
let handled = run_in_mutable_snapshot(|| {
cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
})
.unwrap_or(false);
if handled {
self.mark_dirty();
}
handled
})
}
pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
}
pub fn ime_caret_geometry(
&mut self,
) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
}
pub fn clear_text_field_focus(&mut self) {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(cranpose_ui::text_field_focus::clear_focus);
self.mark_dirty();
self.shell.app.request_layout_pass();
}
pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
let _event_handler = enter_event_handler_scope();
let app_context = Rc::clone(&self.shell.app.app_context);
app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
}
fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
let handled = run_in_mutable_snapshot(|| {
cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
})
.unwrap_or(false);
if handled {
self.mark_dirty();
self.shell.app.request_layout_pass();
}
handled
}
}
impl<R> AppShell<R>
where
R: Renderer,
R::Error: Debug,
{
pub fn set_modifiers(&mut self, modifiers: Modifiers) {
self.app.modifiers = Some(modifiers);
}
pub fn modifiers(&self) -> Option<Modifiers> {
self.app.modifiers
}
pub fn rotary_scroll_factor(&self) -> f32 {
self.app.rotary_scroll_factor
}
pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
if factor.is_finite() && factor > 0.0 {
self.app.rotary_scroll_factor = factor;
}
}
pub fn notify_app_paused(&mut self) {
let app_context = Rc::clone(&self.app.app_context);
app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
}
pub fn notify_app_resumed(&mut self) -> bool {
let app_context = Rc::clone(&self.app.app_context);
app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
}
#[cfg(all(
feature = "clipboard-native",
target_os = "linux",
not(target_arch = "wasm32")
))]
pub fn set_primary_selection(&mut self, text: &str) {
use arboard::{LinuxClipboardKind, SetExtLinux};
if let Some(ref mut clipboard) = self.app.clipboard {
let result = clipboard
.set()
.clipboard(LinuxClipboardKind::Primary)
.text(text.to_string());
if let Err(e) = result {
log::debug!("Primary selection set failed: {:?}", e);
}
}
}
#[cfg(not(all(
feature = "clipboard-native",
target_os = "linux",
not(target_arch = "wasm32")
)))]
pub fn set_primary_selection(&mut self, _text: &str) {}
#[cfg(all(
feature = "clipboard-native",
target_os = "linux",
not(target_arch = "wasm32")
))]
pub fn get_primary_selection(&mut self) -> Option<String> {
use arboard::{GetExtLinux, LinuxClipboardKind};
if let Some(ref mut clipboard) = self.app.clipboard {
clipboard
.get()
.clipboard(LinuxClipboardKind::Primary)
.text()
.ok()
} else {
None
}
}
#[cfg(not(all(
feature = "clipboard-native",
target_os = "linux",
not(target_arch = "wasm32")
)))]
pub fn get_primary_selection(&mut self) -> Option<String> {
None
}
pub fn sync_selection_to_primary(&mut self) {
#[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
{
if let Some(text) = self.on_copy() {
self.set_primary_selection(&text);
}
}
}
pub fn set_pointer_source(&mut self, source: PointerSource) {
self.primary().set_pointer_source(source);
}
pub fn pointer_source(&self) -> PointerSource {
self.surfaces[0].pointer_source
}
pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
self.primary().set_cursor(x, y)
}
pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
self.primary().set_cursor_at_time(x, y, time_ms)
}
pub fn set_cursor_at_event_time(
&mut self,
x: f32,
y: f32,
event_time: PointerEventTime,
) -> bool {
self.primary().set_cursor_at_event_time(x, y, event_time)
}
pub fn pointer_pressed(&mut self) -> bool {
self.primary().pointer_pressed()
}
pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
self.primary().pointer_pressed_at_time(time_ms)
}
pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
self.primary().pointer_pressed_at_event_time(event_time)
}
pub fn pointer_released(&mut self) -> bool {
self.primary().pointer_released()
}
pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
self.primary().pointer_released_at_position(x, y)
}
pub fn pointer_released_at_position_time(
&mut self,
x: f32,
y: f32,
time_ms: Option<i64>,
) -> bool {
self.primary()
.pointer_released_at_position_time(x, y, time_ms)
}
pub fn pointer_released_at_position_event_time(
&mut self,
x: f32,
y: f32,
event_time: PointerEventTime,
) -> bool {
self.primary()
.pointer_released_at_position_event_time(x, y, event_time)
}
pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
self.primary().pointer_released_at_time(time_ms)
}
pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
self.primary().pointer_released_at_event_time(event_time)
}
pub fn secondary_pointer_pressed(
&mut self,
pointer_id: u64,
x: f32,
y: f32,
time_ms: Option<i64>,
) -> bool {
self.primary()
.secondary_pointer_pressed(pointer_id, x, y, time_ms)
}
pub fn secondary_pointer_moved(
&mut self,
pointer_id: u64,
x: f32,
y: f32,
time_ms: Option<i64>,
) -> bool {
self.primary()
.secondary_pointer_moved(pointer_id, x, y, time_ms)
}
pub fn secondary_pointer_released(
&mut self,
pointer_id: u64,
x: f32,
y: f32,
time_ms: Option<i64>,
) -> bool {
self.primary()
.secondary_pointer_released(pointer_id, x, y, time_ms)
}
pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
self.primary().pointer_zoomed(zoom_factor)
}
pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
self.primary().wheel_scrolled(wheel)
}
pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
self.primary().pointer_scrolled(delta_x, delta_y)
}
pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
where
F: Fn(RotaryScrollEvent) -> bool + 'static,
{
self.primary().set_on_rotary_scroll(handler);
}
pub fn clear_on_rotary_scroll(&mut self) {
self.primary().clear_on_rotary_scroll();
}
pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
self.primary()
.rotary_scrolled_by_detents(detents, uptime_millis)
}
pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
self.primary().rotary_scrolled(event)
}
pub fn cancel_gesture(&mut self) {
self.primary().cancel_gesture();
}
pub fn cancel_gesture_unless_pressed(&mut self) {
self.primary().cancel_gesture_unless_pressed();
}
pub fn refresh_pointer_icon(&self) {
self.surfaces[0].pointer_icon.refresh();
}
pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
self.surfaces[0].pointer_icon.take_change()
}
pub fn set_platform_text_input(
&mut self,
handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
) {
self.primary().set_platform_text_input(handler);
}
pub fn dismiss_top_modal(&mut self) -> bool {
self.primary().dismiss_top_modal()
}
pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
self.primary().move_focus_in_context(direction)
}
pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
self.primary().on_key_event(event)
}
pub fn on_paste(&mut self, text: &str) -> bool {
self.primary().on_paste(text)
}
pub fn on_copy(&mut self) -> Option<String> {
self.primary().on_copy()
}
pub fn on_cut(&mut self) -> Option<String> {
self.primary().on_cut()
}
pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
self.primary().on_ime_preedit(text, cursor)
}
pub fn on_ime_finish_composing(&mut self) -> bool {
self.primary().on_ime_finish_composing()
}
pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
self.primary()
.on_ime_set_composing_region(start_bytes, end_bytes)
}
pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
self.primary().on_ime_set_selection(start_bytes, end_bytes)
}
pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
self.primary().ime_editor_state()
}
pub fn ime_caret_geometry(
&mut self,
) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
self.primary().ime_caret_geometry()
}
pub fn clear_text_field_focus(&mut self) {
self.primary().clear_text_field_focus();
}
pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
self.primary()
.on_ime_delete_surrounding(before_bytes, after_bytes)
}
}
fn plain_key_down(event: &KeyEvent) -> bool {
event.event_type == KeyEventType::KeyDown
&& !event.modifiers.ctrl
&& !event.modifiers.meta
&& !event.modifiers.alt
}