vizia_core/systems/
draw.rs

1use crate::{animation::Interpolator, cache::CachedData, prelude::*};
2use morphorm::Node;
3use skia_safe::{
4    canvas::SaveLayerRec, ClipOp, ImageFilter, Matrix, Paint, Rect, SamplingOptions, Surface,
5};
6use std::cmp::Ordering;
7use std::collections::BinaryHeap;
8use vizia_storage::{DrawChildIterator, LayoutTreeIterator};
9use vizia_style::BlendMode;
10
11pub(crate) fn transform_system(cx: &mut Context) {
12    let iter = LayoutTreeIterator::full(&cx.tree);
13
14    for entity in iter {
15        let bounds = cx.cache.bounds.get(entity).copied().unwrap();
16        if let Some(parent) = cx.tree.get_layout_parent(entity) {
17            let parent_transform = cx.cache.transform.get(parent).copied().unwrap();
18            if let Some(tx) = cx.cache.transform.get_mut(entity) {
19                let scale_factor = cx.style.scale_factor();
20
21                // Apply transform origin.
22                let mut origin = cx
23                    .style
24                    .transform_origin
25                    .get(entity)
26                    .map(|transform_origin| {
27                        let mut origin = skia_safe::Matrix::translate(bounds.top_left());
28                        let offset = transform_origin.as_transform(bounds, scale_factor);
29                        origin = offset * origin;
30                        origin
31                    })
32                    .unwrap_or(skia_safe::Matrix::translate(bounds.center()));
33                // transform = origin * transform;
34                let mut transform = origin;
35                origin = origin.invert().unwrap();
36
37                // Apply translation.
38                if let Some(translate) = cx.style.translate.get(entity) {
39                    transform = transform * translate.as_transform(bounds, scale_factor);
40                }
41
42                // Apply rotation.
43                if let Some(rotate) = cx.style.rotate.get(entity) {
44                    transform = transform * rotate.as_transform(bounds, scale_factor);
45                }
46
47                // Apply scaling.
48                if let Some(scale) = cx.style.scale.get(entity) {
49                    transform = transform * scale.as_transform(bounds, scale_factor);
50                }
51
52                // Apply transform functions.
53                if let Some(transforms) = cx.style.transform.get(entity) {
54                    // Check if the transform is currently animating
55                    // Get the animation state
56                    // Manually interpolate the value to get the overall transform for the current frame
57                    if let Some(animation_state) = cx.style.transform.get_active_animation(entity) {
58                        if let Some(start) = animation_state.keyframes.first() {
59                            if let Some(end) = animation_state.keyframes.last() {
60                                let start_transform =
61                                    start.value.as_transform(bounds, scale_factor);
62                                let end_transform = end.value.as_transform(bounds, scale_factor);
63                                let t = animation_state.t;
64                                let animated_transform = skia_safe::Matrix::interpolate(
65                                    &start_transform,
66                                    &end_transform,
67                                    t,
68                                );
69                                transform = transform * animated_transform;
70                            }
71                        }
72                    } else {
73                        transform = transform * transforms.as_transform(bounds, scale_factor);
74                    }
75                }
76
77                transform = transform * origin;
78
79                *tx = parent_transform * transform;
80            }
81
82            let overflowx = cx.style.overflowx.get(entity).copied().unwrap_or_default();
83            let overflowy = cx.style.overflowy.get(entity).copied().unwrap_or_default();
84
85            let scale = cx.style.scale_factor();
86
87            let clip_bounds = cx
88                .style
89                .clip_path
90                .get(entity)
91                .map(|clip| match clip {
92                    ClipPath::Auto => bounds,
93                    ClipPath::Shape(rect) => bounds.shrink_sides(
94                        rect.3.to_pixels(bounds.w, scale),
95                        rect.0.to_pixels(bounds.h, scale),
96                        rect.1.to_pixels(bounds.w, scale),
97                        rect.2.to_pixels(bounds.h, scale),
98                    ),
99                })
100                .unwrap_or(bounds);
101
102            let root_bounds = BoundingBox::from_min_max(
103                -f32::MAX / 2.0,
104                -f32::MAX / 2.0,
105                f32::MAX / 2.0,
106                f32::MAX / 2.0,
107            );
108
109            let clip_bounds = match (overflowx, overflowy) {
110                (Overflow::Visible, Overflow::Visible) => root_bounds,
111                (Overflow::Hidden, Overflow::Visible) => {
112                    let left = clip_bounds.left();
113                    let right = clip_bounds.right();
114                    let top = root_bounds.top();
115                    let bottom = root_bounds.bottom();
116                    BoundingBox::from_min_max(left, top, right, bottom)
117                }
118                (Overflow::Visible, Overflow::Hidden) => {
119                    let left = root_bounds.left();
120                    let right = root_bounds.right();
121                    let top = clip_bounds.top();
122                    let bottom = clip_bounds.bottom();
123                    BoundingBox::from_min_max(left, top, right, bottom)
124                }
125                (Overflow::Hidden, Overflow::Hidden) => clip_bounds,
126            };
127
128            let transform =
129                cx.cache.transform.get(entity).copied().unwrap_or(Matrix::new_identity());
130
131            let rect: skia_safe::Rect = clip_bounds.into();
132            let clip_bounds: BoundingBox = transform.map_rect(rect).0.into();
133
134            let parent_clip_bounds = cx.cache.clip_path.get(parent).copied().unwrap_or(root_bounds);
135
136            if let Some(clip_path) = cx.cache.clip_path.get_mut(entity) {
137                *clip_path = clip_bounds.intersection(&parent_clip_bounds);
138            } else {
139                cx.cache.clip_path.insert(entity, clip_bounds.intersection(&parent_clip_bounds));
140            }
141        }
142    }
143}
144
145pub(crate) fn draw_system(
146    cx: &mut Context,
147    window_entity: Entity,
148    surface: &mut Surface,
149    dirty_surface: &mut Surface,
150) -> bool {
151    if cx.windows.is_empty() {
152        return false;
153    }
154
155    if !cx.entity_manager.is_alive(window_entity) {
156        return false;
157    }
158
159    transform_system(cx);
160
161    let window = cx.windows.get_mut(&window_entity).unwrap();
162
163    let mut dirty_rect = std::mem::take(&mut window.dirty_rect);
164    let redraw_list = std::mem::take(&mut window.redraw_list);
165
166    // if redraw_list.is_empty() {
167    //     return false;
168    // }
169
170    for &entity in &redraw_list {
171        // Skip binding views
172        if cx.tree.is_ignored(entity) {
173            continue;
174        }
175
176        if cx.tree.is_window(entity) && entity != window_entity {
177            continue;
178        }
179
180        if entity.visible(&cx.style) {
181            let draw_bounds = draw_bounds(&cx.style, &cx.cache, &cx.tree, entity);
182
183            let mut dirty_bounds = draw_bounds;
184
185            if let Some(previous_draw_bounds) = cx.cache.draw_bounds.get(entity) {
186                dirty_bounds = dirty_bounds.union(previous_draw_bounds);
187            }
188
189            if dirty_bounds.w != 0.0 && dirty_bounds.h != 0.0 {
190                if let Some(dr) = &mut dirty_rect {
191                    *dr = dr.union(&dirty_bounds);
192                } else {
193                    dirty_rect = Some(dirty_bounds);
194                }
195            }
196
197            if let Some(dr) = cx.cache.draw_bounds.get_mut(entity) {
198                *dr = draw_bounds;
199            } else {
200                cx.cache.draw_bounds.insert(entity, draw_bounds);
201            }
202        }
203    }
204
205    // if dirty_rect.is_none() {
206    //     return true;
207    // }
208
209    let canvas = dirty_surface.canvas();
210
211    canvas.save();
212
213    if let Some(rect) = dirty_rect.map(Rect::from) {
214        canvas.clip_rect(rect, ClipOp::Intersect, false);
215        canvas.clear(Color::transparent());
216    }
217
218    cx.resource_manager.mark_images_unused();
219
220    let mut queue = BinaryHeap::new();
221    queue.push(ZEntity { index: 0, entity: window_entity, visible: true });
222
223    while let Some(zentity) = queue.pop() {
224        canvas.save();
225        draw_entity(
226            &mut DrawContext {
227                current: zentity.entity,
228                style: &cx.style,
229                cache: &mut cx.cache,
230                tree: &cx.tree,
231                models: &cx.models,
232                views: &mut cx.views,
233                resource_manager: &cx.resource_manager,
234                text_context: &mut cx.text_context,
235                modifiers: &cx.modifiers,
236                mouse: &cx.mouse,
237                windows: &mut cx.windows,
238            },
239            &dirty_rect,
240            canvas,
241            zentity.index,
242            &mut queue,
243            zentity.visible,
244        );
245        canvas.restore();
246    }
247
248    canvas.restore();
249
250    surface.canvas().clear(Color::transparent());
251    dirty_surface.draw(surface.canvas(), (0, 0), SamplingOptions::default(), None);
252
253    // Debug draw dirty rect
254    // if let Some(rect) = dirty_rect.map(Rect::from) {
255    //     let mut paint = Paint::default();
256    //     paint.set_style(skia_safe::PaintStyle::Stroke);
257    //     paint.set_color(Color::red());
258    //     paint.set_stroke_width(1.0);
259    //     surface.canvas().draw_rect(rect, &paint);
260    // }
261
262    true
263}
264
265fn draw_entity(
266    cx: &mut DrawContext,
267    dirty_rect: &Option<BoundingBox>,
268    canvas: &Canvas,
269    current_z: i32,
270    queue: &mut BinaryHeap<ZEntity>,
271    visible: bool,
272) {
273    let current = cx.current;
274
275    // Skip views with display: none.
276    if cx.display() == Display::None {
277        return;
278    }
279
280    let z_index = cx.z_index();
281
282    if z_index > current_z {
283        queue.push(ZEntity { index: z_index, entity: current, visible });
284        return;
285    }
286
287    let backdrop_filter = cx.backdrop_filter();
288    let blend_mode = cx.style.blend_mode.get(current).copied().unwrap_or_default();
289
290    canvas.save();
291    let layer_count =
292        if cx.opacity() != 1.0 || backdrop_filter.is_some() || blend_mode != BlendMode::Normal {
293            let mut paint = Paint::default();
294            paint.set_alpha_f(cx.opacity());
295            paint.set_blend_mode(blend_mode.into());
296
297            let rect: Rect = cx.bounds().into();
298            let mut filter = ImageFilter::crop(rect, None, None).unwrap();
299
300            let slr = if let Some(backdrop_filter) = backdrop_filter {
301                match backdrop_filter {
302                    Filter::Blur(radius) => {
303                        let sigma = radius.to_px().unwrap() * cx.scale_factor() / 2.0;
304                        filter = filter.blur(None, (sigma, sigma), None).unwrap();
305                        SaveLayerRec::default().paint(&paint).backdrop(&filter)
306                    }
307                }
308            } else {
309                SaveLayerRec::default().paint(&paint)
310            };
311
312            Some(canvas.save_layer(&slr))
313        } else {
314            None
315        };
316
317    if let Some(transform) = cx.cache.transform.get(current) {
318        canvas.set_matrix(&(transform.into()));
319    }
320
321    if let Some(clip_path) = cx.clip_path() {
322        canvas.clip_path(&clip_path, ClipOp::Intersect, true);
323    }
324
325    let is_visible = match (visible, cx.visibility()) {
326        (v, None) => v,
327        (_, Some(Visibility::Hidden)) => false,
328        (_, Some(Visibility::Visible)) => true,
329    };
330
331    // Draw the view
332    if is_visible {
333        if let Some(dirty_rect) = dirty_rect {
334            let bounds = draw_bounds(cx.style, cx.cache, cx.tree, current);
335            if bounds.intersects(dirty_rect) {
336                if let Some(view) = cx.views.remove(&current) {
337                    view.draw(cx, canvas);
338                    cx.views.insert(current, view);
339                }
340            }
341        }
342    }
343
344    let child_iter = DrawChildIterator::new(cx.tree, cx.current);
345
346    // Draw its children
347    for child in child_iter {
348        cx.current = child;
349        // TODO: Skip views with zero-sized bounding boxes here? Or let user decide if they want to skip?
350        draw_entity(cx, dirty_rect, canvas, current_z, queue, is_visible);
351    }
352
353    if let Some(count) = layer_count {
354        canvas.restore_to_count(count);
355    }
356    canvas.restore();
357    cx.current = current;
358}
359
360// Must be called after transform and clipping systems to be valid.
361pub(crate) fn draw_bounds(
362    style: &Style,
363    cache: &CachedData,
364    tree: &Tree<Entity>,
365    entity: Entity,
366) -> BoundingBox {
367    let mut layout_bounds = cache.bounds.get(entity).copied().unwrap();
368
369    if let Some(shadows) = style.shadow.get(entity) {
370        for shadow in shadows.iter().filter(|shadow| !shadow.inset) {
371            let mut shadow_bounds = layout_bounds;
372
373            let x = shadow.x_offset.to_px().unwrap() * style.scale_factor();
374            let y = shadow.y_offset.to_px().unwrap() * style.scale_factor();
375
376            shadow_bounds = shadow_bounds.offset(x, y);
377
378            let scale_factor = style.scale_factor();
379
380            if let Some(blur_radius) =
381                shadow.blur_radius.as_ref().map(|br| br.clone().to_px().unwrap() * scale_factor)
382            {
383                shadow_bounds = shadow_bounds.expand(blur_radius);
384            }
385
386            if let Some(spread_radius) =
387                shadow.spread_radius.as_ref().map(|sr| sr.clone().to_px().unwrap() * scale_factor)
388            {
389                shadow_bounds = shadow_bounds.expand(spread_radius * style.scale_factor());
390            }
391
392            layout_bounds = layout_bounds.union(&shadow_bounds);
393        }
394    }
395
396    let mut outline_bounds = layout_bounds;
397
398    if let Some(outline_width) = style.outline_width.get(entity) {
399        outline_bounds = outline_bounds
400            .expand(outline_width.to_pixels(layout_bounds.diagonal(), style.scale_factor()));
401    }
402
403    if let Some(outline_offset) = style.outline_offset.get(entity) {
404        outline_bounds = outline_bounds
405            .expand(outline_offset.to_pixels(layout_bounds.diagonal(), style.scale_factor()));
406    }
407
408    layout_bounds = layout_bounds.union(&outline_bounds);
409
410    let matrix = cache.transform.get(entity).copied().unwrap_or_default();
411
412    let rect: Rect = layout_bounds.into();
413    let tr = matrix.map_rect(rect).0;
414
415    let mut dirty_bounds: BoundingBox = tr.into();
416
417    dirty_bounds = BoundingBox::from_min_max(
418        dirty_bounds.left().floor(),
419        dirty_bounds.top().floor(),
420        dirty_bounds.right().ceil(),
421        dirty_bounds.bottom().ceil(),
422    );
423
424    //
425    if style.overflowx.get(entity).copied().unwrap_or_default() == Overflow::Visible
426        || style.overflowy.get(entity).copied().unwrap_or_default() == Overflow::Visible
427    {
428        let child_iter = DrawChildIterator::new(tree, entity);
429        for child in child_iter {
430            dirty_bounds = dirty_bounds.union(&draw_bounds(style, cache, tree, child));
431        }
432    }
433
434    let z_index = style.z_index.get(entity).copied().unwrap_or_default();
435
436    let parent = tree
437        .get_layout_parent(entity)
438        .unwrap_or(tree.get_parent_window(entity).unwrap_or(Entity::root()));
439    if let Some(clip_bounds) = cache.clip_path.get(parent) {
440        let clip_bounds = &BoundingBox::from_min_max(
441            clip_bounds.left().floor(),
442            clip_bounds.top().floor(),
443            clip_bounds.right().ceil(),
444            clip_bounds.bottom().ceil(),
445        );
446
447        if z_index != 0 {
448            dirty_bounds
449        } else {
450            dirty_bounds.intersection(clip_bounds)
451        }
452    } else {
453        dirty_bounds
454    }
455}
456
457struct ZEntity {
458    pub index: i32,
459    pub entity: Entity,
460    pub visible: bool,
461}
462
463impl Ord for ZEntity {
464    fn cmp(&self, other: &Self) -> Ordering {
465        other.index.cmp(&self.index)
466    }
467}
468impl PartialOrd for ZEntity {
469    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
470        Some(self.cmp(other))
471    }
472}
473impl PartialEq for ZEntity {
474    fn eq(&self, other: &Self) -> bool {
475        self.index == other.index
476    }
477}
478
479impl Eq for ZEntity {}