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::{
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;