Skip to main content

fyrox_ui/
utils.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
21use crate::{
22    border::BorderBuilder,
23    button::{Button, ButtonBuilder},
24    core::{algebra::Vector2, color::Color, parking_lot::Mutex, pool::Handle, Uuid},
25    decorator::DecoratorBuilder,
26    formatted_text::WrapMode,
27    grid::{Column, GridBuilder, Row},
28    image::{Image, ImageBuilder},
29    style::{resource::StyleResourceExt, Style, StyledProperty},
30    text::TextBuilder,
31    toggle::{ToggleButton, ToggleButtonBuilder},
32    vector_image::{Primitive, VectorImage, VectorImageBuilder},
33    widget::WidgetBuilder,
34    Brush, BuildContext, HorizontalAlignment, RcUiNodeHandle, Thickness, UiNode, VerticalAlignment,
35};
36use fyrox_texture::{
37    CompressionOptions, TextureImportOptions, TextureMinificationFilter, TextureResource,
38    TextureResourceExtension,
39};
40use std::sync::Arc;
41
42pub enum ArrowDirection {
43    Top,
44    Bottom,
45    Left,
46    Right,
47}
48
49pub fn make_arrow_primitives_non_uniform_size(
50    orientation: ArrowDirection,
51    width: f32,
52    height: f32,
53) -> Vec<Primitive> {
54    vec![match orientation {
55        ArrowDirection::Top => Primitive::Triangle {
56            points: [
57                Vector2::new(width * 0.5, 0.0),
58                Vector2::new(width, height),
59                Vector2::new(0.0, height),
60            ],
61        },
62        ArrowDirection::Bottom => Primitive::Triangle {
63            points: [
64                Vector2::new(0.0, 0.0),
65                Vector2::new(width, 0.0),
66                Vector2::new(width * 0.5, height),
67            ],
68        },
69        ArrowDirection::Right => Primitive::Triangle {
70            points: [
71                Vector2::new(0.0, 0.0),
72                Vector2::new(width, height * 0.5),
73                Vector2::new(0.0, height),
74            ],
75        },
76        ArrowDirection::Left => Primitive::Triangle {
77            points: [
78                Vector2::new(0.0, height * 0.5),
79                Vector2::new(width, 0.0),
80                Vector2::new(width, height),
81            ],
82        },
83    }]
84}
85
86pub fn make_arrow_primitives(orientation: ArrowDirection, size: f32) -> Vec<Primitive> {
87    make_arrow_primitives_non_uniform_size(orientation, size, size)
88}
89
90pub fn make_arrow_non_uniform_size(
91    ctx: &mut BuildContext,
92    orientation: ArrowDirection,
93    width: f32,
94    height: f32,
95) -> Handle<VectorImage> {
96    VectorImageBuilder::new(
97        WidgetBuilder::new()
98            .with_foreground(ctx.style.property(Style::BRUSH_BRIGHT))
99            .with_width(width)
100            .with_height(height)
101            .with_horizontal_alignment(HorizontalAlignment::Center)
102            .with_vertical_alignment(VerticalAlignment::Center),
103    )
104    .with_primitives(make_arrow_primitives_non_uniform_size(
105        orientation,
106        width,
107        height,
108    ))
109    .build(ctx)
110}
111
112pub fn make_arrow(
113    ctx: &mut BuildContext,
114    orientation: ArrowDirection,
115    size: f32,
116) -> Handle<VectorImage> {
117    make_arrow_non_uniform_size(ctx, orientation, size, size)
118}
119
120pub fn make_cross_primitive(size: f32, thickness: f32) -> Vec<Primitive> {
121    vec![
122        Primitive::Line {
123            begin: Vector2::new(0.0, 0.0),
124            end: Vector2::new(size, size),
125            thickness,
126        },
127        Primitive::Line {
128            begin: Vector2::new(size, 0.0),
129            end: Vector2::new(0.0, size),
130            thickness,
131        },
132    ]
133}
134
135pub fn make_cross(ctx: &mut BuildContext, size: f32, thickness: f32) -> Handle<VectorImage> {
136    VectorImageBuilder::new(
137        WidgetBuilder::new()
138            .with_horizontal_alignment(HorizontalAlignment::Center)
139            .with_vertical_alignment(VerticalAlignment::Center)
140            .with_width(size)
141            .with_height(size)
142            .with_foreground(ctx.style.property(Style::BRUSH_BRIGHT)),
143    )
144    .with_primitives(make_cross_primitive(size, thickness))
145    .build(ctx)
146}
147
148pub fn make_simple_tooltip(ctx: &mut BuildContext, text: &str) -> RcUiNodeHandle {
149    let handle = BorderBuilder::new(
150        WidgetBuilder::new()
151            .with_visibility(false)
152            .with_hit_test_visibility(false)
153            .with_foreground(ctx.style.property(Style::BRUSH_DARKEST))
154            .with_background(Brush::Solid(Color::opaque(230, 230, 230)).into())
155            .with_width(300.0)
156            .with_child(
157                TextBuilder::new(
158                    WidgetBuilder::new()
159                        .with_margin(Thickness::uniform(2.0))
160                        .with_foreground(ctx.style.property(Style::BRUSH_DARKER)),
161                )
162                .with_wrap(WrapMode::Word)
163                .with_text(text)
164                .build(ctx),
165            ),
166    )
167    .build(ctx);
168    RcUiNodeHandle::new(handle, ctx.sender())
169}
170
171pub fn make_asset_preview_tooltip(
172    texture: Option<TextureResource>,
173    ctx: &mut BuildContext,
174) -> (RcUiNodeHandle, Handle<Image>) {
175    let size = 120.0;
176    let image_preview;
177    let image_preview_tooltip = BorderBuilder::new(
178        WidgetBuilder::new()
179            .with_visibility(false)
180            .with_hit_test_visibility(false)
181            .with_foreground(ctx.style.property(Style::BRUSH_DARKEST))
182            .with_background(Brush::Solid(Color::opaque(230, 230, 230)).into())
183            .with_width(size + 2.0)
184            .with_height(size + 2.0)
185            .with_child({
186                image_preview = ImageBuilder::new(
187                    WidgetBuilder::new()
188                        .on_column(0)
189                        .with_width(size)
190                        .with_height(size)
191                        .with_margin(Thickness::uniform(1.0)),
192                )
193                .with_checkerboard_background(true)
194                .with_opt_texture(texture)
195                .with_sync_with_texture_size(false)
196                .build(ctx);
197                image_preview
198            }),
199    )
200    .build(ctx);
201    (
202        RcUiNodeHandle::new(image_preview_tooltip, ctx.sender()),
203        image_preview,
204    )
205}
206
207fn adjust_decorator(builder: DecoratorBuilder, ctx: &BuildContext) -> DecoratorBuilder {
208    builder
209        .with_normal_brush(ctx.style.property(Style::BRUSH_DARKER))
210        .with_hover_brush(ctx.style.property(Style::BRUSH_DARK))
211        .with_pressed_brush(ctx.style.property(Style::BRUSH_PRIMARY))
212        .with_selected_brush(ctx.style.property(Style::BRUSH_LIGHTER_PRIMARY))
213}
214
215fn text_margin() -> Thickness {
216    Thickness {
217        left: 5.0,
218        top: 2.0,
219        right: 2.0,
220        bottom: 2.0,
221    }
222}
223
224pub fn make_dropdown_list_option_universal<T: Send + 'static>(
225    ctx: &mut BuildContext,
226    name: &str,
227    height: f32,
228    user_data: T,
229) -> Handle<UiNode> {
230    adjust_decorator(
231        DecoratorBuilder::new(
232            BorderBuilder::new(
233                WidgetBuilder::new()
234                    .with_height(height)
235                    .with_user_data(Arc::new(Mutex::new(user_data)))
236                    .with_child(
237                        TextBuilder::new(WidgetBuilder::new().with_margin(text_margin()))
238                            .with_vertical_text_alignment(VerticalAlignment::Center)
239                            .with_horizontal_text_alignment(HorizontalAlignment::Left)
240                            .with_text(name)
241                            .build(ctx),
242                    ),
243            )
244            .with_corner_radius(4.0f32.into())
245            .with_pad_by_corner_radius(false),
246        ),
247        ctx,
248    )
249    .build(ctx)
250    .to_base()
251}
252
253pub fn make_dropdown_list_option(ctx: &mut BuildContext, name: &str) -> Handle<UiNode> {
254    adjust_decorator(
255        DecoratorBuilder::new(
256            BorderBuilder::new(
257                WidgetBuilder::new().with_child(
258                    TextBuilder::new(
259                        WidgetBuilder::new()
260                            .with_margin(text_margin())
261                            .with_vertical_alignment(VerticalAlignment::Center),
262                    )
263                    .with_vertical_text_alignment(VerticalAlignment::Center)
264                    .with_horizontal_text_alignment(HorizontalAlignment::Left)
265                    .with_text(name)
266                    .build(ctx),
267                ),
268            )
269            .with_corner_radius(4.0f32.into())
270            .with_pad_by_corner_radius(false),
271        ),
272        ctx,
273    )
274    .build(ctx)
275    .to_base()
276}
277
278pub fn make_dropdown_list_option_with_height(
279    ctx: &mut BuildContext,
280    name: &str,
281    height: f32,
282) -> Handle<UiNode> {
283    adjust_decorator(
284        DecoratorBuilder::new(
285            BorderBuilder::new(
286                WidgetBuilder::new().with_height(height).with_child(
287                    TextBuilder::new(WidgetBuilder::new().with_margin(text_margin()))
288                        .with_vertical_text_alignment(VerticalAlignment::Center)
289                        .with_horizontal_text_alignment(HorizontalAlignment::Left)
290                        .with_text(name)
291                        .build(ctx),
292                ),
293            )
294            .with_corner_radius(4.0f32.into())
295            .with_pad_by_corner_radius(false),
296        ),
297        ctx,
298    )
299    .build(ctx)
300    .to_base()
301}
302
303pub struct ImageButtonBuilder<'a> {
304    width: f32,
305    height: f32,
306    image_width: f32,
307    image_height: f32,
308    image: Option<TextureResource>,
309    tooltip: &'a str,
310    tab_index: Option<usize>,
311    image_brush: Option<StyledProperty<Brush>>,
312    column: usize,
313    row: usize,
314    visibility: bool,
315    horizontal_alignment: HorizontalAlignment,
316    vertical_alignment: VerticalAlignment,
317    margin: Thickness,
318    is_toggled: bool,
319}
320
321impl<'a> Default for ImageButtonBuilder<'a> {
322    fn default() -> Self {
323        Self {
324            width: f32::NAN,
325            height: f32::NAN,
326            image_width: 16.0,
327            image_height: 16.0,
328            image: None,
329            tooltip: "",
330            tab_index: None,
331            image_brush: None,
332            column: 0,
333            row: 0,
334            visibility: true,
335            horizontal_alignment: HorizontalAlignment::Stretch,
336            vertical_alignment: VerticalAlignment::Stretch,
337            margin: Thickness::uniform(1.0),
338            is_toggled: false,
339        }
340    }
341}
342
343impl<'a> ImageButtonBuilder<'a> {
344    pub fn with_width(mut self, width: f32) -> Self {
345        self.width = width;
346        self
347    }
348
349    pub fn with_height(mut self, height: f32) -> Self {
350        self.height = height;
351        self
352    }
353
354    pub fn with_size(mut self, size: f32) -> Self {
355        self.width = size;
356        self.height = size;
357        self
358    }
359
360    pub fn with_image_width(mut self, width: f32) -> Self {
361        self.image_width = width;
362        self
363    }
364
365    pub fn with_image_height(mut self, height: f32) -> Self {
366        self.image_height = height;
367        self
368    }
369
370    pub fn with_image_size(mut self, size: f32) -> Self {
371        self.image_width = size;
372        self.image_height = size;
373        self
374    }
375
376    pub fn with_image(mut self, image: Option<TextureResource>) -> Self {
377        self.image = image;
378        self
379    }
380
381    pub fn with_tooltip(mut self, tooltip: &'a str) -> Self {
382        self.tooltip = tooltip;
383        self
384    }
385
386    pub fn with_tab_index(mut self, tab_index: Option<usize>) -> Self {
387        self.tab_index = tab_index;
388        self
389    }
390
391    pub fn with_image_color(mut self, color: Color) -> Self {
392        self.image_brush = Some(Brush::Solid(color).into());
393        self
394    }
395
396    pub fn on_column(mut self, column: usize) -> Self {
397        self.column = column;
398        self
399    }
400
401    pub fn on_row(mut self, row: usize) -> Self {
402        self.row = row;
403        self
404    }
405
406    pub fn with_visibility(mut self, visibility: bool) -> Self {
407        self.visibility = visibility;
408        self
409    }
410
411    pub fn with_horizontal_alignment(mut self, alignment: HorizontalAlignment) -> Self {
412        self.horizontal_alignment = alignment;
413        self
414    }
415
416    pub fn with_vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
417        self.vertical_alignment = alignment;
418        self
419    }
420
421    pub fn with_margin(mut self, margin: Thickness) -> Self {
422        self.margin = margin;
423        self
424    }
425
426    pub fn with_is_toggled(mut self, is_toggled: bool) -> Self {
427        self.is_toggled = is_toggled;
428        self
429    }
430
431    pub fn build_button(self, ctx: &mut BuildContext) -> Handle<Button> {
432        ButtonBuilder::new(
433            WidgetBuilder::new()
434                .with_width(self.width)
435                .with_height(self.height)
436                .on_column(self.column)
437                .on_row(self.row)
438                .with_tab_index(self.tab_index)
439                .with_visibility(self.visibility)
440                .with_horizontal_alignment(self.horizontal_alignment)
441                .with_vertical_alignment(self.vertical_alignment)
442                .with_tooltip(make_simple_tooltip(ctx, self.tooltip))
443                .with_margin(self.margin),
444        )
445        .with_content(
446            ImageBuilder::new(
447                WidgetBuilder::new()
448                    .with_background(
449                        self.image_brush
450                            .unwrap_or_else(|| ctx.style.property(Style::BRUSH_BRIGHTEST)),
451                    )
452                    .with_margin(Thickness::uniform(3.0))
453                    .with_width(self.image_width)
454                    .with_height(self.image_height),
455            )
456            .with_opt_texture(self.image)
457            .build(ctx),
458        )
459        .build(ctx)
460    }
461
462    pub fn build_toggle(self, ctx: &mut BuildContext) -> Handle<ToggleButton> {
463        ToggleButtonBuilder::new(
464            WidgetBuilder::new()
465                .with_width(self.width)
466                .with_height(self.height)
467                .with_tab_index(self.tab_index)
468                .with_visibility(self.visibility)
469                .with_horizontal_alignment(self.horizontal_alignment)
470                .with_vertical_alignment(self.vertical_alignment)
471                .with_tooltip(make_simple_tooltip(ctx, self.tooltip))
472                .with_margin(self.margin),
473        )
474        .with_content(
475            ImageBuilder::new(
476                WidgetBuilder::new()
477                    .with_background(
478                        self.image_brush
479                            .unwrap_or_else(|| ctx.style.property(Style::BRUSH_BRIGHTEST)),
480                    )
481                    .with_margin(Thickness::uniform(3.0))
482                    .with_width(self.image_width)
483                    .with_height(self.image_height),
484            )
485            .with_opt_texture(self.image)
486            .build(ctx),
487        )
488        .with_toggled(self.is_toggled)
489        .build(ctx)
490    }
491}
492
493pub fn make_text_and_image_button_with_tooltip(
494    ctx: &mut BuildContext,
495    text: &str,
496    image_width: f32,
497    image_height: f32,
498    image: Option<TextureResource>,
499    tooltip: &str,
500    row: usize,
501    column: usize,
502    tab_index: Option<usize>,
503    color: Color,
504    font_size: f32,
505) -> Handle<Button> {
506    let margin = 3.0;
507    ButtonBuilder::new(
508        WidgetBuilder::new()
509            .on_row(row)
510            .on_column(column)
511            .with_tab_index(tab_index)
512            .with_tooltip(make_simple_tooltip(ctx, tooltip))
513            .with_margin(Thickness::uniform(1.0)),
514    )
515    .with_content(
516        GridBuilder::new(
517            WidgetBuilder::new()
518                .with_child(
519                    ImageBuilder::new(
520                        WidgetBuilder::new()
521                            .on_row(0)
522                            .on_column(0)
523                            .with_background(Brush::Solid(color).into())
524                            .with_margin(Thickness {
525                                left: 2.0 * margin,
526                                top: margin,
527                                right: margin,
528                                bottom: margin,
529                            })
530                            .with_width(image_width - 2.0 * margin)
531                            .with_height(image_height - 2.0 * margin),
532                    )
533                    .with_opt_texture(image)
534                    .build(ctx),
535                )
536                .with_child(
537                    TextBuilder::new(
538                        WidgetBuilder::new()
539                            .on_row(0)
540                            .on_column(1)
541                            .with_vertical_alignment(VerticalAlignment::Center)
542                            .with_horizontal_alignment(HorizontalAlignment::Center)
543                            .with_margin(Thickness {
544                                left: 4.0,
545                                top: margin,
546                                right: 8.0,
547                                bottom: margin,
548                            }),
549                    )
550                    .with_font_size(font_size.into())
551                    .with_text(text)
552                    .build(ctx),
553                ),
554        )
555        .add_column(Column::auto())
556        .add_column(Column::stretch())
557        .add_row(Row::stretch())
558        .build(ctx),
559    )
560    .build(ctx)
561}
562
563pub fn load_image(data: &[u8]) -> Option<TextureResource> {
564    TextureResource::load_from_memory(
565        Uuid::new_v4(),
566        Default::default(),
567        data,
568        TextureImportOptions::default()
569            .with_compression(CompressionOptions::NoCompression)
570            .with_minification_filter(TextureMinificationFilter::LinearMipMapLinear)
571            .with_lod_bias(-1.0),
572    )
573    .ok()
574}