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 strum_macros::{AsRefStr, EnumString, VariantNames};
42
43/// Primitive is the 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/// # use fyrox_ui::vector_image::VectorImage;
173/// #
174/// fn make_cross_vector_image(
175/// ctx: &mut BuildContext,
176/// size: f32,
177/// thickness: f32,
178/// ) -> Handle<VectorImage> {
179/// VectorImageBuilder::new(
180/// WidgetBuilder::new()
181/// // The 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 /// The 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 .to_base()
218 .into()
219 })
220 .with_group("Visual")
221 }
222}
223
224crate::define_widget_deref!(VectorImage);
225
226uuid_provider!(VectorImage = "7e535b65-0178-414e-b310-e208afc0eeb5");
227
228impl Control for VectorImage {
229 fn measure_override(&self, _ui: &UserInterface, _available_size: Vector2<f32>) -> Vector2<f32> {
230 if self.primitives.is_empty() {
231 Default::default()
232 } else {
233 let mut max = Vector2::new(-f32::MAX, -f32::MAX);
234 let mut min = Vector2::new(f32::MAX, f32::MAX);
235
236 for primitive in self.primitives.iter() {
237 let (pmin, pmax) = primitive.bounds();
238 min = min.per_component_min(&pmin);
239 max = max.per_component_max(&pmax);
240 }
241
242 max - min
243 }
244 }
245
246 fn draw(&self, drawing_context: &mut DrawingContext) {
247 let bounds = self.widget.bounding_rect();
248
249 for primitive in self.primitives.iter() {
250 match primitive {
251 Primitive::Triangle { points } => {
252 let pts = [
253 bounds.position + points[0],
254 bounds.position + points[1],
255 bounds.position + points[2],
256 ];
257
258 drawing_context.push_triangle_filled(pts);
259 }
260 Primitive::Line {
261 begin,
262 end,
263 thickness,
264 } => {
265 drawing_context.push_line(
266 bounds.position + *begin,
267 bounds.position + *end,
268 *thickness,
269 );
270 }
271 Primitive::Circle {
272 center,
273 radius,
274 segments,
275 } => drawing_context.push_circle_filled(
276 bounds.position + *center,
277 *radius,
278 *segments,
279 Color::WHITE,
280 ),
281 Primitive::WireCircle {
282 center,
283 radius,
284 thickness,
285 segments,
286 } => drawing_context.push_circle(
287 bounds.position + *center,
288 *radius,
289 *segments,
290 *thickness,
291 ),
292 Primitive::RectangleFilled { rect } => drawing_context.push_rect_filled(rect, None),
293 Primitive::Rectangle { rect, thickness } => {
294 drawing_context.push_rect(rect, *thickness)
295 }
296 }
297 }
298 drawing_context.commit(
299 self.clip_bounds(),
300 self.widget.foreground(),
301 CommandTexture::None,
302 &self.material,
303 None,
304 );
305 }
306
307 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
308 self.widget.handle_routed_message(ui, message);
309 }
310}
311
312/// Vector image builder creates [`VectorImage`] instances and adds them to the user interface.
313pub struct VectorImageBuilder {
314 widget_builder: WidgetBuilder,
315 primitives: Vec<Primitive>,
316}
317
318impl VectorImageBuilder {
319 /// Creates new vector image builder.
320 pub fn new(widget_builder: WidgetBuilder) -> Self {
321 Self {
322 widget_builder,
323 primitives: Default::default(),
324 }
325 }
326
327 /// Sets the desired set of primitives.
328 pub fn with_primitives(mut self, primitives: Vec<Primitive>) -> Self {
329 self.primitives = primitives;
330 self
331 }
332
333 /// Builds the vector image widget.
334 pub fn build_vector_image(self, ctx: &BuildContext) -> VectorImage {
335 VectorImage {
336 widget: self.widget_builder.build(ctx),
337 primitives: self.primitives.into(),
338 }
339 }
340
341 /// Builds the vector image widget.
342 pub fn build_node(self, ctx: &BuildContext) -> UiNode {
343 UiNode::new(self.build_vector_image(ctx))
344 }
345
346 /// Finishes vector image building and adds it to the user interface.
347 pub fn build(self, ctx: &mut BuildContext) -> Handle<VectorImage> {
348 ctx.add(self.build_vector_image(ctx))
349 }
350}
351
352#[cfg(test)]
353mod test {
354 use crate::vector_image::VectorImageBuilder;
355 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
356
357 #[test]
358 fn test_deletion() {
359 test_widget_deletion(|ctx| VectorImageBuilder::new(WidgetBuilder::new()).build(ctx));
360 }
361}