Skip to main content

floem/paint/
mod.rs

1//! Paint context and state for rendering views.
2//!
3//! This module contains the types used during the paint phase:
4//! - [`PaintCx`] - Context for painting views
5//! - [`PaintState`] - State for the renderer (pending or initialized)
6//! - [`Renderer`] - Backend renderer abstraction
7
8pub mod border_path_iter;
9pub mod renderer;
10
11pub use border_path_iter::{BorderPath, BorderPathEvent};
12pub use renderer::Renderer;
13
14use floem_renderer::Renderer as FloemRenderer;
15use floem_renderer::gpu_resources::{GpuResourceError, GpuResources};
16use peniko::kurbo::{Affine, RoundedRect, Shape, Size};
17use std::ops::{Deref, DerefMut};
18use std::sync::Arc;
19use understory_box_tree::NodeFlags;
20use winit::window::Window;
21
22#[cfg(feature = "crossbeam")]
23use crossbeam::channel::Receiver;
24#[cfg(not(feature = "crossbeam"))]
25use std::sync::mpsc::Receiver;
26
27use crate::ElementId;
28use crate::style::FontSizeCx;
29use crate::view::ViewId;
30use crate::view::stacking::{StackingContextItem, collect_stacking_context_items_into};
31use crate::view::{paint_bg, paint_border, paint_outline};
32use crate::window::state::WindowState;
33
34std::thread_local! {
35    /// Holds the ID of a View being painted very briefly if it is being rendered as
36    /// a moving drag image.  Since that is a relatively unusual thing to need, it
37    /// makes more sense to use a thread local for it and avoid cluttering the fields
38    /// and memory footprint of PaintCx or PaintState or ViewId with a field for it.
39    /// This is ephemerally set before paint calls that are painting the view in a
40    /// location other than its natural one for purposes of drag and drop.
41    pub(crate) static CURRENT_DRAG_PAINTING_ID : std::cell::Cell<Option<ElementId>> = const { std::cell::Cell::new(None) };
42
43    /// Paint order tracker for testing purposes.
44    /// When enabled, records the ViewIds in the order they are painted.
45    /// This is used by HeadlessHarness to verify paint order in tests.
46    static PAINT_ORDER_TRACKER: std::cell::RefCell<PaintOrderTracker> = const { std::cell::RefCell::new(PaintOrderTracker::new()) };
47}
48
49/// Tracker for paint order, used in testing to verify views are painted in the correct order.
50#[derive(Default)]
51pub struct PaintOrderTracker {
52    enabled: bool,
53    order: Vec<ViewId>,
54}
55
56impl PaintOrderTracker {
57    const fn new() -> Self {
58        Self {
59            enabled: false,
60            order: Vec::new(),
61        }
62    }
63
64    fn record(&mut self, id: ViewId) {
65        if self.enabled {
66            self.order.push(id);
67        }
68    }
69}
70
71/// Enable paint order tracking. When enabled, all painted ViewIds are recorded in order.
72pub fn enable_paint_order_tracking() {
73    PAINT_ORDER_TRACKER.with(|tracker| {
74        let mut t = tracker.borrow_mut();
75        t.enabled = true;
76        t.order.clear();
77    });
78}
79
80/// Disable paint order tracking.
81pub fn disable_paint_order_tracking() {
82    PAINT_ORDER_TRACKER.with(|tracker| {
83        tracker.borrow_mut().enabled = false;
84    });
85}
86
87/// Clear the recorded paint order without disabling tracking.
88pub fn clear_paint_order() {
89    PAINT_ORDER_TRACKER.with(|tracker| {
90        tracker.borrow_mut().order.clear();
91    });
92}
93
94/// Get a copy of the recorded paint order.
95pub fn get_paint_order() -> Vec<ViewId> {
96    PAINT_ORDER_TRACKER.with(|tracker| tracker.borrow().order.clone())
97}
98
99/// Check if paint order tracking is enabled.
100pub fn is_paint_order_tracking_enabled() -> bool {
101    PAINT_ORDER_TRACKER.with(|tracker| tracker.borrow().enabled)
102}
103
104/// Record a view being painted (internal use).
105#[inline]
106fn record_paint(id: ViewId) {
107    PAINT_ORDER_TRACKER.with(|tracker| {
108        tracker.borrow_mut().record(id);
109    });
110}
111
112/// Global paint context - holds shared state for entire paint pass
113/// Similar to GlobalEventCx in event dispatch
114pub struct GlobalPaintCx<'a> {
115    pub window_state: &'a mut WindowState,
116    pub(crate) paint_state: &'a mut PaintState,
117    pub gpu_resources: Option<GpuResources>,
118    pub window: Arc<dyn Window>,
119    /// Whether to record paint order for testing. Cached from thread-local at creation.
120    pub(crate) record_paint_order: bool,
121}
122
123/// Per-target paint context - created for each visual node
124/// Similar to EventCx in event dispatch
125pub struct PaintCx<'a> {
126    /// Reference to global paint state
127    pub window_state: &'a mut WindowState,
128    paint_state: &'a mut PaintState,
129    /// The target visual node being painted (CRITICAL for views with multiple visuals)
130    pub target_id: ElementId,
131    /// World transform for this visual node (from box tree)
132    pub world_transform: Affine,
133    /// Local layout bounds for this visual node (from box tree)
134    pub layout_rect_local: peniko::kurbo::Rect,
135    /// Optional clip for this visual node (from box tree)
136    pub clip: Option<RoundedRect>,
137    pub font_size_cx: FontSizeCx,
138}
139
140pub(crate) enum PaintOrPost {
141    Paint(ElementId),
142    Post(ElementId),
143}
144
145/// Collect VisualIds in paint order (depth-first, z-index sorted) without recursion.
146pub(crate) fn collect_visual_order(
147    root_element_id: ElementId,
148    box_tree: &mut crate::BoxTree,
149    paint_order: &mut Vec<PaintOrPost>,
150    is_drag_preview: bool,
151    skip_element_id: Option<ElementId>,
152) {
153    enum TraversalStep {
154        Visit(ElementId),
155        Post(ElementId),
156    }
157
158    // Local closure instead of nested fn
159    let should_paint = |element_id: ElementId, box_tree: &mut crate::BoxTree| {
160        if is_drag_preview {
161            return true;
162        }
163
164        box_tree
165            .world_bounds(element_id.0)
166            .is_none_or(|bounds| bounds.area() != 0.0)
167    };
168
169    let mut stack = Vec::new();
170    let mut stacking_scratch: Vec<StackingContextItem> = Vec::new();
171    stack.push(TraversalStep::Visit(root_element_id));
172
173    while let Some(step) = stack.pop() {
174        match step {
175            TraversalStep::Visit(element_id) => {
176                // Skip specific element and subtree when not drag preview
177                if !is_drag_preview && Some(element_id) == skip_element_id {
178                    continue;
179                }
180
181                // If hidden skip this element and the subtree
182                if box_tree
183                    .flags(element_id.0)
184                    .is_none_or(|f| !f.contains(NodeFlags::VISIBLE))
185                {
186                    continue;
187                }
188
189                let paints_this_node = should_paint(element_id, box_tree);
190                if paints_this_node {
191                    paint_order.push(PaintOrPost::Paint(element_id));
192                    // Keep Paint/Post paired for the same node. If a node is culled
193                    // (e.g. zero world bounds), emitting Post without Paint can pop clip
194                    // state that belongs to an ancestor and corrupt sibling paint order.
195                    stack.push(TraversalStep::Post(element_id));
196                }
197
198                // Push children in reverse so they are visited in forward order.
199                collect_stacking_context_items_into(element_id, box_tree, &mut stacking_scratch);
200                for item in stacking_scratch.iter().rev() {
201                    stack.push(TraversalStep::Visit(item.element_id));
202                }
203            }
204            TraversalStep::Post(element_id) => paint_order.push(PaintOrPost::Post(element_id)),
205        }
206    }
207}
208
209impl GlobalPaintCx<'_> {
210    /// Build explicit paint order for entire view tree.
211    ///
212    /// Returns a flat list of VisualIds in paint order (back-to-front, respecting z-index).
213    /// Filters out hidden views and views with zero-area bounds.
214    ///
215    /// # Arguments
216    /// * `root` - The root VisualId to start traversal from
217    /// * `box_tree` - The box tree for querying spatial information
218    ///
219    /// # Returns
220    /// Vector of VisualIds in paint order (back-to-front)
221    fn build_paint_order(
222        &self,
223        root: ElementId,
224        box_tree: &mut crate::BoxTree,
225    ) -> Vec<PaintOrPost> {
226        let mut paint_order = Vec::new();
227
228        let dragging_element_id = self
229            .window_state
230            .drag_tracker
231            .active_drag
232            .as_ref()
233            .and_then(|ad| ad.dragging_preview.as_ref().map(|p| p.element_id));
234        // Collect main tree
235        collect_visual_order(root, box_tree, &mut paint_order, false, dragging_element_id);
236
237        // Paint drag overlay separately (always on top)
238        if let Some(preview) = self
239            .window_state
240            .drag_tracker
241            .active_drag
242            .as_ref()
243            .and_then(|ad| ad.dragging_preview.as_ref().map(|p| p.element_id))
244        {
245            crate::paint::collect_visual_order(preview, box_tree, &mut paint_order, true, None);
246        }
247
248        paint_order
249    }
250    /// Paint entire tree using explicit traversal
251    pub(crate) fn paint_with_traversal(&mut self, root_id: ViewId) {
252        let root_element_id = root_id.get_element_id();
253        let mut box_tree = self.window_state.box_tree.borrow_mut();
254        let paint_order = self.build_paint_order(root_element_id, &mut box_tree);
255        drop(box_tree);
256
257        for id_or_pop in paint_order {
258            match id_or_pop {
259                PaintOrPost::Paint(element_id) => {
260                    // Record for testing
261                    if self.record_paint_order {
262                        record_paint(element_id.owning_id());
263                    }
264
265                    // Create per-target PaintCx and paint this visual node
266                    self.paint_visual_node(element_id, false);
267                }
268                PaintOrPost::Post(element_id) => {
269                    self.paint_visual_node(element_id, true);
270                }
271            }
272        }
273    }
274
275    /// Paint a single visual node with its absolute transform
276    pub(crate) fn paint_visual_node(&mut self, element_id: ElementId, is_post: bool) {
277        // Get state from box tree for this visual node
278        let box_tree = self.window_state.box_tree.borrow_mut();
279        let world_transform = box_tree.world_transform(element_id.0).unwrap_or_default();
280        let layout_rect_local = box_tree.local_bounds(element_id.0).unwrap_or_default();
281        let clip = box_tree.clipped_local_clip(element_id.0);
282        drop(box_tree);
283
284        // Set absolute transform on renderer
285        let device_transform = world_transform.then_scale(self.window_state.effective_scale());
286        self.paint_state
287            .renderer_mut()
288            .set_transform(device_transform);
289
290        let layout_rect = layout_rect_local;
291        let view_id = element_id.owning_id();
292        let view = view_id.view();
293        let view_state = view_id.state();
294
295        // Create per-target PaintCx
296        let mut cx = PaintCx {
297            window_state: self.window_state,
298            paint_state: self.paint_state,
299            target_id: element_id,
300            world_transform,
301            layout_rect_local,
302            clip,
303            font_size_cx: view_state.borrow().layout_props.font_size_cx(),
304        };
305
306        if !is_post {
307            if element_id.is_view() {
308                let state = view_state.borrow();
309                paint_bg(&mut cx, &state.view_style_props, layout_rect);
310                paint_border(
311                    &mut cx,
312                    &state.layout_props,
313                    &state.view_style_props,
314                    layout_rect,
315                );
316                drop(state);
317                // Apply overflow clip (stays active through children)
318                if let Some(clip_shape) = clip {
319                    cx.clip(&clip_shape);
320                }
321            }
322            view.borrow_mut().paint(&mut cx);
323        } else {
324            if element_id.is_view() && clip.is_some() {
325                cx.clear_clip();
326            }
327            view.borrow_mut().post_paint(&mut cx);
328            if element_id.is_view() {
329                let state = view_state.borrow();
330                paint_outline(&mut cx, &state.view_style_props, layout_rect);
331            }
332        }
333    }
334}
335
336impl PaintCx<'_> {
337    /// Allows a `View` to determine if it is being called in order to
338    /// paint a *draggable* image of itself during a drag (likely
339    /// `draggable()` was called on the `View` or `ViewId`) as opposed
340    /// to a normal paint in order to alter the way it renders itself.
341    pub fn is_drag_paint(&self, id: impl Into<ElementId>) -> bool {
342        let id = id.into();
343        // This could be an associated function, but it is likely
344        // a Good Thing to restrict access to cases when the caller actually
345        // has a PaintCx, and that doesn't make it a breaking change to
346        // use instance methods in the future.
347        if let Some(dragging) = CURRENT_DRAG_PAINTING_ID.get() {
348            return dragging == id;
349        }
350        false
351    }
352
353    /// Clip the drawing area (delegates to helper methods)
354    pub fn clip(&mut self, shape: &impl Shape) {
355        if self.paint_state.renderer().uses_layer_clip() {
356            use peniko::Mix;
357            self.push_layer(Mix::Normal, 1.0, Affine::IDENTITY, shape);
358        } else {
359            self.paint_state.renderer_mut().clip(shape);
360        }
361    }
362
363    /// Clear clip
364    pub fn clear_clip(&mut self) {
365        if self.paint_state.renderer().uses_layer_clip() {
366            self.pop_layer();
367        } else {
368            self.paint_state.renderer_mut().clear_clip();
369        }
370    }
371
372    // Note: get_transform/set_transform removed as Renderer doesn't expose transform()
373    // Views that previously used save/restore should use clip/clear_clip instead
374}
375
376// TODO: should this be private?
377pub enum PaintState {
378    /// The renderer is not yet initialized. This state is used to wait for the GPU resources to be acquired.
379    PendingGpuResources {
380        window: Arc<dyn Window>,
381        rx: Receiver<Result<(GpuResources, wgpu::Surface<'static>), GpuResourceError>>,
382        font_embolden: f32,
383        /// This field holds an instance of `Renderer::Uninitialized` until the GPU resources are acquired,
384        /// which will be returned in `PaintState::renderer` and `PaintState::renderer_mut`.
385        /// All calls to renderer methods will be no-ops until the renderer is initialized.
386        ///
387        /// Previously, `PaintState::renderer` and `PaintState::renderer_mut` would panic if called when the renderer was uninitialized.
388        /// However, this turned out to be hard to handle properly and led to panics, especially since the rest of the application code can't control when the renderer is initialized.
389        renderer: Renderer,
390    },
391    /// The renderer is initialized and ready to paint.
392    Initialized { renderer: Renderer },
393}
394
395impl PaintState {
396    pub fn new_pending(
397        window: Arc<dyn Window>,
398        rx: Receiver<Result<(GpuResources, wgpu::Surface<'static>), GpuResourceError>>,
399        size: Size,
400        font_embolden: f32,
401    ) -> Self {
402        Self::PendingGpuResources {
403            window,
404            rx,
405            font_embolden,
406            renderer: Renderer::Uninitialized { size },
407        }
408    }
409
410    pub fn new(
411        window: Arc<dyn Window>,
412        surface: wgpu::Surface<'static>,
413        gpu_resources: GpuResources,
414        scale: f64,
415        size: Size,
416        font_embolden: f32,
417    ) -> Self {
418        let renderer = Renderer::new(
419            window.clone(),
420            gpu_resources,
421            surface,
422            scale,
423            size,
424            font_embolden,
425        );
426        Self::Initialized { renderer }
427    }
428
429    #[cfg(feature = "skia")]
430    pub fn new_skia(window: Arc<dyn Window>, scale: f64, size: Size, font_embolden: f32) -> Self {
431        let renderer = Renderer::new_skia(window.clone(), scale, size, font_embolden);
432        Self::Initialized { renderer }
433    }
434
435    pub(crate) fn renderer(&self) -> &Renderer {
436        match self {
437            PaintState::PendingGpuResources { renderer, .. } => renderer,
438            PaintState::Initialized { renderer } => renderer,
439        }
440    }
441
442    pub(crate) fn renderer_mut(&mut self) -> &mut Renderer {
443        match self {
444            PaintState::PendingGpuResources { renderer, .. } => renderer,
445            PaintState::Initialized { renderer } => renderer,
446        }
447    }
448
449    pub(crate) fn resize(&mut self, scale: f64, size: Size) {
450        self.renderer_mut().resize(scale, size);
451    }
452
453    pub(crate) fn set_scale(&mut self, scale: f64) {
454        self.renderer_mut().set_scale(scale);
455    }
456}
457
458impl Deref for PaintCx<'_> {
459    type Target = Renderer;
460
461    fn deref(&self) -> &Self::Target {
462        self.paint_state.renderer()
463    }
464}
465
466impl DerefMut for PaintCx<'_> {
467    fn deref_mut(&mut self) -> &mut Self::Target {
468        self.paint_state.renderer_mut()
469    }
470}