fyrox_ui/
scroll_bar.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
21//! Scroll bar is used to represent a value on a finite range. It has a thumb that shows the current value on
22//! on the bar. See [`ScrollBar`] docs for more info and usage examples.
23
24#![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/// A set of messages that can be accepted by [`ScrollBar`] widget.
55#[derive(Debug, Clone, PartialEq)]
56pub enum ScrollBarMessage {
57    /// Used to indicate that the value of the scroll bar has changed ([`MessageDirection::FromWidget`]) or to set a
58    /// new value (with [`MessageDirection::ToWidget`].
59    Value(f32),
60    /// Used to indicate that the min value of the scroll bar has changed ([`MessageDirection::FromWidget`]) or to set a
61    /// new min value (with [`MessageDirection::ToWidget`].
62    MinValue(f32),
63    /// Used to indicate that the max value of the scroll bar has changed ([`MessageDirection::FromWidget`]) or to set a
64    /// new max value (with [`MessageDirection::ToWidget`].
65    MaxValue(f32),
66    /// Used to set the size of the indicator(thumb) adaptively, according to the relative sizes of the container and the
67    /// content in it (with [`MessageDirection::ToWidget`].
68    SizeRatio(f32),
69}
70
71impl ScrollBarMessage {
72    define_constructor!(
73        /// Creates [`ScrollBarMessage::Value`] message.
74        ScrollBarMessage:Value => fn value(f32), layout: false
75    );
76    define_constructor!(
77        /// Creates [`ScrollBarMessage::MaxValue`] message.
78        ScrollBarMessage:MaxValue => fn max_value(f32), layout: false
79    );
80    define_constructor!(
81        /// Creates [`ScrollBarMessage::MinValue`] message.
82        ScrollBarMessage:MinValue => fn min_value(f32), layout: false
83    );
84    define_constructor!(
85        /// Creates [`ScrollBarMessage::SizeRatio`] message.
86        ScrollBarMessage:SizeRatio => fn size_ratio(f32), layout: false
87    );
88}
89
90/// Scroll bar is used to represent a value on a finite range. It has a thumb that shows the current value on
91/// on the bar. Usually it is used in pair with [`crate::scroll_panel::ScrollPanel`] to create something like
92/// [`crate::scroll_viewer::ScrollViewer`] widget. However, it could also be used to create sliders to show some
93/// value that lies within some range.
94///
95/// ## Example
96///
97/// A simple example of how to create a new [`ScrollBar`] could be something like this:
98///
99/// ```rust
100/// # use fyrox_ui::{
101/// #     core::pool::Handle, scroll_bar::ScrollBarBuilder, widget::WidgetBuilder, BuildContext,
102/// #     UiNode,
103/// # };
104/// fn create_scroll_bar(ctx: &mut BuildContext) -> Handle<UiNode> {
105///     ScrollBarBuilder::new(WidgetBuilder::new())
106///         .with_min(0.0)
107///         .with_max(200.0)
108///         .with_value(123.0)
109///         .build(ctx)
110/// }
111/// ```
112///
113/// It creates a horizontal scroll bar with `123.0` value and a range of `[0.0..200.0]`. To fetch the new value
114/// of the scroll bar, use [`ScrollBarMessage::Value`] message:
115///
116/// ```rust
117/// # use fyrox_ui::{
118/// #     core::pool::Handle,
119/// #     message::{MessageDirection, UiMessage},
120/// #     scroll_bar::ScrollBarMessage,
121/// #     UiNode,
122/// # };
123/// # fn foo(scroll_bar: Handle<UiNode>, message: &mut UiMessage) {
124/// if message.destination() == scroll_bar
125///     && message.direction() == MessageDirection::FromWidget
126/// {
127///     if let Some(ScrollBarMessage::Value(value)) = message.data() {
128///         println!("{}", value);
129///     }
130/// }
131/// # }
132/// ```
133///
134/// Please note, that you need to explicitly filter messages by [`MessageDirection::FromWidget`], because it's the only
135/// direction that is used as an "indicator" that the value was accepted by the scroll bar.
136///
137/// ## Orientation
138///
139/// Scroll bar could be either horizontal (default) or vertical. You can select the orientation when building
140/// a scroll bar using [`ScrollBarBuilder::with_orientation`] method and provide a desired value from [`Orientation`]
141/// enum there.
142///
143/// ## Show values
144///
145/// By default, scroll bar does not show its actual value, you can turn it on using [`ScrollBarBuilder::show_value`]
146/// method with `true` as the first argument. To change rounding of the value, use [`ScrollBarBuilder::with_value_precision`]
147/// and provide the desired amount of decimal places there.
148///
149/// ## Step
150///
151/// Scroll bar provides arrows to change the current value using a fixed step value. You can change it using
152/// [`ScrollBarBuilder::with_step`] method.
153#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
154pub struct ScrollBar {
155    /// Base widget of the scroll bar.
156    pub widget: Widget,
157    /// Min value of the scroll bar.
158    pub min: InheritableVariable<f32>,
159    /// Max value of the scroll bar.
160    pub max: InheritableVariable<f32>,
161    /// Current value of the scroll bar.
162    pub value: InheritableVariable<f32>,
163    /// Step of the scroll bar.
164    pub step: InheritableVariable<f32>,
165    /// Current orientation of the scroll bar.
166    pub orientation: InheritableVariable<Orientation>,
167    /// Internal flag, that could be used to check whether the scroll bar's thumb is being dragged or not.
168    pub is_dragging: bool,
169    /// Internal mouse offset that is used for dragging purposes.
170    pub offset: Vector2<f32>,
171    /// A handle of the increase button.
172    pub increase: InheritableVariable<Handle<UiNode>>,
173    /// A handle of the decrease button.
174    pub decrease: InheritableVariable<Handle<UiNode>>,
175    /// A handle of the indicator (thumb).
176    pub indicator: InheritableVariable<Handle<UiNode>>,
177    /// A handle of the canvas that is used for the thumb.
178    pub indicator_canvas: InheritableVariable<Handle<UiNode>>,
179    /// A handle of the [`crate::text::Text`] widget that is used to show the current value of the scroll bar.
180    pub value_text: InheritableVariable<Handle<UiNode>>,
181    /// Current value precison in decimal places.
182    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        // Adjust indicator position according to current value
206        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                                // minimum size of the indicator will be 15 irrespective of size ratio
375                                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                                // minimum size of the indicator will be 15 irrespective of size ratio
388                                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
468/// Scroll bar widget is used to create [`ScrollBar`] widget instances and add them to the user interface.
469pub 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    /// Creates new scroll bar builder instance.
488    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    /// Sets the desired min value.
508    pub fn with_min(mut self, min: f32) -> Self {
509        self.min = Some(min);
510        self
511    }
512
513    /// Sets the desired max value.
514    pub fn with_max(mut self, max: f32) -> Self {
515        self.max = Some(max);
516        self
517    }
518
519    /// Sets the desired value.
520    pub fn with_value(mut self, value: f32) -> Self {
521        self.value = Some(value);
522        self
523    }
524
525    /// Sets the desired orientation.
526    pub fn with_orientation(mut self, orientation: Orientation) -> Self {
527        self.orientation = Some(orientation);
528        self
529    }
530
531    /// Sets the desired step.
532    pub fn with_step(mut self, step: f32) -> Self {
533        self.step = Some(step);
534        self
535    }
536
537    /// Sets the new handle to a button, that is used to increase values of the scroll bar.
538    pub fn with_increase(mut self, increase: Handle<UiNode>) -> Self {
539        self.increase = Some(increase);
540        self
541    }
542
543    /// Sets the new handle to a button, that is used to decrease values of the scroll bar.
544    pub fn with_decrease(mut self, decrease: Handle<UiNode>) -> Self {
545        self.decrease = Some(decrease);
546        self
547    }
548
549    /// Sets the new handle to a widget, that is used as a thumb of the scroll bar.
550    pub fn with_indicator(mut self, indicator: Handle<UiNode>) -> Self {
551        self.indicator = Some(indicator);
552        self
553    }
554
555    /// Sets the new handle to a widget, that is used as a background of the scroll bar.
556    pub fn with_body(mut self, body: Handle<UiNode>) -> Self {
557        self.body = Some(body);
558        self
559    }
560
561    /// Show or hide the value of the scroll bar.
562    pub fn show_value(mut self, state: bool) -> Self {
563        self.show_value = state;
564        self
565    }
566
567    /// Sets the desired value precision of the scroll bar.
568    pub fn with_value_precision(mut self, precision: usize) -> Self {
569        self.value_precision = precision;
570        self
571    }
572
573    /// Sets the desired font.
574    pub fn with_font(mut self, font: FontResource) -> Self {
575        self.font = Some(font);
576        self
577    }
578
579    /// Sets the desired font size.
580    pub fn with_font_size(mut self, size: StyledProperty<f32>) -> Self {
581        self.font_size = Some(size);
582        self
583    }
584
585    /// Creates new scroll bar instance and adds it to the user interface.
586    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}