Skip to main content

ferroui_base/media/
drawing_image.rs

1use crate::media::ref_adapter::RefAdapter;
2use crate::media::{Drawing, DrawingContext, IAffectsRender, IImage};
3use crate::reactive::{Disposable, IDisposable};
4use crate::utilities::HandlerList;
5use crate::{
6    ferro_class, ferro_property, instantiate, FerroObject, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
7    FerroPropertyChangedEventArgs, Matrix, Nullable, Rect, Ref, Size, StyledProperty,
8};
9use std::any::Any;
10use std::cell::RefCell;
11use std::rc::Rc;
12
13/// An image that uses a [`Drawing`] for content.
14#[repr(C)]
15pub struct DrawingImage {
16    base: FerroObject,
17    invalidated: HandlerList<dyn Fn()>,
18    drawing_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
19}
20
21ferro_class!(DrawingImage: FerroObject);
22crate::ferro_class_info!(DrawingImage { new: DrawingImage::new, interfaces: [std::rc::Rc<dyn crate::media::IImage>] });
23
24impl FerroObjectImpl for DrawingImage {
25    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
26        Self::parent_on_property_changed(this, change);
27
28        if change.property() == Self::drawing_property().as_property() {
29            let new_value = change.get_new_value::<Option<Ref<Drawing>>>();
30
31            if let Some(subscription) = this.drawing_subscription.take() {
32                subscription.dispose();
33            }
34            if let Some(new_value) = new_value {
35                let weak = this.to_ref().downgrade();
36                let subscription = new_value.invalidated(move || {
37                    if let Some(this) = weak.upgrade() {
38                        this.raise_invalidated();
39                    }
40                });
41                *this.drawing_subscription.borrow_mut() = Some(subscription);
42            }
43
44            this.raise_invalidated();
45        } else if change.property() == Self::viewbox_property().as_property() {
46            this.raise_invalidated();
47        }
48    }
49}
50
51crate::ferro_properties! { impl DrawingImage {
52    ferro_property!(
53        /// Defines the `Drawing` property.
54        pub fn drawing_property() -> StyledProperty<Option<Ref<Drawing>>> {
55            FerroProperty::register::<DrawingImage, _>("Drawing", None)
56        }
57    );
58
59    ferro_property!(
60        /// Defines the `Viewbox` property.
61        pub fn viewbox_property() -> StyledProperty<Option<Rect>> {
62            FerroProperty::register::<DrawingImage, _>("Viewbox", None)
63        }
64    );
65} }
66
67impl DrawingImage {
68    /// Creates the class data.
69    pub fn construct() -> Self {
70        Self {
71            base: FerroObject::construct(),
72            invalidated: HandlerList::new(),
73            drawing_subscription: RefCell::new(None),
74        }
75    }
76
77    pub fn new() -> Ref<Self> {
78        instantiate(Self::construct())
79    }
80
81    /// Creates an image showing `drawing`.
82    pub fn with_drawing(drawing: impl Into<Nullable<Drawing>>) -> Ref<Self> {
83        let result = Self::new();
84        result.set_drawing(drawing);
85        result
86    }
87
88    /// Raised when the image changes.
89    pub fn invalidated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
90        let token = self.invalidated.add(Rc::new(handler));
91        let weak = self.to_ref().downgrade();
92        Disposable::create(move || {
93            if let Some(this) = weak.upgrade() {
94                this.invalidated.remove(token);
95            }
96        })
97    }
98
99    /// Raises the invalidated notification.
100    pub fn raise_invalidated(&self) {
101        if self.invalidated.is_empty() {
102            return;
103        }
104        for (_, handler) in self.invalidated.snapshot().iter() {
105            handler();
106        }
107    }
108
109    /// The drawing content.
110    pub fn drawing(&self) -> Option<Ref<Drawing>> {
111        self.get_value(Self::drawing_property())
112    }
113
114    pub fn set_drawing(&self, value: impl Into<Nullable<Drawing>>) {
115        self.set_value(Self::drawing_property(), value.into().0)
116    }
117
118    /// A rectangular region of the drawing, in device independent pixels, to
119    /// display when rendering this image.
120    ///
121    /// This value can be used to display only part of the drawing, or to
122    /// surround it with empty space. If `None`, the bounds of the drawing
123    /// are used.
124    pub fn viewbox(&self) -> Option<Rect> {
125        self.get_value(Self::viewbox_property())
126    }
127
128    pub fn set_viewbox(&self, value: Option<Rect>) {
129        self.set_value(Self::viewbox_property(), value)
130    }
131
132    /// The size of the image, in device independent pixels.
133    pub fn size(&self) -> Size {
134        self.get_bounds().size()
135    }
136
137    fn get_bounds(&self) -> Rect {
138        match self.viewbox() {
139            Some(viewbox) => viewbox,
140            None => self.drawing().map(|drawing| drawing.get_bounds()).unwrap_or_default(),
141        }
142    }
143
144    /// Draws the part of the drawing inside `source_rect` into `dest_rect`.
145    pub fn draw(&self, context: &mut DrawingContext<'_>, source_rect: Rect, dest_rect: Rect) {
146        let Some(drawing) = self.drawing() else { return };
147        if source_rect.size() == Size::default() || dest_rect.size() == Size::default() {
148            return;
149        }
150
151        let bounds = self.get_bounds();
152
153        if bounds.size() == Size::default() {
154            return;
155        }
156
157        let scale = Matrix::create_scale(dest_rect.width / source_rect.width, dest_rect.height / source_rect.height);
158        let translate = Matrix::create_translation(
159            -source_rect.x + dest_rect.x - bounds.x,
160            -source_rect.y + dest_rect.y - bounds.y,
161        );
162
163        let clip = context.push_clip(dest_rect);
164        let transform = context.push_transform(translate * scale);
165        drawing.draw(context);
166        context.pop(transform);
167        context.pop(clip);
168    }
169}
170
171impl IImage for RefAdapter<DrawingImage> {
172    fn size(&self) -> Size {
173        self.0.size()
174    }
175
176    fn draw(&self, context: &mut DrawingContext<'_>, source_rect: Rect, dest_rect: Rect) {
177        self.0.draw(context, source_rect, dest_rect)
178    }
179
180    fn as_any(&self) -> &dyn Any {
181        &*self.0
182    }
183
184    fn as_object(&self) -> Option<&FerroObject> {
185        Some(self.object())
186    }
187
188    fn as_affects_render(&self) -> Option<&dyn IAffectsRender> {
189        Some(&*self.0)
190    }
191
192    fn reference_id(&self) -> *const () {
193        RefAdapter::reference_id(self)
194    }
195}
196
197impl From<Ref<DrawingImage>> for Rc<dyn IImage> {
198    #[inline]
199    fn from(value: Ref<DrawingImage>) -> Self {
200        Rc::new(RefAdapter(value))
201    }
202}
203
204impl From<&Ref<DrawingImage>> for Rc<dyn IImage> {
205    #[inline]
206    fn from(value: &Ref<DrawingImage>) -> Self {
207        Rc::new(RefAdapter(value.clone()))
208    }
209}