1#![warn(missing_docs)]
25
26use crate::font::FontResource;
27use crate::style::resource::StyleResourceExt;
28use crate::style::{Style, StyledProperty};
29use crate::{
30 border::BorderBuilder,
31 brush::Brush,
32 button::{ButtonBuilder, ButtonMessage},
33 canvas::CanvasBuilder,
34 core::{
35 algebra::Vector2, color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
36 visitor::prelude::*,
37 },
38 decorator::DecoratorBuilder,
39 define_constructor,
40 grid::{Column, GridBuilder, Row},
41 message::{MessageDirection, UiMessage},
42 text::{TextBuilder, TextMessage},
43 utils::{make_arrow, ArrowDirection},
44 widget::{Widget, WidgetBuilder, WidgetMessage},
45 BuildContext, Control, HorizontalAlignment, Orientation, Thickness, UiNode, UserInterface,
46 VerticalAlignment,
47};
48
49use fyrox_core::uuid_provider;
50use fyrox_core::variable::InheritableVariable;
51use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
52use fyrox_graph::BaseSceneGraph;
53use std::ops::{Deref, DerefMut};
54
55#[derive(Debug, Clone, PartialEq)]
57pub enum ScrollBarMessage {
58 Value(f32),
61 MinValue(f32),
64 MaxValue(f32),
67 SizeRatio(f32),
70}
71
72impl ScrollBarMessage {
73 define_constructor!(
74 ScrollBarMessage:Value => fn value(f32), layout: false
76 );
77 define_constructor!(
78 ScrollBarMessage:MaxValue => fn max_value(f32), layout: false
80 );
81 define_constructor!(
82 ScrollBarMessage:MinValue => fn min_value(f32), layout: false
84 );
85 define_constructor!(
86 ScrollBarMessage:SizeRatio => fn size_ratio(f32), layout: false
88 );
89}
90
91#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
155#[reflect(derived_type = "UiNode")]
156pub struct ScrollBar {
157 pub widget: Widget,
159 pub min: InheritableVariable<f32>,
161 pub max: InheritableVariable<f32>,
163 pub value: InheritableVariable<f32>,
165 pub step: InheritableVariable<f32>,
167 pub orientation: InheritableVariable<Orientation>,
169 pub is_dragging: bool,
171 pub offset: Vector2<f32>,
173 pub increase: InheritableVariable<Handle<UiNode>>,
175 pub decrease: InheritableVariable<Handle<UiNode>>,
177 pub indicator: InheritableVariable<Handle<UiNode>>,
179 pub indicator_canvas: InheritableVariable<Handle<UiNode>>,
181 pub value_text: InheritableVariable<Handle<UiNode>>,
183 pub value_precision: InheritableVariable<usize>,
185}
186
187impl ConstructorProvider<UiNode, UserInterface> for ScrollBar {
188 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
189 GraphNodeConstructor::new::<Self>()
190 .with_variant("Scroll Bar", |ui| {
191 ScrollBarBuilder::new(WidgetBuilder::new().with_name("Scroll Bar"))
192 .build(&mut ui.build_ctx())
193 .into()
194 })
195 .with_group("Input")
196 }
197}
198
199crate::define_widget_deref!(ScrollBar);
200
201uuid_provider!(ScrollBar = "92accc96-b334-424d-97ea-332c4787acf6");
202
203impl Control for ScrollBar {
204 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
205 let size = self.widget.arrange_override(ui, final_size);
206
207 let percent = (*self.value - *self.min) / (*self.max - *self.min);
209
210 let field_size = ui.node(*self.indicator_canvas).actual_local_size();
211
212 let indicator = ui.node(*self.indicator);
213 match *self.orientation {
214 Orientation::Horizontal => {
215 ui.send_message(WidgetMessage::height(
216 *self.indicator,
217 MessageDirection::ToWidget,
218 field_size.y,
219 ));
220 ui.send_message(WidgetMessage::width(
221 *self.decrease,
222 MessageDirection::ToWidget,
223 field_size.y,
224 ));
225 ui.send_message(WidgetMessage::width(
226 *self.increase,
227 MessageDirection::ToWidget,
228 field_size.y,
229 ));
230
231 let position = Vector2::new(
232 percent * (field_size.x - indicator.actual_local_size().x).max(0.0),
233 0.0,
234 );
235 ui.send_message(WidgetMessage::desired_position(
236 *self.indicator,
237 MessageDirection::ToWidget,
238 position,
239 ));
240 }
241 Orientation::Vertical => {
242 ui.send_message(WidgetMessage::width(
243 *self.indicator,
244 MessageDirection::ToWidget,
245 field_size.x,
246 ));
247 ui.send_message(WidgetMessage::height(
248 *self.decrease,
249 MessageDirection::ToWidget,
250 field_size.x,
251 ));
252 ui.send_message(WidgetMessage::height(
253 *self.increase,
254 MessageDirection::ToWidget,
255 field_size.x,
256 ));
257
258 let position = Vector2::new(
259 0.0,
260 percent * (field_size.y - indicator.actual_local_size().y).max(0.0),
261 );
262 ui.send_message(WidgetMessage::desired_position(
263 *self.indicator,
264 MessageDirection::ToWidget,
265 position,
266 ));
267 }
268 }
269
270 size
271 }
272
273 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
274 self.widget.handle_routed_message(ui, message);
275
276 if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
277 if message.destination() == *self.increase {
278 ui.send_message(ScrollBarMessage::value(
279 self.handle(),
280 MessageDirection::ToWidget,
281 *self.value + *self.step,
282 ));
283 } else if message.destination() == *self.decrease {
284 ui.send_message(ScrollBarMessage::value(
285 self.handle(),
286 MessageDirection::ToWidget,
287 *self.value - *self.step,
288 ));
289 }
290 } else if let Some(msg) = message.data::<ScrollBarMessage>() {
291 if message.destination() == self.handle()
292 && message.direction() == MessageDirection::ToWidget
293 {
294 match *msg {
295 ScrollBarMessage::Value(value) => {
296 let old_value = *self.value;
297 let new_value = value.clamp(*self.min, *self.max);
298 if (new_value - old_value).abs() > f32::EPSILON {
299 self.value.set_value_and_mark_modified(new_value);
300 self.invalidate_arrange();
301
302 if self.value_text.is_some() {
303 ui.send_message(TextMessage::text(
304 *self.value_text,
305 MessageDirection::ToWidget,
306 format!("{:.1$}", value, *self.value_precision),
307 ));
308 }
309
310 let mut response = ScrollBarMessage::value(
311 self.handle,
312 MessageDirection::FromWidget,
313 *self.value,
314 );
315 response.flags = message.flags;
316 response.set_handled(message.handled());
317 ui.send_message(response);
318 }
319 }
320 ScrollBarMessage::MinValue(min) => {
321 if *self.min != min {
322 self.min.set_value_and_mark_modified(min);
323 if *self.min > *self.max {
324 std::mem::swap(&mut self.min, &mut self.max);
325 }
326 let old_value = *self.value;
327 let new_value = self.value.clamp(*self.min, *self.max);
328 if (new_value - old_value).abs() > f32::EPSILON {
329 ui.send_message(ScrollBarMessage::value(
330 self.handle(),
331 MessageDirection::ToWidget,
332 new_value,
333 ));
334 }
335
336 let response = ScrollBarMessage::min_value(
337 self.handle,
338 MessageDirection::FromWidget,
339 *self.min,
340 );
341 response.set_handled(message.handled());
342 ui.send_message(response);
343 }
344 }
345 ScrollBarMessage::MaxValue(max) => {
346 if *self.max != max {
347 self.max.set_value_and_mark_modified(max);
348 if *self.max < *self.min {
349 std::mem::swap(&mut self.min, &mut self.max);
350 }
351 let old_value = *self.value;
352 let value = self.value.clamp(*self.min, *self.max);
353 if (value - old_value).abs() > f32::EPSILON {
354 ui.send_message(ScrollBarMessage::value(
355 self.handle(),
356 MessageDirection::ToWidget,
357 value,
358 ));
359 }
360
361 let response = ScrollBarMessage::max_value(
362 self.handle,
363 MessageDirection::FromWidget,
364 *self.max,
365 );
366 response.set_handled(message.handled());
367 ui.send_message(response);
368 }
369 }
370 ScrollBarMessage::SizeRatio(size_ratio) => {
371 let field_size = ui.node(*self.indicator_canvas).actual_global_size();
372 let indicator_size = ui.node(*self.indicator).actual_global_size();
373
374 match *self.orientation {
375 Orientation::Horizontal => {
376 let new_size = (size_ratio * field_size.x).max(15.0);
378 let old_size = indicator_size.x;
379
380 if new_size != old_size {
381 ui.send_message(WidgetMessage::width(
382 *self.indicator,
383 MessageDirection::ToWidget,
384 new_size,
385 ));
386 }
387 }
388 Orientation::Vertical => {
389 let new_size = (size_ratio * field_size.y).max(15.0);
391 let old_size = indicator_size.y;
392
393 if new_size != old_size {
394 ui.send_message(WidgetMessage::height(
395 *self.indicator,
396 MessageDirection::ToWidget,
397 new_size,
398 ));
399 }
400 }
401 }
402 }
403 }
404 }
405 } else if let Some(msg) = message.data::<WidgetMessage>() {
406 if message.destination() == *self.indicator {
407 match msg {
408 WidgetMessage::MouseDown { pos, .. } => {
409 if self.indicator.is_some() {
410 let indicator_pos = ui.nodes.borrow(*self.indicator).screen_position();
411 self.is_dragging = true;
412 self.offset = indicator_pos - *pos;
413 ui.capture_mouse(*self.indicator);
414 message.set_handled(true);
415 }
416 }
417 WidgetMessage::MouseUp { .. } => {
418 self.is_dragging = false;
419 ui.release_mouse_capture();
420 message.set_handled(true);
421 }
422 WidgetMessage::MouseMove { pos: mouse_pos, .. } => {
423 if self.indicator.is_some() {
424 let indicator_canvas = ui.node(*self.indicator_canvas);
425 let indicator_size =
426 ui.nodes.borrow(*self.indicator).actual_global_size();
427 if self.is_dragging {
428 let percent = match *self.orientation {
429 Orientation::Horizontal => {
430 let span = indicator_canvas.actual_global_size().x
431 - indicator_size.x;
432 let offset = mouse_pos.x
433 - indicator_canvas.screen_position().x
434 + self.offset.x;
435 if span > 0.0 {
436 (offset / span).clamp(0.0, 1.0)
437 } else {
438 0.0
439 }
440 }
441 Orientation::Vertical => {
442 let span = indicator_canvas.actual_global_size().y
443 - indicator_size.y;
444 let offset = mouse_pos.y
445 - indicator_canvas.screen_position().y
446 + self.offset.y;
447 if span > 0.0 {
448 (offset / span).clamp(0.0, 1.0)
449 } else {
450 0.0
451 }
452 }
453 };
454 ui.send_message(ScrollBarMessage::value(
455 self.handle(),
456 MessageDirection::ToWidget,
457 *self.min + percent * (*self.max - *self.min),
458 ));
459 message.set_handled(true);
460 }
461 }
462 }
463 _ => (),
464 }
465 }
466 }
467 }
468}
469
470pub struct ScrollBarBuilder {
472 widget_builder: WidgetBuilder,
473 min: Option<f32>,
474 max: Option<f32>,
475 value: Option<f32>,
476 step: Option<f32>,
477 orientation: Option<Orientation>,
478 increase: Option<Handle<UiNode>>,
479 decrease: Option<Handle<UiNode>>,
480 indicator: Option<Handle<UiNode>>,
481 body: Option<Handle<UiNode>>,
482 show_value: bool,
483 value_precision: usize,
484 font: Option<FontResource>,
485 font_size: Option<StyledProperty<f32>>,
486}
487
488impl ScrollBarBuilder {
489 pub fn new(widget_builder: WidgetBuilder) -> Self {
491 Self {
492 widget_builder,
493 min: None,
494 max: None,
495 value: None,
496 step: None,
497 orientation: None,
498 increase: None,
499 decrease: None,
500 indicator: None,
501 body: None,
502 show_value: false,
503 value_precision: 3,
504 font: None,
505 font_size: None,
506 }
507 }
508
509 pub fn with_min(mut self, min: f32) -> Self {
511 self.min = Some(min);
512 self
513 }
514
515 pub fn with_max(mut self, max: f32) -> Self {
517 self.max = Some(max);
518 self
519 }
520
521 pub fn with_value(mut self, value: f32) -> Self {
523 self.value = Some(value);
524 self
525 }
526
527 pub fn with_orientation(mut self, orientation: Orientation) -> Self {
529 self.orientation = Some(orientation);
530 self
531 }
532
533 pub fn with_step(mut self, step: f32) -> Self {
535 self.step = Some(step);
536 self
537 }
538
539 pub fn with_increase(mut self, increase: Handle<UiNode>) -> Self {
541 self.increase = Some(increase);
542 self
543 }
544
545 pub fn with_decrease(mut self, decrease: Handle<UiNode>) -> Self {
547 self.decrease = Some(decrease);
548 self
549 }
550
551 pub fn with_indicator(mut self, indicator: Handle<UiNode>) -> Self {
553 self.indicator = Some(indicator);
554 self
555 }
556
557 pub fn with_body(mut self, body: Handle<UiNode>) -> Self {
559 self.body = Some(body);
560 self
561 }
562
563 pub fn show_value(mut self, state: bool) -> Self {
565 self.show_value = state;
566 self
567 }
568
569 pub fn with_value_precision(mut self, precision: usize) -> Self {
571 self.value_precision = precision;
572 self
573 }
574
575 pub fn with_font(mut self, font: FontResource) -> Self {
577 self.font = Some(font);
578 self
579 }
580
581 pub fn with_font_size(mut self, size: StyledProperty<f32>) -> Self {
583 self.font_size = Some(size);
584 self
585 }
586
587 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
589 let orientation = self.orientation.unwrap_or(Orientation::Horizontal);
590
591 let increase = self.increase.unwrap_or_else(|| {
592 ButtonBuilder::new(WidgetBuilder::new())
593 .with_content(match orientation {
594 Orientation::Horizontal => make_arrow(ctx, ArrowDirection::Right, 8.0),
595 Orientation::Vertical => make_arrow(ctx, ArrowDirection::Bottom, 8.0),
596 })
597 .with_repeat_clicks_on_hold(true)
598 .build(ctx)
599 });
600
601 match orientation {
602 Orientation::Vertical => {
603 ctx[increase].set_height(30.0).set_row(2).set_column(0);
604 }
605 Orientation::Horizontal => {
606 ctx[increase].set_width(30.0).set_row(0).set_column(2);
607 }
608 }
609
610 let decrease = self.decrease.unwrap_or_else(|| {
611 ButtonBuilder::new(WidgetBuilder::new())
612 .with_content(match orientation {
613 Orientation::Horizontal => make_arrow(ctx, ArrowDirection::Left, 8.0),
614 Orientation::Vertical => make_arrow(ctx, ArrowDirection::Top, 8.0),
615 })
616 .with_repeat_clicks_on_hold(true)
617 .build(ctx)
618 });
619
620 ctx[decrease].set_row(0).set_column(0);
621
622 match orientation {
623 Orientation::Vertical => ctx[decrease].set_height(30.0),
624 Orientation::Horizontal => ctx[decrease].set_width(30.0),
625 };
626
627 let indicator = self.indicator.unwrap_or_else(|| {
628 DecoratorBuilder::new(
629 BorderBuilder::new(
630 WidgetBuilder::new().with_foreground(Brush::Solid(Color::TRANSPARENT).into()),
631 )
632 .with_corner_radius(8.0f32.into())
633 .with_pad_by_corner_radius(false)
634 .with_stroke_thickness(Thickness::uniform(1.0).into()),
635 )
636 .with_normal_brush(ctx.style.property(Style::BRUSH_LIGHT))
637 .with_hover_brush(ctx.style.property(Style::BRUSH_LIGHTER))
638 .with_pressed_brush(ctx.style.property(Style::BRUSH_LIGHTEST))
639 .build(ctx)
640 });
641
642 match orientation {
643 Orientation::Vertical => {
644 ctx[indicator].set_min_size(Vector2::new(0.0, 15.0));
645 }
646 Orientation::Horizontal => {
647 ctx[indicator].set_min_size(Vector2::new(15.0, 0.0));
648 }
649 }
650
651 let min = self.min.unwrap_or(0.0);
652 let max = self.max.unwrap_or(100.0);
653 let value = self.value.unwrap_or(0.0).clamp(min, max);
654
655 let value_text = if self.show_value {
656 let value_text = TextBuilder::new(
657 WidgetBuilder::new()
658 .with_visibility(self.show_value)
659 .with_horizontal_alignment(HorizontalAlignment::Center)
660 .with_vertical_alignment(VerticalAlignment::Center)
661 .with_hit_test_visibility(false)
662 .with_margin(Thickness::uniform(3.0))
663 .on_column(match orientation {
664 Orientation::Horizontal => 1,
665 Orientation::Vertical => 0,
666 })
667 .on_row(match orientation {
668 Orientation::Horizontal => 0,
669 Orientation::Vertical => 1,
670 }),
671 )
672 .with_font(self.font.unwrap_or_else(|| ctx.default_font()))
673 .with_font_size(
674 self.font_size
675 .unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
676 )
677 .with_text(format!("{:.1$}", value, self.value_precision))
678 .build(ctx);
679
680 ctx.link(value_text, indicator);
681
682 value_text
683 } else {
684 Handle::NONE
685 };
686
687 let indicator_canvas = CanvasBuilder::new(
688 WidgetBuilder::new()
689 .on_column(match orientation {
690 Orientation::Horizontal => 1,
691 Orientation::Vertical => 0,
692 })
693 .on_row(match orientation {
694 Orientation::Horizontal => 0,
695 Orientation::Vertical => 1,
696 })
697 .with_child(indicator),
698 )
699 .build(ctx);
700
701 let grid = GridBuilder::new(
702 WidgetBuilder::new()
703 .with_child(decrease)
704 .with_child(indicator_canvas)
705 .with_child(increase),
706 )
707 .add_rows(match orientation {
708 Orientation::Horizontal => vec![Row::stretch()],
709 Orientation::Vertical => vec![Row::auto(), Row::stretch(), Row::auto()],
710 })
711 .add_columns(match orientation {
712 Orientation::Horizontal => vec![Column::auto(), Column::stretch(), Column::auto()],
713 Orientation::Vertical => vec![Column::stretch()],
714 })
715 .build(ctx);
716
717 let body = self.body.unwrap_or_else(|| {
718 BorderBuilder::new(
719 WidgetBuilder::new().with_background(ctx.style.property(Style::BRUSH_DARK)),
720 )
721 .with_stroke_thickness(Thickness::uniform(1.0).into())
722 .build(ctx)
723 });
724 ctx.link(grid, body);
725
726 let node = UiNode::new(ScrollBar {
727 widget: self.widget_builder.with_child(body).build(ctx),
728 min: min.into(),
729 max: max.into(),
730 value: value.into(),
731 step: self.step.unwrap_or(1.0).into(),
732 orientation: orientation.into(),
733 is_dragging: false,
734 offset: Vector2::default(),
735 increase: increase.into(),
736 decrease: decrease.into(),
737 indicator: indicator.into(),
738 indicator_canvas: indicator_canvas.into(),
739 value_text: value_text.into(),
740 value_precision: self.value_precision.into(),
741 });
742 ctx.add_node(node)
743 }
744}
745
746#[cfg(test)]
747mod test {
748 use crate::scroll_bar::ScrollBarBuilder;
749 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
750
751 #[test]
752 fn test_deletion() {
753 test_widget_deletion(|ctx| ScrollBarBuilder::new(WidgetBuilder::new()).build(ctx));
754 }
755}