uzor_core/widgets/
scrollbar.rs1use crate::types::Rect;
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct ScrollbarConfig {
11 pub content_size: f64,
13 pub viewport_size: f64,
15 pub scroll_offset: f64,
17 pub min_handle_size: f64,
19 pub horizontal: bool,
21}
22
23impl Default for ScrollbarConfig {
24 fn default() -> Self {
25 Self {
26 content_size: 0.0,
27 viewport_size: 0.0,
28 scroll_offset: 0.0,
29 min_handle_size: 30.0,
30 horizontal: false,
31 }
32 }
33}
34
35impl ScrollbarConfig {
36 pub fn new(content_size: f64, viewport_size: f64, scroll_offset: f64) -> Self {
37 Self {
38 content_size,
39 viewport_size,
40 scroll_offset,
41 ..Default::default()
42 }
43 }
44
45 pub fn needs_scrollbar(&self) -> bool {
46 self.content_size > self.viewport_size
47 }
48
49 fn visible_ratio(&self) -> f64 {
50 if self.content_size <= 0.0 { 1.0 } else { (self.viewport_size / self.content_size).clamp(0.0, 1.0) }
51 }
52
53 fn scroll_ratio(&self) -> f64 {
54 let max_scroll = (self.content_size - self.viewport_size).max(0.0);
55 if max_scroll <= 0.0 { 0.0 } else { (self.scroll_offset / max_scroll).clamp(0.0, 1.0) }
56 }
57
58 pub fn max_scroll(&self) -> f64 {
59 (self.content_size - self.viewport_size).max(0.0)
60 }
61}
62
63#[derive(Clone, Debug, Default, Serialize, Deserialize)]
65pub struct ScrollbarResponse {
66 pub track_rect: Rect,
68 pub handle_rect: Rect,
70 pub scroll_offset: f64,
72 pub dragged: bool,
74}
75
76impl ScrollbarConfig {
77 pub fn calculate_geometry(&self, track_rect: Rect, drag_pos: Option<f64>) -> ScrollbarResponse {
79 if !self.needs_scrollbar() {
80 return ScrollbarResponse {
81 track_rect,
82 handle_rect: Rect::default(),
83 scroll_offset: self.scroll_offset,
84 dragged: false,
85 };
86 }
87
88 let visible_ratio = self.visible_ratio();
89 let scroll_ratio = self.scroll_ratio();
90
91 let (handle_rect, new_scroll_offset) = if self.horizontal {
92 let handle_width = (visible_ratio * track_rect.width).max(self.min_handle_size);
93 let available_width = track_rect.width - handle_width;
94
95 let mut offset = self.scroll_offset;
96 let mut handle_x = track_rect.x + scroll_ratio * available_width;
97
98 if let Some(x) = drag_pos {
99 let new_ratio = ((x - track_rect.x - handle_width / 2.0) / available_width).clamp(0.0, 1.0);
100 offset = new_ratio * self.max_scroll();
101 handle_x = track_rect.x + new_ratio * available_width;
102 }
103
104 (Rect::new(handle_x, track_rect.y, handle_width, track_rect.height), offset)
105 } else {
106 let handle_height = (visible_ratio * track_rect.height).max(self.min_handle_size);
107 let available_height = track_rect.height - handle_height;
108
109 let mut offset = self.scroll_offset;
110 let mut handle_y = track_rect.y + scroll_ratio * available_height;
111
112 if let Some(y) = drag_pos {
113 let new_ratio = ((y - track_rect.y - handle_height / 2.0) / available_height).clamp(0.0, 1.0);
114 offset = new_ratio * self.max_scroll();
115 handle_y = track_rect.y + new_ratio * available_height;
116 }
117
118 (Rect::new(track_rect.x, handle_y, track_rect.width, handle_height), offset)
119 };
120
121 ScrollbarResponse {
122 track_rect,
123 handle_rect,
124 scroll_offset: new_scroll_offset,
125 dragged: drag_pos.is_some(),
126 }
127 }
128}