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