ferroui_base/media/
path_figure.rs1use crate::media::media_collection::track_item_property_changed;
2use crate::media::PathSegments;
3use crate::platform::IGeometryContext;
4use crate::reactive::{Disposable, IDisposable};
5use crate::utilities::HandlerList;
6use crate::{
7 ferro_class, ferro_property, instantiate, DirectProperty, FerroObject, FerroObjectImpl, FerroObjectImplExt,
8 FerroProperty, Point, Ref, StyledProperty,
9};
10use std::cell::RefCell;
11use std::fmt;
12use std::rc::Rc;
13
14#[repr(C)]
17pub struct PathFigure {
18 base: FerroObject,
19 segments_invalidated: HandlerList<dyn Fn()>,
20 segments: RefCell<Option<PathSegments>>,
21 segments_disposable: RefCell<Option<Rc<dyn IDisposable>>>,
22 segments_properties_disposable: RefCell<Option<Rc<dyn IDisposable>>>,
23}
24
25ferro_class!(PathFigure: FerroObject);
26crate::ferro_class_info!(PathFigure { new: PathFigure::new });
27
28impl FerroObjectImpl for PathFigure {
29 fn constructed(this: &Self) {
30 Self::parent_constructed(this);
31 this.set_segments(Some(PathSegments::new()));
32 }
33}
34
35crate::ferro_properties! { impl PathFigure {
36 ferro_property!(pub fn is_closed_property() -> StyledProperty<bool> {
37 FerroProperty::register::<PathFigure, _>("IsClosed", true)
38 });
39
40 ferro_property!(pub fn is_filled_property() -> StyledProperty<bool> {
41 FerroProperty::register::<PathFigure, _>("IsFilled", true)
42 });
43
44 ferro_property!(pub fn segments_property() -> DirectProperty<PathFigure, Option<PathSegments>> {
45 FerroProperty::register_direct::<PathFigure, _>(
46 "Segments",
47 |f| f.segments.borrow().clone(),
48 Some(|f, s| f.set_segments(s)),
49 None,
50 )
51 });
52
53 ferro_property!(pub fn start_point_property() -> StyledProperty<Point> {
54 FerroProperty::register::<PathFigure, _>("StartPoint", Point::default())
55 });
56} }
57
58impl PathFigure {
59 fn static_constructor() {
60 Self::segments_property().changed().add_class_handler::<PathFigure>(|s, _| s.on_segments_changed());
61 }
62
63 pub fn construct() -> Self {
65 Self {
66 base: FerroObject::construct(),
67 segments_invalidated: HandlerList::new(),
68 segments: RefCell::new(None),
69 segments_disposable: RefCell::new(None),
70 segments_properties_disposable: RefCell::new(None),
71 }
72 }
73
74 pub fn new() -> Ref<Self> {
76 instantiate(Self::construct())
77 }
78
79 pub(crate) fn segments_invalidated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
82 let token = self.segments_invalidated.add(Rc::new(handler));
83 let weak = self.to_ref().downgrade();
84 Disposable::create(move || {
85 if let Some(this) = weak.upgrade() {
86 this.segments_invalidated.remove(token);
87 }
88 })
89 }
90
91 fn on_segments_changed(&self) {
92 if let Some(disposable) = self.segments_disposable.take() {
93 disposable.dispose();
94 }
95 if let Some(disposable) = self.segments_properties_disposable.take() {
96 disposable.dispose();
97 }
98
99 let segments = self.segments.borrow().clone();
100 if let Some(segments) = segments {
101 let weak = self.to_ref().downgrade();
102 let invalidate = move || {
103 if let Some(this) = weak.upgrade() {
104 this.invalidate_segments();
105 }
106 };
107
108 let (added, removed, reset) = (invalidate.clone(), invalidate.clone(), invalidate.clone());
109 let disposable = segments.for_each_item(move |_, _| added(), move |_, _| removed(), reset);
110 *self.segments_disposable.borrow_mut() = Some(disposable);
111
112 let disposable = track_item_property_changed(&segments, invalidate);
113 *self.segments_properties_disposable.borrow_mut() = Some(disposable);
114 }
115 }
116
117 fn invalidate_segments(&self) {
118 if self.segments_invalidated.is_empty() {
119 return;
120 }
121 for (_, handler) in self.segments_invalidated.snapshot().iter() {
122 handler();
123 }
124 }
125
126 pub fn is_closed(&self) -> bool {
128 self.get_value(Self::is_closed_property())
129 }
130
131 pub fn set_is_closed(&self, value: bool) {
132 self.set_value(Self::is_closed_property(), value)
133 }
134
135 pub fn is_filled(&self) -> bool {
137 self.get_value(Self::is_filled_property())
138 }
139
140 pub fn set_is_filled(&self, value: bool) {
141 self.set_value(Self::is_filled_property(), value)
142 }
143
144 pub fn segments(&self) -> Option<PathSegments> {
146 self.segments.borrow().clone()
147 }
148
149 pub fn set_segments(&self, value: Option<PathSegments>) {
150 self.set_and_raise(Self::segments_property(), &self.segments, value);
151 }
152
153 pub fn start_point(&self) -> Point {
155 self.get_value(Self::start_point_property())
156 }
157
158 pub fn set_start_point(&self, value: Point) {
159 self.set_value(Self::start_point_property(), value)
160 }
161
162 pub(crate) fn apply_to(&self, ctx: &mut dyn IGeometryContext) {
164 ctx.begin_figure(self.start_point(), self.is_filled());
165
166 if let Some(segments) = self.segments() {
167 for segment in segments.iter() {
168 segment.apply_to(ctx);
169 }
170 }
171
172 ctx.end_figure(self.is_closed());
173 }
174}
175
176impl fmt::Display for PathFigure {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 let segments: Vec<String> = match self.segments() {
180 Some(segments) => segments.iter().map(|segment| segment.to_string()).collect(),
181 None => Vec::new(),
182 };
183 write!(f, "M {} {}{}", self.start_point(), segments.join(" "), if self.is_closed() { "Z" } else { "" })
184 }
185}