floem/context.rs
1use peniko::kurbo::{Affine, Point, Rect};
2use smallvec::SmallVec;
3use std::{cell::RefCell, rc::Rc};
4
5use crate::{
6 ElementId, custom_event,
7 event::{EventPropagation, Phase},
8 platform::menu::Menu,
9 style::recalc::StyleReason,
10 view::ViewId,
11};
12
13pub type EventCallback = dyn FnMut(&mut EventCx) -> EventPropagation;
14pub type ResizeCallback = dyn Fn(Rect);
15pub type MenuCallback = dyn Fn() -> Menu;
16
17bitflags::bitflags! {
18 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19 pub struct Phases: u8 {
20 const CAPTURE = 0b1;
21 const TARGET = 0b10;
22 const BUBBLE = 0b100;
23 const BROADCAST = 0b1000;
24 }
25}
26impl Phases {
27 /// Target and Bubble phases
28 pub const TARGET_AND_BUBBLE: Phases =
29 Phases::from_bits_truncate(Phases::TARGET.bits() | Phases::BUBBLE.bits());
30
31 /// Capture and Target phases
32 pub const CAPTURE_AND_TARGET: Phases =
33 Phases::from_bits_truncate(Phases::CAPTURE.bits() | Phases::TARGET.bits());
34
35 /// Capture and Bubble phases
36 pub const CAPTURE_AND_BUBBLE: Phases =
37 Phases::from_bits_truncate(Phases::CAPTURE.bits() | Phases::BUBBLE.bits());
38
39 /// Broadcast phase only (for global keyboard shortcuts, etc.)
40 pub const BROADCAST_ONLY: Phases = Phases::BROADCAST;
41 /// All standard phases (capture/target/bubble) - common for pointer events
42 pub const STANDARD: Phases = Phases::from_bits_truncate(
43 Phases::CAPTURE.bits() | Phases::TARGET.bits() | Phases::BUBBLE.bits(),
44 );
45
46 /// Check if this phase set includes the given phase.
47 pub fn matches(&self, phase: &Phase) -> bool {
48 match phase {
49 Phase::Capture => self.contains(Phases::CAPTURE),
50 Phase::Target => self.contains(Phases::TARGET),
51 Phase::Bubble => self.contains(Phases::BUBBLE),
52 Phase::Broadcast => self.contains(Phases::BROADCAST), // <- Add this
53 }
54 }
55}
56
57/// Configuration for event callbacks that determines when they should be invoked during event propagation.
58///
59/// Event listeners in Floem are called during different phases of the event propagation pipeline,
60/// similar to the DOM event model. This config allows you to specify which phases your callback
61/// should respond to.
62///
63/// # Examples
64///
65/// Listen during the target and bubble phases (default):
66/// ```ignore
67/// view
68/// .on_event_with_config(
69/// EventListener::Click,
70/// EventCallbackConfig::default(),
71/// |cx, _| EventPropagation::Continue,
72/// )
73/// ```
74///
75/// Listen only during the capture phase (useful for intercepting before child handlers):
76/// ```ignore
77/// view
78/// .on_event_with_config(
79/// EventListener::KeyDown,
80/// EventCallbackConfig {
81/// phases: Phases::CAPTURE,
82/// },
83/// |cx, event| {
84/// // Handle key down during capture phase
85/// EventPropagation::Continue
86/// },
87/// )
88/// ```
89///
90/// Listen during all phases:
91/// ```ignore
92/// view
93/// .on_event_with_config(
94/// EventListener::Focus,
95/// EventCallbackConfig {
96/// phases: Phases::CAPTURE | Phases::TARGET | Phases::BUBBLE,
97/// },
98/// |cx, _| EventPropagation::Continue,
99/// )
100/// ```
101#[derive(Clone, Copy, PartialEq)]
102pub struct EventCallbackConfig {
103 /// Determines which event propagation phases should trigger this callback.
104 pub phases: Phases,
105}
106impl Default for EventCallbackConfig {
107 fn default() -> Self {
108 Self {
109 phases: Phases::TARGET | Phases::BUBBLE,
110 }
111 }
112}
113
114/// Vector of event listeners, optimized for the common case of 0-1 listeners per event type.
115/// Uses SmallVec to avoid heap allocation when there's only one listener.
116/// Inspired by Chromium's HeapVector<..., 1> pattern for event listener storage.
117pub type EventListenerVec = SmallVec<[(Rc<RefCell<EventCallback>>, EventCallbackConfig); 1]>;
118
119/// Event fired when a view's layout changes
120///
121/// This is fired when the view's size or position in the layout changes.
122/// It does not fire for visual-only changes from transforms (translation, scale, rotation).
123///
124/// # Important: Layout vs Visual Coordinates
125///
126/// **WARNING**: The window coordinates provided by this event (`new_window_origin`, `box_window()`,
127/// `content_box_window()`) represent the position in the **box layout tree**, NOT the final visual
128/// position after transforms are applied. If transforms like translation, scale, or rotation are
129/// applied to this view or its ancestors, the actual rendered position will differ from these coordinates.
130///
131/// **Use `VisualChanged` instead if you need the actual rendered position in the window after all
132/// transforms have been applied.**
133///
134/// ## When to use LayoutChanged vs VisualChanged
135///
136/// - Use `LayoutChanged`: When you need layout box sizes or relative positioning within the layout tree
137/// - Use `VisualChanged`: When you need to know where the view actually appears on screen, or to
138/// position elements relative to the view's visual appearance
139///
140/// ## Coordinate Spaces in Floem
141///
142/// **Note**: All event handling and painting in Floem happen in the view's **local coordinate space**.
143/// Floem automatically handles transformations to and from local coordinates. You typically don't need
144/// window coordinates unless you're positioning elements relative to other views' visual positions or
145/// interacting with platform APIs that expect window coordinates.
146///
147/// Use `box_local()` and `content_box_local()` to get coordinates in the view's local space.
148#[derive(Debug, Clone, Copy, PartialEq)]
149pub struct LayoutChanged {
150 /// The new layout box relative to the parent layout
151 pub new_box: Rect,
152 /// The new content box relative to the parent layout
153 pub new_content_box: Rect,
154 /// The position of the layout box's origin in window coordinates (box layout position, NOT visual position)
155 ///
156 /// **WARNING**: This does point is not the final vistual position of the view because it intentionally does not include transforms.
157 /// Use `VisualChanged` for actual rendered position.
158 pub new_window_origin: Point,
159}
160custom_event!(LayoutChanged, allow_disabled = |_event| true);
161
162impl LayoutChanged {
163 /// Get the layout box in the view's local coordinate space (relative to its own origin which is `Point::ZERO`)
164 pub fn box_local(&self) -> Rect {
165 self.new_box.with_origin(Point::ZERO)
166 }
167
168 /// Get the content box in the view's local coordinate space (relative to the box origin)
169 pub fn content_box_local(&self) -> Rect {
170 self.new_content_box
171 .with_origin(Point::ZERO - self.new_box.origin().to_vec2())
172 }
173
174 /// Get the layout box in window coordinates (box layout position, NOT visual position)
175 ///
176 /// **WARNING**: This does not include transforms. The actual rendered position may differ
177 /// if transforms are applied. Use `VisualChanged` for the actual visual position.
178 pub fn box_window(&self) -> Rect {
179 self.new_box.with_origin(self.new_window_origin)
180 }
181
182 /// Get the content box in window coordinates (box layout position, NOT visual position)
183 ///
184 /// **WARNING**: This does not include transforms. The actual rendered position may differ
185 /// if transforms are applied. Use `VisualChanged` for the actual visual position.
186 pub fn content_box_window(&self) -> Rect {
187 let content_offset = self.new_content_box.origin() - self.new_box.origin();
188 self.new_content_box
189 .with_origin(self.new_window_origin + content_offset)
190 }
191}
192
193/// Event fired when a view's visual representation in the window changes.
194///
195/// This is fired when the view's final rendered position or transform changes,
196/// including changes from CSS-like transforms (translation, scale, rotation).
197/// This represents the view's actual visual appearance in the window after all
198/// transforms have been applied.
199///
200/// # Important: Visual vs Layout Coordinates
201///
202/// **Use this event when you need to know where the view actually appears on screen.**
203/// The coordinates and transform provided here reflect the final rendered state after
204/// all transforms (translation, scale, rotation) from this view and all ancestors have
205/// been applied.
206///
207/// ## When to use VisualChanged vs LayoutChanged
208///
209/// - Use `VisualChanged`: When you need the actual rendered position on screen, or to position
210/// elements relative to where a view visually appears (e.g., tooltips, popovers, overlays)
211/// - Use `LayoutChanged`: When you only care about layout box sizes or relative positioning
212/// within the layout tree, ignoring transforms
213///
214/// ## Coordinate Spaces in Floem
215///
216/// **Note**: All event handling and painting in Floem happen in the view's **local coordinate space**.
217/// Floem automatically handles transformations to and from local coordinates. You typically only need
218/// visual window coordinates when positioning separate elements (like overlays or platform windows)
219/// relative to a view's visual appearance, or when interacting with platform APIs that expect
220/// window coordinates.
221///
222/// ## Use Cases for VisualChanged
223///
224/// - Positioning tooltips or popovers relative to a transformed view
225/// - Implementing drag-and-drop with visual feedback
226/// - Coordinating with platform overlays or native windows
227/// - Calculating whether two views visually overlap on screen
228#[derive(Debug, Clone, Copy, PartialEq)]
229pub struct VisualChanged {
230 /// The new axis-aligned bounding box in window coordinates after all transforms
231 ///
232 /// This is the smallest rectangle that contains the view after all transforms are applied.
233 /// For rotated views, this will be larger than the view's layout box.
234 pub new_visual_aabb: Rect,
235 /// The new world transform matrix combining all parent and local transforms
236 ///
237 /// This transform maps from the view's local coordinate space to window coordinates.
238 pub new_world_transform: Affine,
239}
240custom_event!(VisualChanged, allow_disabled = |_event| true);
241
242impl VisualChanged {
243 /// Get the visual origin of the view in window coordinates
244 ///
245 /// This returns the top-left corner of the visual bounding box after all transforms.
246 /// Note that for rotated or scaled views, this may not correspond directly to the
247 /// layout box origin transformed by `new_world_transform`.
248 pub fn visual_window_origin(&self) -> Point {
249 self.new_visual_aabb.origin()
250 }
251
252 /// Transform a point from the view's local coordinate space to window coordinates
253 ///
254 /// This applies the full world transform to convert a point in the view's local
255 /// coordinate space to its position in the window.
256 pub fn local_to_window(&self, local_point: Point) -> Point {
257 self.new_world_transform * local_point
258 }
259
260 /// Transform a point from window coordinates to the view's local coordinate space
261 ///
262 /// This applies the inverse world transform to convert a point in window coordinates
263 /// to the view's local coordinate space. Returns `None` if the transform is not invertible.
264 pub fn window_to_local(&self, window_point: Point) -> Point {
265 self.new_world_transform.inverse() * window_point
266 }
267}
268
269pub(crate) type CleanupListeners = Vec<Rc<dyn Fn()>>;
270
271pub(crate) enum FrameUpdate {
272 Style(ElementId, StyleReason),
273 Layout,
274 BoxTreeCommit,
275 Paint(ViewId),
276}
277
278// Re-export EventCx from event module for backward compatibility
279pub use crate::event::EventCx;
280
281// Re-export style context types from style module for backward compatibility
282pub use crate::style::{InteractionState, StyleCx};
283// Re-export paint context types from paint module for backward compatibility
284pub use crate::paint::{PaintCx, PaintState};
285// Re-export update context types from message module for backward compatibility
286pub use crate::message::UpdateCx;