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};
37use fyrox_core::uuid_provider;
38use fyrox_core::variable::InheritableVariable;
39use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
40use std::ops::{Deref, DerefMut};
41use strum_macros::{AsRefStr, EnumString, VariantNames};
42
43/// Primitive is a simplest shape, that consists of one or multiple lines of the same thickness.
44#[derive(Clone, Debug, PartialEq, Visit, Reflect, AsRefStr, EnumString, VariantNames)]
45pub enum Primitive {
46    /// Solid triangle primitive.
47    Triangle {
48        /// Points of the triangle in local coordinates.
49        points: [Vector2<f32>; 3],
50    },
51    /// A line of fixed thickness between two points.
52    Line {
53        /// Beginning of the line in local coordinates.
54        begin: Vector2<f32>,
55        /// End of the line in local coordinates.
56        end: Vector2<f32>,
57        /// Thickness of the line in absolute units.
58        thickness: f32,
59    },
60    /// Solid circle primitive.
61    Circle {
62        /// Center of the circle in local coordinates.
63        center: Vector2<f32>,
64        /// Radius of the circle in absolute units.
65        radius: f32,
66        /// Amount of segments that is used to approximate the circle using triangles. The higher the value, the smoother the
67        /// circle and vice versa.
68        segments: usize,
69    },
70    /// Solid circle primitive.
71    WireCircle {
72        /// Center of the circle in local coordinates.
73        center: Vector2<f32>,
74        /// Radius of the circle in absolute units.
75        radius: f32,
76        /// Thickness of the circle.
77        thickness: f32,
78        /// Amount of segments that is used to approximate the circle using triangles. The higher the value, the smoother the
79        /// circle and vice versa.
80        segments: usize,
81    },
82    /// Wireframe rectangle primitive.
83    Rectangle {
84        /// Rectangle bounds in local coordinates.
85        rect: Rect<f32>,
86        /// Thickness of the lines on the rectangle in absolute units.
87        thickness: f32,
88    },
89    /// Solid rectangle primitive.
90    RectangleFilled {
91        /// Rectangle bounds in local coordinates.
92        rect: Rect<f32>,
93    },
94}
95
96uuid_provider!(Primitive = "766be1b3-6d1c-4466-bcf3-7093017c9e31");
97
98impl Default for Primitive {
99    fn default() -> Self {
100        Self::Line {
101            begin: Default::default(),
102            end: Default::default(),
103            thickness: 0.0,
104        }
105    }
106}
107
108fn line_thickness_vector(a: Vector2<f32>, b: Vector2<f32>, thickness: f32) -> Vector2<f32> {
109    if let Some(dir) = (b - a).try_normalize(f32::EPSILON) {
110        Vector2::new(dir.y, -dir.x).scale(thickness * 0.5)
111    } else {
112        Vector2::default()
113    }
114}
115
116impl Primitive {
117    /// Returns current bounds of the primitive as `min, max` tuple.
118    pub fn bounds(&self) -> (Vector2<f32>, Vector2<f32>) {
119        match self {
120            Primitive::Triangle { points } => {
121                let min = points[0]
122                    .per_component_min(&points[1])
123                    .per_component_min(&points[2]);
124                let max = points[0]
125                    .per_component_max(&points[1])
126                    .per_component_max(&points[2]);
127                (min, max)
128            }
129            Primitive::Line {
130                begin,
131                end,
132                thickness,
133            } => {
134                let tv = line_thickness_vector(*begin, *end, *thickness);
135                let mut min = begin + tv;
136                let mut max = min;
137                for v in &[begin - tv, end + tv, end - tv] {
138                    min = min.per_component_min(v);
139                    max = max.per_component_max(v);
140                }
141                (min, max)
142            }
143            Primitive::Circle { radius, center, .. }
144            | Primitive::WireCircle { radius, center, .. } => {
145                let radius = Vector2::new(*radius, *radius);
146                (center - radius, center + radius)
147            }
148            Primitive::Rectangle { rect, .. } | Primitive::RectangleFilled { rect } => {
149                (rect.left_top_corner(), rect.right_bottom_corner())
150            }
151        }
152    }
153}
154
155/// Vector image is used to create images, that consists from a fixed set of basic primitives, such as lines,
156/// triangles, rectangles, etc. It could be used to create simple images that can be infinitely scaled without
157/// aliasing issues.
158///
159/// ## Examples
160///
161/// The following example creates a cross shape with given size and thickness:
162///
163/// ```rust
164/// # use fyrox_ui::{
165/// #     core::{algebra::Vector2, pool::Handle},
166/// #     vector_image::{Primitive, VectorImageBuilder},
167/// #     widget::WidgetBuilder,
168/// #     BuildContext, UiNode,
169/// # };
170/// # use fyrox_ui::style::resource::StyleResourceExt;
171/// # use fyrox_ui::style::Style;
172/// #
173/// fn make_cross_vector_image(
174///     ctx: &mut BuildContext,
175///     size: f32,
176///     thickness: f32,
177/// ) -> Handle<UiNode> {
178///     VectorImageBuilder::new(
179///         WidgetBuilder::new()
180///             // Color of the image is defined by the foreground brush of the base widget.
181///             .with_foreground(ctx.style.property(Style::BRUSH_BRIGHT)),
182///     )
183///     .with_primitives(vec![
184///         Primitive::Line {
185///             begin: Vector2::new(0.0, 0.0),
186///             end: Vector2::new(size, size),
187///             thickness,
188///         },
189///         Primitive::Line {
190///             begin: Vector2::new(size, 0.0),
191///             end: Vector2::new(0.0, size),
192///             thickness,
193///         },
194///     ])
195///     .build(ctx)
196/// }
197/// ```
198///
199/// Keep in mind that all primitives located in local coordinates. The color of the vector image can be changed by
200/// setting a new foreground brush.
201#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
202pub struct VectorImage {
203    /// Base widget of the image.
204    pub widget: Widget,
205    /// Current set of primitives that will be drawn.
206    pub primitives: InheritableVariable<Vec<Primitive>>,
207}
208
209impl ConstructorProvider<UiNode, UserInterface> for VectorImage {
210    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
211        GraphNodeConstructor::new::<Self>()
212            .with_variant("Vector Image", |ui| {
213                VectorImageBuilder::new(WidgetBuilder::new())
214                    .build(&mut ui.build_ctx())
215                    .into()
216            })
217            .with_group("Visual")
218    }
219}
220
221crate::define_widget_deref!(VectorImage);
222
223uuid_provider!(VectorImage = "7e535b65-0178-414e-b310-e208afc0eeb5");
224
225impl Control for VectorImage {
226    fn measure_override(&self, _ui: &UserInterface, _available_size: Vector2<f32>) -> Vector2<f32> {
227        if self.primitives.is_empty() {
228            Default::default()
229        } else {
230            let mut max = Vector2::new(-f32::MAX, -f32::MAX);
231            let mut min = Vector2::new(f32::MAX, f32::MAX);
232
233            for primitive in self.primitives.iter() {
234                let (pmin, pmax) = primitive.bounds();
235                min = min.per_component_min(&pmin);
236                max = max.per_component_max(&pmax);
237            }
238
239            max - min
240        }
241    }
242
243    fn draw(&self, drawing_context: &mut DrawingContext) {
244        let bounds = self.widget.bounding_rect();
245
246        for primitive in self.primitives.iter() {
247            match primitive {
248                Primitive::Triangle { points } => {
249                    let pts = [
250                        bounds.position + points[0],
251                        bounds.position + points[1],
252                        bounds.position + points[2],
253                    ];
254
255                    drawing_context.push_triangle_filled(pts);
256                }
257                Primitive::Line {
258                    begin,
259                    end,
260                    thickness,
261                } => {
262                    drawing_context.push_line(
263                        bounds.position + *begin,
264                        bounds.position + *end,
265                        *thickness,
266                    );
267                }
268                Primitive::Circle {
269                    center,
270                    radius,
271                    segments,
272                } => drawing_context.push_circle_filled(
273                    bounds.position + *center,
274                    *radius,
275                    *segments,
276                    Color::WHITE,
277                ),
278                Primitive::WireCircle {
279                    center,
280                    radius,
281                    thickness,
282                    segments,
283                } => drawing_context.push_circle(
284                    bounds.position + *center,
285                    *radius,
286                    *segments,
287                    *thickness,
288                ),
289                Primitive::RectangleFilled { rect } => drawing_context.push_rect_filled(rect, None),
290                Primitive::Rectangle { rect, thickness } => {
291                    drawing_context.push_rect(rect, *thickness)
292                }
293            }
294        }
295        drawing_context.commit(
296            self.clip_bounds(),
297            self.widget.foreground(),
298            CommandTexture::None,
299            None,
300        );
301    }
302
303    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
304        self.widget.handle_routed_message(ui, message);
305    }
306}
307
308/// Vector image builder creates [`VectorImage`] instances and adds them to the user interface.
309pub struct VectorImageBuilder {
310    widget_builder: WidgetBuilder,
311    primitives: Vec<Primitive>,
312}
313
314impl VectorImageBuilder {
315    /// Creates new vector image builder.
316    pub fn new(widget_builder: WidgetBuilder) -> Self {
317        Self {
318            widget_builder,
319            primitives: Default::default(),
320        }
321    }
322
323    /// Sets the desired set of primitives.
324    pub fn with_primitives(mut self, primitives: Vec<Primitive>) -> Self {
325        self.primitives = primitives;
326        self
327    }
328
329    /// Builds the vector image widget.
330    pub fn build_node(self, ctx: &BuildContext) -> UiNode {
331        let image = VectorImage {
332            widget: self.widget_builder.build(ctx),
333            primitives: self.primitives.into(),
334        };
335        UiNode::new(image)
336    }
337
338    /// Finishes vector image building and adds it to the user interface.
339    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
340        ctx.add_node(self.build_node(ctx))
341    }
342}
343
344#[cfg(test)]
345mod test {
346    use crate::vector_image::VectorImageBuilder;
347    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
348
349    #[test]
350    fn test_deletion() {
351        test_widget_deletion(|ctx| VectorImageBuilder::new(WidgetBuilder::new()).build(ctx));
352    }
353}