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::button::Button;
22use crate::image::Image;
23use crate::vector_image::VectorImage;
24use crate::{
25    border::BorderBuilder,
26    button::ButtonBuilder,
27    core::{algebra::Vector2, color::Color, parking_lot::Mutex, pool::Handle},
28    decorator::DecoratorBuilder,
29    formatted_text::WrapMode,
30    grid::{Column, GridBuilder, Row},
31    image::ImageBuilder,
32    style::{resource::StyleResourceExt, Style},
33    text::TextBuilder,
34    vector_image::{Primitive, VectorImageBuilder},
35    widget::WidgetBuilder,
36    Brush, BuildContext, HorizontalAlignment, RcUiNodeHandle, Thickness, UiNode, VerticalAlignment,
37};
38use fyrox_core::Uuid;
39use fyrox_texture::{
40    CompressionOptions, TextureImportOptions, TextureMinificationFilter, TextureResource,
41    TextureResourceExtension,
42};
43use std::sync::Arc;
44
45pub enum ArrowDirection {
46    Top,
47    Bottom,
48    Left,
49    Right,
50}
51
52pub fn make_arrow_primitives_non_uniform_size(
53    orientation: ArrowDirection,
54    width: f32,
55    height: f32,
56) -> Vec<Primitive> {
57    vec![match orientation {
58        ArrowDirection::Top => Primitive::Triangle {
59            points: [
60                Vector2::new(width * 0.5, 0.0),
61                Vector2::new(width, height),
62                Vector2::new(0.0, height),
63            ],
64        },
65        ArrowDirection::Bottom => Primitive::Triangle {
66            points: [
67                Vector2::new(0.0, 0.0),
68                Vector2::new(width, 0.0),
69                Vector2::new(width * 0.5, height),
70            ],
71        },
72        ArrowDirection::Right => Primitive::Triangle {
73            points: [
74                Vector2::new(0.0, 0.0),
75                Vector2::new(width, height * 0.5),
76                Vector2::new(0.0, height),
77            ],
78        },
79        ArrowDirection::Left => Primitive::Triangle {
80            points: [
81                Vector2::new(0.0, height * 0.5),
82                Vector2::new(width, 0.0),
83                Vector2::new(width, height),
84            ],
85        },
86    }]
87}
88
89pub fn make_arrow_primitives(orientation: ArrowDirection, size: f32) -> Vec<Primitive> {
90    make_arrow_primitives_non_uniform_size(orientation, size, size)
91}
92
93pub fn make_arrow_non_uniform_size(
94    ctx: &mut BuildContext,
95    orientation: ArrowDirection,
96    width: f32,
97    height: f32,
98) -> Handle<VectorImage> {
99    VectorImageBuilder::new(
100        WidgetBuilder::new()
101            .with_foreground(ctx.style.property(Style::BRUSH_BRIGHT))
102            .with_width(width)
103            .with_height(height)
104            .with_horizontal_alignment(HorizontalAlignment::Center)
105            .with_vertical_alignment(VerticalAlignment::Center),
106    )
107    .with_primitives(make_arrow_primitives_non_uniform_size(
108        orientation,
109        width,
110        height,
111    ))
112    .build(ctx)
113}
114
115pub fn make_arrow(
116    ctx: &mut BuildContext,
117    orientation: ArrowDirection,
118    size: f32,
119) -> Handle<VectorImage> {
120    make_arrow_non_uniform_size(ctx, orientation, size, size)
121}
122
123pub fn make_cross_primitive(size: f32, thickness: f32) -> Vec<Primitive> {
124    vec![
125        Primitive::Line {
126            begin: Vector2::new(0.0, 0.0),
127            end: Vector2::new(size, size),
128            thickness,
129        },
130        Primitive::Line {
131            begin: Vector2::new(size, 0.0),
132            end: Vector2::new(0.0, size),
133            thickness,
134        },
135    ]
136}
137
138pub fn make_cross(ctx: &mut BuildContext, size: f32, thickness: f32) -> Handle<VectorImage> {
139    VectorImageBuilder::new(
140        WidgetBuilder::new()
141            .with_horizontal_alignment(HorizontalAlignment::Center)
142            .with_vertical_alignment(VerticalAlignment::Center)
143            .with_width(size)
144            .with_height(size)
145            .with_foreground(ctx.style.property(Style::BRUSH_BRIGHT)),
146    )
147    .with_primitives(make_cross_primitive(size, thickness))
148    .build(ctx)
149}
150
151pub fn make_simple_tooltip(ctx: &mut BuildContext, text: &str) -> RcUiNodeHandle {
152    let handle = BorderBuilder::new(
153        WidgetBuilder::new()
154            .with_visibility(false)
155            .with_hit_test_visibility(false)
156            .with_foreground(ctx.style.property(Style::BRUSH_DARKEST))
157            .with_background(Brush::Solid(Color::opaque(230, 230, 230)).into())
158            .with_width(300.0)
159            .with_child(
160                TextBuilder::new(
161                    WidgetBuilder::new()
162                        .with_margin(Thickness::uniform(2.0))
163                        .with_foreground(ctx.style.property(Style::BRUSH_DARKER)),
164                )
165                .with_wrap(WrapMode::Word)
166                .with_text(text)
167                .build(ctx),
168            ),
169    )
170    .build(ctx);
171    RcUiNodeHandle::new(handle, ctx.sender())
172}
173
174pub fn make_asset_preview_tooltip(
175    texture: Option<TextureResource>,
176    ctx: &mut BuildContext,
177) -> (RcUiNodeHandle, Handle<Image>) {
178    let size = 120.0;
179    let image_preview;
180    let image_preview_tooltip = BorderBuilder::new(
181        WidgetBuilder::new()
182            .with_visibility(false)
183            .with_hit_test_visibility(false)
184            .with_foreground(ctx.style.property(Style::BRUSH_DARKEST))
185            .with_background(Brush::Solid(Color::opaque(230, 230, 230)).into())
186            .with_width(size + 2.0)
187            .with_height(size + 2.0)
188            .with_child({
189                image_preview = ImageBuilder::new(
190                    WidgetBuilder::new()
191                        .on_column(0)
192                        .with_width(size)
193                        .with_height(size)
194                        .with_margin(Thickness::uniform(1.0)),
195                )
196                .with_checkerboard_background(true)
197                .with_opt_texture(texture)
198                .with_sync_with_texture_size(false)
199                .build(ctx);
200                image_preview
201            }),
202    )
203    .build(ctx);
204    (
205        RcUiNodeHandle::new(image_preview_tooltip, ctx.sender()),
206        image_preview,
207    )
208}
209
210fn adjust_decorator(builder: DecoratorBuilder, ctx: &BuildContext) -> DecoratorBuilder {
211    builder
212        .with_normal_brush(ctx.style.property(Style::BRUSH_DARKER))
213        .with_hover_brush(ctx.style.property(Style::BRUSH_DARK))
214        .with_pressed_brush(ctx.style.property(Style::BRUSH_PRIMARY))
215        .with_selected_brush(ctx.style.property(Style::BRUSH_LIGHTER_PRIMARY))
216}
217
218fn text_margin() -> Thickness {
219    Thickness {
220        left: 5.0,
221        top: 2.0,
222        right: 2.0,
223        bottom: 2.0,
224    }
225}
226
227pub fn make_dropdown_list_option_universal<T: Send + 'static>(
228    ctx: &mut BuildContext,
229    name: &str,
230    height: f32,
231    user_data: T,
232) -> Handle<UiNode> {
233    adjust_decorator(
234        DecoratorBuilder::new(
235            BorderBuilder::new(
236                WidgetBuilder::new()
237                    .with_height(height)
238                    .with_user_data(Arc::new(Mutex::new(user_data)))
239                    .with_child(
240                        TextBuilder::new(WidgetBuilder::new().with_margin(text_margin()))
241                            .with_vertical_text_alignment(VerticalAlignment::Center)
242                            .with_horizontal_text_alignment(HorizontalAlignment::Left)
243                            .with_text(name)
244                            .build(ctx),
245                    ),
246            )
247            .with_corner_radius(4.0f32.into())
248            .with_pad_by_corner_radius(false),
249        ),
250        ctx,
251    )
252    .build(ctx)
253    .to_base()
254}
255
256pub fn make_dropdown_list_option(ctx: &mut BuildContext, name: &str) -> Handle<UiNode> {
257    adjust_decorator(
258        DecoratorBuilder::new(
259            BorderBuilder::new(
260                WidgetBuilder::new().with_child(
261                    TextBuilder::new(
262                        WidgetBuilder::new()
263                            .with_margin(text_margin())
264                            .with_vertical_alignment(VerticalAlignment::Center),
265                    )
266                    .with_vertical_text_alignment(VerticalAlignment::Center)
267                    .with_horizontal_text_alignment(HorizontalAlignment::Left)
268                    .with_text(name)
269                    .build(ctx),
270                ),
271            )
272            .with_corner_radius(4.0f32.into())
273            .with_pad_by_corner_radius(false),
274        ),
275        ctx,
276    )
277    .build(ctx)
278    .to_base()
279}
280
281pub fn make_dropdown_list_option_with_height(
282    ctx: &mut BuildContext,
283    name: &str,
284    height: f32,
285) -> Handle<UiNode> {
286    adjust_decorator(
287        DecoratorBuilder::new(
288            BorderBuilder::new(
289                WidgetBuilder::new().with_height(height).with_child(
290                    TextBuilder::new(WidgetBuilder::new().with_margin(text_margin()))
291                        .with_vertical_text_alignment(VerticalAlignment::Center)
292                        .with_horizontal_text_alignment(HorizontalAlignment::Left)
293                        .with_text(name)
294                        .build(ctx),
295                ),
296            )
297            .with_corner_radius(4.0f32.into())
298            .with_pad_by_corner_radius(false),
299        ),
300        ctx,
301    )
302    .build(ctx)
303    .to_base()
304}
305
306pub fn make_image_button_with_tooltip(
307    ctx: &mut BuildContext,
308    width: f32,
309    height: f32,
310    image: Option<TextureResource>,
311    tooltip: &str,
312    tab_index: Option<usize>,
313) -> Handle<Button> {
314    ButtonBuilder::new(
315        WidgetBuilder::new()
316            .with_tab_index(tab_index)
317            .with_tooltip(make_simple_tooltip(ctx, tooltip))
318            .with_margin(Thickness::uniform(1.0)),
319    )
320    .with_content(
321        ImageBuilder::new(
322            WidgetBuilder::new()
323                .with_background(ctx.style.property(Style::BRUSH_BRIGHTEST))
324                .with_margin(Thickness::uniform(3.0))
325                .with_width(width)
326                .with_height(height),
327        )
328        .with_opt_texture(image)
329        .build(ctx),
330    )
331    .build(ctx)
332}
333
334pub fn make_text_and_image_button_with_tooltip(
335    ctx: &mut BuildContext,
336    text: &str,
337    image_width: f32,
338    image_height: f32,
339    image: Option<TextureResource>,
340    tooltip: &str,
341    row: usize,
342    column: usize,
343    tab_index: Option<usize>,
344    color: Color,
345    font_size: f32,
346) -> Handle<Button> {
347    let margin = 3.0;
348    ButtonBuilder::new(
349        WidgetBuilder::new()
350            .on_row(row)
351            .on_column(column)
352            .with_tab_index(tab_index)
353            .with_tooltip(make_simple_tooltip(ctx, tooltip))
354            .with_margin(Thickness::uniform(1.0)),
355    )
356    .with_content(
357        GridBuilder::new(
358            WidgetBuilder::new()
359                .with_child(
360                    ImageBuilder::new(
361                        WidgetBuilder::new()
362                            .on_row(0)
363                            .on_column(0)
364                            .with_background(Brush::Solid(color).into())
365                            .with_margin(Thickness {
366                                left: 2.0 * margin,
367                                top: margin,
368                                right: margin,
369                                bottom: margin,
370                            })
371                            .with_width(image_width - 2.0 * margin)
372                            .with_height(image_height - 2.0 * margin),
373                    )
374                    .with_opt_texture(image)
375                    .build(ctx),
376                )
377                .with_child(
378                    TextBuilder::new(
379                        WidgetBuilder::new()
380                            .on_row(0)
381                            .on_column(1)
382                            .with_vertical_alignment(VerticalAlignment::Center)
383                            .with_horizontal_alignment(HorizontalAlignment::Center)
384                            .with_margin(Thickness {
385                                left: 4.0,
386                                top: margin,
387                                right: 8.0,
388                                bottom: margin,
389                            }),
390                    )
391                    .with_font_size(font_size.into())
392                    .with_text(text)
393                    .build(ctx),
394                ),
395        )
396        .add_column(Column::auto())
397        .add_column(Column::stretch())
398        .add_row(Row::stretch())
399        .build(ctx),
400    )
401    .build(ctx)
402}
403
404pub fn load_image(data: &[u8]) -> Option<TextureResource> {
405    TextureResource::load_from_memory(
406        Uuid::new_v4(),
407        Default::default(),
408        data,
409        TextureImportOptions::default()
410            .with_compression(CompressionOptions::NoCompression)
411            .with_minification_filter(TextureMinificationFilter::LinearMipMapLinear)
412            .with_lod_bias(-1.0),
413    )
414    .ok()
415}