1use crate::{Control, ControlImpl};
2use ferroui_base::input::InputElementImpl;
3use ferroui_base::interactivity::InteractiveImpl;
4use ferroui_base::layout::LayoutableImpl;
5use ferroui_base::media::{
6 DrawingContext, Geometry, IBrush, IPen, MatrixTransform, MediaCollection, Pen, PenLineCap, PenLineJoin, Stretch,
7 StrokeDashArray,
8};
9use ferroui_base::reactive::IDisposable;
10use ferroui_base::{
11 ferro_class, ferro_impl_classes, ferro_property, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
12 FerroPropertyChangedEventArgs, Matrix, ObjectType, Rect, Ref, Size, StyledElementImpl, StyledProperty, Upcast,
13 Vector, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
14};
15use std::cell::{Cell, RefCell};
16use std::rc::Rc;
17
18#[repr(C)]
21pub struct Shape {
22 base: Control,
23 transform: Cell<Matrix>,
24 defining_geometry: RefCell<Option<Ref<Geometry>>>,
25 rendered_geometry: RefCell<Option<Ref<Geometry>>>,
26 stroke_pen: RefCell<Option<Rc<dyn IPen>>>,
27 geometry_changed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
28}
29
30ferro_class! {
31 Shape: Control, virtuals ShapeImpl: ControlImpl {
32 fn create_defining_geometry(this) -> Option<Ref<Geometry>>;
34
35 fn on_geometry_changed(this);
37 }
38}
39
40ferro_impl_classes!(Shape: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
41
42impl ShapeImpl for Shape {
43 fn create_defining_geometry(_this: &Self) -> Option<Ref<Geometry>> {
44 panic!("Shape is abstract: 'create_defining_geometry' must be implemented by the deriving class")
45 }
46
47 fn on_geometry_changed(this: &Self) {
48 *this.rendered_geometry.borrow_mut() = None;
49
50 this.invalidate_measure();
51 }
52}
53
54impl FerroObjectImpl for Shape {
55 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
56 Self::parent_on_property_changed(this, change);
57
58 let property = change.property();
59 let is_stroke = property == Self::stroke_property().as_property();
60 let is_stroke_thickness = property == Self::stroke_thickness_property().as_property();
61
62 if is_stroke
63 || is_stroke_thickness
64 || property == Self::stroke_dash_array_property().as_property()
65 || property == Self::stroke_dash_offset_property().as_property()
66 || property == Self::stroke_line_cap_property().as_property()
67 || property == Self::stroke_join_property().as_property()
68 || property == Self::stroke_miter_limit_property().as_property()
69 {
70 if is_stroke || is_stroke_thickness {
71 this.invalidate_measure();
72 }
73
74 let mut pen = this.stroke_pen.borrow().clone();
77 let dash_array = this.stroke_dash_array();
78 let changed = Pen::try_modify_or_create(
79 &mut pen,
80 this.stroke(),
81 this.stroke_thickness(),
82 dash_array.as_ref().map(StrokeDashArray::Observable),
83 this.stroke_dash_offset(),
84 this.stroke_line_cap(),
85 this.stroke_join(),
86 this.stroke_miter_limit(),
87 );
88 *this.stroke_pen.borrow_mut() = pen;
89
90 if changed {
91 this.invalidate_visual();
92 }
93 } else if property == Self::fill_property().as_property() {
94 this.invalidate_visual();
95 }
96 }
97}
98
99impl VisualImpl for Shape {
100 fn render(this: &Self, context: &mut DrawingContext) {
101 let geometry = this.rendered_geometry();
102
103 if let Some(geometry) = geometry {
104 let fill = this.fill();
105 let pen = this.stroke_pen.borrow().clone();
106 context.draw_geometry(fill.as_ref(), pen.as_ref(), &geometry);
107 }
108 }
109
110 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
111 Self::parent_on_attached_to_visual_tree(this, e);
112
113 let defining_geometry = this.defining_geometry.borrow().clone();
114 if let Some(defining_geometry) = defining_geometry {
115 this.subscribe_to_geometry(&defining_geometry);
116 }
117 }
118
119 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
120 Self::parent_on_detached_from_visual_tree(this, e);
121
122 if this.defining_geometry.borrow().is_some() {
123 this.unsubscribe_from_geometry();
124 }
125 }
126}
127
128impl LayoutableImpl for Shape {
129 fn measure_override(this: &Self, available_size: Size) -> Size {
130 let Some(defining_geometry) = this.defining_geometry() else {
131 return Size::default();
132 };
133
134 Self::calculate_size_and_transform(available_size, defining_geometry.bounds(), this.stretch()).0
135 }
136
137 fn arrange_override(this: &Self, final_size: Size) -> Size {
138 if let Some(defining_geometry) = this.defining_geometry() {
139 let (_, transform) =
143 Self::calculate_size_and_transform(final_size, defining_geometry.bounds(), this.stretch());
144
145 if this.transform.get() != transform {
146 this.transform.set(transform);
147 *this.rendered_geometry.borrow_mut() = None;
148 }
149
150 return final_size;
151 }
152
153 Size::default()
154 }
155}
156
157ferroui_base::ferro_properties! { impl Shape {
158 ferro_property!(
159 pub fn fill_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
161 FerroProperty::register::<Shape, _>("Fill", None)
162 }
163 );
164
165 ferro_property!(
166 pub fn stretch_property() -> StyledProperty<Stretch> {
168 FerroProperty::register::<Shape, _>("Stretch", Stretch::None)
169 }
170 );
171
172 ferro_property!(
173 pub fn stroke_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
175 FerroProperty::register::<Shape, _>("Stroke", None)
176 }
177 );
178
179 ferro_property!(
180 pub fn stroke_dash_array_property() -> StyledProperty<Option<MediaCollection<f64>>> {
182 FerroProperty::register::<Shape, _>("StrokeDashArray", None)
183 }
184 );
185
186 ferro_property!(
187 pub fn stroke_dash_offset_property() -> StyledProperty<f64> {
189 FerroProperty::register::<Shape, _>("StrokeDashOffset", 0.0)
190 }
191 );
192
193 ferro_property!(
194 pub fn stroke_thickness_property() -> StyledProperty<f64> {
196 FerroProperty::register::<Shape, _>("StrokeThickness", 0.0)
197 }
198 );
199
200 ferro_property!(
201 pub fn stroke_line_cap_property() -> StyledProperty<PenLineCap> {
203 FerroProperty::register::<Shape, _>("StrokeLineCap", PenLineCap::Flat)
204 }
205 );
206
207 ferro_property!(
208 pub fn stroke_join_property() -> StyledProperty<PenLineJoin> {
210 FerroProperty::register::<Shape, _>("StrokeJoin", PenLineJoin::Miter)
211 }
212 );
213
214 ferro_property!(
215 pub fn stroke_miter_limit_property() -> StyledProperty<f64> {
217 FerroProperty::register::<Shape, _>("StrokeMiterLimit", 10.0)
218 }
219 );
220} }
221
222impl Shape {
223 pub fn construct() -> Self {
225 Self {
226 base: Control::construct(),
227 transform: Cell::new(Matrix::IDENTITY),
228 defining_geometry: RefCell::new(None),
229 rendered_geometry: RefCell::new(None),
230 stroke_pen: RefCell::new(None),
231 geometry_changed_subscription: RefCell::new(None),
232 }
233 }
234
235 pub fn defining_geometry(&self) -> Option<Ref<Geometry>> {
237 let current = self.defining_geometry.borrow().clone();
238 if current.is_some() {
239 return current;
240 }
241
242 let created = self.create_defining_geometry();
243 *self.defining_geometry.borrow_mut() = created.clone();
244
245 if let Some(geometry) = &created {
246 if self.visual_root().is_some() {
247 self.subscribe_to_geometry(geometry);
248 }
249 }
250
251 created
252 }
253
254 pub fn rendered_geometry(&self) -> Option<Ref<Geometry>> {
257 let current = self.rendered_geometry.borrow().clone();
258 if current.is_some() {
259 return current;
260 }
261
262 let defining_geometry = self.defining_geometry()?;
263 let transform = self.transform.get();
264
265 let rendered = if transform == Matrix::IDENTITY {
266 defining_geometry
267 } else {
268 let rendered = defining_geometry.clone_geometry();
269
270 match rendered.transform().map(|t| t.value()) {
271 Some(value) if value != Matrix::IDENTITY => {
272 rendered.set_transform(MatrixTransform::with_matrix(value * transform));
273 }
274 _ => rendered.set_transform(MatrixTransform::with_matrix(transform)),
275 }
276
277 rendered
278 };
279
280 *self.rendered_geometry.borrow_mut() = Some(rendered.clone());
281 Some(rendered)
282 }
283
284 pub fn fill(&self) -> Option<Rc<dyn IBrush>> {
286 self.get_value(Self::fill_property())
287 }
288
289 pub fn set_fill(&self, value: Option<Rc<dyn IBrush>>) {
291 self.set_value(Self::fill_property(), value)
292 }
293
294 pub fn stretch(&self) -> Stretch {
297 self.get_value(Self::stretch_property())
298 }
299
300 pub fn set_stretch(&self, value: Stretch) {
303 self.set_value(Self::stretch_property(), value)
304 }
305
306 pub fn stroke(&self) -> Option<Rc<dyn IBrush>> {
308 self.get_value(Self::stroke_property())
309 }
310
311 pub fn set_stroke(&self, value: Option<Rc<dyn IBrush>>) {
313 self.set_value(Self::stroke_property(), value)
314 }
315
316 pub fn stroke_dash_array(&self) -> Option<MediaCollection<f64>> {
319 self.get_value(Self::stroke_dash_array_property())
320 }
321
322 pub fn set_stroke_dash_array(&self, value: Option<MediaCollection<f64>>) {
325 self.set_value(Self::stroke_dash_array_property(), value)
326 }
327
328 pub fn stroke_dash_offset(&self) -> f64 {
331 self.get_value(Self::stroke_dash_offset_property())
332 }
333
334 pub fn set_stroke_dash_offset(&self, value: f64) {
337 self.set_value(Self::stroke_dash_offset_property(), value)
338 }
339
340 pub fn stroke_thickness(&self) -> f64 {
342 self.get_value(Self::stroke_thickness_property())
343 }
344
345 pub fn set_stroke_thickness(&self, value: f64) {
347 self.set_value(Self::stroke_thickness_property(), value)
348 }
349
350 pub fn stroke_line_cap(&self) -> PenLineCap {
353 self.get_value(Self::stroke_line_cap_property())
354 }
355
356 pub fn set_stroke_line_cap(&self, value: PenLineCap) {
359 self.set_value(Self::stroke_line_cap_property(), value)
360 }
361
362 pub fn stroke_join(&self) -> PenLineJoin {
365 self.get_value(Self::stroke_join_property())
366 }
367
368 pub fn set_stroke_join(&self, value: PenLineJoin) {
371 self.set_value(Self::stroke_join_property(), value)
372 }
373
374 pub fn stroke_miter_limit(&self) -> f64 {
377 self.get_value(Self::stroke_miter_limit_property())
378 }
379
380 pub fn set_stroke_miter_limit(&self, value: f64) {
383 self.set_value(Self::stroke_miter_limit_property(), value)
384 }
385
386 pub fn affects_geometry<T: ObjectType + Upcast<Shape>>(properties: &[&'static FerroProperty]) {
393 for property in properties {
394 property.changed().subscribe(|e| {
395 if let Some(sender) = e.sender().downcast_ref::<T>() {
396 let shape: &Shape = sender.upcast();
397 Shape::affects_geometry_invalidate(shape, e);
398 }
399 });
400 }
401 }
402
403 pub fn invalidate_geometry(&self) {
405 if self.defining_geometry.borrow().is_some() {
406 self.unsubscribe_from_geometry();
407 }
408
409 *self.rendered_geometry.borrow_mut() = None;
410 *self.defining_geometry.borrow_mut() = None;
411
412 self.invalidate_measure();
413 }
414
415 pub(crate) fn calculate_size_and_transform(
418 available_size: Size,
419 shape_bounds: Rect,
420 stretch: Stretch,
421 ) -> (Size, Matrix) {
422 let mut shape_size = Size::new(shape_bounds.right(), shape_bounds.bottom());
423 let mut translate = Matrix::IDENTITY;
424 let mut desired_x = available_size.width;
425 let mut desired_y = available_size.height;
426 let mut sx = 0.0;
427 let mut sy = 0.0;
428
429 if stretch != Stretch::None {
430 shape_size = shape_bounds.size();
431 translate = Matrix::create_translation_vector(-Vector::from(shape_bounds.position()));
432 }
433
434 if available_size.width.is_infinite() {
435 desired_x = shape_size.width;
436 }
437
438 if available_size.height.is_infinite() {
439 desired_y = shape_size.height;
440 }
441
442 if shape_bounds.width > 0.0 {
443 sx = desired_x / shape_size.width;
444 }
445
446 if shape_bounds.height > 0.0 {
447 sy = desired_y / shape_size.height;
448 }
449
450 if available_size.width.is_infinite() {
451 sx = sy;
452 }
453
454 if available_size.height.is_infinite() {
455 sy = sx;
456 }
457
458 match stretch {
459 Stretch::Uniform => {
460 sx = f64::min(sx, sy);
461 sy = sx;
462 }
463 Stretch::UniformToFill => {
464 sx = f64::max(sx, sy);
465 sy = sx;
466 }
467 Stretch::Fill => {
468 if available_size.width.is_infinite() {
469 sx = 1.0;
470 }
471
472 if available_size.height.is_infinite() {
473 sy = 1.0;
474 }
475 }
476 Stretch::None => {
477 sx = 1.0;
478 sy = 1.0;
479 }
480 }
481
482 let transform = translate * Matrix::create_scale(sx, sy);
483 let size = Size::new(shape_size.width * sx, shape_size.height * sy);
484 (size, transform)
485 }
486
487 fn affects_geometry_invalidate(control: &Shape, e: &FerroPropertyChangedEventArgs<'_>) {
488 if e.property() == Visual::bounds_property().as_property() {
491 let (old_bounds, new_bounds) = e.get_old_and_new_value::<Rect>();
492
493 if old_bounds.size() == new_bounds.size() {
494 return;
495 }
496 }
497
498 control.invalidate_geometry();
499 }
500
501 fn subscribe_to_geometry(&self, geometry: &Ref<Geometry>) {
504 let weak = self.to_ref().downgrade();
505 let subscription = geometry.changed(move || {
506 if let Some(this) = weak.upgrade() {
507 this.on_geometry_changed();
508 }
509 });
510
511 let previous = self.geometry_changed_subscription.replace(Some(subscription));
512 if let Some(previous) = previous {
513 previous.dispose();
514 }
515 }
516
517 fn unsubscribe_from_geometry(&self) {
518 let subscription = self.geometry_changed_subscription.take();
519 if let Some(subscription) = subscription {
520 subscription.dispose();
521 }
522 }
523}