Skip to main content

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;