rive_rs/shapes/
clipping_shape.rs
1use 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 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 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}