Skip to main content

freya_components/scrollviews/
scrollbar.rs

1use freya_core::prelude::*;
2use torin::{
3    prelude::{
4        Alignment,
5        Direction,
6        Position,
7    },
8    size::Size,
9};
10
11use crate::{
12    define_theme,
13    get_theme,
14    scrollviews::{
15        ScrollThumb,
16        shared::Axis,
17    },
18};
19
20define_theme! {
21    %[component]
22    pub ScrollBar {
23        %[fields]
24        background: Color,
25        thumb_background: Color,
26        hover_thumb_background: Color,
27        active_thumb_background: Color,
28        size: f32,
29    }
30}
31
32#[derive(Clone, Copy, PartialEq, Debug)]
33enum ScrollBarState {
34    Idle,
35    Hovering,
36}
37
38#[derive(Clone, PartialEq)]
39pub struct ScrollBar {
40    pub(crate) theme: Option<ScrollBarThemePartial>,
41    pub clicking_scrollbar: State<Option<(Axis, f64)>>,
42    pub axis: Axis,
43    pub offset: f32,
44    pub size: Size,
45    pub thumb: ScrollThumb,
46}
47
48impl ComponentOwned for ScrollBar {
49    fn render(self) -> impl IntoElement {
50        let scrollbar_theme = get_theme!(&self.theme, ScrollBarThemePreference, "scrollbar");
51
52        let mut state = use_state(|| ScrollBarState::Idle);
53
54        let (cross_size, cross_offset, opacity) = match *state.read() {
55            _ if self.clicking_scrollbar.read().is_some() => (16., 0., 160),
56            ScrollBarState::Idle => (12., 3., 0),
57            ScrollBarState::Hovering => (16., 0., 160),
58        };
59
60        let (
61            width,
62            height,
63            offset_x,
64            offset_y,
65            inner_offset_x,
66            inner_offset_y,
67            inner_width,
68            inner_height,
69        ) = match self.axis {
70            Axis::X => (
71                self.size.clone(),
72                Size::px(16.),
73                0.,
74                -16.,
75                self.offset,
76                cross_offset,
77                self.size.clone(),
78                Size::px(cross_size),
79            ),
80            Axis::Y => (
81                Size::px(16.),
82                self.size.clone(),
83                -16.,
84                0.,
85                cross_offset,
86                self.offset,
87                Size::px(cross_size),
88                self.size.clone(),
89            ),
90        };
91
92        let on_pointer_over = move |_| {
93            if !cfg!(target_os = "android") {
94                state.set(ScrollBarState::Hovering);
95            }
96        };
97        let on_pointer_out = move |_| {
98            if !cfg!(target_os = "android") {
99                state.set(ScrollBarState::Idle);
100            }
101        };
102
103        rect()
104            .position(Position::new_absolute())
105            .width(width)
106            .height(height)
107            .offset_x(offset_x)
108            .offset_y(offset_y)
109            .layer(999)
110            .child(
111                rect()
112                    .width(Size::fill())
113                    .height(Size::fill())
114                    .direction(if self.axis == Axis::Y {
115                        Direction::vertical()
116                    } else {
117                        Direction::horizontal()
118                    })
119                    .cross_align(Alignment::end())
120                    .background(scrollbar_theme.background.with_a(opacity))
121                    .on_pointer_over(on_pointer_over)
122                    .on_pointer_out(on_pointer_out)
123                    .child(
124                        rect()
125                            .width(inner_width)
126                            .height(inner_height)
127                            .offset_x(inner_offset_x)
128                            .offset_y(inner_offset_y)
129                            .child(self.thumb),
130                    ),
131            )
132    }
133}