use super::ElementHostWidget;
use crate::declarative::prelude::*;
use fret_runtime::DragHost;
fn position_local(bounds: Rect, mapped: Point) -> Point {
Point::new(
fret_core::Px(mapped.x.0 - bounds.origin.x.0),
fret_core::Px(mapped.y.0 - bounds.origin.y.0),
)
}
pub(super) fn handle_pointer_region<H: UiHost>(
this: &mut ElementHostWidget,
cx: &mut EventCx<'_, H>,
window: AppWindowId,
props: PointerRegionProps,
event: &Event,
) {
if !props.enabled {
return;
}
if cx.input_ctx.dispatch_phase == fret_runtime::InputDispatchPhase::Bubble {
match event {
Event::Pointer(fret_core::PointerEvent::Down { .. })
| Event::Pointer(fret_core::PointerEvent::Up { .. })
| Event::PointerCancel(_) => return,
Event::Pointer(fret_core::PointerEvent::Move { .. })
if props.capture_phase_pointer_moves || cx.captured.is_some() =>
{
return;
}
_ => {}
}
}
let pixels_per_point = cx
.app
.global::<fret_core::WindowMetricsService>()
.and_then(|svc| svc.scale_factor(window))
.unwrap_or(1.0);
struct PointerHookHost<'a, H: UiHost> {
app: &'a mut H,
window: AppWindowId,
element: crate::GlobalElementId,
node: NodeId,
bounds: Rect,
input_ctx: &'a fret_runtime::InputContext,
prevented_default_actions: &'a mut fret_runtime::DefaultActionSet,
requested_focus_target: &'a mut Option<crate::GlobalElementId>,
requested_capture: &'a mut Option<Option<NodeId>>,
requested_cursor: &'a mut Option<fret_core::CursorIcon>,
notify_requested: &'a mut bool,
notify_requested_location: &'a mut Option<crate::widget::UiSourceLocation>,
invalidations: &'a mut Vec<(NodeId, Invalidation)>,
}
impl<H: UiHost> action::UiActionHost for PointerHookHost<'_, H> {
fn models_mut(&mut self) -> &mut fret_runtime::ModelStore {
self.app.models_mut()
}
fn push_effect(&mut self, effect: Effect) {
match effect {
Effect::SetTimer {
window: Some(window),
token,
..
} if window == self.window => {
crate::elements::record_timer_target(
&mut *self.app,
window,
token,
self.element,
);
}
Effect::CancelTimer { token } => {
crate::elements::clear_timer_target(&mut *self.app, self.window, token);
}
_ => {}
}
self.app.push_effect(effect);
}
fn request_redraw(&mut self, window: AppWindowId) {
self.app.request_redraw(window);
}
fn next_timer_token(&mut self) -> fret_runtime::TimerToken {
self.app.next_timer_token()
}
fn next_clipboard_token(&mut self) -> fret_runtime::ClipboardToken {
self.app.next_clipboard_token()
}
fn next_share_sheet_token(&mut self) -> fret_runtime::ShareSheetToken {
self.app.next_share_sheet_token()
}
fn record_pending_command_dispatch_source(
&mut self,
cx: action::ActionCx,
command: &fret_runtime::CommandId,
reason: action::ActivateReason,
) {
let kind = match reason {
action::ActivateReason::Pointer => {
fret_runtime::CommandDispatchSourceKindV1::Pointer
}
action::ActivateReason::Keyboard => {
fret_runtime::CommandDispatchSourceKindV1::Keyboard
}
};
let source = fret_runtime::CommandDispatchSourceV1 {
kind,
element: Some(cx.target.0),
test_id: None,
};
self.app.with_global_mut(
fret_runtime::WindowPendingCommandDispatchSourceService::default,
|svc, app| {
svc.record(cx.window, app.tick_id(), command.clone(), source);
},
);
}
fn record_pending_action_payload(
&mut self,
cx: action::ActionCx,
action: &fret_runtime::ActionId,
payload: Box<dyn std::any::Any + Send + Sync>,
) {
self.app.with_global_mut(
fret_runtime::WindowPendingActionPayloadService::default,
|svc, app| {
svc.record(cx.window, app.tick_id(), action.clone(), payload);
},
);
}
fn record_transient_event(&mut self, cx: action::ActionCx, key: u64) {
crate::elements::record_transient_event(&mut *self.app, cx.window, cx.target, key);
}
#[track_caller]
fn notify(&mut self, _cx: action::ActionCx) {
*self.notify_requested = true;
if self.notify_requested_location.is_none() {
let caller = std::panic::Location::caller();
*self.notify_requested_location = Some(crate::widget::UiSourceLocation {
file: caller.file(),
line: caller.line(),
column: caller.column(),
});
}
}
}
impl<H: UiHost> action::UiFocusActionHost for PointerHookHost<'_, H> {
fn request_focus(&mut self, target: crate::GlobalElementId) {
*self.requested_focus_target = Some(target);
}
}
impl<H: UiHost> action::UiPointerActionHost for PointerHookHost<'_, H> {
fn bounds(&self) -> Rect {
self.bounds
}
fn capture_pointer(&mut self) {
*self.requested_capture = Some(Some(self.node));
}
fn release_pointer_capture(&mut self) {
*self.requested_capture = Some(None);
}
fn set_cursor_icon(&mut self, icon: fret_core::CursorIcon) {
if !self.input_ctx.caps.ui.cursor_icons {
return;
}
*self.requested_cursor = Some(icon);
}
fn prevent_default(&mut self, action: fret_runtime::DefaultAction) {
self.prevented_default_actions.insert(action);
}
fn invalidate(&mut self, invalidation: Invalidation) {
self.invalidations.push((self.node, invalidation));
}
}
impl<H: UiHost> action::UiDragActionHost for PointerHookHost<'_, H> {
fn begin_drag_with_kind(
&mut self,
pointer_id: fret_core::PointerId,
kind: fret_runtime::DragKindId,
source_window: AppWindowId,
start: Point,
) {
DragHost::begin_drag_with_kind(
&mut *self.app,
pointer_id,
kind,
source_window,
start,
(),
);
}
fn begin_cross_window_drag_with_kind(
&mut self,
pointer_id: fret_core::PointerId,
kind: fret_runtime::DragKindId,
source_window: AppWindowId,
start: Point,
) {
DragHost::begin_cross_window_drag_with_kind(
&mut *self.app,
pointer_id,
kind,
source_window,
start,
(),
);
}
fn drag(&self, pointer_id: fret_core::PointerId) -> Option<&fret_runtime::DragSession> {
DragHost::drag(&*self.app, pointer_id)
}
fn drag_mut(
&mut self,
pointer_id: fret_core::PointerId,
) -> Option<&mut fret_runtime::DragSession> {
DragHost::drag_mut(&mut *self.app, pointer_id)
}
fn cancel_drag(&mut self, pointer_id: fret_core::PointerId) {
DragHost::cancel_drag(&mut *self.app, pointer_id);
}
}
match event {
Event::Pointer(fret_core::PointerEvent::Down {
position,
button,
modifiers,
pointer_type,
click_count,
pointer_id,
..
}) => {
let hook = crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::action::PointerActionHooks::default,
|hooks| hooks.on_pointer_down.clone(),
);
let down = action::PointerDownCx {
pointer_id: *pointer_id,
position: *position,
position_local: position_local(cx.bounds, *position),
position_window: cx.event_window_position,
tick_id: cx.app.tick_id(),
pixels_per_point,
button: *button,
modifiers: *modifiers,
click_count: *click_count,
pointer_type: *pointer_type,
hit_is_text_input: cx.pointer_hit_is_text_input,
hit_is_pressable: cx.pointer_hit_is_pressable,
hit_pressable_target: cx.pointer_hit_pressable_target,
hit_pressable_target_in_descendant_subtree: cx
.pointer_hit_pressable_target_in_descendant_subtree,
};
let Some(h) = hook else {
return;
};
crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::element::PointerRegionState::default,
|state| {
state.last_down = Some(down);
},
);
let mut host = PointerHookHost {
app: &mut *cx.app,
window,
element: this.element,
node: cx.node,
bounds: cx.bounds,
input_ctx: &cx.input_ctx,
prevented_default_actions: cx.prevented_default_actions,
requested_focus_target: &mut cx.requested_focus_target,
requested_capture: &mut cx.requested_capture,
requested_cursor: &mut cx.requested_cursor,
notify_requested: &mut cx.notify_requested,
notify_requested_location: &mut cx.notify_requested_location,
invalidations: &mut cx.invalidations,
};
let handled = h(
&mut host,
action::ActionCx {
window,
target: this.element,
},
down,
);
if handled {
cx.stop_propagation();
}
}
Event::Pointer(fret_core::PointerEvent::Move {
position,
buttons,
modifiers,
pointer_type,
pointer_id,
..
}) => {
if cx.input_ctx.dispatch_phase == fret_runtime::InputDispatchPhase::Preview {
return;
}
let hook = crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::action::PointerActionHooks::default,
|hooks| hooks.on_pointer_move.clone(),
);
let Some(h) = hook else {
return;
};
let velocity_window = cx
.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.velocity_window(window, *pointer_id));
let mv = action::PointerMoveCx {
pointer_id: *pointer_id,
position: *position,
position_local: position_local(cx.bounds, *position),
position_window: cx.event_window_position,
tick_id: cx.app.tick_id(),
pixels_per_point,
velocity_window,
buttons: *buttons,
modifiers: *modifiers,
pointer_type: *pointer_type,
};
#[cfg(debug_assertions)]
if crate::runtime_config::ui_runtime_config().debug_pointer_region_move_hook {
eprintln!(
"pointer_region_move_hook: element={:?} node={:?} phase={:?} pos={:?} buttons={:?}",
this.element, cx.node, cx.input_ctx.dispatch_phase, position, buttons
);
}
#[cfg(debug_assertions)]
if crate::runtime_config::ui_runtime_config().debug_pointer_region_move_backtrace {
eprintln!(
"pointer_region_move_hook backtrace:\n{}",
std::backtrace::Backtrace::force_capture()
);
}
let mut host = PointerHookHost {
app: &mut *cx.app,
window,
element: this.element,
node: cx.node,
bounds: cx.bounds,
input_ctx: &cx.input_ctx,
prevented_default_actions: cx.prevented_default_actions,
requested_focus_target: &mut cx.requested_focus_target,
requested_capture: &mut cx.requested_capture,
requested_cursor: &mut cx.requested_cursor,
notify_requested: &mut cx.notify_requested,
notify_requested_location: &mut cx.notify_requested_location,
invalidations: &mut cx.invalidations,
};
let handled = h(
&mut host,
action::ActionCx {
window,
target: this.element,
},
mv,
);
if handled {
cx.stop_propagation();
}
}
Event::Pointer(fret_core::PointerEvent::Wheel {
position,
delta,
modifiers,
pointer_id,
pointer_type,
..
}) => {
let hook = crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::action::PointerActionHooks::default,
|hooks| hooks.on_wheel.clone(),
);
let Some(h) = hook else {
return;
};
let wheel = action::WheelCx {
pointer_id: *pointer_id,
position: *position,
position_local: position_local(cx.bounds, *position),
position_window: cx.event_window_position,
tick_id: cx.app.tick_id(),
pixels_per_point,
delta: *delta,
delta_window: cx.event_window_wheel_delta,
modifiers: *modifiers,
pointer_type: *pointer_type,
};
let mut host = PointerHookHost {
app: &mut *cx.app,
window,
element: this.element,
node: cx.node,
bounds: cx.bounds,
input_ctx: &cx.input_ctx,
prevented_default_actions: cx.prevented_default_actions,
requested_focus_target: &mut cx.requested_focus_target,
requested_capture: &mut cx.requested_capture,
requested_cursor: &mut cx.requested_cursor,
notify_requested: &mut cx.notify_requested,
notify_requested_location: &mut cx.notify_requested_location,
invalidations: &mut cx.invalidations,
};
let handled = h(
&mut host,
action::ActionCx {
window,
target: this.element,
},
wheel,
);
if handled {
cx.stop_propagation();
}
}
Event::Pointer(fret_core::PointerEvent::PinchGesture {
position,
delta,
modifiers,
pointer_type,
pointer_id,
..
}) => {
let hook = crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::action::PointerActionHooks::default,
|hooks| hooks.on_pinch_gesture.clone(),
);
let Some(h) = hook else {
return;
};
let pinch = action::PinchGestureCx {
pointer_id: *pointer_id,
position: *position,
position_local: position_local(cx.bounds, *position),
position_window: cx.event_window_position,
tick_id: cx.app.tick_id(),
pixels_per_point,
delta: *delta,
modifiers: *modifiers,
pointer_type: *pointer_type,
};
let mut host = PointerHookHost {
app: &mut *cx.app,
window,
element: this.element,
node: cx.node,
bounds: cx.bounds,
input_ctx: &cx.input_ctx,
prevented_default_actions: cx.prevented_default_actions,
requested_focus_target: &mut cx.requested_focus_target,
requested_capture: &mut cx.requested_capture,
requested_cursor: &mut cx.requested_cursor,
notify_requested: &mut cx.notify_requested,
notify_requested_location: &mut cx.notify_requested_location,
invalidations: &mut cx.invalidations,
};
let handled = h(
&mut host,
action::ActionCx {
window,
target: this.element,
},
pinch,
);
if handled {
cx.stop_propagation();
}
}
Event::Pointer(fret_core::PointerEvent::Up {
position,
button,
modifiers,
is_click,
pointer_type,
click_count,
pointer_id,
..
}) => {
let was_captured = cx.captured == Some(cx.node);
let hook = crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::action::PointerActionHooks::default,
|hooks| hooks.on_pointer_up.clone(),
);
let up = action::PointerUpCx {
pointer_id: *pointer_id,
position: *position,
position_local: position_local(cx.bounds, *position),
position_window: cx.event_window_position,
tick_id: cx.app.tick_id(),
pixels_per_point,
velocity_window: cx
.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.velocity_window(window, *pointer_id)),
button: *button,
modifiers: *modifiers,
is_click: *is_click,
click_count: *click_count,
pointer_type: *pointer_type,
down_hit_pressable_target: crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::element::PointerRegionState::default,
|state| state.last_down.and_then(|d| d.hit_pressable_target),
),
down_hit_pressable_target_in_descendant_subtree:
crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::element::PointerRegionState::default,
|state| {
state
.last_down
.is_some_and(|d| d.hit_pressable_target_in_descendant_subtree)
},
),
};
if let Some(h) = hook {
let mut host = PointerHookHost {
app: &mut *cx.app,
window,
element: this.element,
node: cx.node,
bounds: cx.bounds,
input_ctx: &cx.input_ctx,
prevented_default_actions: cx.prevented_default_actions,
requested_focus_target: &mut cx.requested_focus_target,
requested_capture: &mut cx.requested_capture,
requested_cursor: &mut cx.requested_cursor,
notify_requested: &mut cx.notify_requested,
notify_requested_location: &mut cx.notify_requested_location,
invalidations: &mut cx.invalidations,
};
let handled = h(
&mut host,
action::ActionCx {
window,
target: this.element,
},
up,
);
if handled {
cx.stop_propagation();
}
}
if was_captured {
cx.release_pointer_capture();
}
}
Event::PointerCancel(e) => {
let was_captured = cx.captured == Some(cx.node);
let hook = crate::elements::with_element_state(
&mut *cx.app,
window,
this.element,
crate::action::PointerActionHooks::default,
|hooks| hooks.on_pointer_cancel.clone(),
);
if let Some(h) = hook {
let cancel = action::PointerCancelCx {
pointer_id: e.pointer_id,
position: e.position,
position_local: e.position.map(|p| position_local(cx.bounds, p)),
position_window: cx.event_window_position,
tick_id: cx.app.tick_id(),
pixels_per_point,
buttons: e.buttons,
modifiers: e.modifiers,
pointer_type: e.pointer_type,
reason: e.reason,
};
let mut host = PointerHookHost {
app: &mut *cx.app,
window,
element: this.element,
node: cx.node,
bounds: cx.bounds,
input_ctx: &cx.input_ctx,
prevented_default_actions: cx.prevented_default_actions,
requested_focus_target: &mut cx.requested_focus_target,
requested_capture: &mut cx.requested_capture,
requested_cursor: &mut cx.requested_cursor,
notify_requested: &mut cx.notify_requested,
notify_requested_location: &mut cx.notify_requested_location,
invalidations: &mut cx.invalidations,
};
let handled = h(
&mut host,
action::ActionCx {
window,
target: this.element,
},
cancel,
);
if handled {
cx.stop_propagation();
}
}
if was_captured {
cx.release_pointer_capture();
}
}
_ => {}
}
}