use crate::{Theme, UiHost};
use fret_core::{
AppWindowId, Corners, Event, NodeId, Point, Rect, Scene, SemanticsCheckedState, SemanticsFlags,
SemanticsInvalid, SemanticsLive, SemanticsOrientation, SemanticsPressedState, SemanticsRole,
Size, Transform2D, UiServices,
};
use fret_runtime::{
CommandId, DefaultAction, DefaultActionSet, Effect, InputContext, Model, ModelId,
};
use std::any::{Any, TypeId};
use std::collections::HashMap;
use crate::layout_constraints::LayoutConstraints;
use crate::layout_pass::LayoutPassKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Invalidation {
Layout,
Paint,
HitTest,
HitTestOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UiSourceLocation {
pub file: &'static str,
pub line: u32,
pub column: u32,
}
pub struct EventCx<'a, H: UiHost> {
pub app: &'a mut H,
pub services: &'a mut dyn UiServices,
pub node: NodeId,
pub layer_root: Option<NodeId>,
pub window: Option<AppWindowId>,
pub pointer_id: Option<fret_core::PointerId>,
pub scale_factor: f32,
pub event_window_position: Option<Point>,
pub event_window_wheel_delta: Option<Point>,
pub input_ctx: InputContext,
pub pointer_hit_is_text_input: bool,
pub pointer_hit_is_pressable: bool,
pub pointer_hit_pressable_target: Option<crate::GlobalElementId>,
pub pointer_hit_pressable_target_in_descendant_subtree: bool,
pub prevented_default_actions: &'a mut DefaultActionSet,
pub children: &'a [NodeId],
pub focus: Option<NodeId>,
pub captured: Option<NodeId>,
pub bounds: Rect,
pub invalidations: Vec<(NodeId, Invalidation)>,
pub(crate) scroll_handle_invalidations: Vec<ScrollHandleInvalidationRequest>,
pub(crate) scroll_target_invalidations: Vec<crate::GlobalElementId>,
pub requested_focus: Option<NodeId>,
pub requested_focus_target: Option<crate::GlobalElementId>,
pub requested_capture: Option<Option<NodeId>>,
pub requested_cursor: Option<fret_core::CursorIcon>,
pub notify_requested: bool,
pub notify_requested_location: Option<UiSourceLocation>,
pub stop_propagation: bool,
}
impl<'a, H: UiHost> EventCx<'a, H> {
#[allow(clippy::too_many_arguments)]
pub fn new(
app: &'a mut H,
services: &'a mut dyn UiServices,
node: NodeId,
layer_root: Option<NodeId>,
window: Option<AppWindowId>,
input_ctx: InputContext,
pointer_id: Option<fret_core::PointerId>,
scale_factor: f32,
event_window_position: Option<Point>,
event_window_wheel_delta: Option<Point>,
pointer_hit_is_text_input: bool,
pointer_hit_is_pressable: bool,
pointer_hit_pressable_target: Option<crate::GlobalElementId>,
pointer_hit_pressable_target_in_descendant_subtree: bool,
prevented_default_actions: &'a mut DefaultActionSet,
children: &'a [NodeId],
focus: Option<NodeId>,
captured: Option<NodeId>,
bounds: Rect,
) -> Self {
Self {
app,
services,
node,
layer_root,
window,
pointer_id,
scale_factor,
event_window_position,
event_window_wheel_delta,
input_ctx,
pointer_hit_is_text_input,
pointer_hit_is_pressable,
pointer_hit_pressable_target,
pointer_hit_pressable_target_in_descendant_subtree,
prevented_default_actions,
children,
focus,
captured,
bounds,
invalidations: Vec::new(),
scroll_handle_invalidations: Vec::new(),
scroll_target_invalidations: Vec::new(),
requested_focus: None,
requested_focus_target: None,
requested_capture: None,
requested_cursor: None,
notify_requested: false,
notify_requested_location: None,
stop_propagation: false,
}
}
pub fn theme(&self) -> &Theme {
Theme::global(&*self.app)
}
pub fn pointer_position_local(&self, event: &Event) -> Option<Point> {
let pos = Self::pointer_position_mapped(event)?;
Some(Point::new(
fret_core::Px(pos.x.0 - self.bounds.origin.x.0),
fret_core::Px(pos.y.0 - self.bounds.origin.y.0),
))
}
pub fn pointer_position_window(&self, event: &Event) -> Option<Point> {
Self::pointer_position_mapped(event).and(self.event_window_position)
}
pub fn pointer_delta_local(&self, event: &Event) -> Option<Point> {
match event {
Event::Pointer(fret_core::PointerEvent::Wheel { delta, .. }) => Some(*delta),
_ => None,
}
}
pub fn pointer_delta_window(&self, event: &Event) -> Option<Point> {
self.pointer_delta_local(event)
.and(self.event_window_wheel_delta)
}
fn pointer_position_mapped(event: &Event) -> Option<Point> {
match event {
Event::Pointer(e) => match e {
fret_core::PointerEvent::Move { position, .. }
| fret_core::PointerEvent::Down { position, .. }
| fret_core::PointerEvent::Up { position, .. }
| fret_core::PointerEvent::Wheel { position, .. }
| fret_core::PointerEvent::PinchGesture { position, .. } => Some(*position),
},
Event::PointerCancel(e) => e.position,
Event::ExternalDrag(e) => Some(e.position),
Event::InternalDrag(e) => Some(e.position),
_ => None,
}
}
pub fn frame_clock(&self) -> Option<fret_core::WindowFrameClockSnapshot> {
let window = self.window?;
self.app
.global::<fret_core::WindowFrameClockService>()
.and_then(|svc| svc.snapshot(window))
}
pub fn prefers_reduced_motion(&self) -> Option<bool> {
let window = self.window?;
self.app
.global::<fret_core::WindowMetricsService>()
.and_then(|svc| {
svc.prefers_reduced_motion_is_known(window)
.then(|| svc.prefers_reduced_motion(window))
.flatten()
})
}
pub fn pointer_position_window_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window = self.window?;
self.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.position_window(window, pointer_id))
}
pub fn pointer_velocity_window_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window = self.window?;
self.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.velocity_window(window, pointer_id))
}
pub fn invalidate(&mut self, node: NodeId, kind: Invalidation) {
self.invalidations.push((node, kind));
}
pub fn invalidate_scroll_handle_bindings(&mut self, handle_key: usize, kind: Invalidation) {
self.scroll_handle_invalidations
.push(ScrollHandleInvalidationRequest { handle_key, kind });
}
pub(crate) fn invalidate_scroll_target(&mut self, element: crate::GlobalElementId) {
self.scroll_target_invalidations.push(element);
}
pub fn invalidate_self(&mut self, kind: Invalidation) {
self.invalidate(self.node, kind);
}
pub fn dispatch_command(&mut self, command: CommandId) {
self.app.push_effect(Effect::Command {
window: self.window,
command,
});
}
pub fn request_focus(&mut self, node: NodeId) {
self.requested_focus = Some(node);
}
pub fn capture_pointer(&mut self, node: NodeId) {
if self.pointer_id.is_none() {
return;
}
self.requested_capture = Some(Some(node));
}
pub fn release_pointer_capture(&mut self) {
if self.pointer_id.is_none() {
return;
}
self.requested_capture = Some(None);
}
pub fn stop_propagation(&mut self) {
self.stop_propagation = true;
}
pub fn prevent_default(&mut self, action: DefaultAction) {
self.prevented_default_actions.insert(action);
}
pub fn default_prevented(&self, action: DefaultAction) -> bool {
self.prevented_default_actions.contains(action)
}
pub fn request_redraw(&mut self) {
let Some(window) = self.window else {
return;
};
self.app.request_redraw(window);
}
#[track_caller]
pub fn notify(&mut self) {
self.notify_requested = true;
if self.notify_requested_location.is_none() {
let caller = std::panic::Location::caller();
self.notify_requested_location = Some(UiSourceLocation {
file: caller.file(),
line: caller.line(),
column: caller.column(),
});
}
}
pub fn set_cursor_icon(&mut self, icon: fret_core::CursorIcon) {
if !self.input_ctx.caps.ui.cursor_icons {
return;
}
self.requested_cursor = Some(icon);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ScrollHandleInvalidationRequest {
pub(crate) handle_key: usize,
pub(crate) kind: Invalidation,
}
pub struct ObserverCx<'a, H: UiHost> {
pub app: &'a mut H,
pub services: &'a mut dyn UiServices,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub pointer_id: Option<fret_core::PointerId>,
pub input_ctx: InputContext,
pub children: &'a [NodeId],
pub focus: Option<NodeId>,
pub captured: Option<NodeId>,
pub bounds: Rect,
pub invalidations: Vec<(NodeId, Invalidation)>,
pub notify_requested: bool,
pub notify_requested_location: Option<UiSourceLocation>,
}
impl<'a, H: UiHost> ObserverCx<'a, H> {
pub fn theme(&self) -> &Theme {
Theme::global(&*self.app)
}
pub fn invalidate(&mut self, node: NodeId, kind: Invalidation) {
self.invalidations.push((node, kind));
}
pub fn invalidate_self(&mut self, kind: Invalidation) {
self.invalidate(self.node, kind);
}
pub fn dispatch_command(&mut self, command: CommandId) {
self.app.push_effect(Effect::Command {
window: self.window,
command,
});
}
pub fn request_redraw(&mut self) {
let Some(window) = self.window else {
return;
};
self.app.request_redraw(window);
}
#[track_caller]
pub fn notify(&mut self) {
self.notify_requested = true;
if self.notify_requested_location.is_none() {
let caller = std::panic::Location::caller();
self.notify_requested_location = Some(UiSourceLocation {
file: caller.file(),
line: caller.line(),
column: caller.column(),
});
}
}
}
pub struct CommandCx<'a, H: UiHost> {
pub app: &'a mut H,
pub services: &'a mut dyn UiServices,
pub tree: &'a mut crate::tree::UiTree<H>,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub input_ctx: InputContext,
pub focus: Option<NodeId>,
pub invalidations: Vec<(NodeId, Invalidation)>,
pub requested_focus: Option<NodeId>,
pub notify_requested: bool,
pub notify_requested_location: Option<UiSourceLocation>,
pub stop_propagation: bool,
}
impl<'a, H: UiHost> CommandCx<'a, H> {
pub fn theme(&self) -> &Theme {
Theme::global(&*self.app)
}
pub fn invalidate(&mut self, node: NodeId, kind: Invalidation) {
self.invalidations.push((node, kind));
}
pub fn invalidate_self(&mut self, kind: Invalidation) {
self.invalidate(self.node, kind);
}
pub fn request_focus(&mut self, node: NodeId) {
self.requested_focus = Some(node);
}
pub fn stop_propagation(&mut self) {
self.stop_propagation = true;
}
pub fn request_redraw(&mut self) {
let Some(window) = self.window else {
return;
};
self.app.request_redraw(window);
}
#[track_caller]
pub fn notify(&mut self) {
self.notify_requested = true;
if self.notify_requested_location.is_none() {
let caller = std::panic::Location::caller();
self.notify_requested_location = Some(UiSourceLocation {
file: caller.file(),
line: caller.line(),
column: caller.column(),
});
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum CommandAvailability {
#[default]
NotHandled,
Available,
Blocked,
}
pub struct CommandAvailabilityCx<'a, H: UiHost> {
pub app: &'a mut H,
pub tree: &'a crate::tree::UiTree<H>,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub input_ctx: InputContext,
pub focus: Option<NodeId>,
}
pub struct LayoutCx<'a, H: UiHost> {
pub app: &'a mut H,
pub tree: &'a mut crate::tree::UiTree<H>,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub focus: Option<NodeId>,
pub children: &'a [NodeId],
pub bounds: Rect,
pub available: Size,
pub pass_kind: LayoutPassKind,
pub overflow_ctx: crate::layout::overflow::LayoutOverflowContext,
pub scale_factor: f32,
pub services: &'a mut dyn UiServices,
pub observe_model: &'a mut dyn FnMut(ModelId, Invalidation),
pub observe_global: &'a mut dyn FnMut(TypeId, Invalidation),
}
impl<'a, H: UiHost> LayoutCx<'a, H> {
pub fn probe_constraints_for_size(&self, size: Size) -> LayoutConstraints {
self.overflow_ctx.probe_constraints_for_size(size)
}
pub fn with_overflow_context<R>(
&mut self,
overflow_ctx: crate::layout::overflow::LayoutOverflowContext,
f: impl FnOnce(&mut Self) -> R,
) -> R {
let prev = self.overflow_ctx;
self.overflow_ctx = overflow_ctx;
let out = f(self);
self.overflow_ctx = prev;
out
}
pub fn theme(&mut self) -> &Theme {
self.observe_global::<Theme>(Invalidation::Layout);
Theme::global(&*self.app)
}
pub fn request_redraw(&mut self) {
let Some(window) = self.window else {
return;
};
self.app.request_redraw(window);
}
pub fn request_animation_frame(&mut self) {
self.tree.invalidate_with_source_and_detail(
self.node,
Invalidation::Paint,
crate::tree::UiDebugInvalidationSource::Notify,
crate::tree::UiDebugInvalidationDetail::AnimationFrameRequest,
);
let Some(window) = self.window else {
return;
};
self.app.push_effect(Effect::RequestAnimationFrame(window));
}
pub fn frame_clock(&self) -> Option<fret_core::WindowFrameClockSnapshot> {
let window = self.window?;
self.app
.global::<fret_core::WindowFrameClockService>()
.and_then(|svc| svc.snapshot(window))
}
pub fn pointer_position_window_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window = self.window?;
self.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.position_window(window, pointer_id))
}
pub fn pointer_velocity_window_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window = self.window?;
self.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.velocity_window(window, pointer_id))
}
pub fn pointer_position_local_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window_pos = self.pointer_position_window_snapshot(pointer_id)?;
let mapped = self
.tree
.map_window_point_to_node_layout_space(self.node, window_pos)?;
Some(Point::new(
fret_core::Px(mapped.x.0 - self.bounds.origin.x.0),
fret_core::Px(mapped.y.0 - self.bounds.origin.y.0),
))
}
pub fn pointer_velocity_local_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window_vec = self.pointer_velocity_window_snapshot(pointer_id)?;
self.tree
.map_window_vector_to_node_layout_space(self.node, window_vec)
}
pub fn observe_model<T>(&mut self, model: &Model<T>, invalidation: Invalidation) {
(self.observe_model)(model.id(), invalidation);
}
pub fn observe_global<T: Any>(&mut self, invalidation: Invalidation) {
(self.observe_global)(TypeId::of::<T>(), invalidation);
}
pub fn layout(&mut self, child: NodeId, available: Size) -> Size {
let rect = Rect::new(self.bounds.origin, available);
self.layout_in(child, rect)
}
pub fn layout_in(&mut self, child: NodeId, bounds: Rect) -> Size {
self.tree.layout_in_with_pass_kind(
self.app,
self.services,
child,
bounds,
self.scale_factor,
self.pass_kind,
self.overflow_ctx,
)
}
pub fn layout_in_probe(&mut self, child: NodeId, bounds: Rect) -> Size {
self.tree.layout_in_with_pass_kind(
self.app,
self.services,
child,
bounds,
self.scale_factor,
LayoutPassKind::Probe,
self.overflow_ctx,
)
}
pub fn layout_engine_child_bounds(&mut self, child: NodeId) -> Option<Rect> {
let local = self
.tree
.layout_engine_child_local_rect_profiled(self.node, child)?;
Some(Rect::new(
Point::new(
fret_core::Px(self.bounds.origin.x.0 + local.origin.x.0),
fret_core::Px(self.bounds.origin.y.0 + local.origin.y.0),
),
local.size,
))
}
pub fn layout_viewport_root(&mut self, child: NodeId, bounds: Rect) -> Size {
if self.pass_kind == LayoutPassKind::Probe {
return bounds.size;
}
self.tree.register_viewport_root(child, bounds);
bounds.size
}
pub fn solve_barrier_child_root(&mut self, child: NodeId, bounds: Rect) {
if self.pass_kind != LayoutPassKind::Final {
return;
}
self.tree.solve_barrier_flow_root(
self.app,
self.services,
child,
bounds,
self.scale_factor,
);
}
pub fn solve_barrier_child_root_if_needed(&mut self, child: NodeId, bounds: Rect) {
if self.pass_kind != LayoutPassKind::Final {
return;
}
self.tree.solve_barrier_flow_root_if_needed(
self.app,
self.services,
child,
bounds,
self.scale_factor,
);
}
pub fn solve_barrier_child_roots_if_needed(&mut self, roots: &[(NodeId, Rect)]) {
if self.pass_kind != LayoutPassKind::Final {
return;
}
self.tree.solve_barrier_flow_roots_if_needed(
self.app,
self.services,
roots,
self.scale_factor,
);
}
pub fn measure_in(&mut self, child: NodeId, constraints: LayoutConstraints) -> Size {
self.tree.measure_in(
self.app,
self.services,
child,
constraints,
self.scale_factor,
)
}
}
pub struct MeasureCx<'a, H: UiHost> {
pub app: &'a mut H,
pub tree: &'a mut crate::tree::UiTree<H>,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub focus: Option<NodeId>,
pub children: &'a [NodeId],
pub constraints: LayoutConstraints,
pub scale_factor: f32,
pub services: &'a mut dyn UiServices,
pub observe_model: &'a mut dyn FnMut(ModelId, Invalidation),
pub observe_global: &'a mut dyn FnMut(TypeId, Invalidation),
}
impl<'a, H: UiHost> MeasureCx<'a, H> {
pub fn theme(&mut self) -> &Theme {
self.observe_global::<Theme>(Invalidation::Layout);
Theme::global(&*self.app)
}
pub fn request_redraw(&mut self) {
let Some(window) = self.window else {
return;
};
self.app.request_redraw(window);
}
pub fn request_animation_frame(&mut self) {
self.tree.invalidate_with_source_and_detail(
self.node,
Invalidation::Paint,
crate::tree::UiDebugInvalidationSource::Notify,
crate::tree::UiDebugInvalidationDetail::AnimationFrameRequest,
);
let Some(window) = self.window else {
return;
};
self.app.push_effect(Effect::RequestAnimationFrame(window));
}
pub fn frame_clock(&self) -> Option<fret_core::WindowFrameClockSnapshot> {
let window = self.window?;
self.app
.global::<fret_core::WindowFrameClockService>()
.and_then(|svc| svc.snapshot(window))
}
pub fn pointer_position_window_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window = self.window?;
self.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.position_window(window, pointer_id))
}
pub fn pointer_velocity_window_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window = self.window?;
self.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.velocity_window(window, pointer_id))
}
pub fn pointer_position_local_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window_pos = self.pointer_position_window_snapshot(pointer_id)?;
let bounds = self.tree.node_bounds(self.node)?;
let mapped = self
.tree
.map_window_point_to_node_layout_space(self.node, window_pos)?;
Some(Point::new(
fret_core::Px(mapped.x.0 - bounds.origin.x.0),
fret_core::Px(mapped.y.0 - bounds.origin.y.0),
))
}
pub fn pointer_velocity_local_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window_vec = self.pointer_velocity_window_snapshot(pointer_id)?;
self.tree
.map_window_vector_to_node_layout_space(self.node, window_vec)
}
pub fn observe_model<T>(&mut self, model: &Model<T>, invalidation: Invalidation) {
(self.observe_model)(model.id(), invalidation);
}
pub fn observe_global<T: Any>(&mut self, invalidation: Invalidation) {
(self.observe_global)(TypeId::of::<T>(), invalidation);
}
pub fn measure_in(&mut self, child: NodeId, constraints: LayoutConstraints) -> Size {
if !self.tree.debug_enabled() {
return self.tree.measure_in(
self.app,
self.services,
child,
constraints,
self.scale_factor,
);
}
let started = fret_core::time::Instant::now();
let size = self.tree.measure_in(
self.app,
self.services,
child,
constraints,
self.scale_factor,
);
let elapsed = started.elapsed();
self.tree
.debug_record_measure_child(self.node, child, elapsed);
size
}
}
pub struct PrepaintCx<'a, H: UiHost> {
pub app: &'a mut H,
pub tree: &'a mut crate::tree::UiTree<H>,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub bounds: Rect,
pub scale_factor: f32,
}
impl<'a, H: UiHost> PrepaintCx<'a, H> {
pub fn set_output<T: std::any::Any>(&mut self, value: T) {
self.tree.set_prepaint_output(self.node, value);
}
pub fn output<T: std::any::Any>(&mut self) -> Option<&T> {
self.tree.prepaint_output(self.node)
}
pub fn output_mut<T: std::any::Any>(&mut self) -> Option<&mut T> {
self.tree.prepaint_output_mut(self.node)
}
pub fn invalidate(&mut self, node: NodeId, kind: Invalidation) {
self.tree
.debug_record_prepaint_action(crate::tree::UiDebugPrepaintAction {
node: self.node,
target: Some(node),
kind: crate::tree::UiDebugPrepaintActionKind::Invalidate,
invalidation: Some(kind),
element: None,
virtual_list_window_shift_kind: None,
virtual_list_window_shift_reason: None,
chart_sampling_window_key: None,
node_graph_cull_window_key: None,
frame_id: self.app.frame_id(),
});
self.tree.invalidate_with_detail(
node,
kind,
crate::tree::UiDebugInvalidationDetail::Unknown,
);
}
pub fn invalidate_self(&mut self, kind: Invalidation) {
self.invalidate(self.node, kind);
}
pub fn request_redraw(&mut self) {
self.tree
.debug_record_prepaint_action(crate::tree::UiDebugPrepaintAction {
node: self.node,
target: None,
kind: crate::tree::UiDebugPrepaintActionKind::RequestRedraw,
invalidation: None,
element: None,
virtual_list_window_shift_kind: None,
virtual_list_window_shift_reason: None,
chart_sampling_window_key: None,
node_graph_cull_window_key: None,
frame_id: self.app.frame_id(),
});
let Some(window) = self.window else {
return;
};
self.app.request_redraw(window);
}
pub fn request_animation_frame(&mut self) {
self.tree
.debug_record_prepaint_action(crate::tree::UiDebugPrepaintAction {
node: self.node,
target: Some(self.node),
kind: crate::tree::UiDebugPrepaintActionKind::RequestAnimationFrame,
invalidation: Some(Invalidation::Paint),
element: None,
virtual_list_window_shift_kind: None,
virtual_list_window_shift_reason: None,
chart_sampling_window_key: None,
node_graph_cull_window_key: None,
frame_id: self.app.frame_id(),
});
self.tree.invalidate_with_source_and_detail(
self.node,
Invalidation::Paint,
crate::tree::UiDebugInvalidationSource::Notify,
crate::tree::UiDebugInvalidationDetail::AnimationFrameRequest,
);
let Some(window) = self.window else {
return;
};
self.app.push_effect(Effect::RequestAnimationFrame(window));
}
pub fn debug_record_chart_sampling_window_shift(&mut self, sampling_window_key: u64) {
self.tree
.debug_record_prepaint_action(crate::tree::UiDebugPrepaintAction {
node: self.node,
target: None,
kind: crate::tree::UiDebugPrepaintActionKind::ChartSamplingWindowShift,
invalidation: None,
element: None,
virtual_list_window_shift_kind: None,
virtual_list_window_shift_reason: None,
chart_sampling_window_key: Some(sampling_window_key),
node_graph_cull_window_key: None,
frame_id: self.app.frame_id(),
});
}
pub fn debug_record_node_graph_cull_window_shift(&mut self, cull_window_key: u64) {
self.tree
.debug_record_prepaint_action(crate::tree::UiDebugPrepaintAction {
node: self.node,
target: None,
kind: crate::tree::UiDebugPrepaintActionKind::NodeGraphCullWindowShift,
invalidation: None,
element: None,
virtual_list_window_shift_kind: None,
virtual_list_window_shift_reason: None,
chart_sampling_window_key: None,
node_graph_cull_window_key: Some(cull_window_key),
frame_id: self.app.frame_id(),
});
}
}
pub struct PaintCx<'a, H: UiHost> {
pub app: &'a mut H,
pub tree: &'a mut crate::tree::UiTree<H>,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub focus: Option<NodeId>,
pub children: &'a [NodeId],
pub bounds: Rect,
pub scale_factor: f32,
pub(crate) paint_style: crate::tree::paint_style::PaintStyleState,
pub accumulated_transform: Transform2D,
pub children_render_transform: Option<Transform2D>,
pub services: &'a mut dyn UiServices,
pub observe_model: &'a mut dyn FnMut(ModelId, Invalidation),
pub observe_global: &'a mut dyn FnMut(TypeId, Invalidation),
pub scene: &'a mut Scene,
}
impl<'a, H: UiHost> PaintCx<'a, H> {
#[allow(clippy::too_many_arguments)]
pub fn new(
app: &'a mut H,
tree: &'a mut crate::tree::UiTree<H>,
node: NodeId,
window: Option<AppWindowId>,
focus: Option<NodeId>,
children: &'a [NodeId],
bounds: Rect,
scale_factor: f32,
accumulated_transform: Transform2D,
children_render_transform: Option<Transform2D>,
services: &'a mut dyn UiServices,
observe_model: &'a mut dyn FnMut(ModelId, Invalidation),
observe_global: &'a mut dyn FnMut(TypeId, Invalidation),
scene: &'a mut Scene,
) -> Self {
Self {
app,
tree,
node,
window,
focus,
children,
bounds,
scale_factor,
paint_style: Default::default(),
accumulated_transform,
children_render_transform,
services,
observe_model,
observe_global,
scene,
}
}
pub fn inherited_foreground(&self) -> Option<fret_core::Color> {
self.paint_style.foreground
}
pub fn prepaint_output<T: std::any::Any>(&mut self) -> Option<&T> {
self.tree.prepaint_output(self.node)
}
pub fn prepaint_output_mut<T: std::any::Any>(&mut self) -> Option<&mut T> {
self.tree.prepaint_output_mut(self.node)
}
pub fn theme(&mut self) -> &Theme {
self.observe_global::<Theme>(Invalidation::Paint);
Theme::global(&*self.app)
}
pub fn frame_clock(&self) -> Option<fret_core::WindowFrameClockSnapshot> {
let window = self.window?;
self.app
.global::<fret_core::WindowFrameClockService>()
.and_then(|svc| svc.snapshot(window))
}
pub fn pointer_position_window_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window = self.window?;
self.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.position_window(window, pointer_id))
}
pub fn pointer_velocity_window_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window = self.window?;
self.app
.global::<crate::pointer_motion::WindowPointerMotionService>()
.and_then(|svc| svc.velocity_window(window, pointer_id))
}
pub fn pointer_position_local_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window_pos = self.pointer_position_window_snapshot(pointer_id)?;
let mapped = self
.tree
.map_window_point_to_node_layout_space(self.node, window_pos)?;
Some(Point::new(
fret_core::Px(mapped.x.0 - self.bounds.origin.x.0),
fret_core::Px(mapped.y.0 - self.bounds.origin.y.0),
))
}
pub fn pointer_velocity_local_snapshot(
&self,
pointer_id: fret_core::PointerId,
) -> Option<Point> {
let window_vec = self.pointer_velocity_window_snapshot(pointer_id)?;
self.tree
.map_window_vector_to_node_layout_space(self.node, window_vec)
}
pub fn visual_rect_aabb(&self, rect: Rect) -> Rect {
let t = self.accumulated_transform;
if t == Transform2D::IDENTITY {
return rect;
}
let x0 = rect.origin.x.0;
let y0 = rect.origin.y.0;
let x1 = x0 + rect.size.width.0;
let y1 = y0 + rect.size.height.0;
let p00 = t.apply_point(Point::new(fret_core::Px(x0), fret_core::Px(y0)));
let p10 = t.apply_point(Point::new(fret_core::Px(x1), fret_core::Px(y0)));
let p01 = t.apply_point(Point::new(fret_core::Px(x0), fret_core::Px(y1)));
let p11 = t.apply_point(Point::new(fret_core::Px(x1), fret_core::Px(y1)));
let min_x = p00.x.0.min(p10.x.0).min(p01.x.0).min(p11.x.0);
let max_x = p00.x.0.max(p10.x.0).max(p01.x.0).max(p11.x.0);
let min_y = p00.y.0.min(p10.y.0).min(p01.y.0).min(p11.y.0);
let max_y = p00.y.0.max(p10.y.0).max(p01.y.0).max(p11.y.0);
if !min_x.is_finite() || !max_x.is_finite() || !min_y.is_finite() || !max_y.is_finite() {
return rect;
}
Rect::new(
Point::new(fret_core::Px(min_x), fret_core::Px(min_y)),
Size::new(
fret_core::Px((max_x - min_x).max(0.0)),
fret_core::Px((max_y - min_y).max(0.0)),
),
)
}
pub fn request_redraw(&mut self) {
let Some(window) = self.window else {
return;
};
self.app.request_redraw(window);
}
pub fn request_animation_frame(&mut self) {
self.tree.invalidate_with_source_and_detail(
self.node,
Invalidation::Paint,
crate::tree::UiDebugInvalidationSource::Notify,
crate::tree::UiDebugInvalidationDetail::AnimationFrameRequest,
);
let Some(window) = self.window else {
return;
};
self.app.push_effect(Effect::RequestAnimationFrame(window));
}
pub fn request_animation_frame_paint_only(&mut self) {
self.tree.invalidate_with_source_and_detail(
self.node,
Invalidation::Paint,
crate::tree::UiDebugInvalidationSource::Other,
crate::tree::UiDebugInvalidationDetail::AnimationFrameRequest,
);
let Some(window) = self.window else {
return;
};
self.app.push_effect(Effect::RequestAnimationFrame(window));
}
pub fn observe_model<T>(&mut self, model: &Model<T>, invalidation: Invalidation) {
(self.observe_model)(model.id(), invalidation);
}
pub fn observe_global<T: Any>(&mut self, invalidation: Invalidation) {
(self.observe_global)(TypeId::of::<T>(), invalidation);
}
pub fn paint(&mut self, child: NodeId, bounds: Rect) {
let was_widget_timer_running = self.tree.debug_paint_widget_exclusive_pause();
let child_transform = self.children_render_transform;
if let Some(transform) = child_transform {
self.scene
.push(fret_core::SceneOp::PushTransform { transform });
}
let accumulated = child_transform
.map(|t| self.accumulated_transform.compose(t))
.unwrap_or(self.accumulated_transform);
self.tree.paint_node(
self.app,
self.services,
child,
bounds,
self.scene,
self.scale_factor,
self.paint_style,
accumulated,
);
if child_transform.is_some() {
self.scene.push(fret_core::SceneOp::PopTransform);
}
if was_widget_timer_running {
self.tree.debug_paint_widget_exclusive_resume();
}
}
pub fn paint_children(&mut self) {
for &child in self.children {
if let Some(bounds) = self.child_bounds(child) {
self.paint(child, bounds);
} else {
self.paint(child, self.bounds);
}
}
}
pub fn child_bounds(&self, child: NodeId) -> Option<Rect> {
self.tree.node_bounds(child)
}
}
pub struct SemanticsCx<'a, H: UiHost> {
pub app: &'a mut H,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub element_id_map: Option<&'a HashMap<u64, NodeId>>,
pub bounds: Rect,
pub children: &'a [NodeId],
pub focus: Option<NodeId>,
pub captured: Option<NodeId>,
pub(crate) role: &'a mut SemanticsRole,
pub(crate) flags: &'a mut SemanticsFlags,
pub(crate) label: &'a mut Option<String>,
pub(crate) value: &'a mut Option<String>,
pub(crate) test_id: &'a mut Option<String>,
pub(crate) extra: &'a mut fret_core::SemanticsNodeExtra,
pub(crate) text_selection: &'a mut Option<(u32, u32)>,
pub(crate) text_composition: &'a mut Option<(u32, u32)>,
pub(crate) actions: &'a mut fret_core::SemanticsActions,
pub(crate) active_descendant: &'a mut Option<NodeId>,
pub(crate) pos_in_set: &'a mut Option<u32>,
pub(crate) set_size: &'a mut Option<u32>,
pub(crate) labelled_by: &'a mut Vec<NodeId>,
pub(crate) described_by: &'a mut Vec<NodeId>,
pub(crate) controls: &'a mut Vec<NodeId>,
pub(crate) inline_spans: &'a mut Vec<fret_core::SemanticsInlineSpan>,
}
impl<'a, H: UiHost> SemanticsCx<'a, H> {
pub fn resolve_declarative_element(&mut self, element: u64) -> Option<NodeId> {
if let Some(node) = self.element_id_map.and_then(|m| m.get(&element).copied()) {
return Some(node);
}
let window = self.window?;
crate::elements::live_node_for_element(
self.app,
window,
crate::elements::GlobalElementId(element),
)
}
pub fn set_role(&mut self, role: SemanticsRole) {
*self.role = role;
}
pub fn set_label(&mut self, label: impl Into<String>) {
*self.label = Some(label.into());
}
pub fn set_test_id(&mut self, id: impl Into<String>) {
*self.test_id = Some(id.into());
}
pub fn clear_test_id(&mut self) {
*self.test_id = None;
}
pub fn set_value(&mut self, value: impl Into<String>) {
*self.value = Some(value.into());
}
pub fn set_placeholder<T: Into<String>>(&mut self, placeholder: Option<T>) {
self.extra.placeholder = placeholder.map(Into::into);
}
pub fn set_url<T: Into<String>>(&mut self, url: Option<T>) {
self.extra.url = url.map(Into::into);
}
pub fn set_role_description<T: Into<String>>(&mut self, role_description: Option<T>) {
self.extra.role_description = role_description.map(Into::into);
}
pub fn clear_role_description(&mut self) {
self.extra.role_description = None;
}
pub fn set_level(&mut self, level: Option<u32>) {
self.extra.level = level;
}
pub fn set_orientation(&mut self, orientation: Option<SemanticsOrientation>) {
self.extra.orientation = orientation;
}
pub fn clear_orientation(&mut self) {
self.extra.orientation = None;
}
pub fn set_numeric_value(&mut self, value: Option<f64>) {
self.extra.numeric.value = value;
}
pub fn set_numeric_range(&mut self, min: Option<f64>, max: Option<f64>) {
self.extra.numeric.min = min;
self.extra.numeric.max = max;
}
pub fn set_numeric_step(&mut self, step: Option<f64>) {
self.extra.numeric.step = step;
}
pub fn set_numeric_jump(&mut self, jump: Option<f64>) {
self.extra.numeric.jump = jump;
}
pub fn set_scroll_x(&mut self, x: Option<f64>, min: Option<f64>, max: Option<f64>) {
self.extra.scroll.x = x;
self.extra.scroll.x_min = min;
self.extra.scroll.x_max = max;
}
pub fn set_scroll_y(&mut self, y: Option<f64>, min: Option<f64>, max: Option<f64>) {
self.extra.scroll.y = y;
self.extra.scroll.y_min = min;
self.extra.scroll.y_max = max;
}
pub fn set_text_selection(&mut self, anchor: u32, focus: u32) {
*self.text_selection = Some((anchor, focus));
}
pub fn clear_text_selection(&mut self) {
*self.text_selection = None;
}
pub fn set_text_composition(&mut self, start: u32, end: u32) {
*self.text_composition = Some((start, end));
}
pub fn clear_text_composition(&mut self) {
*self.text_composition = None;
}
pub fn set_focusable(&mut self, focusable: bool) {
self.actions.focus = focusable;
}
pub fn set_invokable(&mut self, invokable: bool) {
self.actions.invoke = invokable;
}
pub fn set_value_editable(&mut self, editable: bool) {
match *self.role {
SemanticsRole::TextField => {
self.actions.set_value = editable;
}
SemanticsRole::Slider | SemanticsRole::SpinButton | SemanticsRole::Splitter => {
self.actions.increment = editable;
self.actions.decrement = editable;
}
_ => {
self.actions.set_value = editable;
}
}
}
pub fn set_increment_supported(&mut self, supported: bool) {
self.actions.increment = supported;
}
pub fn set_decrement_supported(&mut self, supported: bool) {
self.actions.decrement = supported;
}
pub fn set_scroll_by_supported(&mut self, supported: bool) {
self.actions.scroll_by = supported;
}
pub fn set_text_selection_supported(&mut self, supported: bool) {
self.actions.set_text_selection = supported;
}
pub fn set_disabled(&mut self, disabled: bool) {
self.flags.disabled = disabled;
}
pub fn set_read_only(&mut self, read_only: bool) {
self.flags.read_only = read_only;
}
pub fn set_hidden(&mut self, hidden: bool) {
self.flags.hidden = hidden;
}
pub fn set_visited(&mut self, visited: bool) {
self.flags.visited = visited;
}
pub fn set_multiselectable(&mut self, multiselectable: bool) {
self.flags.multiselectable = multiselectable;
}
pub fn set_selected(&mut self, selected: bool) {
self.flags.selected = selected;
}
pub fn set_expanded(&mut self, expanded: bool) {
self.flags.expanded = expanded;
}
pub fn set_checked(&mut self, checked: Option<bool>) {
self.flags.checked = checked;
}
pub fn set_checked_state(&mut self, checked: Option<SemanticsCheckedState>) {
self.flags.checked_state = checked;
match checked {
Some(SemanticsCheckedState::True) => self.flags.checked = Some(true),
Some(SemanticsCheckedState::False) => self.flags.checked = Some(false),
Some(SemanticsCheckedState::Mixed) => self.flags.checked = None,
None => {}
_ => {}
}
}
pub fn clear_checked_state(&mut self) {
self.flags.checked_state = None;
}
pub fn set_pressed_state(&mut self, pressed: Option<SemanticsPressedState>) {
self.flags.pressed_state = pressed;
}
pub fn clear_pressed_state(&mut self) {
self.flags.pressed_state = None;
}
pub fn set_required(&mut self, required: bool) {
self.flags.required = required;
}
pub fn set_invalid(&mut self, invalid: Option<SemanticsInvalid>) {
self.flags.invalid = invalid;
}
pub fn clear_invalid(&mut self) {
self.flags.invalid = None;
}
pub fn set_busy(&mut self, busy: bool) {
self.flags.busy = busy;
}
pub fn set_live(&mut self, live: Option<SemanticsLive>) {
self.flags.live = live;
}
pub fn clear_live(&mut self) {
self.flags.live = None;
}
pub fn set_live_atomic(&mut self, live_atomic: bool) {
self.flags.live_atomic = live_atomic;
}
pub fn set_active_descendant(&mut self, node: Option<NodeId>) {
*self.active_descendant = node;
}
pub fn set_pos_in_set(&mut self, pos_in_set: Option<u32>) {
*self.pos_in_set = pos_in_set;
}
pub fn set_set_size(&mut self, set_size: Option<u32>) {
*self.set_size = set_size;
}
pub fn set_collection_position(&mut self, pos_in_set: Option<u32>, set_size: Option<u32>) {
*self.pos_in_set = pos_in_set;
*self.set_size = set_size;
}
pub fn push_labelled_by(&mut self, node: NodeId) {
if self.labelled_by.contains(&node) {
return;
}
self.labelled_by.push(node);
}
pub fn clear_labelled_by(&mut self) {
self.labelled_by.clear();
}
pub fn push_described_by(&mut self, node: NodeId) {
if self.described_by.contains(&node) {
return;
}
self.described_by.push(node);
}
pub fn clear_described_by(&mut self) {
self.described_by.clear();
}
pub fn push_controlled(&mut self, node: NodeId) {
if self.controls.contains(&node) {
return;
}
self.controls.push(node);
}
pub fn push_inline_span(&mut self, span: fret_core::SemanticsInlineSpan) {
self.inline_spans.push(span);
}
pub fn push_inline_link_span(&mut self, start_utf8: u32, end_utf8: u32, tag: Option<String>) {
self.push_inline_span(fret_core::SemanticsInlineSpan {
range_utf8: (start_utf8, end_utf8),
role: SemanticsRole::Link,
tag,
});
}
pub fn clear_controls(&mut self) {
self.controls.clear();
}
}
pub trait Widget<H: UiHost> {
fn event_capture(&mut self, _cx: &mut EventCx<'_, H>, _event: &Event) {}
fn event_observer(&mut self, _cx: &mut ObserverCx<'_, H>, _event: &Event) {}
fn debug_type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn event(&mut self, _cx: &mut EventCx<'_, H>, _event: &Event) {}
fn command(&mut self, _cx: &mut CommandCx<'_, H>, _command: &CommandId) -> bool {
false
}
fn command_availability(
&self,
_cx: &mut CommandAvailabilityCx<'_, H>,
_command: &CommandId,
) -> CommandAvailability {
CommandAvailability::NotHandled
}
fn cleanup_resources(&mut self, _services: &mut dyn UiServices) {}
fn render_transform(&self, _bounds: Rect) -> Option<Transform2D> {
None
}
fn children_render_transform(&self, _bounds: Rect) -> Option<Transform2D> {
None
}
fn cursor_icon_at(
&self,
_bounds: Rect,
_position: Point,
_input_ctx: &fret_runtime::InputContext,
) -> Option<fret_core::CursorIcon> {
None
}
fn clips_hit_test(&self, _bounds: Rect) -> bool {
true
}
fn clip_hit_test_corner_radii(&self, _bounds: Rect) -> Option<Corners> {
None
}
fn hit_test(&self, _bounds: Rect, _position: Point) -> bool {
true
}
fn hit_test_children(&self, _bounds: Rect, _position: Point) -> bool {
true
}
fn semantics_present(&self) -> bool {
true
}
fn semantics_children(&self) -> bool {
true
}
fn sync_interactivity_gate(&mut self, _present: bool, _interactive: bool) {}
fn sync_hit_test_gate(&mut self, _hit_test: bool) {}
fn sync_focus_traversal_gate(&mut self, _traverse: bool) {}
fn focus_traversal_children(&self) -> bool {
true
}
fn is_focusable(&self) -> bool {
false
}
fn is_text_input(&self) -> bool {
false
}
fn platform_text_input_snapshot(&self) -> Option<fret_runtime::WindowTextInputSnapshot> {
None
}
fn platform_text_input_selected_range_utf16(&self) -> Option<fret_runtime::Utf16Range> {
None
}
fn platform_text_input_marked_range_utf16(&self) -> Option<fret_runtime::Utf16Range> {
None
}
fn platform_text_input_text_for_range_utf16(
&self,
_range: fret_runtime::Utf16Range,
) -> Option<String> {
None
}
fn platform_text_input_bounds_for_range_utf16(
&mut self,
_cx: &mut PlatformTextInputCx<'_, H>,
_range: fret_runtime::Utf16Range,
) -> Option<Rect> {
None
}
fn platform_text_input_character_index_for_point_utf16(
&mut self,
_cx: &mut PlatformTextInputCx<'_, H>,
_point: Point,
) -> Option<u32> {
None
}
fn platform_text_input_replace_text_in_range_utf16(
&mut self,
_cx: &mut PlatformTextInputCx<'_, H>,
_range: fret_runtime::Utf16Range,
_text: &str,
) -> bool {
false
}
fn platform_text_input_replace_and_mark_text_in_range_utf16(
&mut self,
_cx: &mut PlatformTextInputCx<'_, H>,
_range: fret_runtime::Utf16Range,
_text: &str,
_marked: Option<fret_runtime::Utf16Range>,
_selected: Option<fret_runtime::Utf16Range>,
) -> bool {
false
}
fn can_scroll_by(&self) -> bool {
false
}
fn scroll_by(&mut self, _cx: &mut ScrollByCx<'_, H>, _delta: Point) -> ScrollByResult {
ScrollByResult::NotHandled
}
fn can_scroll_descendant_into_view(&self) -> bool {
false
}
fn scroll_descendant_into_view(
&mut self,
_cx: &mut ScrollIntoViewCx<'_, H>,
_descendant_bounds: Rect,
) -> ScrollIntoViewResult {
ScrollIntoViewResult::NotHandled
}
fn measure(&mut self, _cx: &mut MeasureCx<'_, H>) -> Size {
Size::default()
}
fn layout(&mut self, _cx: &mut LayoutCx<'_, H>) -> Size {
Size::default()
}
fn prepaint(&mut self, _cx: &mut PrepaintCx<'_, H>) {}
fn paint(&mut self, cx: &mut PaintCx<'_, H>) {
cx.paint_children();
}
fn semantics(&mut self, _cx: &mut SemanticsCx<'_, H>) {}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ScrollByResult {
#[default]
NotHandled,
Handled {
did_scroll: bool,
},
}
pub struct ScrollByCx<'a, H: UiHost> {
pub app: &'a mut H,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub bounds: Rect,
}
#[derive(Debug, Default, Clone, Copy)]
pub enum ScrollIntoViewResult {
#[default]
NotHandled,
Handled {
did_scroll: bool,
propagated_bounds: Option<Rect>,
},
}
pub struct ScrollIntoViewCx<'a, H: UiHost> {
pub app: &'a mut H,
pub node: NodeId,
pub window: Option<AppWindowId>,
pub bounds: Rect,
}
pub struct PlatformTextInputCx<'a, H: UiHost> {
pub app: &'a mut H,
pub services: &'a mut dyn UiServices,
pub window: Option<AppWindowId>,
pub node: NodeId,
pub bounds: Rect,
pub scale_factor: f32,
}
impl<'a, H: UiHost> PlatformTextInputCx<'a, H> {
pub fn theme(&self) -> &Theme {
Theme::global(&*self.app)
}
}