rive_rs/shapes/paint/
linear_gradient.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::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            // Parent's parent must be a shape paint container.
134            let object: Object = Object::from(parents_parent.clone());
135            assert!(Object::<ShapePaintContainer>::try_from(object).is_ok());
136
137            // TODO: see if artboard should inherit from some TransformComponent
138            // that can return a world transform. We store the container just for
139            // doing the transform to world in update. If it's the artboard, then
140            // we're already in world so no need to transform.
141            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        // Do the stops need to be re-ordered?
153        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        // Check if we need to update the world space gradient (if there's no
172        // shape container, presumably it's the artboard and we're already in
173        // world).
174        match (self.paints_in_world_space(), self.shape_paint_container.get()) {
175            (true, Some(shape_paint_container)) => {
176                // Get the start and end of the gradient in world coordinates (world
177                // transform of the shape).
178                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}