1use 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}