rive_rs/shapes/
clipping_shape.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::{fmt, iter};
6
7use crate::component::Component;
8use crate::component_dirt::ComponentDirt;
9use crate::core::{Core, CoreContext, Object, ObjectRef, OnAdded, Property};
10use crate::drawable::Drawable;
11use crate::dyn_vec::DynVec;
12use crate::node::Node;
13use crate::option_cell::OptionCell;
14use crate::shapes::{FillRule, PathSpace, Shape};
15use crate::status_code::StatusCode;
16use crate::ContainerComponent;
17
18use super::command_path::CommandPathBuilder;
19use super::{CommandPath, ShapePaintContainer};
20
21pub struct ClippingShape {
22    component: Component,
23    source_id: Property<u64>,
24    fill_rule: Property<FillRule>,
25    is_visible: Property<bool>,
26    shapes: DynVec<Object<Shape>>,
27    source: OptionCell<Object<Node>>,
28    command_path: OptionCell<CommandPath>,
29}
30
31impl fmt::Debug for ClippingShape {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        self.command_path.with(|command_path| {
34            f.debug_struct("ClippingShape")
35                .field("component", &self.component)
36                .field("source_id", &self.source_id)
37                .field("fill_rule", &self.fill_rule)
38                .field("is_visible", &self.is_visible)
39                .field("shapes", &self.shapes)
40                .field("source", &self.source)
41                .field("command_path", &command_path)
42                .finish()
43        })
44    }
45}
46
47impl ObjectRef<'_, ClippingShape> {
48    pub fn source_id(&self) -> u64 {
49        self.source_id.get()
50    }
51
52    pub fn set_source_id(&self, source_id: u64) {
53        self.source_id.set(source_id);
54    }
55
56    pub fn fill_rule(&self) -> FillRule {
57        self.fill_rule.get()
58    }
59
60    pub fn set_fill_rule(&self, fill_rule: FillRule) {
61        self.fill_rule.set(fill_rule);
62    }
63
64    pub fn is_visible(&self) -> bool {
65        self.is_visible.get()
66    }
67
68    pub fn set_is_visible(&self, is_visible: bool) {
69        self.is_visible.set(is_visible);
70    }
71}
72
73impl ObjectRef<'_, ClippingShape> {
74    pub(crate) fn with_command_path(&self, f: impl FnMut(Option<&CommandPath>)) {
75        self.command_path.with(f);
76    }
77
78    pub fn build_dependencies(&self) {
79        for shape in self.shapes.iter() {
80            shape
81                .as_ref()
82                .path_composer()
83                .cast::<Component>()
84                .push_dependent(self.as_object().cast());
85        }
86    }
87
88    pub fn update(&self, value: ComponentDirt) {
89        if Component::value_has_dirt(value, ComponentDirt::PATH | ComponentDirt::WORLD_TRANSFORM) {
90            let mut builder = CommandPathBuilder::new();
91
92            for shape in self.shapes.iter() {
93                shape.as_ref().path_composer().with_world_path(|path| {
94                    builder.path(
95                        path.expect("world_path should already be set on PathComposer"),
96                        None,
97                    );
98                });
99            }
100
101            self.command_path.set(Some(builder.build()));
102        }
103    }
104}
105
106impl Core for ClippingShape {
107    parent_types![(component, Component)];
108
109    properties![
110        (92, source_id, set_source_id),
111        (93, fill_rule, set_fill_rule),
112        (94, is_visible, set_is_visible),
113        component,
114    ];
115}
116
117impl OnAdded for ObjectRef<'_, ClippingShape> {
118    on_added!([import], Component);
119
120    fn on_added_dirty(&self, context: &dyn CoreContext) -> StatusCode {
121        let code = self.cast::<Component>().on_added_dirty(context);
122        if code != StatusCode::Ok {
123            return code;
124        }
125
126        if let Some(node) =
127            context.resolve(self.source_id() as usize).and_then(|object| object.try_cast::<Node>())
128        {
129            self.source.set(Some(node));
130            StatusCode::Ok
131        } else {
132            StatusCode::MissingObject
133        }
134    }
135
136    fn on_added_clean(&self, context: &dyn CoreContext) -> StatusCode {
137        let clipping_holder = self.cast::<Component>().parent();
138        let artboard = context.artboard();
139
140        for object in &*artboard.as_ref().objects() {
141            // Iterate artboard to find drawables that are parented to this clipping
142            // shape, they need to know they'll be clipped by this shape.
143            if let Some(drawable) = object.try_cast::<Drawable>() {
144                let drawable = drawable.as_ref();
145                let mut components = iter::once(drawable.as_object().cast())
146                    .chain(drawable.cast::<Component>().parents());
147
148                if components.any(|component| Some(component) == clipping_holder) {
149                    drawable.push_clipping_shape(self.as_object());
150                }
151            }
152
153            // Iterate artboard to find shapes that are parented to the source,
154            // their paths will need to be RenderPaths in order to be used for
155            // clipping operations.
156            if let Some(shape) = object.try_cast::<Shape>() {
157                let component = object.cast::<ContainerComponent>();
158
159                if Some(component.clone()) != clipping_holder {
160                    let mut parents = iter::once(component.clone())
161                        .chain(component.as_ref().cast::<Component>().parents());
162
163                    let source = self.source.get().map(|source| source.cast());
164
165                    if parents.any(|component| Some(component) == source) {
166                        shape
167                            .as_ref()
168                            .cast::<ShapePaintContainer>()
169                            .add_default_path_space(PathSpace::WORLD | PathSpace::CLIPPING);
170                        self.shapes.push(shape);
171                    }
172                }
173            }
174        }
175
176        StatusCode::Ok
177    }
178}
179
180impl Default for ClippingShape {
181    fn default() -> Self {
182        Self {
183            component: Component::default(),
184            source_id: Property::new(0),
185            fill_rule: Property::new(FillRule::NonZero),
186            is_visible: Property::new(true),
187            shapes: DynVec::new(),
188            source: OptionCell::new(),
189            command_path: OptionCell::new(),
190        }
191    }
192}