Skip to main content

vizia_core/context/
draw.rs

1use skia_safe::canvas::SaveLayerRec;
2use skia_safe::wrapper::PointerWrapper;
3use skia_safe::{
4    BlurStyle, ClipOp, MaskFilter, Matrix, Paint, PaintStyle, Path, PathBuilder, PathEffect, Point,
5    RRect, Rect, SamplingOptions, TileMode,
6};
7use std::any::{Any, TypeId};
8use std::f32::consts::SQRT_2;
9use vizia_style::LengthPercentageOrAuto;
10
11use hashbrown::HashMap;
12
13use crate::cache::CachedData;
14use crate::events::ViewHandler;
15use crate::prelude::*;
16use crate::resource::{ImageOrSvg, ResourceManager};
17use crate::text::{TextContext, resolved_text_direction};
18use vizia_input::MouseState;
19
20use super::ModelData;
21
22/// A context used when drawing a view.
23///
24/// The `DrawContext` is provided by the [`draw`](crate::view::View::draw) method in [`View`] and can be used to immutably access the
25/// computed style and layout properties of the current view.
26///
27/// # Example
28/// ```
29/// # use vizia_core::prelude::*;
30/// # use vizia_core::vg;
31/// # let cx = &mut Context::default();
32///
33/// pub struct CustomView {}
34///
35/// impl CustomView {
36///     pub fn new(cx: &mut Context) -> Handle<Self> {
37///         Self{}.build(cx, |_|{})
38///     }
39/// }
40///
41/// impl View for CustomView {
42///     fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
43///         // Get the computed bounds after layout of the current view
44///         let bounds = cx.bounds();
45///         // Draw to the canvas using the bounds of the current view
46///         let path = vg::Path::new();
47///         path.rect(bounds.x, bounds.y, bounds.w, bounds.h);
48///         let mut paint = vg::Paint::default();
49///         paint.set_color(Color::rgb(200, 100, 100));
50///         canvas.draw_path(&path, &paint);
51///     }
52/// }
53/// ```
54pub struct DrawContext<'a> {
55    pub(crate) current: Entity,
56    pub(crate) style: &'a Style,
57    pub(crate) cache: &'a mut CachedData,
58    pub(crate) tree: &'a Tree<Entity>,
59    pub(crate) models: &'a HashMap<Entity, HashMap<TypeId, Box<dyn ModelData>>>,
60    pub(crate) views: &'a mut HashMap<Entity, Box<dyn ViewHandler>>,
61    pub(crate) resource_manager: &'a ResourceManager,
62    pub(crate) text_context: &'a mut TextContext,
63    pub(crate) modifiers: &'a Modifiers,
64    pub(crate) mouse: &'a MouseState<Entity>,
65    pub(crate) windows: &'a mut HashMap<Entity, WindowState>,
66}
67
68macro_rules! get_units_property {
69    (
70        $(#[$meta:meta])*
71        $name:ident
72    ) => {
73        $(#[$meta])*
74        pub fn $name(&self) -> Units {
75            let result = self.style.$name.get(self.current);
76            if let Some(Units::Pixels(p)) = result {
77                Units::Pixels(self.logical_to_physical(*p))
78            } else {
79                result.copied().unwrap_or_default()
80            }
81        }
82    };
83}
84
85impl DrawContext<'_> {
86    pub fn with_current<T>(&mut self, entity: Entity, f: impl FnOnce(&mut DrawContext) -> T) -> T {
87        let current = self.current;
88        self.current = entity;
89        let t = f(self);
90        self.current = current;
91        t
92    }
93
94    /// Returns the bounds of the current view.
95    pub fn bounds(&self) -> BoundingBox {
96        self.cache.get_bounds(self.current)
97    }
98
99    /// Marks the current view as needing to be redrawn.
100    pub fn needs_redraw(&mut self) {
101        let parent_window = self.tree.get_parent_window(self.current).unwrap_or(Entity::root());
102        if let Some(window_state) = self.windows.get_mut(&parent_window) {
103            window_state.redraw_list.insert(self.current);
104        }
105    }
106
107    /// Returns the z-index of the current view.
108    pub fn z_index(&self) -> i32 {
109        self.style.z_index.get(self.current).copied().unwrap_or_default()
110    }
111
112    /// Returns the scale factor.
113    pub fn scale_factor(&self) -> f32 {
114        self.style.dpi_factor as f32
115    }
116
117    /// Returns a reference to the keyboard modifiers state.
118    pub fn modifiers(&self) -> &Modifiers {
119        self.modifiers
120    }
121
122    /// Returns a reference to the mouse state.
123    pub fn mouse(&self) -> &MouseState<Entity> {
124        self.mouse
125    }
126
127    /// Returns the clip path of the current view.
128    pub fn clip_path(&self) -> Option<skia_safe::Path> {
129        // A cached entry (including None) is authoritative for this entity.
130        if let Some(clip_path) = self.cache.clip_path.get(self.current) {
131            return clip_path.clone();
132        }
133
134        if self.style.ignore_clipping.get(self.current).copied().unwrap_or(false) {
135            return None;
136        }
137
138        // If there is no cached value yet, walk ancestors to find an inherited clip.
139        let mut current = self.current;
140        while let Some(parent) = self.tree.get_parent(current) {
141            // A cached parent entry (including None) is authoritative.
142            if let Some(clip_path) = self.cache.clip_path.get(parent) {
143                return clip_path.clone();
144            }
145
146            if self.style.ignore_clipping.get(parent).copied().unwrap_or(false) {
147                return None;
148            }
149            current = parent;
150        }
151
152        None
153    }
154
155    /// Returns the 2D transform of the current view.
156    pub fn transform(&self) -> Matrix {
157        self.cache.transform.get(self.current).copied().unwrap_or_default()
158    }
159
160    /// Returns the visibility of the current view.
161    pub fn visibility(&self) -> Option<Visibility> {
162        self.style.visibility.get(self.current).copied()
163    }
164
165    /// Returns the display of the current view.
166    pub fn display(&self) -> Display {
167        self.style.display.get(self.current).copied().unwrap_or(Display::Flex)
168    }
169
170    /// Returns the opacity of the current view.
171    pub fn opacity(&self) -> f32 {
172        self.style
173            .opacity
174            .get_resolved(self.current, &self.style.custom_opacity_props)
175            .unwrap_or(Opacity(1.0))
176            .0
177    }
178
179    /// Returns the lookup pattern to pick the default font.
180    pub fn default_font(&self) -> &[FamilyOwned] {
181        &self.style.default_font
182    }
183
184    /// Returns the font-size of the current view in physical pixels.
185    pub fn font_size(&self) -> f32 {
186        let fs = self
187            .style
188            .font_size
189            .get_resolved(self.current, &self.style.custom_font_size_props)
190            .and_then(|f| f.0.to_px())
191            .unwrap_or(16.0);
192        self.logical_to_physical(fs)
193    }
194
195    /// Returns the font-weight of the current view.
196    pub fn font_weight(&self) -> FontWeight {
197        self.style.font_weight.get(self.current).copied().unwrap_or_default()
198    }
199
200    /// Returns the font-width of the current view.
201    pub fn font_width(&self) -> FontWidth {
202        self.style.font_width.get(self.current).copied().unwrap_or_default()
203    }
204
205    /// Returns the font-slant of the current view.
206    pub fn font_slant(&self) -> FontSlant {
207        self.style.font_slant.get(self.current).copied().unwrap_or_default()
208    }
209
210    /// Returns the font variation settings of the current view.
211    pub fn font_variation_settings(&self) -> &[FontVariation] {
212        self.style.font_variation_settings.get(self.current).map(Vec::as_slice).unwrap_or_default()
213    }
214
215    /// Function to convert logical points to physical pixels.
216    pub fn logical_to_physical(&self, logical: f32) -> f32 {
217        self.style.logical_to_physical(logical)
218    }
219
220    /// Function to convert physical pixels to logical points.
221    pub fn physical_to_logical(&self, physical: f32) -> f32 {
222        self.style.physical_to_logical(physical)
223    }
224
225    /// Returns the top border width of the current view in physical pixels.
226    pub fn border_top_width(&self) -> f32 {
227        let bounds = self.bounds();
228        self.style
229            .border_top_width
230            .get_resolved(self.current, &self.style.custom_length_props)
231            .map(|l| l.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round())
232            .unwrap_or(0.0)
233    }
234
235    /// Returns the right border width of the current view in physical pixels.
236    pub fn border_right_width(&self) -> f32 {
237        let bounds = self.bounds();
238        self.style
239            .border_right_width
240            .get_resolved(self.current, &self.style.custom_length_props)
241            .map(|l| l.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round())
242            .unwrap_or(0.0)
243    }
244
245    /// Returns the bottom border width of the current view in physical pixels.
246    pub fn border_bottom_width(&self) -> f32 {
247        let bounds = self.bounds();
248        self.style
249            .border_bottom_width
250            .get_resolved(self.current, &self.style.custom_length_props)
251            .map(|l| l.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round())
252            .unwrap_or(0.0)
253    }
254
255    /// Returns the left border width of the current view in physical pixels.
256    pub fn border_left_width(&self) -> f32 {
257        let bounds = self.bounds();
258        self.style
259            .border_left_width
260            .get_resolved(self.current, &self.style.custom_length_props)
261            .map(|l| l.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round())
262            .unwrap_or(0.0)
263    }
264
265    /// Returns the top border color of the current view.
266    pub fn border_top_color(&self) -> Color {
267        self.style
268            .border_top_color
269            .get_resolved(self.current, &self.style.custom_color_props)
270            .map(|c| Color::rgba(c.r(), c.g(), c.b(), c.a()))
271            .unwrap_or(Color::rgba(0, 0, 0, 0))
272    }
273
274    /// Returns the right border color of the current view.
275    pub fn border_right_color(&self) -> Color {
276        self.style
277            .border_right_color
278            .get_resolved(self.current, &self.style.custom_color_props)
279            .map(|c| Color::rgba(c.r(), c.g(), c.b(), c.a()))
280            .unwrap_or(Color::rgba(0, 0, 0, 0))
281    }
282
283    /// Returns the bottom border color of the current view.
284    pub fn border_bottom_color(&self) -> Color {
285        self.style
286            .border_bottom_color
287            .get_resolved(self.current, &self.style.custom_color_props)
288            .map(|c| Color::rgba(c.r(), c.g(), c.b(), c.a()))
289            .unwrap_or(Color::rgba(0, 0, 0, 0))
290    }
291
292    /// Returns the left border color of the current view.
293    pub fn border_left_color(&self) -> Color {
294        self.style
295            .border_left_color
296            .get_resolved(self.current, &self.style.custom_color_props)
297            .map(|c| Color::rgba(c.r(), c.g(), c.b(), c.a()))
298            .unwrap_or(Color::rgba(0, 0, 0, 0))
299    }
300
301    /// Returns the top border style of the current view.
302    pub fn border_top_style(&self) -> BorderStyleKeyword {
303        self.style.border_top_style.get(self.current).copied().unwrap_or_default()
304    }
305
306    /// Returns the right border style of the current view.
307    pub fn border_right_style(&self) -> BorderStyleKeyword {
308        self.style.border_right_style.get(self.current).copied().unwrap_or_default()
309    }
310
311    /// Returns the bottom border style of the current view.
312    pub fn border_bottom_style(&self) -> BorderStyleKeyword {
313        self.style.border_bottom_style.get(self.current).copied().unwrap_or_default()
314    }
315
316    /// Returns the left border style of the current view.
317    pub fn border_left_style(&self) -> BorderStyleKeyword {
318        self.style.border_left_style.get(self.current).copied().unwrap_or_default()
319    }
320
321    /// Returns the outline color of the current view.
322    pub fn outline_color(&self) -> Color {
323        if let Some(col) =
324            self.style.outline_color.get_resolved(self.current, &self.style.custom_color_props)
325        {
326            Color::rgba(col.r(), col.g(), col.b(), col.a())
327        } else {
328            Color::rgba(0, 0, 0, 0)
329        }
330    }
331
332    /// Returns the outline width of the current view in physical pixels.
333    pub fn outline_width(&self) -> f32 {
334        if let Some(length) =
335            self.style.outline_width.get_resolved(self.current, &self.style.custom_length_props)
336        {
337            let bounds = self.bounds();
338            return length.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round();
339        }
340        0.0
341    }
342
343    /// Returns the outline offset of the current view in physical pixels.
344    pub fn outline_offset(&self) -> f32 {
345        if let Some(length) =
346            self.style.outline_offset.get_resolved(self.current, &self.style.custom_length_props)
347        {
348            let bounds = self.bounds();
349            return length.to_pixels(bounds.w.min(bounds.h), self.scale_factor()).round();
350        }
351        0.0
352    }
353
354    /// Returns the corner radius for the top-left corner of the current view.
355    pub fn corner_top_left_radius(&self) -> f32 {
356        let bounds = self.bounds();
357        let scale = self.scale_factor();
358        self.style
359            .corner_top_left_radius
360            .get_resolved(self.current, &self.style.custom_length_props)
361            .map(|l| l.to_pixels(bounds.w.min(bounds.h), scale).round())
362            .unwrap_or(0.0)
363    }
364
365    /// Returns the corner radius for the top-right corner of the current view.
366    pub fn corner_top_right_radius(&self) -> f32 {
367        let bounds = self.bounds();
368        let scale = self.scale_factor();
369        self.style
370            .corner_top_right_radius
371            .get_resolved(self.current, &self.style.custom_length_props)
372            .map(|l| l.to_pixels(bounds.w.min(bounds.h), scale).round())
373            .unwrap_or(0.0)
374    }
375
376    /// Returns the corner radius for the bottom-left corner of the current view.
377    pub fn corner_bottom_left_radius(&self) -> f32 {
378        let bounds = self.bounds();
379        let scale = self.scale_factor();
380        self.style
381            .corner_bottom_left_radius
382            .get_resolved(self.current, &self.style.custom_length_props)
383            .map(|l| l.to_pixels(bounds.w.min(bounds.h), scale).round())
384            .unwrap_or(0.0)
385    }
386
387    /// Returns the corner radius for the bottom-right corner of the current view.
388    pub fn corner_bottom_right_radius(&self) -> f32 {
389        let bounds = self.bounds();
390        let scale = self.scale_factor();
391        self.style
392            .corner_bottom_right_radius
393            .get_resolved(self.current, &self.style.custom_length_props)
394            .map(|l| l.to_pixels(bounds.w.min(bounds.h), scale).round())
395            .unwrap_or(0.0)
396    }
397
398    get_units_property!(
399        /// Returns the padding-left space of the current view.
400        padding_left
401    );
402
403    get_units_property!(
404        /// Returns the padding-right space of the current view.
405        padding_right
406    );
407
408    get_units_property!(
409        /// Returns the padding-top space of the current view.
410        padding_top
411    );
412
413    get_units_property!(
414        /// Returns the padding-bottom space of the current view.
415        padding_bottom
416    );
417
418    /// Returns the alignment of the current view.
419    pub fn alignment(&self) -> Alignment {
420        self.style.alignment.get(self.current).copied().unwrap_or_default()
421    }
422
423    /// Returns the background color of the current view.
424    pub fn background_color(&self) -> Color {
425        if let Some(col) =
426            self.style.background_color.get_resolved(self.current, &self.style.custom_color_props)
427        {
428            Color::rgba(col.r(), col.g(), col.b(), col.a())
429        } else {
430            Color::rgba(0, 0, 0, 0)
431        }
432    }
433
434    /// Returns the border color of the current view.
435    /// This returns the top border color; use side-specific getters for per-side access.
436    pub fn border_color(&self) -> Color {
437        self.border_top_color()
438    }
439
440    /// Returns the border style of the current view.
441    /// This returns the top border style; use side-specific getters for per-side access.
442    pub fn border_style(&self) -> BorderStyleKeyword {
443        self.border_top_style()
444    }
445
446    /// Returns the border width of the current view in physical pixels.
447    /// This returns the top border width; use side-specific getters for per-side access.
448    pub fn border_width(&self) -> f32 {
449        self.border_top_width()
450    }
451
452    /// Returns the text selection color for the current view.
453    pub fn selection_color(&self) -> Color {
454        if let Some(col) =
455            self.style.selection_color.get_resolved(self.current, &self.style.custom_color_props)
456        {
457            Color::rgba(col.r(), col.g(), col.b(), col.a())
458        } else {
459            Color::rgba(0, 0, 0, 0)
460        }
461    }
462
463    /// Returns the text caret color for the current view.
464    pub fn caret_color(&self) -> Color {
465        if let Some(col) =
466            self.style.caret_color.get_resolved(self.current, &self.style.custom_color_props)
467        {
468            Color::rgba(col.r(), col.g(), col.b(), col.a())
469        } else {
470            Color::rgba(0, 0, 0, 0)
471        }
472    }
473
474    /// Returns the font color for the current view.
475    pub fn font_color(&self) -> Color {
476        if let Some(col) =
477            self.style.font_color.get_resolved(self.current, &self.style.custom_color_props)
478        {
479            Color::rgba(col.r(), col.g(), col.b(), col.a())
480        } else {
481            Color::rgba(0, 0, 0, 0)
482        }
483    }
484
485    /// Returns whether the current view should have its text wrapped.
486    pub fn text_wrap(&self) -> bool {
487        self.style.text_wrap.get(self.current).copied().unwrap_or(true)
488    }
489
490    /// Returns the text alignment of the current view.
491    pub fn text_align(&self) -> TextAlign {
492        self.style.text_align.get(self.current).copied().unwrap_or_default()
493    }
494
495    /// Returns the text overflow preference of the current view.
496    pub fn text_overflow(&self) -> TextOverflow {
497        self.style.text_overflow.get(self.current).copied().unwrap_or_default()
498    }
499
500    /// Returns the line clamp Of the current view.
501    pub fn line_clamp(&self) -> Option<usize> {
502        self.style.line_clamp.get(self.current).copied().map(|lc| lc.0 as usize)
503    }
504
505    /// Returns the resolved shadows of the current view.
506    pub fn shadows(&self) -> Option<Vec<Shadow>> {
507        self.style.shadow.get_resolved(self.current, &self.style.custom_shadow_props)
508    }
509
510    /// Returns a reference to any filter applied to the current view.
511    pub fn filter(&self) -> Option<&Filter> {
512        self.style.filter.get(self.current)
513    }
514
515    /// Return to reference to any filter applied to the current view.
516    pub fn backdrop_filter(&self) -> Option<&Filter> {
517        self.style.backdrop_filter.get(self.current)
518    }
519
520    /// Returns a reference to any images of the current view.
521    pub fn background_images(&self) -> Option<&Vec<ImageOrGradient>> {
522        self.style.background_image.get(self.current)
523    }
524
525    ///  Returns a list of background sizes for the current view.
526    pub fn background_size(&self) -> Vec<BackgroundSize> {
527        self.style.background_size.get(self.current).cloned().unwrap_or_default()
528    }
529
530    /// Returns a list of background positions for the current view.
531    pub fn background_position(&self) -> Vec<Position> {
532        self.style.background_position.get(self.current).cloned().unwrap_or_default()
533    }
534
535    /// Returns a list of background repeat modes for the current view.
536    pub fn background_repeat(&self) -> Vec<BackgroundRepeat> {
537        self.style.background_repeat.get(self.current).cloned().unwrap_or_default()
538    }
539
540    pub fn path(&mut self) -> Path {
541        if self.cache.path.get(self.current).is_none() {
542            self.cache.path.insert(self.current, self.build_path(self.bounds(), (0.0, 0.0)));
543        }
544        let bounds = self.bounds();
545        self.cache.path.get(self.current).unwrap().make_offset(bounds.top_left())
546    }
547
548    /// Get the vector path of the current view.
549    pub fn build_path(&self, bounds: BoundingBox, outset: (f32, f32)) -> Path {
550        self.build_path_with_radii(
551            bounds,
552            outset,
553            (
554                self.corner_top_left_radius(),
555                self.corner_top_right_radius(),
556                self.corner_bottom_right_radius(),
557                self.corner_bottom_left_radius(),
558            ),
559        )
560    }
561
562    fn build_rrect_with_radii(
563        &self,
564        bounds: BoundingBox,
565        outset: (f32, f32),
566        corner_radii: (f32, f32, f32, f32),
567    ) -> RRect {
568        let (top_left, top_right, bottom_right, bottom_left) = corner_radii;
569        RRect::new_rect_radii(
570            Rect::from_xywh(0.0, 0.0, bounds.w, bounds.h),
571            &[
572                Point::new(top_left, top_left),
573                Point::new(top_right, top_right),
574                Point::new(bottom_right, bottom_right),
575                Point::new(bottom_left, bottom_left),
576            ],
577        )
578        .with_outset(outset)
579    }
580
581    fn build_path_with_radii(
582        &self,
583        bounds: BoundingBox,
584        outset: (f32, f32),
585        corner_radii: (f32, f32, f32, f32),
586    ) -> Path {
587        let mut path = PathBuilder::new();
588        path.add_rrect(self.build_rrect_with_radii(bounds, outset, corner_radii), None, None);
589        path.detach()
590    }
591
592    fn rrect(&self, bounds: BoundingBox, outset: (f32, f32)) -> RRect {
593        self.build_rrect_with_radii(
594            bounds,
595            outset,
596            (
597                self.corner_top_left_radius(),
598                self.corner_top_right_radius(),
599                self.corner_bottom_right_radius(),
600                self.corner_bottom_left_radius(),
601            ),
602        )
603        .with_offset(bounds.top_left())
604    }
605
606    fn corner_oval(center_x: f32, center_y: f32, radius: f32) -> Rect {
607        Rect::from_xywh(center_x - radius, center_y - radius, radius * 2.0, radius * 2.0)
608    }
609
610    fn round_corner_side_path(
611        side_ix: usize,
612        bounds: BoundingBox,
613        outer_radii: (f32, f32, f32, f32),
614        inner_radii: (f32, f32, f32, f32),
615        widths: (f32, f32, f32, f32),
616    ) -> Option<Path> {
617        let bx = bounds.x;
618        let by = bounds.y;
619        let bw = bounds.w;
620        let bh = bounds.h;
621
622        let (r_tl, r_tr, r_br, r_bl) = outer_radii;
623        let (ir_tl, ir_tr, ir_br, ir_bl) = inner_radii;
624        let (top_width, right_width, bottom_width, left_width) = widths;
625
626        let mut path = PathBuilder::new();
627        let diag = SQRT_2.recip();
628
629        let outer_tl_center = Point::new(bx + r_tl, by + r_tl);
630        let outer_tr_center = Point::new(bx + bw - r_tr, by + r_tr);
631        let outer_br_center = Point::new(bx + bw - r_br, by + bh - r_br);
632        let outer_bl_center = Point::new(bx + r_bl, by + bh - r_bl);
633
634        let inner_tl_center = Point::new(bx + left_width + ir_tl, by + top_width + ir_tl);
635        let inner_tr_center = Point::new(bx + bw - right_width - ir_tr, by + top_width + ir_tr);
636        let inner_br_center =
637            Point::new(bx + bw - right_width - ir_br, by + bh - bottom_width - ir_br);
638        let inner_bl_center = Point::new(bx + left_width + ir_bl, by + bh - bottom_width - ir_bl);
639
640        match side_ix {
641            0 => {
642                let outer_start = if r_tl > 0.0 {
643                    Point::new(outer_tl_center.x - r_tl * diag, outer_tl_center.y - r_tl * diag)
644                } else {
645                    Point::new(bx, by)
646                };
647                path.move_to(outer_start);
648                if r_tl > 0.0 {
649                    path.arc_to(
650                        Self::corner_oval(outer_tl_center.x, outer_tl_center.y, r_tl),
651                        225.0,
652                        45.0,
653                        false,
654                    );
655                }
656                path.line_to((bx + bw - r_tr, by));
657                if r_tr > 0.0 {
658                    path.arc_to(
659                        Self::corner_oval(outer_tr_center.x, outer_tr_center.y, r_tr),
660                        270.0,
661                        45.0,
662                        false,
663                    );
664                }
665
666                let inner_split = if ir_tr > 0.0 {
667                    Point::new(inner_tr_center.x + ir_tr * diag, inner_tr_center.y - ir_tr * diag)
668                } else {
669                    Point::new(bx + bw - right_width, by + top_width)
670                };
671                path.line_to(inner_split);
672                if ir_tr > 0.0 {
673                    path.arc_to(
674                        Self::corner_oval(inner_tr_center.x, inner_tr_center.y, ir_tr),
675                        315.0,
676                        -45.0,
677                        false,
678                    );
679                }
680                path.line_to((bx + left_width + ir_tl, by + top_width));
681                if ir_tl > 0.0 {
682                    path.arc_to(
683                        Self::corner_oval(inner_tl_center.x, inner_tl_center.y, ir_tl),
684                        270.0,
685                        -45.0,
686                        false,
687                    );
688                }
689            }
690            1 => {
691                let outer_start = if r_tr > 0.0 {
692                    Point::new(outer_tr_center.x + r_tr * diag, outer_tr_center.y - r_tr * diag)
693                } else {
694                    Point::new(bx + bw, by)
695                };
696                path.move_to(outer_start);
697                if r_tr > 0.0 {
698                    path.arc_to(
699                        Self::corner_oval(outer_tr_center.x, outer_tr_center.y, r_tr),
700                        315.0,
701                        45.0,
702                        false,
703                    );
704                }
705                path.line_to((bx + bw, by + bh - r_br));
706                if r_br > 0.0 {
707                    path.arc_to(
708                        Self::corner_oval(outer_br_center.x, outer_br_center.y, r_br),
709                        0.0,
710                        45.0,
711                        false,
712                    );
713                }
714
715                let inner_split = if ir_br > 0.0 {
716                    Point::new(inner_br_center.x + ir_br * diag, inner_br_center.y + ir_br * diag)
717                } else {
718                    Point::new(bx + bw - right_width, by + bh - bottom_width)
719                };
720                path.line_to(inner_split);
721                if ir_br > 0.0 {
722                    path.arc_to(
723                        Self::corner_oval(inner_br_center.x, inner_br_center.y, ir_br),
724                        45.0,
725                        -45.0,
726                        false,
727                    );
728                }
729                path.line_to((bx + bw - right_width, by + top_width + ir_tr));
730                if ir_tr > 0.0 {
731                    path.arc_to(
732                        Self::corner_oval(inner_tr_center.x, inner_tr_center.y, ir_tr),
733                        0.0,
734                        -45.0,
735                        false,
736                    );
737                }
738            }
739            2 => {
740                let outer_start = if r_br > 0.0 {
741                    Point::new(outer_br_center.x + r_br * diag, outer_br_center.y + r_br * diag)
742                } else {
743                    Point::new(bx + bw, by + bh)
744                };
745                path.move_to(outer_start);
746                if r_br > 0.0 {
747                    path.arc_to(
748                        Self::corner_oval(outer_br_center.x, outer_br_center.y, r_br),
749                        45.0,
750                        45.0,
751                        false,
752                    );
753                }
754                path.line_to((bx + r_bl, by + bh));
755                if r_bl > 0.0 {
756                    path.arc_to(
757                        Self::corner_oval(outer_bl_center.x, outer_bl_center.y, r_bl),
758                        90.0,
759                        45.0,
760                        false,
761                    );
762                }
763
764                let inner_split = if ir_bl > 0.0 {
765                    Point::new(inner_bl_center.x - ir_bl * diag, inner_bl_center.y + ir_bl * diag)
766                } else {
767                    Point::new(bx + left_width, by + bh - bottom_width)
768                };
769                path.line_to(inner_split);
770                if ir_bl > 0.0 {
771                    path.arc_to(
772                        Self::corner_oval(inner_bl_center.x, inner_bl_center.y, ir_bl),
773                        135.0,
774                        -45.0,
775                        false,
776                    );
777                }
778                path.line_to((bx + bw - right_width - ir_br, by + bh - bottom_width));
779                if ir_br > 0.0 {
780                    path.arc_to(
781                        Self::corner_oval(inner_br_center.x, inner_br_center.y, ir_br),
782                        90.0,
783                        -45.0,
784                        false,
785                    );
786                }
787            }
788            3 => {
789                let outer_start = if r_bl > 0.0 {
790                    Point::new(outer_bl_center.x - r_bl * diag, outer_bl_center.y + r_bl * diag)
791                } else {
792                    Point::new(bx, by + bh)
793                };
794                path.move_to(outer_start);
795                if r_bl > 0.0 {
796                    path.arc_to(
797                        Self::corner_oval(outer_bl_center.x, outer_bl_center.y, r_bl),
798                        135.0,
799                        45.0,
800                        false,
801                    );
802                }
803                path.line_to((bx, by + r_tl));
804                if r_tl > 0.0 {
805                    path.arc_to(
806                        Self::corner_oval(outer_tl_center.x, outer_tl_center.y, r_tl),
807                        180.0,
808                        45.0,
809                        false,
810                    );
811                }
812
813                let inner_split = if ir_tl > 0.0 {
814                    Point::new(inner_tl_center.x - ir_tl * diag, inner_tl_center.y - ir_tl * diag)
815                } else {
816                    Point::new(bx + left_width, by + top_width)
817                };
818                path.line_to(inner_split);
819                if ir_tl > 0.0 {
820                    path.arc_to(
821                        Self::corner_oval(inner_tl_center.x, inner_tl_center.y, ir_tl),
822                        225.0,
823                        -45.0,
824                        false,
825                    );
826                }
827                path.line_to((bx + left_width, by + bh - bottom_width - ir_bl));
828                if ir_bl > 0.0 {
829                    path.arc_to(
830                        Self::corner_oval(inner_bl_center.x, inner_bl_center.y, ir_bl),
831                        180.0,
832                        -45.0,
833                        false,
834                    );
835                }
836            }
837            _ => return None,
838        }
839
840        path.close();
841        Some(path.detach())
842    }
843
844    fn round_corner_side_stroke_path(
845        side_ix: usize,
846        bounds: BoundingBox,
847        outer_radii: (f32, f32, f32, f32),
848    ) -> Option<Path> {
849        let bx = bounds.x;
850        let by = bounds.y;
851        let bw = bounds.w;
852        let bh = bounds.h;
853
854        let (r_tl, r_tr, r_br, r_bl) = outer_radii;
855
856        let mut path = PathBuilder::new();
857        let diag = SQRT_2.recip();
858
859        let outer_tl_center = Point::new(bx + r_tl, by + r_tl);
860        let outer_tr_center = Point::new(bx + bw - r_tr, by + r_tr);
861        let outer_br_center = Point::new(bx + bw - r_br, by + bh - r_br);
862        let outer_bl_center = Point::new(bx + r_bl, by + bh - r_bl);
863
864        match side_ix {
865            0 => {
866                let outer_start = if r_tl > 0.0 {
867                    Point::new(outer_tl_center.x - r_tl * diag, outer_tl_center.y - r_tl * diag)
868                } else {
869                    Point::new(bx, by)
870                };
871                path.move_to(outer_start);
872                if r_tl > 0.0 {
873                    path.arc_to(
874                        Self::corner_oval(outer_tl_center.x, outer_tl_center.y, r_tl),
875                        225.0,
876                        45.0,
877                        false,
878                    );
879                }
880                path.line_to((bx + bw - r_tr, by));
881                if r_tr > 0.0 {
882                    path.arc_to(
883                        Self::corner_oval(outer_tr_center.x, outer_tr_center.y, r_tr),
884                        270.0,
885                        45.0,
886                        false,
887                    );
888                }
889            }
890            1 => {
891                let outer_start = if r_tr > 0.0 {
892                    Point::new(outer_tr_center.x + r_tr * diag, outer_tr_center.y - r_tr * diag)
893                } else {
894                    Point::new(bx + bw, by)
895                };
896                path.move_to(outer_start);
897                if r_tr > 0.0 {
898                    path.arc_to(
899                        Self::corner_oval(outer_tr_center.x, outer_tr_center.y, r_tr),
900                        315.0,
901                        45.0,
902                        false,
903                    );
904                }
905                path.line_to((bx + bw, by + bh - r_br));
906                if r_br > 0.0 {
907                    path.arc_to(
908                        Self::corner_oval(outer_br_center.x, outer_br_center.y, r_br),
909                        0.0,
910                        45.0,
911                        false,
912                    );
913                }
914            }
915            2 => {
916                let outer_start = if r_br > 0.0 {
917                    Point::new(outer_br_center.x + r_br * diag, outer_br_center.y + r_br * diag)
918                } else {
919                    Point::new(bx + bw, by + bh)
920                };
921                path.move_to(outer_start);
922                if r_br > 0.0 {
923                    path.arc_to(
924                        Self::corner_oval(outer_br_center.x, outer_br_center.y, r_br),
925                        45.0,
926                        45.0,
927                        false,
928                    );
929                }
930                path.line_to((bx + r_bl, by + bh));
931                if r_bl > 0.0 {
932                    path.arc_to(
933                        Self::corner_oval(outer_bl_center.x, outer_bl_center.y, r_bl),
934                        90.0,
935                        45.0,
936                        false,
937                    );
938                }
939            }
940            3 => {
941                let outer_start = if r_bl > 0.0 {
942                    Point::new(outer_bl_center.x - r_bl * diag, outer_bl_center.y + r_bl * diag)
943                } else {
944                    Point::new(bx, by + bh)
945                };
946                path.move_to(outer_start);
947                if r_bl > 0.0 {
948                    path.arc_to(
949                        Self::corner_oval(outer_bl_center.x, outer_bl_center.y, r_bl),
950                        135.0,
951                        45.0,
952                        false,
953                    );
954                }
955                path.line_to((bx, by + r_tl));
956                if r_tl > 0.0 {
957                    path.arc_to(
958                        Self::corner_oval(outer_tl_center.x, outer_tl_center.y, r_tl),
959                        180.0,
960                        45.0,
961                        false,
962                    );
963                }
964            }
965            _ => return None,
966        }
967
968        Some(path.detach())
969    }
970
971    /// Draw background color or background image (including gradients) for the current view.
972    pub fn draw_background(&mut self, canvas: &Canvas) {
973        let background_color = self.background_color();
974        if background_color.a() > 0 {
975            let mut paint = Paint::default();
976            paint.set_color(skia_safe::Color::from_argb(
977                background_color.a(),
978                background_color.r(),
979                background_color.g(),
980                background_color.b(),
981            ));
982            paint.set_anti_alias(true);
983            canvas.draw_rrect(self.rrect(self.bounds(), (0.0, 0.0)), &paint);
984        }
985
986        self.draw_background_images(canvas);
987    }
988
989    /// Draw the border of the current view.
990    ///
991    /// Supports individual side widths, colors, and styles. Borders are rendered from a
992    /// ring path (outer minus inner), then split into per-side ownership paths using path
993    /// intersections. Dashed/dotted use side-local center-line strokes clipped by the
994    /// corresponding side ownership path.
995    pub fn draw_border(&mut self, canvas: &Canvas) {
996        let top_width = self.border_top_width();
997        let right_width = self.border_right_width();
998        let bottom_width = self.border_bottom_width();
999        let left_width = self.border_left_width();
1000
1001        let top_color = self.border_top_color();
1002        let right_color = self.border_right_color();
1003        let bottom_color = self.border_bottom_color();
1004        let left_color = self.border_left_color();
1005
1006        let top_style = self.border_top_style();
1007        let right_style = self.border_right_style();
1008        let bottom_style = self.border_bottom_style();
1009        let left_style = self.border_left_style();
1010
1011        let top_vis = top_width > 0.0 && top_color.a() > 0 && top_style != BorderStyleKeyword::None;
1012        let right_vis =
1013            right_width > 0.0 && right_color.a() > 0 && right_style != BorderStyleKeyword::None;
1014        let bottom_vis =
1015            bottom_width > 0.0 && bottom_color.a() > 0 && bottom_style != BorderStyleKeyword::None;
1016        let left_vis =
1017            left_width > 0.0 && left_color.a() > 0 && left_style != BorderStyleKeyword::None;
1018
1019        if !top_vis && !right_vis && !bottom_vis && !left_vis {
1020            return;
1021        }
1022
1023        let bounds = self.bounds();
1024        let bx = bounds.x;
1025        let by = bounds.y;
1026        let bw = bounds.w;
1027        let bh = bounds.h;
1028
1029        // Outer corner radii (from style).
1030        let r_tl = self.corner_top_left_radius();
1031        let r_tr = self.corner_top_right_radius();
1032        let r_br = self.corner_bottom_right_radius();
1033        let r_bl = self.corner_bottom_left_radius();
1034
1035        // Inner corner radii: outer radius minus the thicker adjacent border, clamped to 0.
1036        let ir_tl = (r_tl - top_width.max(left_width)).max(0.0);
1037        let ir_tr = (r_tr - top_width.max(right_width)).max(0.0);
1038        let ir_br = (r_br - bottom_width.max(right_width)).max(0.0);
1039        let ir_bl = (r_bl - bottom_width.max(left_width)).max(0.0);
1040
1041        // Inner rect dimensions.
1042        let inner_w = (bw - left_width - right_width).max(0.0);
1043        let inner_h = (bh - top_width - bottom_width).max(0.0);
1044        let inner_is_empty = inner_w <= 0.0 || inner_h <= 0.0;
1045
1046        let uniform_borders = top_width == right_width
1047            && right_width == bottom_width
1048            && bottom_width == left_width
1049            && top_color == right_color
1050            && right_color == bottom_color
1051            && bottom_color == left_color
1052            && top_style == right_style
1053            && right_style == bottom_style
1054            && bottom_style == left_style;
1055        let corner_radii = (r_tl, r_tr, r_br, r_bl);
1056        let outer_rrect = self
1057            .build_rrect_with_radii(bounds, (0.0, 0.0), corner_radii)
1058            .with_offset(bounds.top_left());
1059        let inner_rrect = (!inner_is_empty).then(|| {
1060            self.build_rrect_with_radii(
1061                BoundingBox::from_min_max(0.0, 0.0, inner_w, inner_h),
1062                (0.0, 0.0),
1063                (ir_tl, ir_tr, ir_br, ir_bl),
1064            )
1065            .with_offset((bx + left_width, by + top_width))
1066        });
1067
1068        if uniform_borders {
1069            let mut paint = Paint::default();
1070            paint.set_color(top_color);
1071            paint.set_anti_alias(true);
1072
1073            match top_style {
1074                BorderStyleKeyword::Dashed | BorderStyleKeyword::Dotted => {
1075                    paint.set_style(PaintStyle::Stroke);
1076                    paint.set_stroke_width(top_width);
1077                    if top_style == BorderStyleKeyword::Dashed {
1078                        paint.set_path_effect(PathEffect::dash(&[top_width * 2.0, top_width], 0.0));
1079                    } else {
1080                        paint.set_path_effect(PathEffect::dash(&[0.0, top_width * 2.0], 0.0));
1081                        paint.set_stroke_cap(skia_safe::PaintCap::Round);
1082                    }
1083                    canvas.draw_rrect(
1084                        outer_rrect.with_inset((top_width * 0.5, top_width * 0.5)),
1085                        &paint,
1086                    );
1087                }
1088                _ => {
1089                    if let Some(inner_rrect) = inner_rrect {
1090                        canvas.draw_drrect(outer_rrect, inner_rrect, &paint);
1091                    } else {
1092                        canvas.draw_rrect(outer_rrect, &paint);
1093                    }
1094                }
1095            }
1096            return;
1097        }
1098
1099        // Outer path in world coordinates.
1100        let outer_path = self
1101            .build_path_with_radii(bounds, (0.0, 0.0), corner_radii)
1102            .make_offset(bounds.top_left());
1103
1104        // Inner path in world coordinates uses the same corner shape/smoothing pipeline as outer.
1105        let inner_path = if inner_is_empty {
1106            None
1107        } else {
1108            let inner_bounds = BoundingBox::from_min_max(0.0, 0.0, inner_w, inner_h);
1109            Some(
1110                self.build_path_with_radii(inner_bounds, (0.0, 0.0), (ir_tl, ir_tr, ir_br, ir_bl))
1111                    .make_offset((bx + left_width, by + top_width)),
1112            )
1113        };
1114
1115        let ring_path = (if let Some(inner) = inner_path.as_ref() {
1116            outer_path
1117                .op(inner, skia_safe::PathOp::Difference)
1118                .unwrap_or_else(|| outer_path.clone())
1119        } else {
1120            outer_path.clone()
1121        })
1122        .with_is_volatile(true);
1123
1124        // Side ownership masks split corner ownership at the outer->inner corner join line.
1125        let mask_top: [Point; 4] = [
1126            Point::new(bx, by),
1127            Point::new(bx + bw, by),
1128            Point::new(bx + bw - right_width, by + top_width),
1129            Point::new(bx + left_width, by + top_width),
1130        ];
1131        let mask_right: [Point; 4] = [
1132            Point::new(bx + bw - right_width, by + top_width),
1133            Point::new(bx + bw, by),
1134            Point::new(bx + bw, by + bh),
1135            Point::new(bx + bw - right_width, by + bh - bottom_width),
1136        ];
1137        let mask_bottom: [Point; 4] = [
1138            Point::new(bx + left_width, by + bh - bottom_width),
1139            Point::new(bx + bw - right_width, by + bh - bottom_width),
1140            Point::new(bx + bw, by + bh),
1141            Point::new(bx, by + bh),
1142        ];
1143        let mask_left: [Point; 4] = [
1144            Point::new(bx, by),
1145            Point::new(bx + left_width, by + top_width),
1146            Point::new(bx + left_width, by + bh - bottom_width),
1147            Point::new(bx, by + bh),
1148        ];
1149
1150        let sides: [(usize, bool, Color, BorderStyleKeyword, f32, [Point; 4]); 4] = [
1151            (0, top_vis, top_color, top_style, top_width, mask_top),
1152            (1, right_vis, right_color, right_style, right_width, mask_right),
1153            (2, bottom_vis, bottom_color, bottom_style, bottom_width, mask_bottom),
1154            (3, left_vis, left_color, left_style, left_width, mask_left),
1155        ];
1156
1157        for (side_ix, vis, color, style, side_width, mask_pts) in sides {
1158            if !vis {
1159                continue;
1160            }
1161
1162            let mut side_mask = PathBuilder::new();
1163            side_mask.move_to(mask_pts[0]);
1164            side_mask.line_to(mask_pts[1]);
1165            side_mask.line_to(mask_pts[2]);
1166            side_mask.line_to(mask_pts[3]);
1167            side_mask.close();
1168            let side_mask = side_mask.detach();
1169            let Some(side_region) = ring_path.op(&side_mask, skia_safe::PathOp::Intersect) else {
1170                continue;
1171            };
1172            let side_region = side_region.with_is_volatile(true);
1173
1174            match style {
1175                BorderStyleKeyword::Dashed | BorderStyleKeyword::Dotted => {
1176                    canvas.save();
1177                    canvas.clip_path(&side_region, ClipOp::Intersect, true);
1178
1179                    let stroke_path = (if !inner_is_empty {
1180                        Self::round_corner_side_stroke_path(
1181                            side_ix,
1182                            bounds,
1183                            (r_tl, r_tr, r_br, r_bl),
1184                        )
1185                        .unwrap_or_else(|| {
1186                            let mut side_path = PathBuilder::new();
1187                            let (sx, sy, ex, ey) = match side_ix {
1188                                0 => (
1189                                    bx + left_width * 0.5,
1190                                    by + top_width * 0.5,
1191                                    bx + bw - right_width * 0.5,
1192                                    by + top_width * 0.5,
1193                                ),
1194                                1 => (
1195                                    bx + bw - right_width * 0.5,
1196                                    by + top_width * 0.5,
1197                                    bx + bw - right_width * 0.5,
1198                                    by + bh - bottom_width * 0.5,
1199                                ),
1200                                2 => (
1201                                    bx + left_width * 0.5,
1202                                    by + bh - bottom_width * 0.5,
1203                                    bx + bw - right_width * 0.5,
1204                                    by + bh - bottom_width * 0.5,
1205                                ),
1206                                _ => (
1207                                    bx + left_width * 0.5,
1208                                    by + top_width * 0.5,
1209                                    bx + left_width * 0.5,
1210                                    by + bh - bottom_width * 0.5,
1211                                ),
1212                            };
1213                            side_path.move_to((sx, sy));
1214                            side_path.line_to((ex, ey));
1215                            side_path.detach()
1216                        })
1217                    } else {
1218                        let mut side_path = PathBuilder::new();
1219                        let (sx, sy, ex, ey) = match side_ix {
1220                            0 => (
1221                                bx + left_width * 0.5,
1222                                by + top_width * 0.5,
1223                                bx + bw - right_width * 0.5,
1224                                by + top_width * 0.5,
1225                            ),
1226                            1 => (
1227                                bx + bw - right_width * 0.5,
1228                                by + top_width * 0.5,
1229                                bx + bw - right_width * 0.5,
1230                                by + bh - bottom_width * 0.5,
1231                            ),
1232                            2 => (
1233                                bx + left_width * 0.5,
1234                                by + bh - bottom_width * 0.5,
1235                                bx + bw - right_width * 0.5,
1236                                by + bh - bottom_width * 0.5,
1237                            ),
1238                            _ => (
1239                                bx + left_width * 0.5,
1240                                by + top_width * 0.5,
1241                                bx + left_width * 0.5,
1242                                by + bh - bottom_width * 0.5,
1243                            ),
1244                        };
1245                        side_path.move_to((sx, sy));
1246                        side_path.line_to((ex, ey));
1247                        side_path.detach()
1248                    })
1249                    .with_is_volatile(true);
1250                    let mut paint = Paint::default();
1251                    paint.set_style(PaintStyle::Stroke);
1252                    paint.set_color(color);
1253                    paint.set_stroke_width(side_width);
1254                    if style == BorderStyleKeyword::Dashed {
1255                        paint.set_path_effect(PathEffect::dash(
1256                            &[side_width * 2.0, side_width],
1257                            0.0,
1258                        ));
1259                    } else {
1260                        paint.set_path_effect(PathEffect::dash(&[0.0, side_width * 2.0], 0.0));
1261                        paint.set_stroke_cap(skia_safe::PaintCap::Round);
1262                    }
1263                    paint.set_anti_alias(true);
1264                    canvas.draw_path(&stroke_path, &paint);
1265                    canvas.restore();
1266                }
1267                _ => {
1268                    let exact_side_path = if !inner_is_empty {
1269                        Self::round_corner_side_path(
1270                            side_ix,
1271                            bounds,
1272                            (r_tl, r_tr, r_br, r_bl),
1273                            (ir_tl, ir_tr, ir_br, ir_bl),
1274                            (top_width, right_width, bottom_width, left_width),
1275                        )
1276                    } else {
1277                        None
1278                    };
1279                    // Solid and any other opaque style: fill this side's owned part of the ring.
1280                    let mut paint = Paint::default();
1281                    paint.set_color(color);
1282                    paint.set_anti_alias(true);
1283                    if let Some(path) = exact_side_path {
1284                        let Some(constrained_path) =
1285                            path.op(&ring_path, skia_safe::PathOp::Intersect)
1286                        else {
1287                            continue;
1288                        };
1289                        let constrained_path = constrained_path.with_is_volatile(true);
1290                        canvas.draw_path(&constrained_path, &paint);
1291                    } else {
1292                        canvas.draw_path(&side_region, &paint);
1293                    }
1294                }
1295            }
1296        }
1297    }
1298
1299    /// Draw the outline of the current view.
1300    pub fn draw_outline(&mut self, canvas: &Canvas) {
1301        let outline_width = self.outline_width();
1302        let outline_color = self.outline_color();
1303
1304        if outline_width > 0.0 && outline_color.a() != 0 {
1305            let outline_offset = self.outline_offset();
1306
1307            let bounds = self.bounds();
1308
1309            let half_outline_width = outline_width / 2.0;
1310            let outline_rrect = self.rrect(
1311                bounds,
1312                (half_outline_width + outline_offset, half_outline_width + outline_offset),
1313            );
1314
1315            let mut outline_paint = Paint::default();
1316            outline_paint.set_color(outline_color);
1317            outline_paint.set_stroke_width(outline_width);
1318            outline_paint.set_style(PaintStyle::Stroke);
1319            outline_paint.set_anti_alias(true);
1320            canvas.draw_rrect(outline_rrect, &outline_paint);
1321        }
1322    }
1323
1324    /// Draw shadows for the current view.
1325    pub fn draw_shadows(&mut self, canvas: &Canvas) {
1326        if let Some(shadows) = self.shadows() {
1327            if shadows.is_empty() {
1328                return;
1329            }
1330
1331            let bounds = self.bounds();
1332
1333            let rrect = self.rrect(bounds, (0.0, 0.0));
1334
1335            for shadow in shadows.iter().rev() {
1336                let shadow_color = shadow.color.unwrap_or_default();
1337
1338                let shadow_x_offset = shadow.x_offset.to_px().unwrap_or(0.0) * self.scale_factor();
1339                let shadow_y_offset = shadow.y_offset.to_px().unwrap_or(0.0) * self.scale_factor();
1340                let spread_radius =
1341                    shadow.spread_radius.as_ref().and_then(|l| l.to_px()).unwrap_or(0.0)
1342                        * self.scale_factor();
1343
1344                let blur_radius =
1345                    shadow.blur_radius.as_ref().and_then(|br| br.to_px()).unwrap_or(0.0);
1346
1347                if shadow_color.a() == 0
1348                    || (shadow_x_offset == 0.0
1349                        && shadow_y_offset == 0.0
1350                        && spread_radius == 0.0
1351                        && blur_radius == 0.0)
1352                {
1353                    continue;
1354                }
1355
1356                let mut shadow_paint = Paint::default();
1357
1358                let outset = if shadow.inset { -spread_radius } else { spread_radius };
1359
1360                shadow_paint.set_style(PaintStyle::Fill);
1361
1362                shadow_paint.set_color(shadow_color);
1363
1364                if blur_radius > 0.0 {
1365                    shadow_paint.set_mask_filter(MaskFilter::blur(
1366                        BlurStyle::Normal,
1367                        blur_radius / 2.0,
1368                        false,
1369                    ));
1370                }
1371
1372                canvas.save();
1373                if shadow.inset {
1374                    let path = self.build_path(bounds, (0.0, 0.0)).make_offset(bounds.top_left());
1375                    let shadow_path = self
1376                        .build_path(bounds, (outset, outset))
1377                        .make_offset(bounds.top_left())
1378                        .make_offset((shadow_x_offset, shadow_y_offset));
1379                    let shadow_path = path
1380                        .op(&shadow_path, skia_safe::PathOp::Difference)
1381                        .unwrap()
1382                        .with_is_volatile(true);
1383                    canvas.clip_rrect(rrect, ClipOp::Intersect, true);
1384                    canvas.draw_path(&shadow_path, &shadow_paint);
1385                } else {
1386                    let shadow_rrect = self
1387                        .rrect(bounds, (outset, outset))
1388                        .with_offset((shadow_x_offset, shadow_y_offset));
1389                    canvas.clip_rrect(rrect, ClipOp::Difference, true);
1390                    canvas.draw_rrect(shadow_rrect, &shadow_paint);
1391                }
1392                canvas.restore();
1393            }
1394        }
1395    }
1396
1397    /// Draw background images (including gradients) for the current view.
1398    fn draw_background_images(&mut self, canvas: &Canvas) {
1399        let bounds = self.bounds();
1400
1401        if self.background_images().is_some() {
1402            let rrect = self.rrect(bounds, (0.0, 0.0));
1403            if let Some(images) = self.background_images() {
1404                let image_sizes = self.background_size();
1405                let image_positions = self.background_position();
1406                let image_repeats = self.background_repeat();
1407
1408                for (index, image) in images.iter().enumerate() {
1409                    match image {
1410                        ImageOrGradient::Gradient(gradient) => match gradient {
1411                            Gradient::Linear(linear_gradient) => {
1412                                let (start, end, parent_length) = match linear_gradient.direction {
1413                                    LineDirection::Horizontal(horizontal_keyword) => {
1414                                        match horizontal_keyword {
1415                                            HorizontalPositionKeyword::Left => (
1416                                                bounds.center_right(),
1417                                                bounds.center_left(),
1418                                                bounds.width(),
1419                                            ),
1420
1421                                            HorizontalPositionKeyword::Right => (
1422                                                bounds.center_left(),
1423                                                bounds.center_right(),
1424                                                bounds.width(),
1425                                            ),
1426                                        }
1427                                    }
1428
1429                                    LineDirection::Vertical(vertical_keyword) => {
1430                                        match vertical_keyword {
1431                                            VerticalPositionKeyword::Top => (
1432                                                bounds.center_bottom(),
1433                                                bounds.center_top(),
1434                                                bounds.height(),
1435                                            ),
1436
1437                                            VerticalPositionKeyword::Bottom => (
1438                                                bounds.center_top(),
1439                                                bounds.center_bottom(),
1440                                                bounds.height(),
1441                                            ),
1442                                        }
1443                                    }
1444
1445                                    LineDirection::Corner { horizontal, vertical } => {
1446                                        match (horizontal, vertical) {
1447                                            (
1448                                                HorizontalPositionKeyword::Right,
1449                                                VerticalPositionKeyword::Bottom,
1450                                            ) => (
1451                                                bounds.top_left(),
1452                                                bounds.bottom_right(),
1453                                                bounds.diagonal(),
1454                                            ),
1455
1456                                            (
1457                                                HorizontalPositionKeyword::Right,
1458                                                VerticalPositionKeyword::Top,
1459                                            ) => (
1460                                                bounds.bottom_left(),
1461                                                bounds.top_right(),
1462                                                bounds.diagonal(),
1463                                            ),
1464
1465                                            _ => (bounds.top_left(), bounds.bottom_right(), 0.0),
1466                                        }
1467                                    }
1468
1469                                    LineDirection::Angle(angle) => {
1470                                        let angle_rad = angle.to_radians();
1471                                        let start_x = bounds.x
1472                                            + ((angle_rad.sin() * bounds.w) - bounds.w) / -2.0;
1473                                        let end_x = bounds.x
1474                                            + ((angle_rad.sin() * bounds.w) + bounds.w) / 2.0;
1475                                        let start_y = bounds.y
1476                                            + ((angle_rad.cos() * bounds.h) + bounds.h) / 2.0;
1477                                        let end_y = bounds.y
1478                                            + ((angle_rad.cos() * bounds.h) - bounds.h) / -2.0;
1479
1480                                        let x = (end_x - start_x).abs();
1481                                        let y = (end_y - start_y).abs();
1482
1483                                        let dist = (x * x + y * y).sqrt();
1484
1485                                        ((start_x, start_y), (end_x, end_y), dist)
1486                                    }
1487                                };
1488
1489                                let num_stops = linear_gradient.stops.len();
1490
1491                                let mut stops = linear_gradient
1492                                    .stops
1493                                    .iter()
1494                                    .enumerate()
1495                                    .map(|(index, stop)| {
1496                                        let pos = if let Some(pos) = &stop.position {
1497                                            pos.to_pixels(parent_length, self.scale_factor())
1498                                                / parent_length
1499                                        } else {
1500                                            index as f32 / (num_stops - 1) as f32
1501                                        };
1502                                        (pos, skia_safe::Color::from(stop.color))
1503                                    })
1504                                    .collect::<Vec<_>>();
1505
1506                                // Insert a stop at the front if the first stop is not at 0.
1507                                if let Some(first) = stops.first() {
1508                                    if first.0 != 0.0 {
1509                                        stops.insert(0, (0.0, first.1));
1510                                    }
1511                                }
1512
1513                                // Insert a stop at the end if the last stop is not at 1.0.
1514                                if let Some(last) = stops.last() {
1515                                    if last.0 != 1.0 {
1516                                        stops.push((1.0, last.1));
1517                                    }
1518                                }
1519
1520                                let (offsets, colors): (Vec<f32>, Vec<skia_safe::Color>) =
1521                                    stops.into_iter().unzip();
1522                                let colors4f: Vec<skia_safe::Color4f> =
1523                                    colors.iter().copied().map(Into::into).collect();
1524
1525                                let gradient_colors =
1526                                    skia_safe::gradient_shader::GradientColors::new(
1527                                        &colors4f,
1528                                        Some(&offsets[..]),
1529                                        TileMode::Clamp,
1530                                        None,
1531                                    );
1532                                let gradient = skia_safe::gradient_shader::Gradient::new(
1533                                    gradient_colors,
1534                                    skia_safe::gradient_shader::Interpolation::default(),
1535                                );
1536                                let shader = skia_safe::shaders::linear_gradient(
1537                                    (Point::from(start), Point::from(end)),
1538                                    &gradient,
1539                                    None,
1540                                );
1541
1542                                let mut paint = Paint::default();
1543                                paint.set_shader(shader);
1544                                paint.set_anti_alias(true);
1545
1546                                canvas.draw_rrect(rrect, &paint);
1547                            }
1548
1549                            Gradient::Radial(radial_gradient) => {
1550                                let num_stops = radial_gradient.stops.len();
1551
1552                                let mut stops = radial_gradient
1553                                    .stops
1554                                    .iter()
1555                                    .enumerate()
1556                                    .map(|(index, stop)| {
1557                                        let pos = if let Some(pos) = &stop.position {
1558                                            pos.to_pixels(bounds.width(), self.scale_factor())
1559                                                / bounds.width()
1560                                        } else {
1561                                            index as f32 / (num_stops - 1) as f32
1562                                        };
1563
1564                                        (pos, skia_safe::Color::from(stop.color))
1565                                    })
1566                                    .collect::<Vec<_>>();
1567
1568                                // Insert a stop at the front if the first stop is not at 0.
1569                                if let Some(first) = stops.first() {
1570                                    if first.0 != 0.0 {
1571                                        stops.insert(0, (0.0, first.1));
1572                                    }
1573                                }
1574
1575                                // Insert a stop at the end if the last stop is not at 1.0.
1576                                if let Some(last) = stops.last() {
1577                                    if last.0 != 1.0 {
1578                                        stops.push((1.0, last.1));
1579                                    }
1580                                }
1581
1582                                let (offsets, colors): (Vec<f32>, Vec<skia_safe::Color>) =
1583                                    stops.into_iter().unzip();
1584                                let colors4f: Vec<skia_safe::Color4f> =
1585                                    colors.iter().copied().map(Into::into).collect();
1586
1587                                let gradient_colors =
1588                                    skia_safe::gradient_shader::GradientColors::new(
1589                                        &colors4f,
1590                                        Some(&offsets[..]),
1591                                        TileMode::Clamp,
1592                                        None,
1593                                    );
1594                                let gradient = skia_safe::gradient_shader::Gradient::new(
1595                                    gradient_colors,
1596                                    skia_safe::gradient_shader::Interpolation::default(),
1597                                );
1598                                let shader = skia_safe::shaders::radial_gradient(
1599                                    (Point::from(bounds.center()), bounds.w.max(bounds.h)),
1600                                    &gradient,
1601                                    None,
1602                                );
1603
1604                                let mut paint = Paint::default();
1605                                paint.set_shader(shader);
1606                                paint.set_anti_alias(true);
1607
1608                                canvas.draw_rrect(rrect, &paint);
1609                            }
1610
1611                            _ => {}
1612                        },
1613
1614                        ImageOrGradient::Image(image_name) => {
1615                            if let Some(image_id) = self.resource_manager.image_ids.get(image_name)
1616                            {
1617                                if let Some(image) = self.resource_manager.images.get(image_id) {
1618                                    match &image.image {
1619                                        ImageOrSvg::Image(image) => {
1620                                            let image_width = image.width();
1621                                            let image_height = image.height();
1622                                            let (width, height) = if let Some(background_size) =
1623                                                image_sizes.get(index)
1624                                            {
1625                                                match background_size {
1626                                                    BackgroundSize::Explicit { width, height } => {
1627                                                        let w = match width {
1628                                                LengthPercentageOrAuto::LengthPercentage(
1629                                                    length,
1630                                                ) => {
1631                                                    length.to_pixels(bounds.w, self.scale_factor())
1632                                                }
1633                                                LengthPercentageOrAuto::Auto => image_width as f32,
1634                                            };
1635
1636                                                        let h = match height {
1637                                                LengthPercentageOrAuto::LengthPercentage(
1638                                                    length,
1639                                                ) => {
1640                                                    length.to_pixels(bounds.h, self.scale_factor())
1641                                                }
1642                                                LengthPercentageOrAuto::Auto => image_height as f32,
1643                                            };
1644
1645                                                        (w, h)
1646                                                    }
1647
1648                                                    BackgroundSize::Contain => {
1649                                                        let image_ratio = image_width as f32
1650                                                            / image_height as f32;
1651                                                        let container_ratio = bounds.w / bounds.h;
1652
1653                                                        let (w, h) =
1654                                                            if image_ratio > container_ratio {
1655                                                                (bounds.w, bounds.w / image_ratio)
1656                                                            } else {
1657                                                                (bounds.h * image_ratio, bounds.h)
1658                                                            };
1659
1660                                                        (w, h)
1661                                                    }
1662
1663                                                    BackgroundSize::Cover => {
1664                                                        let image_ratio = image_width as f32
1665                                                            / image_height as f32;
1666                                                        let container_ratio = bounds.w / bounds.h;
1667
1668                                                        let (w, h) =
1669                                                            if image_ratio < container_ratio {
1670                                                                (bounds.w, bounds.w / image_ratio)
1671                                                            } else {
1672                                                                (bounds.h * image_ratio, bounds.h)
1673                                                            };
1674
1675                                                        (w, h)
1676                                                    }
1677                                                }
1678                                            } else {
1679                                                (image_width as f32, image_height as f32)
1680                                            };
1681
1682                                            let position = image_positions
1683                                                .get(index)
1684                                                .cloned()
1685                                                .or_else(|| image_positions.last().cloned())
1686                                                .unwrap_or_default();
1687
1688                                            let posx =
1689                                                position.x.to_length_or_percentage().to_pixels(
1690                                                    bounds.width() - width,
1691                                                    self.scale_factor(),
1692                                                );
1693                                            let posy =
1694                                                position.y.to_length_or_percentage().to_pixels(
1695                                                    bounds.height() - height,
1696                                                    self.scale_factor(),
1697                                                );
1698                                            let repeat = image_repeats
1699                                                .get(index)
1700                                                .copied()
1701                                                .or_else(|| image_repeats.last().copied())
1702                                                .unwrap_or(BackgroundRepeat::Repeat);
1703
1704                                            if width <= 0.0 || height <= 0.0 {
1705                                                continue;
1706                                            }
1707
1708                                            let mut paint = Paint::default();
1709                                            paint.set_anti_alias(true);
1710
1711                                            let origin_x = bounds.left() + posx;
1712                                            let origin_y = bounds.top() + posy;
1713
1714                                            let mut start_x = origin_x;
1715                                            let mut start_y = origin_y;
1716
1717                                            if matches!(
1718                                                repeat,
1719                                                BackgroundRepeat::Repeat
1720                                                    | BackgroundRepeat::RepeatX
1721                                            ) {
1722                                                let tiles_to_left =
1723                                                    ((bounds.left() - origin_x) / width).floor();
1724                                                start_x = origin_x + tiles_to_left * width;
1725                                                if start_x > bounds.left() {
1726                                                    start_x -= width;
1727                                                }
1728                                            }
1729
1730                                            if matches!(
1731                                                repeat,
1732                                                BackgroundRepeat::Repeat
1733                                                    | BackgroundRepeat::RepeatY
1734                                            ) {
1735                                                let tiles_to_top =
1736                                                    ((bounds.top() - origin_y) / height).floor();
1737                                                start_y = origin_y + tiles_to_top * height;
1738                                                if start_y > bounds.top() {
1739                                                    start_y -= height;
1740                                                }
1741                                            }
1742
1743                                            canvas.save();
1744                                            canvas.clip_rrect(rrect, ClipOp::Intersect, true);
1745
1746                                            match repeat {
1747                                                BackgroundRepeat::NoRepeat => {
1748                                                    let dst = Rect::new(
1749                                                        origin_x,
1750                                                        origin_y,
1751                                                        origin_x + width,
1752                                                        origin_y + height,
1753                                                    );
1754                                                    canvas.draw_image_rect_with_sampling_options(
1755                                                        image,
1756                                                        None,
1757                                                        dst,
1758                                                        SamplingOptions::default(),
1759                                                        &paint,
1760                                                    );
1761                                                }
1762
1763                                                BackgroundRepeat::RepeatX => {
1764                                                    let mut x = start_x;
1765                                                    while x < bounds.right() {
1766                                                        let dst = Rect::new(
1767                                                            x,
1768                                                            origin_y,
1769                                                            x + width,
1770                                                            origin_y + height,
1771                                                        );
1772                                                        canvas
1773                                                            .draw_image_rect_with_sampling_options(
1774                                                                image,
1775                                                                None,
1776                                                                dst,
1777                                                                SamplingOptions::default(),
1778                                                                &paint,
1779                                                            );
1780                                                        x += width;
1781                                                    }
1782                                                }
1783
1784                                                BackgroundRepeat::RepeatY => {
1785                                                    let mut y = start_y;
1786                                                    while y < bounds.bottom() {
1787                                                        let dst = Rect::new(
1788                                                            origin_x,
1789                                                            y,
1790                                                            origin_x + width,
1791                                                            y + height,
1792                                                        );
1793                                                        canvas
1794                                                            .draw_image_rect_with_sampling_options(
1795                                                                image,
1796                                                                None,
1797                                                                dst,
1798                                                                SamplingOptions::default(),
1799                                                                &paint,
1800                                                            );
1801                                                        y += height;
1802                                                    }
1803                                                }
1804
1805                                                BackgroundRepeat::Repeat => {
1806                                                    let mut y = start_y;
1807                                                    while y < bounds.bottom() {
1808                                                        let mut x = start_x;
1809                                                        while x < bounds.right() {
1810                                                            let dst = Rect::new(
1811                                                                x,
1812                                                                y,
1813                                                                x + width,
1814                                                                y + height,
1815                                                            );
1816                                                            canvas.draw_image_rect_with_sampling_options(
1817                                                                image,
1818                                                                None,
1819                                                                dst,
1820                                                                SamplingOptions::default(),
1821                                                                &paint,
1822                                                            );
1823                                                            x += width;
1824                                                        }
1825                                                        y += height;
1826                                                    }
1827                                                }
1828                                            }
1829
1830                                            canvas.restore();
1831                                        }
1832
1833                                        ImageOrSvg::Svg(svg) => {
1834                                            canvas.save_layer(&SaveLayerRec::default());
1835                                            canvas.translate((bounds.x, bounds.y));
1836                                            let (scale_x, scale_y) = (
1837                                                bounds.width() / svg.inner().fContainerSize.fWidth,
1838                                                bounds.height()
1839                                                    / svg.inner().fContainerSize.fHeight,
1840                                            );
1841
1842                                            if scale_x.is_finite() && scale_y.is_finite() {
1843                                                canvas.scale((scale_x, scale_y));
1844                                            } else {
1845                                                svg.clone().set_container_size((
1846                                                    bounds.width(),
1847                                                    bounds.height(),
1848                                                ));
1849                                            }
1850
1851                                            svg.render(canvas);
1852
1853                                            if let Some(color) = self.style.fill.get_resolved(
1854                                                self.current,
1855                                                &self.style.custom_color_props,
1856                                            ) {
1857                                                // Escape hatch for multi-color SVGs (logos,
1858                                                // illustrations) that would otherwise be flooded
1859                                                // by the root-level `fill: var(--foreground)`
1860                                                // tint in the default theme. Setting
1861                                                // `fill: transparent` on such elements makes the
1862                                                // SrcIn pass a no-op, so the SVG's own path
1863                                                // fills render untouched. Icon SVGs that want
1864                                                // the tint still work — they set a
1865                                                // non-transparent fill explicitly. See #636.
1866                                                if color.a() != 0 {
1867                                                    let mut paint = Paint::default();
1868                                                    paint.set_anti_alias(true);
1869                                                    paint.set_blend_mode(
1870                                                        skia_safe::BlendMode::SrcIn,
1871                                                    );
1872                                                    paint.set_color(color);
1873                                                    canvas.draw_paint(&paint);
1874                                                }
1875                                            }
1876                                            canvas.restore();
1877                                        }
1878                                    }
1879                                }
1880                            }
1881                        }
1882                    }
1883                }
1884            }
1885        }
1886    }
1887
1888    /// Draw any text for the current view.
1889    pub fn draw_text(&mut self, canvas: &Canvas) {
1890        if let Some(paragraph) = self.text_context.text_paragraphs.get(self.current) {
1891            let bounds = self.bounds();
1892
1893            let alignment = self.alignment();
1894
1895            let (mut top, _) = match alignment {
1896                Alignment::TopLeft => (0.0, 0.0),
1897                Alignment::TopCenter => (0.0, 0.5),
1898                Alignment::TopRight => (0.0, 1.0),
1899                Alignment::Left => (0.5, 0.0),
1900                Alignment::Center => (0.5, 0.5),
1901                Alignment::Right => (0.5, 1.0),
1902                Alignment::BottomLeft => (1.0, 0.0),
1903                Alignment::BottomCenter => (1.0, 0.5),
1904                Alignment::BottomRight => (1.0, 1.0),
1905            };
1906
1907            let padding_top = match self.padding_top() {
1908                Units::Pixels(val) => val,
1909                _ => 0.0,
1910            };
1911
1912            let padding_bottom = match self.padding_bottom() {
1913                Units::Pixels(val) => val,
1914                _ => 0.0,
1915            };
1916
1917            top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
1918
1919            let mut padding_left = match self.padding_left() {
1920                Units::Pixels(val) => val,
1921                _ => 0.0,
1922            };
1923
1924            let mut padding_right = match self.padding_right() {
1925                Units::Pixels(val) => val,
1926                _ => 0.0,
1927            };
1928
1929            if resolved_text_direction(self.style, self.current) == Direction::RightToLeft {
1930                std::mem::swap(&mut padding_left, &mut padding_right);
1931            }
1932
1933            paragraph.paint(
1934                canvas,
1935                ((bounds.x + padding_left).round(), (bounds.y + padding_top + top).round()),
1936            );
1937        }
1938    }
1939}
1940
1941impl DataContext for DrawContext<'_> {
1942    fn try_data<T: 'static>(&self) -> Option<&T> {
1943        // Return data for the static model.
1944        if let Some(t) = <dyn Any>::downcast_ref::<T>(&()) {
1945            return Some(t);
1946        }
1947
1948        for entity in self.current.parent_iter(self.tree) {
1949            // Return model data.
1950            if let Some(models) = self.models.get(&entity) {
1951                if let Some(model) = models.get(&TypeId::of::<T>()) {
1952                    return model.downcast_ref::<T>();
1953                }
1954            }
1955
1956            // Return view data.
1957            if let Some(view_handler) = self.views.get(&entity) {
1958                if let Some(data) = view_handler.downcast_ref::<T>() {
1959                    return Some(data);
1960                }
1961            }
1962        }
1963
1964        None
1965    }
1966}