rive_rs/shapes/paint/
stroke.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::RefCell;
6use std::rc::Rc;
7
8use crate::core::{Core, Object, ObjectRef, OnAdded, Property, TryFromU64};
9use crate::math::Mat;
10use crate::option_cell::OptionCell;
11use crate::renderer::{StrokeStyle, Style};
12use crate::shapes::paint::shape_paint_mutator::ShapePaintMutator;
13use crate::shapes::paint::stroke_effect::{self, StrokeEffect};
14use crate::shapes::paint::ShapePaint;
15use crate::shapes::path_space::PathSpace;
16use crate::shapes::{CommandPath, MetricsPath};
17use crate::{RenderPaint, Renderer};
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum StrokeCap {
21    Butt,
22    Round,
23    Square,
24}
25
26impl Default for StrokeCap {
27    fn default() -> Self {
28        Self::Butt
29    }
30}
31
32impl TryFromU64 for StrokeCap {
33    fn try_from(val: u64) -> Option<Self> {
34        match val {
35            0 => Some(Self::Butt),
36            1 => Some(Self::Round),
37            2 => Some(Self::Square),
38            _ => None,
39        }
40    }
41}
42
43#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub enum StrokeJoin {
45    Miter,
46    Round,
47    Bevel,
48}
49
50impl Default for StrokeJoin {
51    fn default() -> Self {
52        Self::Miter
53    }
54}
55
56impl TryFromU64 for StrokeJoin {
57    fn try_from(val: u64) -> Option<Self> {
58        match val {
59            0 => Some(Self::Miter),
60            1 => Some(Self::Round),
61            2 => Some(Self::Bevel),
62            _ => None,
63        }
64    }
65}
66
67#[derive(Debug)]
68pub struct Stroke {
69    shape_paint: ShapePaint,
70    thickness: Property<f32>,
71    cap: Property<StrokeCap>,
72    join: Property<StrokeJoin>,
73    transform_affects_stroke: Property<bool>,
74    metrics_path: OptionCell<MetricsPath>,
75    effect: OptionCell<Object>,
76    pub(crate) outlined_stroke: OptionCell<CommandPath>,
77}
78
79impl ObjectRef<'_, Stroke> {
80    pub fn thickness(&self) -> f32 {
81        self.thickness.get()
82    }
83
84    pub fn set_thickness(&self, thickness: f32) {
85        if self.thickness() == thickness {
86            return;
87        }
88
89        self.thickness.set(thickness);
90        self.cast::<ShapePaint>().render_paint().borrow_mut().style = self.style();
91        self.outlined_stroke.set(None);
92    }
93
94    pub fn cap(&self) -> StrokeCap {
95        self.cap.get()
96    }
97
98    pub fn set_cap(&self, cap: StrokeCap) {
99        if self.cap() == cap {
100            return;
101        }
102
103        self.cap.set(cap);
104        self.cast::<ShapePaint>().render_paint().borrow_mut().style = self.style();
105        self.outlined_stroke.set(None);
106    }
107
108    pub fn join(&self) -> StrokeJoin {
109        self.join.get()
110    }
111
112    pub fn set_join(&self, join: StrokeJoin) {
113        if self.join() == join {
114            return;
115        }
116
117        self.join.set(join);
118        self.cast::<ShapePaint>().render_paint().borrow_mut().style = self.style();
119        self.outlined_stroke.set(None);
120    }
121
122    pub fn transform_affects_stroke(&self) -> bool {
123        self.transform_affects_stroke.get()
124    }
125
126    pub fn set_transform_affects_stroke(&self, transform_affects_stroke: bool) {
127        self.transform_affects_stroke.set(transform_affects_stroke);
128    }
129}
130
131impl ObjectRef<'_, Stroke> {
132    pub fn init_render_paint(
133        &self,
134        mutator: Object<ShapePaintMutator>,
135    ) -> Option<Rc<RefCell<RenderPaint>>> {
136        let render_paint = self.cast::<ShapePaint>().init_render_paint(mutator).unwrap();
137        render_paint.borrow_mut().style = self.style();
138
139        Some(render_paint)
140    }
141
142    pub(crate) fn style(&self) -> Style {
143        Style::Stroke(StrokeStyle {
144            thickness: self.thickness(),
145            cap: self.cap(),
146            join: self.join(),
147        })
148    }
149
150    pub fn path_space(&self) -> PathSpace {
151        if self.transform_affects_stroke() {
152            PathSpace::LOCAL
153        } else {
154            PathSpace::WORLD
155        }
156    }
157
158    pub fn has_stroke_effect(&self) -> bool {
159        self.effect.get().is_some()
160    }
161
162    pub(crate) fn set_stroke_effect(&self, effect: Option<Object>) {
163        self.effect.set(effect);
164    }
165
166    pub fn invalidate_effects(&self) {
167        if let Some(effect) = self.effect.get() {
168            stroke_effect::as_ref(effect.as_ref()).invalidate_effect();
169        }
170        self.metrics_path.set(None);
171        self.outlined_stroke.set(None);
172    }
173
174    pub fn draw(&self, renderer: &mut impl Renderer, path: &CommandPath, transform: Mat) {
175        if !self.cast::<ShapePaint>().is_visible() {
176            return;
177        }
178
179        let mut new_outline = None;
180        self.outlined_stroke.with(|outline| {
181            if let Some(outline) = outline {
182                renderer.draw(
183                    outline,
184                    transform,
185                    &*self.cast::<ShapePaint>().render_paint().borrow(),
186                );
187            } else {
188                let new_path;
189                let path = if let Some(effect) = self.effect.get() {
190                    let effect = stroke_effect::as_ref(effect.as_ref());
191
192                    self.metrics_path.maybe_init(|| MetricsPath::new(path));
193                    new_path = self
194                        .metrics_path
195                        .with_mut(|metrics_path| effect.effect_path(metrics_path.unwrap()));
196
197                    &new_path
198                } else {
199                    path
200                };
201
202                let outline = path.outline_strokes(&StrokeStyle {
203                    thickness: self.thickness(),
204                    cap: self.cap(),
205                    join: self.join(),
206                });
207
208                renderer.draw(
209                    &outline,
210                    transform,
211                    &*self.cast::<ShapePaint>().render_paint().borrow(),
212                );
213
214                new_outline = Some(outline);
215            }
216        });
217
218        if new_outline.is_some() {
219            self.outlined_stroke.set(new_outline);
220        }
221    }
222}
223
224impl Core for Stroke {
225    parent_types![(shape_paint, ShapePaint)];
226
227    properties![
228        (47, thickness, set_thickness),
229        (48, cap, set_cap),
230        (49, join, set_join),
231        (50, transform_affects_stroke, set_transform_affects_stroke),
232        shape_paint,
233    ];
234}
235
236impl OnAdded for ObjectRef<'_, Stroke> {
237    on_added!(ShapePaint);
238}
239
240impl Default for Stroke {
241    fn default() -> Self {
242        Self {
243            shape_paint: ShapePaint::default(),
244            thickness: Property::new(1.0),
245            cap: Property::new(StrokeCap::Butt),
246            join: Property::new(StrokeJoin::Miter),
247            transform_affects_stroke: Property::new(true),
248            metrics_path: OptionCell::new(),
249            effect: OptionCell::new(),
250            outlined_stroke: OptionCell::new(),
251        }
252    }
253}