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