fyrox_ui/
vector_image.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Vector image is used to create images, that consists from a fixed set of basic primitives, such as lines,
22//! triangles, rectangles, etc. It could be used to create simple images that can be infinitely scaled without
23//! aliasing issues. See [`VectorImage`] docs for more info and usage examples.
24
25#![warn(missing_docs)]
26
27use crate::{
28    core::{
29        algebra::Vector2, color::Color, math::Rect, math::Vector2Ext, pool::Handle,
30        reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*,
31    },
32    draw::{CommandTexture, Draw, DrawingContext},
33    message::UiMessage,
34    widget::{Widget, WidgetBuilder},
35    BuildContext, Control, UiNode, UserInterface,
36};
37
38use fyrox_core::uuid_provider;
39use fyrox_core::variable::InheritableVariable;
40use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
41use std::ops::{Deref, DerefMut};
42use strum_macros::{AsRefStr, EnumString, VariantNames};
43
44/// Primitive is a simplest shape, that consists of one or multiple lines of the same thickness.
45#[derive(Clone, Debug, PartialEq, Visit, Reflect, AsRefStr, EnumString, VariantNames)]
46pub enum Primitive {
47    /// Solid triangle primitive.
48    Triangle {
49        /// Points of the triangle in local coordinates.
50        points: [Vector2<f32>; 3],
51    },
52    /// A line of fixed thickness between two points.
53    Line {
54        /// Beginning of the line in local coordinates.
55        begin: Vector2<f32>,
56        /// End of the line in local coordinates.
57        end: Vector2<f32>,
58        /// Thickness of the line in absolute units.
59        thickness: f32,
60    },
61    /// Solid circle primitive.
62    Circle {
63        /// Center of the circle in local coordinates.
64        center: Vector2<f32>,
65        /// Radius of the circle in absolute units.
66        radius: f32,
67        /// Amount of segments that is used to approximate the circle using triangles. The higher the value, the smoother the
68        /// circle and vice versa.
69        segments: usize,
70    },
71    /// Solid circle primitive.
72    WireCircle {
73        /// Center of the circle in local coordinates.
74        center: Vector2<f32>,
75        /// Radius of the circle in absolute units.
76        radius: f32,
77        /// Thickness of the circle.
78        thickness: f32,
79        /// Amount of segments that is used to approximate the circle using triangles. The higher the value, the smoother the
80        /// circle and vice versa.
81        segments: usize,
82    },
83    /// Wireframe rectangle primitive.
84    Rectangle {
85        /// Rectangle bounds in local coordinates.
86        rect: Rect<f32>,
87        /// Thickness of the lines on the rectangle in absolute units.
88        thickness: f32,
89    },
90    /// Solid rectangle primitive.
91    RectangleFilled {
92        /// Rectangle bounds in local coordinates.
93        rect: Rect<f32>,
94    },
95}
96
97uuid_provider!(Primitive = "766be1b3-6d1c-4466-bcf3-7093017c9e31");
98
99impl Default for Primitive {
100    fn default() -> Self {
101        Self::Line {
102            begin: Default::default(),
103            end: Default::default(),
104            thickness: 0.0,
105        }
106    }
107}
108
109fn line_thickness_vector(a: Vector2<f32>, b: Vector2<f32>, thickness: f32) -> Vector2<f32> {
110    if let Some(dir) = (b - a).try_normalize(f32::EPSILON) {
111        Vector2::new(dir.y, -dir.x).scale(thickness * 0.5)
112    } else {
113        Vector2::default()
114    }
115}
116
117impl Primitive {
118    /// Returns current bounds of the primitive as `min, max` tuple.
119    pub fn bounds(&self) -> (Vector2<f32>, Vector2<f32>) {
120        match self {
121            Primitive::Triangle { points } => {
122                let min = points[0]
123                    .per_component_min(&points[1])
124                    .per_component_min(&points[2]);
125                let max = points[0]
126                    .per_component_max(&points[1])
127                    .per_component_max(&points[2]);
128                (min, max)
129            }
130            Primitive::Line {
131                begin,
132                end,
133                thickness,
134            } => {
135                let tv = line_thickness_vector(*begin, *end, *thickness);
136                let mut min = begin + tv;
137                let mut max = min;
138                for v in &[begin - tv, end + tv, end - tv] {
139                    min = min.per_component_min(v);
140                    max = max.per_component_max(v);
141                }
142                (min, max)
143            }
144            Primitive::Circle { radius, center, .. }
145            | Primitive::WireCircle { radius, center, .. } => {
146                let radius = Vector2::new(*radius, *radius);
147                (center - radius, center + radius)
148            }
149            Primitive::Rectangle { rect, .. } | Primitive::RectangleFilled { rect } => {
150                (rect.left_top_corner(), rect.right_bottom_corner())
151            }
152        }
153    }
154}
155
156/// Vector image is used to create images, that consists from a fixed set of basic primitives, such as lines,
157/// triangles, rectangles, etc. It could be used to create simple images that can be infinitely scaled without
158/// aliasing issues.
159///
160/// ## Examples
161///
162/// The following example creates a cross shape with given size and thickness:
163///
164/// ```rust
165/// # use fyrox_ui::{
166/// #     core::{algebra::Vector2, pool::Handle},
167/// #     vector_image::{Primitive, VectorImageBuilder},
168/// #     widget::WidgetBuilder,
169/// #     BuildContext, UiNode,
170/// # };
171/// # use fyrox_ui::style::resource::StyleResourceExt;
172/// # use fyrox_ui::style::Style;
173/// #
174/// fn make_cross_vector_image(
175///     ctx: &mut BuildContext,
176///     size: f32,
177///     thickness: f32,
178/// ) -> Handle<UiNode> {
179///     VectorImageBuilder::new(
180///         WidgetBuilder::new()
181///             // Color of the image is defined by the foreground brush of the base widget.
182///             .with_foreground(ctx.style.property(Style::BRUSH_BRIGHT)),
183///     )
184///     .with_primitives(vec![
185///         Primitive::Line {
186///             begin: Vector2::new(0.0, 0.0),
187///             end: Vector2::new(size, size),
188///             thickness,
189///         },
190///         Primitive::Line {
191///             begin: Vector2::new(size, 0.0),
192///             end: Vector2::new(0.0, size),
193///             thickness,
194///         },
195///     ])
196///     .build(ctx)
197/// }
198/// ```
199///
200/// Keep in mind that all primitives located in local coordinates. The color of the vector image can be changed by
201/// setting a new foreground brush.
202#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
203#[reflect(derived_type = "UiNode")]
204pub struct VectorImage {
205    /// Base widget of the image.
206    pub widget: Widget,
207    /// Current set of primitives that will be drawn.
208    pub primitives: InheritableVariable<Vec<Primitive>>,
209}
210
211impl ConstructorProvider<UiNode, UserInterface> for VectorImage {
212    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
213        GraphNodeConstructor::new::<Self>()
214            .with_variant("Vector Image", |ui| {
215                VectorImageBuilder::new(WidgetBuilder::new())
216                    .build(&mut ui.build_ctx())
217                    .into()
218            })
219            .with_group("Visual")
220    }
221}
222
223crate::define_widget_deref!(VectorImage);
224
225uuid_provider!(VectorImage = "7e535b65-0178-414e-b310-e208afc0eeb5");
226
227impl Control for VectorImage {
228    fn measure_override(&self, _ui: &UserInterface, _available_size: Vector2<f32>) -> Vector2<f32> {
229        if self.primitives.is_empty() {
230            Default::default()
231        } else {
232            let mut max = Vector2::new(-f32::MAX, -f32::MAX);
233            let mut min = Vector2::new(f32::MAX, f32::MAX);
234
235            for primitive in self.primitives.iter() {
236                let (pmin, pmax) = primitive.bounds();
237                min = min.per_component_min(&pmin);
238                max = max.per_component_max(&pmax);
239            }
240
241            max - min
242        }
243    }
244
245    fn draw(&self, drawing_context: &mut DrawingContext) {
246        let bounds = self.widget.bounding_rect();
247
248        for primitive in self.primitives.iter() {
249            match primitive {
250                Primitive::Triangle { points } => {
251                    let pts = [
252                        bounds.position + points[0],
253                        bounds.position + points[1],
254                        bounds.position + points[2],
255                    ];
256
257                    drawing_context.push_triangle_filled(pts);
258                }
259                Primitive::Line {
260                    begin,
261                    end,
262                    thickness,
263                } => {
264                    drawing_context.push_line(
265                        bounds.position + *begin,
266                        bounds.position + *end,
267                        *thickness,
268                    );
269                }
270                Primitive::Circle {
271                    center,
272                    radius,
273                    segments,
274                } => drawing_context.push_circle_filled(
275                    bounds.position + *center,
276                    *radius,
277                    *segments,
278                    Color::WHITE,
279                ),
280                Primitive::WireCircle {
281                    center,
282                    radius,
283                    thickness,
284                    segments,
285                } => drawing_context.push_circle(
286                    bounds.position + *center,
287                    *radius,
288                    *segments,
289                    *thickness,
290                ),
291                Primitive::RectangleFilled { rect } => drawing_context.push_rect_filled(rect, None),
292                Primitive::Rectangle { rect, thickness } => {
293                    drawing_context.push_rect(rect, *thickness)
294                }
295            }
296        }
297        drawing_context.commit(
298            self.clip_bounds(),
299            self.widget.foreground(),
300            CommandTexture::None,
301            &self.material,
302            None,
303        );
304    }
305
306    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
307        self.widget.handle_routed_message(ui, message);
308    }
309}
310
311/// Vector image builder creates [`VectorImage`] instances and adds them to the user interface.
312pub struct VectorImageBuilder {
313    widget_builder: WidgetBuilder,
314    primitives: Vec<Primitive>,
315}
316
317impl VectorImageBuilder {
318    /// Creates new vector image builder.
319    pub fn new(widget_builder: WidgetBuilder) -> Self {
320        Self {
321            widget_builder,
322            primitives: Default::default(),
323        }
324    }
325
326    /// Sets the desired set of primitives.
327    pub fn with_primitives(mut self, primitives: Vec<Primitive>) -> Self {
328        self.primitives = primitives;
329        self
330    }
331
332    /// Builds the vector image widget.
333    pub fn build_node(self, ctx: &BuildContext) -> UiNode {
334        let image = VectorImage {
335            widget: self.widget_builder.build(ctx),
336            primitives: self.primitives.into(),
337        };
338        UiNode::new(image)
339    }
340
341    /// Finishes vector image building and adds it to the user interface.
342    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
343        ctx.add_node(self.build_node(ctx))
344    }
345}
346
347#[cfg(test)]
348mod test {
349    use crate::vector_image::VectorImageBuilder;
350    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
351
352    #[test]
353    fn test_deletion() {
354        test_widget_deletion(|ctx| VectorImageBuilder::new(WidgetBuilder::new()).build(ctx));
355    }
356}