veloren_voxygen/ui/ice/renderer/widget/
scrollable.rs

1use super::super::{super::Rotation, IcedRenderer, Primitive, style};
2use common::util::srgba_to_linear;
3use iced::{Rectangle, mouse, scrollable};
4use style::scrollable::{Scroller, Track};
5
6const SCROLLBAR_MIN_HEIGHT: u16 = 6;
7
8impl scrollable::Renderer for IcedRenderer {
9    type Style = style::scrollable::Style;
10
11    // Interesting that this is here
12    // I guess we can take advantage of this to keep a constant size despite
13    // scaling?
14    fn scrollbar(
15        &self,
16        bounds: Rectangle,
17        content_bounds: Rectangle,
18        offset: u32,
19        scrollbar_width: u16,
20        scrollbar_margin: u16,
21        scroller_width: u16,
22    ) -> Option<scrollable::Scrollbar> {
23        if content_bounds.height > bounds.height {
24            // Area containing both scrollbar and scroller
25            let outer_width = (scrollbar_width.max(scroller_width) + 2 * scrollbar_margin) as f32;
26            let outer_bounds = Rectangle {
27                x: bounds.x + bounds.width - outer_width,
28                width: outer_width,
29                ..bounds
30            };
31
32            // Background scrollbar (i.e. the track)
33            let scrollbar_bounds = Rectangle {
34                x: bounds.x + bounds.width - outer_width / 2.0 - (scrollbar_width / 2) as f32,
35                width: scrollbar_width as f32,
36                ..bounds
37            };
38
39            // Interactive scroller
40            let visible_fraction = bounds.height / content_bounds.height;
41            let scroller_height =
42                (bounds.height * visible_fraction).max((2 * SCROLLBAR_MIN_HEIGHT) as f32);
43            let y_offset = offset as f32 * visible_fraction;
44
45            let scroller_bounds = Rectangle {
46                x: bounds.x + bounds.width - outer_width / 2.0 - (scrollbar_width / 2) as f32,
47                y: scrollbar_bounds.y + y_offset,
48                width: scroller_width as f32,
49                height: scroller_height,
50            };
51            Some(scrollable::Scrollbar {
52                outer_bounds,
53                bounds: scrollbar_bounds,
54                margin: scrollbar_margin,
55                scroller: scrollable::Scroller {
56                    bounds: scroller_bounds,
57                },
58            })
59        } else {
60            None
61        }
62    }
63
64    fn draw(
65        &mut self,
66        state: &scrollable::State,
67        bounds: Rectangle,
68        _content_bounds: Rectangle,
69        is_mouse_over: bool,
70        is_mouse_over_scrollbar: bool,
71        scrollbar: Option<scrollable::Scrollbar>,
72        offset: u32,
73        style_sheet: &Self::Style,
74        (content, mouse_interaction): Self::Output,
75    ) -> Self::Output {
76        (
77            if let Some(scrollbar) = scrollbar {
78                let mut primitives = Vec::with_capacity(5);
79
80                // Scrolled content
81                primitives.push(Primitive::Clip {
82                    bounds,
83                    offset: (0, offset).into(),
84                    content: Box::new(content),
85                });
86
87                let style = style_sheet;
88                // Note: for future use if we vary style with the state of the scrollable
89                //let style = if state.is_scroller_grabbed() {
90                //    style_sheet.dragging()
91                //} else if is_mouse_over_scrollbar {
92                //    style_sheet.hovered()
93                //} else {
94                //    style_sheet.active();
95                //};
96
97                let is_scrollbar_visible = style.track.is_some();
98
99                if is_mouse_over || state.is_scroller_grabbed() || is_scrollbar_visible {
100                    let bounds = scrollbar.scroller.bounds;
101
102                    match style.scroller {
103                        Scroller::Color(color) => primitives.push(Primitive::Rectangle {
104                            bounds,
105                            linear_color: srgba_to_linear(color.map(|e| e as f32 / 255.0)),
106                        }),
107                        Scroller::Image { ends, mid, color } => {
108                            // Calculate sizes of ends pieces based on image aspect ratio
109                            let (img_w, img_h) = self.image_dims(ends);
110                            let end_height = bounds.width * img_h as f32 / img_w as f32;
111
112                            // Calcutate size of middle piece based on available space
113                            // Note: Might want to scale into real pixels for parts of this
114                            let (end_height, middle_height) =
115                                if end_height * 2.0 + 1.0 <= bounds.height {
116                                    (end_height, bounds.height - end_height * 2.0)
117                                } else {
118                                    // Take 1 logical pixel for the middle height
119                                    let remaining_height = bounds.height - 1.0;
120                                    (remaining_height / 2.0, 1.0)
121                                };
122
123                            // Top
124                            primitives.push(Primitive::Image {
125                                handle: (ends, Rotation::None),
126                                bounds: Rectangle {
127                                    height: end_height,
128                                    ..bounds
129                                },
130                                color,
131                                source_rect: None,
132                            });
133                            // Middle
134                            primitives.push(Primitive::Image {
135                                handle: (mid, Rotation::None),
136                                bounds: Rectangle {
137                                    y: bounds.y + end_height,
138                                    height: middle_height,
139                                    ..bounds
140                                },
141                                color,
142                                source_rect: None,
143                            });
144                            // Bottom
145                            primitives.push(Primitive::Image {
146                                handle: (ends, Rotation::Cw180),
147                                bounds: Rectangle {
148                                    y: bounds.y + end_height + middle_height,
149                                    height: end_height,
150                                    ..bounds
151                                },
152                                color,
153                                source_rect: None,
154                            });
155                        },
156                    }
157                }
158
159                if let Some(track) = style.track {
160                    primitives.push(match track {
161                        Track::Color(color) => Primitive::Rectangle {
162                            bounds: scrollbar.bounds,
163                            linear_color: srgba_to_linear(color.map(|e| e as f32 / 255.0)),
164                        },
165                        Track::Image(handle, color) => Primitive::Image {
166                            handle: (handle, Rotation::None),
167                            bounds: scrollbar.bounds,
168                            color,
169                            source_rect: None,
170                        },
171                    });
172                }
173
174                Primitive::Group { primitives }
175            } else {
176                content
177            },
178            if is_mouse_over_scrollbar || state.is_scroller_grabbed() {
179                mouse::Interaction::Idle
180            } else {
181                mouse_interaction
182            },
183        )
184    }
185}