1use 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}