Skip to main content

ferroui_base/media/
path_figure.rs

1use 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/// Represents a subsection of a geometry: a single connected series of
15/// segments.
16#[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    /// Creates the class data.
64    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    /// Creates a figure with an empty segments collection.
75    pub fn new() -> Ref<Self> {
76        instantiate(Self::construct())
77    }
78
79    /// Subscribes to invalidation of the figure's segments. Disposing the
80    /// returned handle unsubscribes.
81    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    /// Whether the figure is closed.
127    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    /// Whether the figure is filled.
136    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    /// The segments of the figure.
145    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    /// The start point of the figure.
154    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    /// Draws the figure into `ctx`.
163    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
176/// Writes the path markup of the figure.
177impl 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}