rive_rs/shapes/paint/
linear_gradient.rs
1use std::cell::Cell;
6
7use crate::component::Component;
8use crate::component_dirt::ComponentDirt;
9use crate::container_component::ContainerComponent;
10use crate::core::{Core, CoreContext, Object, ObjectRef, OnAdded, Property};
11use crate::dyn_vec::DynVec;
12use crate::node::Node;
13use crate::option_cell::OptionCell;
14use crate::renderer::{GradientBuilder, GradientType};
15use crate::shapes::paint::shape_paint_mutator::ShapePaintMutator;
16use crate::shapes::paint::{GradientStop, RadialGradient};
17use crate::shapes::ShapePaintContainer;
18use crate::status_code::StatusCode;
19use crate::{math, TransformComponent};
20
21#[derive(Debug)]
22pub struct LinearGradient {
23 container_component: ContainerComponent,
24 shape_paint_mutator: ShapePaintMutator,
25 start_x: Property<f32>,
26 start_y: Property<f32>,
27 end_x: Property<f32>,
28 end_y: Property<f32>,
29 opacity: Property<f32>,
30 stops: DynVec<Object<GradientStop>>,
31 paints_in_world_space: Cell<bool>,
32 shape_paint_container: OptionCell<Object<Node>>,
33}
34
35impl ObjectRef<'_, LinearGradient> {
36 pub fn start_x(&self) -> f32 {
37 self.start_x.get()
38 }
39
40 pub fn set_start_x(&self, start_x: f32) {
41 if self.start_x() == start_x {
42 return;
43 }
44
45 self.start_x.set(start_x);
46 self.cast::<Component>().add_dirt(ComponentDirt::TRANSFORM, false);
47 }
48
49 pub fn start_y(&self) -> f32 {
50 self.start_y.get()
51 }
52
53 pub fn set_start_y(&self, start_y: f32) {
54 if self.start_y() == start_y {
55 return;
56 }
57
58 self.start_y.set(start_y);
59 self.cast::<Component>().add_dirt(ComponentDirt::TRANSFORM, false);
60 }
61
62 pub fn end_x(&self) -> f32 {
63 self.end_x.get()
64 }
65
66 pub fn set_end_x(&self, end_x: f32) {
67 if self.end_x() == end_x {
68 return;
69 }
70
71 self.end_x.set(end_x);
72 self.cast::<Component>().add_dirt(ComponentDirt::TRANSFORM, false);
73 }
74
75 pub fn end_y(&self) -> f32 {
76 self.end_y.get()
77 }
78
79 pub fn set_end_y(&self, end_y: f32) {
80 if self.end_y() == end_y {
81 return;
82 }
83
84 self.end_y.set(end_y);
85 self.cast::<Component>().add_dirt(ComponentDirt::TRANSFORM, false);
86 }
87
88 pub fn opacity(&self) -> f32 {
89 self.opacity.get()
90 }
91
92 pub fn set_opacity(&self, opacity: f32) {
93 if self.opacity() == opacity {
94 return;
95 }
96
97 self.opacity.set(opacity);
98 self.mark_gradient_dirty();
99 }
100}
101
102impl ObjectRef<'_, LinearGradient> {
103 pub fn push_stop(&self, stop: Object<GradientStop>) {
104 self.stops.push(stop);
105 }
106
107 pub fn paints_in_world_space(&self) -> bool {
108 self.paints_in_world_space.get()
109 }
110
111 pub fn set_paints_in_world_space(&self, paints_in_world_space: bool) {
112 if self.paints_in_world_space() != paints_in_world_space {
113 self.paints_in_world_space.set(paints_in_world_space);
114 self.cast::<Component>().add_dirt(ComponentDirt::PAINT, false);
115 }
116 }
117
118 pub fn mark_gradient_dirty(&self) {
119 self.cast::<Component>().add_dirt(ComponentDirt::PAINT, false);
120 }
121
122 pub fn mark_stops_dirty(&self) {
123 self.cast::<Component>().add_dirt(ComponentDirt::PAINT & ComponentDirt::STOPS, false);
124 }
125
126 pub fn build_dependencies(&self) {
127 let component = self.cast::<Component>();
128 let parent = component.parent();
129
130 if let Some(parents_parent) =
131 parent.and_then(|parent| parent.cast::<Component>().as_ref().parent())
132 {
133 let object: Object = Object::from(parents_parent.clone());
135 assert!(Object::<ShapePaintContainer>::try_from(object).is_ok());
136
137 self.shape_paint_container.set(parents_parent.try_cast::<Node>());
142 parents_parent.cast::<Component>().as_ref().push_dependent(component.as_object());
143 }
144 }
145
146 pub fn update(&self, value: ComponentDirt) {
147 let mut builder = GradientBuilder::new(match self.try_cast::<RadialGradient>() {
148 Some(_) => GradientType::Radial,
149 None => GradientType::Linear,
150 });
151
152 if Component::value_has_dirt(value, ComponentDirt::STOPS) {
154 self.stops
155 .sort_by(|a, b| a.as_ref().position().partial_cmp(&b.as_ref().position()).unwrap());
156 }
157
158 let world_transformed = Component::value_has_dirt(value, ComponentDirt::WORLD_TRANSFORM);
159 let rebuild_gradient = Component::value_has_dirt(
160 value,
161 ComponentDirt::PAINT | ComponentDirt::RENDER_OPACITY | ComponentDirt::TRANSFORM,
162 ) || self.paints_in_world_space() && world_transformed;
163
164 if !rebuild_gradient {
165 return;
166 }
167
168 let start = math::Vec::new(self.start_x(), self.start_y());
169 let end = math::Vec::new(self.end_x(), self.end_y());
170
171 match (self.paints_in_world_space(), self.shape_paint_container.get()) {
175 (true, Some(shape_paint_container)) => {
176 let world =
179 shape_paint_container.cast::<TransformComponent>().as_ref().world_transform();
180 let world_start = world * start;
181 let world_end = world * end;
182
183 builder.start(world_start).end(world_end);
184 }
185 _ => {
186 builder.start(start).end(end);
187 }
188 }
189
190 let ro = self.opacity() * self.cast::<ShapePaintMutator>().render_opacity();
191 for stop in self.stops.iter() {
192 builder.push_stop(stop.as_ref().color().mul_opacity(ro), stop.as_ref().position());
193 }
194
195 self.cast::<ShapePaintMutator>().set_gradient(builder.build());
196 }
197}
198
199impl Core for LinearGradient {
200 parent_types![
201 (container_component, ContainerComponent),
202 (shape_paint_mutator, ShapePaintMutator),
203 ];
204
205 properties![
206 (42, start_x, set_start_x),
207 (33, start_y, set_start_y),
208 (34, end_x, set_end_x),
209 (35, end_y, set_end_y),
210 (46, opacity, set_opacity),
211 container_component,
212 ];
213}
214
215impl OnAdded for ObjectRef<'_, LinearGradient> {
216 on_added!([on_added_clean, import], ContainerComponent);
217
218 fn on_added_dirty(&self, context: &dyn CoreContext) -> StatusCode {
219 let code = self.cast::<ContainerComponent>().on_added_dirty(context);
220 if code != StatusCode::Ok {
221 return code;
222 }
223
224 if let Some(parent) =
225 self.cast::<Component>().parent().and_then(|parent| parent.try_cast::<Component>())
226 {
227 self.cast::<ShapePaintMutator>().init_paint_mutator(parent);
228 StatusCode::Ok
229 } else {
230 StatusCode::MissingObject
231 }
232 }
233}
234
235impl Default for LinearGradient {
236 fn default() -> Self {
237 Self {
238 container_component: ContainerComponent::default(),
239 shape_paint_mutator: ShapePaintMutator::default(),
240 start_x: Property::new(0.0),
241 start_y: Property::new(0.0),
242 end_x: Property::new(0.0),
243 end_y: Property::new(0.0),
244 opacity: Property::new(1.0),
245 stops: DynVec::new(),
246 paints_in_world_space: Cell::new(false),
247 shape_paint_container: OptionCell::new(),
248 }
249 }
250}