Skip to main content

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
22//! current value of the bar. See [`ScrollBar`] docs for more info and usage examples.
23
24#![warn(missing_docs)]
25
26use crate::button::Button;
27use crate::canvas::Canvas;
28use crate::text::Text;
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    font::FontResource,
40    grid::{Column, GridBuilder, Row},
41    message::{MessageData, UiMessage},
42    style::{resource::StyleResourceExt, Style, StyledProperty},
43    text::{TextBuilder, TextMessage},
44    utils::{make_arrow, ArrowDirection},
45    widget::{Widget, WidgetBuilder, WidgetMessage},
46    BuildContext, Control, HorizontalAlignment, Orientation, Thickness, UiNode, UserInterface,
47    VerticalAlignment,
48};
49use fyrox_core::uuid_provider;
50use fyrox_core::variable::InheritableVariable;
51use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
52use fyrox_graph::SceneGraph;
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 ([`crate::message::MessageDirection::FromWidget`]) or to set a
58    /// new value (with [`crate::message::MessageDirection::ToWidget`].
59    Value(f32),
60    /// Used to indicate that the min value of the scroll bar has changed ([`crate::message::MessageDirection::FromWidget`]) or to set a
61    /// new min value (with [`crate::message::MessageDirection::ToWidget`].
62    MinValue(f32),
63    /// Used to indicate that the max value of the scroll bar has changed ([`crate::message::MessageDirection::FromWidget`]) or to set a
64    /// new max value (with [`crate::message::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 [`crate::message::MessageDirection::ToWidget`].
68    SizeRatio(f32),
69}
70impl MessageData for ScrollBarMessage {}
71
72/// Scroll bar is used to represent a value on a finite range. It has a thumb that shows the current value
73/// on the bar. Usually it is used in pair with [`crate::scroll_panel::ScrollPanel`] to create something like
74/// [`crate::scroll_viewer::ScrollViewer`] widget. However, it could also be used to create sliders to show some
75/// value that lies within some range.
76///
77/// ## Example
78///
79/// A simple example of how to create a new [`ScrollBar`] could be something like this:
80///
81/// ```rust
82/// # use fyrox_ui::{
83/// #     core::pool::Handle, scroll_bar::ScrollBarBuilder, widget::WidgetBuilder, BuildContext,
84/// #     UiNode,
85/// # };
86/// # use fyrox_ui::scroll_bar::ScrollBar;
87///
88/// fn create_scroll_bar(ctx: &mut BuildContext) -> Handle<ScrollBar> {
89///     ScrollBarBuilder::new(WidgetBuilder::new())
90///         .with_min(0.0)
91///         .with_max(200.0)
92///         .with_value(123.0)
93///         .build(ctx)
94/// }
95/// ```
96///
97/// It creates a horizontal scroll bar with `123.0` value and a range of `[0.0..200.0]`. To fetch the new value
98/// of the scroll bar, use [`ScrollBarMessage::Value`] message:
99///
100/// ```rust
101/// # use fyrox_ui::{
102/// #     core::pool::Handle,
103/// #     message::{MessageDirection, UiMessage},
104/// #     scroll_bar::ScrollBarMessage,
105/// #     UiNode,
106/// # };
107/// # fn foo(scroll_bar: Handle<UiNode>, message: &mut UiMessage) {
108/// if let Some(ScrollBarMessage::Value(value)) = message.data_from(scroll_bar) {
109///     println!("{}", value);
110/// }
111/// # }
112/// ```
113///
114/// Please note, that you need to explicitly filter messages by [`crate::message::MessageDirection::FromWidget`], because it's the only
115/// direction that is used as an "indicator" that the value was accepted by the scroll bar.
116///
117/// ## Orientation
118///
119/// Scroll bar could be either horizontal (default) or vertical. You can select the orientation when building
120/// a scroll bar using [`ScrollBarBuilder::with_orientation`] method and provide a desired value from [`Orientation`]
121/// enum there.
122///
123/// ## Show values
124///
125/// By default, scroll bar does not show its actual value, you can turn it on using [`ScrollBarBuilder::show_value`]
126/// method with `true` as the first argument. To change rounding of the value, use [`ScrollBarBuilder::with_value_precision`]
127/// and provide the desired amount of decimal places there.
128///
129/// ## Step
130///
131/// Scroll bar provides arrows to change the current value using a fixed step value. You can change it using
132/// [`ScrollBarBuilder::with_step`] method.
133#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
134#[reflect(derived_type = "UiNode")]
135pub struct ScrollBar {
136    /// Base widget of the scroll bar.
137    pub widget: Widget,
138    /// Min value of the scroll bar.
139    pub min: InheritableVariable<f32>,
140    /// Max value of the scroll bar.
141    pub max: InheritableVariable<f32>,
142    /// Current value of the scroll bar.
143    pub value: InheritableVariable<f32>,
144    /// Step of the scroll bar.
145    pub step: InheritableVariable<f32>,
146    /// Current orientation of the scroll bar.
147    pub orientation: InheritableVariable<Orientation>,
148    /// Internal flag, that could be used to check whether the scroll bar's thumb is being dragged or not.
149    pub is_dragging: bool,
150    /// Internal mouse offset that is used for dragging purposes.
151    pub offset: Vector2<f32>,
152    /// A handle of the increase button.
153    pub increase: InheritableVariable<Handle<Button>>,
154    /// A handle of the decrease button.
155    pub decrease: InheritableVariable<Handle<Button>>,
156    /// A handle of the indicator (thumb).
157    pub indicator: InheritableVariable<Handle<UiNode>>,
158    /// A handle of the canvas that is used for the thumb.
159    pub indicator_canvas: InheritableVariable<Handle<Canvas>>,
160    /// A handle of the [`Text`] widget that is used to show the current value of the scroll bar.
161    pub value_text: InheritableVariable<Handle<Text>>,
162    /// Current value precision in decimal places.
163    pub value_precision: InheritableVariable<usize>,
164}
165
166impl ConstructorProvider<UiNode, UserInterface> for ScrollBar {
167    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
168        GraphNodeConstructor::new::<Self>()
169            .with_variant("Scroll Bar", |ui| {
170                ScrollBarBuilder::new(WidgetBuilder::new().with_name("Scroll Bar"))
171                    .build(&mut ui.build_ctx())
172                    .to_base()
173                    .into()
174            })
175            .with_group("Input")
176    }
177}
178
179crate::define_widget_deref!(ScrollBar);
180
181uuid_provider!(ScrollBar = "92accc96-b334-424d-97ea-332c4787acf6");
182
183impl Control for ScrollBar {
184    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
185        let size = self.widget.arrange_override(ui, final_size);
186
187        // Adjust indicator position according to current value
188        let percent = (*self.value - *self.min) / (*self.max - *self.min);
189
190        let field_size = ui[*self.indicator_canvas].actual_local_size();
191
192        let indicator = ui.node(*self.indicator);
193        match *self.orientation {
194            Orientation::Horizontal => {
195                ui.send(*self.indicator, WidgetMessage::Height(field_size.y));
196                ui.send(*self.decrease, WidgetMessage::Width(field_size.y));
197                ui.send(*self.increase, WidgetMessage::Width(field_size.y));
198
199                let position = Vector2::new(
200                    percent * (field_size.x - indicator.actual_local_size().x).max(0.0),
201                    0.0,
202                );
203                ui.send(*self.indicator, WidgetMessage::DesiredPosition(position));
204            }
205            Orientation::Vertical => {
206                ui.send(*self.indicator, WidgetMessage::Width(field_size.x));
207                ui.send(*self.decrease, WidgetMessage::Height(field_size.x));
208                ui.send(*self.increase, WidgetMessage::Height(field_size.x));
209
210                let position = Vector2::new(
211                    0.0,
212                    percent * (field_size.y - indicator.actual_local_size().y).max(0.0),
213                );
214                ui.send(*self.indicator, WidgetMessage::DesiredPosition(position));
215            }
216        }
217
218        size
219    }
220
221    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
222        self.widget.handle_routed_message(ui, message);
223
224        if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
225            if message.destination() == *self.increase {
226                ui.send(
227                    self.handle(),
228                    ScrollBarMessage::Value(*self.value + *self.step),
229                );
230            } else if message.destination() == *self.decrease {
231                ui.send(
232                    self.handle(),
233                    ScrollBarMessage::Value(*self.value - *self.step),
234                );
235            }
236        } else if let Some(msg) = message.data_for::<ScrollBarMessage>(self.handle()) {
237            match *msg {
238                ScrollBarMessage::Value(value) => {
239                    let old_value = *self.value;
240                    let new_value = value.clamp(*self.min, *self.max);
241                    if (new_value - old_value).abs() > f32::EPSILON {
242                        self.value.set_value_and_mark_modified(new_value);
243                        self.invalidate_arrange();
244
245                        if self.value_text.is_some() {
246                            ui.send(
247                                *self.value_text,
248                                TextMessage::Text(format!("{:.1$}", value, *self.value_precision)),
249                            );
250                        }
251
252                        let mut response = UiMessage::from_widget(
253                            self.handle,
254                            ScrollBarMessage::Value(*self.value),
255                        );
256                        response.flags = message.flags;
257                        response.delivery_mode = message.delivery_mode;
258                        response.set_handled(message.handled());
259                        ui.send_message(response);
260                    }
261                }
262                ScrollBarMessage::MinValue(min) => {
263                    if *self.min != min {
264                        self.min.set_value_and_mark_modified(min);
265                        if *self.min > *self.max {
266                            std::mem::swap(&mut self.min, &mut self.max);
267                        }
268                        let old_value = *self.value;
269                        let new_value = self.value.clamp(*self.min, *self.max);
270                        if (new_value - old_value).abs() > f32::EPSILON {
271                            ui.send(self.handle(), ScrollBarMessage::Value(new_value));
272                        }
273
274                        let mut response = UiMessage::from_widget(
275                            self.handle,
276                            ScrollBarMessage::MinValue(*self.min),
277                        );
278                        response.flags = message.flags;
279                        response.delivery_mode = message.delivery_mode;
280                        response.set_handled(message.handled());
281                        ui.send_message(response);
282                    }
283                }
284                ScrollBarMessage::MaxValue(max) => {
285                    if *self.max != max {
286                        self.max.set_value_and_mark_modified(max);
287                        if *self.max < *self.min {
288                            std::mem::swap(&mut self.min, &mut self.max);
289                        }
290                        let old_value = *self.value;
291                        let value = self.value.clamp(*self.min, *self.max);
292                        if (value - old_value).abs() > f32::EPSILON {
293                            ui.send(self.handle(), ScrollBarMessage::Value(value));
294                        }
295
296                        let mut response = UiMessage::from_widget(
297                            self.handle,
298                            ScrollBarMessage::MaxValue(*self.max),
299                        );
300                        response.flags = message.flags;
301                        response.delivery_mode = message.delivery_mode;
302                        response.set_handled(message.handled());
303                        ui.send_message(response);
304                    }
305                }
306                ScrollBarMessage::SizeRatio(size_ratio) => {
307                    let field_size = ui[*self.indicator_canvas].actual_global_size();
308                    let indicator_size = ui.node(*self.indicator).actual_global_size();
309
310                    match *self.orientation {
311                        Orientation::Horizontal => {
312                            // the minimum size of the indicator will be 15 irrespective of size ratio
313                            let new_size = (size_ratio * field_size.x).max(15.0);
314                            let old_size = indicator_size.x;
315
316                            if new_size != old_size {
317                                ui.send(*self.indicator, WidgetMessage::Width(new_size));
318                            }
319                        }
320                        Orientation::Vertical => {
321                            // the minimum size of the indicator will be 15 irrespective of size ratio
322                            let new_size = (size_ratio * field_size.y).max(15.0);
323                            let old_size = indicator_size.y;
324
325                            if new_size != old_size {
326                                ui.send(*self.indicator, WidgetMessage::Height(new_size));
327                            }
328                        }
329                    }
330                }
331            }
332        } else if let Some(msg) = message.data::<WidgetMessage>() {
333            if message.destination() == *self.indicator {
334                match msg {
335                    WidgetMessage::MouseDown { pos, .. } => {
336                        if self.indicator.is_some() {
337                            let indicator_pos = ui.nodes.borrow(*self.indicator).screen_position();
338                            self.is_dragging = true;
339                            self.offset = indicator_pos - *pos;
340                            ui.capture_mouse(*self.indicator);
341                            message.set_handled(true);
342                        }
343                    }
344                    WidgetMessage::MouseUp { .. } => {
345                        self.is_dragging = false;
346                        ui.release_mouse_capture();
347                        message.set_handled(true);
348                    }
349                    WidgetMessage::MouseMove { pos: mouse_pos, .. } => {
350                        if self.indicator.is_some() {
351                            let indicator_canvas = &ui[*self.indicator_canvas];
352                            let indicator_size =
353                                ui.nodes.borrow(*self.indicator).actual_global_size();
354                            if self.is_dragging {
355                                let percent = match *self.orientation {
356                                    Orientation::Horizontal => {
357                                        let span = indicator_canvas.actual_global_size().x
358                                            - indicator_size.x;
359                                        let offset = mouse_pos.x
360                                            - indicator_canvas.screen_position().x
361                                            + self.offset.x;
362                                        if span > 0.0 {
363                                            (offset / span).clamp(0.0, 1.0)
364                                        } else {
365                                            0.0
366                                        }
367                                    }
368                                    Orientation::Vertical => {
369                                        let span = indicator_canvas.actual_global_size().y
370                                            - indicator_size.y;
371                                        let offset = mouse_pos.y
372                                            - indicator_canvas.screen_position().y
373                                            + self.offset.y;
374                                        if span > 0.0 {
375                                            (offset / span).clamp(0.0, 1.0)
376                                        } else {
377                                            0.0
378                                        }
379                                    }
380                                };
381                                ui.send(
382                                    self.handle(),
383                                    ScrollBarMessage::Value(
384                                        *self.min + percent * (*self.max - *self.min),
385                                    ),
386                                );
387                                message.set_handled(true);
388                            }
389                        }
390                    }
391                    _ => (),
392                }
393            }
394        }
395    }
396}
397
398/// Scroll bar widget is used to create [`ScrollBar`] widget instances and add them to the user interface.
399pub struct ScrollBarBuilder {
400    widget_builder: WidgetBuilder,
401    min: Option<f32>,
402    max: Option<f32>,
403    value: Option<f32>,
404    step: Option<f32>,
405    orientation: Option<Orientation>,
406    increase: Option<Handle<Button>>,
407    decrease: Option<Handle<Button>>,
408    indicator: Option<Handle<UiNode>>,
409    body: Option<Handle<UiNode>>,
410    show_value: bool,
411    value_precision: usize,
412    font: Option<FontResource>,
413    font_size: Option<StyledProperty<f32>>,
414}
415
416impl ScrollBarBuilder {
417    /// Creates new scroll bar builder instance.
418    pub fn new(widget_builder: WidgetBuilder) -> Self {
419        Self {
420            widget_builder,
421            min: None,
422            max: None,
423            value: None,
424            step: None,
425            orientation: None,
426            increase: None,
427            decrease: None,
428            indicator: None,
429            body: None,
430            show_value: false,
431            value_precision: 3,
432            font: None,
433            font_size: None,
434        }
435    }
436
437    /// Sets the desired min value.
438    pub fn with_min(mut self, min: f32) -> Self {
439        self.min = Some(min);
440        self
441    }
442
443    /// Sets the desired max value.
444    pub fn with_max(mut self, max: f32) -> Self {
445        self.max = Some(max);
446        self
447    }
448
449    /// Sets the desired value.
450    pub fn with_value(mut self, value: f32) -> Self {
451        self.value = Some(value);
452        self
453    }
454
455    /// Sets the desired orientation.
456    pub fn with_orientation(mut self, orientation: Orientation) -> Self {
457        self.orientation = Some(orientation);
458        self
459    }
460
461    /// Sets the desired step.
462    pub fn with_step(mut self, step: f32) -> Self {
463        self.step = Some(step);
464        self
465    }
466
467    /// Sets the new handle to a button, that is used to increase the values of the scroll bar.
468    pub fn with_increase(mut self, increase: Handle<Button>) -> Self {
469        self.increase = Some(increase);
470        self
471    }
472
473    /// Sets the new handle to a button, that is used to decrease values of the scroll bar.
474    pub fn with_decrease(mut self, decrease: Handle<Button>) -> Self {
475        self.decrease = Some(decrease);
476        self
477    }
478
479    /// Sets the new handle to a widget, that is used as a thumb of the scroll bar.
480    pub fn with_indicator(mut self, indicator: Handle<UiNode>) -> Self {
481        self.indicator = Some(indicator);
482        self
483    }
484
485    /// Sets the new handle to a widget, that is used as a background of the scroll bar.
486    pub fn with_body(mut self, body: Handle<UiNode>) -> Self {
487        self.body = Some(body);
488        self
489    }
490
491    /// Show or hide the value of the scroll bar.
492    pub fn show_value(mut self, state: bool) -> Self {
493        self.show_value = state;
494        self
495    }
496
497    /// Sets the desired value precision of the scroll bar.
498    pub fn with_value_precision(mut self, precision: usize) -> Self {
499        self.value_precision = precision;
500        self
501    }
502
503    /// Sets the desired font.
504    pub fn with_font(mut self, font: FontResource) -> Self {
505        self.font = Some(font);
506        self
507    }
508
509    /// Sets the desired font size.
510    pub fn with_font_size(mut self, size: StyledProperty<f32>) -> Self {
511        self.font_size = Some(size);
512        self
513    }
514
515    /// Creates new scroll bar instance and adds it to the user interface.
516    pub fn build(self, ctx: &mut BuildContext) -> Handle<ScrollBar> {
517        let orientation = self.orientation.unwrap_or(Orientation::Horizontal);
518
519        let increase = self.increase.unwrap_or_else(|| {
520            ButtonBuilder::new(WidgetBuilder::new())
521                .with_content(match orientation {
522                    Orientation::Horizontal => make_arrow(ctx, ArrowDirection::Right, 8.0),
523                    Orientation::Vertical => make_arrow(ctx, ArrowDirection::Bottom, 8.0),
524                })
525                .with_repeat_clicks_on_hold(true)
526                .build(ctx)
527        });
528
529        match orientation {
530            Orientation::Vertical => {
531                ctx[increase].set_height(30.0).set_row(2).set_column(0);
532            }
533            Orientation::Horizontal => {
534                ctx[increase].set_width(30.0).set_row(0).set_column(2);
535            }
536        }
537
538        let decrease = self.decrease.unwrap_or_else(|| {
539            ButtonBuilder::new(WidgetBuilder::new())
540                .with_content(match orientation {
541                    Orientation::Horizontal => make_arrow(ctx, ArrowDirection::Left, 8.0),
542                    Orientation::Vertical => make_arrow(ctx, ArrowDirection::Top, 8.0),
543                })
544                .with_repeat_clicks_on_hold(true)
545                .build(ctx)
546        });
547
548        ctx[decrease].set_row(0).set_column(0);
549
550        match orientation {
551            Orientation::Vertical => ctx[decrease].set_height(30.0),
552            Orientation::Horizontal => ctx[decrease].set_width(30.0),
553        };
554
555        let indicator = self.indicator.unwrap_or_else(|| {
556            DecoratorBuilder::new(
557                BorderBuilder::new(
558                    WidgetBuilder::new().with_foreground(Brush::Solid(Color::TRANSPARENT).into()),
559                )
560                .with_corner_radius(8.0f32.into())
561                .with_pad_by_corner_radius(false)
562                .with_stroke_thickness(Thickness::uniform(1.0).into()),
563            )
564            .with_normal_brush(ctx.style.property(Style::BRUSH_LIGHT))
565            .with_hover_brush(ctx.style.property(Style::BRUSH_LIGHTER))
566            .with_pressed_brush(ctx.style.property(Style::BRUSH_LIGHTEST))
567            .build(ctx)
568            .to_base()
569        });
570
571        match orientation {
572            Orientation::Vertical => {
573                ctx[indicator].set_min_size(Vector2::new(0.0, 15.0));
574            }
575            Orientation::Horizontal => {
576                ctx[indicator].set_min_size(Vector2::new(15.0, 0.0));
577            }
578        }
579
580        let min = self.min.unwrap_or(0.0);
581        let max = self.max.unwrap_or(100.0);
582        let value = self.value.unwrap_or(0.0).clamp(min, max);
583
584        let value_text = if self.show_value {
585            let value_text = TextBuilder::new(
586                WidgetBuilder::new()
587                    .with_visibility(self.show_value)
588                    .with_horizontal_alignment(HorizontalAlignment::Center)
589                    .with_vertical_alignment(VerticalAlignment::Center)
590                    .with_hit_test_visibility(false)
591                    .with_margin(Thickness::uniform(3.0))
592                    .on_column(match orientation {
593                        Orientation::Horizontal => 1,
594                        Orientation::Vertical => 0,
595                    })
596                    .on_row(match orientation {
597                        Orientation::Horizontal => 0,
598                        Orientation::Vertical => 1,
599                    }),
600            )
601            .with_font(self.font.unwrap_or_else(|| ctx.default_font()))
602            .with_font_size(
603                self.font_size
604                    .unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
605            )
606            .with_text(format!("{:.1$}", value, self.value_precision))
607            .build(ctx);
608
609            ctx.link(value_text, indicator);
610
611            value_text
612        } else {
613            Handle::NONE
614        };
615
616        let indicator_canvas = CanvasBuilder::new(
617            WidgetBuilder::new()
618                .on_column(match orientation {
619                    Orientation::Horizontal => 1,
620                    Orientation::Vertical => 0,
621                })
622                .on_row(match orientation {
623                    Orientation::Horizontal => 0,
624                    Orientation::Vertical => 1,
625                })
626                .with_child(indicator),
627        )
628        .build(ctx);
629
630        let grid = GridBuilder::new(
631            WidgetBuilder::new()
632                .with_child(decrease)
633                .with_child(indicator_canvas)
634                .with_child(increase),
635        )
636        .add_rows(match orientation {
637            Orientation::Horizontal => vec![Row::stretch()],
638            Orientation::Vertical => vec![Row::auto(), Row::stretch(), Row::auto()],
639        })
640        .add_columns(match orientation {
641            Orientation::Horizontal => vec![Column::auto(), Column::stretch(), Column::auto()],
642            Orientation::Vertical => vec![Column::stretch()],
643        })
644        .build(ctx);
645
646        let body = self.body.unwrap_or_else(|| {
647            BorderBuilder::new(
648                WidgetBuilder::new().with_background(ctx.style.property(Style::BRUSH_DARK)),
649            )
650            .with_stroke_thickness(Thickness::uniform(1.0).into())
651            .build(ctx)
652            .to_base()
653        });
654        ctx.link(grid, body);
655
656        let node = ScrollBar {
657            widget: self.widget_builder.with_child(body).build(ctx),
658            min: min.into(),
659            max: max.into(),
660            value: value.into(),
661            step: self.step.unwrap_or(1.0).into(),
662            orientation: orientation.into(),
663            is_dragging: false,
664            offset: Vector2::default(),
665            increase: increase.into(),
666            decrease: decrease.into(),
667            indicator: indicator.into(),
668            indicator_canvas: indicator_canvas.into(),
669            value_text: value_text.into(),
670            value_precision: self.value_precision.into(),
671        };
672        ctx.add(node)
673    }
674}
675
676#[cfg(test)]
677mod test {
678    use crate::scroll_bar::ScrollBarBuilder;
679    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
680
681    #[test]
682    fn test_deletion() {
683        test_widget_deletion(|ctx| ScrollBarBuilder::new(WidgetBuilder::new()).build(ctx));
684    }
685}