1use super::{GripMsg, GripPart};
9use kas::event::TimerHandle;
10use kas::prelude::*;
11use kas::theme::Feature;
12use std::fmt::Debug;
13
14#[impl_default(ScrollBarMode::Auto)]
18#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
19pub enum ScrollBarMode {
20 Auto,
25 Fixed(bool, bool),
29 Invisible(bool, bool),
34}
35
36#[derive(Copy, Clone, Debug)]
38pub struct ScrollBarMsg(pub i32);
39
40const TIMER_HIDE: TimerHandle = TimerHandle::new(0, false);
41
42#[impl_self]
43mod ScrollBar {
44 #[derive(Debug, Default)]
57 #[widget]
58 pub struct ScrollBar<D: Directional = Direction> {
59 core: widget_core!(),
60 direction: D,
61 min_grip_len: i32, grip_len: i32, grip_size: i32, max_value: i32,
67 value: i32,
68 invisible: bool,
69 is_under_mouse: bool,
70 force_visible: bool,
71 #[widget]
72 grip: GripPart,
73 }
74
75 impl Self
76 where
77 D: Default,
78 {
79 #[inline]
83 pub fn new() -> Self {
84 ScrollBar::new_dir(D::default())
85 }
86 }
87 impl ScrollBar<kas::dir::Down> {
88 pub fn down() -> Self {
92 ScrollBar::new()
93 }
94 }
95 impl ScrollBar<kas::dir::Right> {
96 pub fn right() -> Self {
100 ScrollBar::new()
101 }
102 }
103
104 impl Self {
105 #[inline]
109 pub fn new_dir(direction: D) -> Self {
110 ScrollBar {
111 core: Default::default(),
112 direction,
113 min_grip_len: 0,
114 grip_len: 0,
115 grip_size: 1,
116 max_value: 0,
117 value: 0,
118 invisible: false,
119 is_under_mouse: false,
120 force_visible: false,
121 grip: GripPart::new(),
122 }
123 }
124
125 #[inline]
127 pub fn direction(&self) -> Direction {
128 self.direction.as_direction()
129 }
130
131 #[inline]
136 pub fn is_invisible(&self) -> bool {
137 self.invisible
138 }
139
140 #[inline]
144 pub fn set_invisible(&mut self, invisible: bool) {
145 self.invisible = invisible;
146 }
147
148 #[inline]
152 pub fn with_invisible(mut self, invisible: bool) -> Self {
153 self.invisible = invisible;
154 self
155 }
156
157 #[inline]
161 #[must_use]
162 pub fn with_limits(mut self, max_value: i32, grip_size: i32) -> Self {
163 self.grip_size = grip_size.max(1);
165
166 self.max_value = max_value.max(0);
167 self.value = self.value.clamp(0, self.max_value);
168 self
169 }
170
171 #[inline]
173 #[must_use]
174 pub fn with_value(mut self, value: i32) -> Self {
175 self.value = value.clamp(0, self.max_value);
176 self
177 }
178
179 pub fn set_limits(&mut self, cx: &mut EventState, max_value: i32, grip_size: i32) {
194 self.grip_size = grip_size.max(1);
196
197 self.max_value = max_value.max(0);
198 self.value = self.value.clamp(0, self.max_value);
199 self.update_widgets(cx);
200 }
201
202 #[inline]
206 pub fn max_value(&self) -> i32 {
207 self.max_value
208 }
209
210 #[inline]
214 pub fn grip_size(&self) -> i32 {
215 self.grip_size
216 }
217
218 #[inline]
220 pub fn value(&self) -> i32 {
221 self.value
222 }
223
224 pub fn set_value(&mut self, cx: &mut EventState, value: i32) -> bool {
228 let value = value.clamp(0, self.max_value);
229 let changed = value != self.value;
230 if changed {
231 self.value = value;
232 self.grip.set_offset(cx, self.offset());
233 }
234 if !self.is_under_mouse {
235 self.force_visible = true;
236 let delay = cx.config().event().touch_select_delay();
237 cx.request_timer(self.id(), TIMER_HIDE, delay);
238 }
239 changed
240 }
241
242 #[inline]
243 fn bar_len(&self) -> i32 {
244 match self.direction.is_vertical() {
245 false => self.rect().size.0,
246 true => self.rect().size.1,
247 }
248 }
249
250 fn update_widgets(&mut self, cx: &mut EventState) {
251 let len = self.bar_len();
252 let total = 1i64.max(i64::from(self.max_value) + i64::from(self.grip_size));
253 let grip_len = i64::from(self.grip_size) * i64::conv(len) / total;
254 self.grip_len = i32::conv(grip_len).max(self.min_grip_len).min(len);
255 let mut size = self.rect().size;
256 size.set_component(self.direction, self.grip_len);
257 self.grip.set_size(size);
258 self.grip.set_offset(cx, self.offset());
259 }
260
261 fn offset(&self) -> Offset {
263 let len = self.bar_len() - self.grip_len;
264 let lhs = i64::from(self.value) * i64::conv(len);
265 let rhs = i64::from(self.max_value);
266 let mut pos = if rhs == 0 {
267 0
268 } else {
269 i32::conv((lhs + (rhs / 2)) / rhs).min(len)
270 };
271 if self.direction.is_reversed() {
272 pos = len - pos;
273 }
274 match self.direction.is_vertical() {
275 false => Offset(pos, 0),
276 true => Offset(0, pos),
277 }
278 }
279
280 fn apply_grip_offset(&mut self, cx: &mut EventCx, offset: Offset) {
282 let offset = self.grip.set_offset(cx, offset);
283
284 let len = self.bar_len() - self.grip_len;
285 let mut offset = match self.direction.is_vertical() {
286 false => offset.0,
287 true => offset.1,
288 };
289 if self.direction.is_reversed() {
290 offset = len - offset;
291 }
292
293 let lhs = i64::from(offset) * i64::from(self.max_value);
294 let rhs = i64::conv(len);
295 if rhs == 0 {
296 debug_assert_eq!(self.value, 0);
297 return;
298 }
299 let value = i32::conv((lhs + (rhs / 2)) / rhs);
300 if self.set_value(cx, value) {
301 cx.push(ScrollBarMsg(value));
302 }
303 }
304
305 #[inline]
312 pub fn currently_visible(&self, ev_state: &EventState) -> bool {
313 !self.invisible
314 || (self.max_value != 0 && self.force_visible)
315 || ev_state.is_depressed(self.grip.id_ref())
316 }
317 }
318
319 impl Layout for Self {
320 fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
321 let _ = self.grip.size_rules(cx, axis);
322 cx.feature(Feature::ScrollBar(self.direction()), axis)
323 }
324
325 fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, hints: AlignHints) {
326 let align = match self.direction.is_vertical() {
327 false => AlignPair::new(Align::Stretch, hints.vert.unwrap_or(Align::Center)),
328 true => AlignPair::new(hints.horiz.unwrap_or(Align::Center), Align::Stretch),
329 };
330 let rect = cx.align_feature(Feature::ScrollBar(self.direction()), rect, align);
331 self.core.set_rect(rect);
332 self.grip.set_track(rect);
333
334 self.grip.set_rect(cx, Rect::ZERO, AlignHints::NONE);
336
337 self.min_grip_len = cx.grip_len();
338 self.update_widgets(cx);
339 }
340
341 #[inline]
342 fn draw(&self, mut draw: DrawCx) {
343 if self.currently_visible(draw.ev_state()) {
344 let dir = self.direction.as_direction();
345 draw.scroll_bar(self.rect(), &self.grip, dir);
346 }
347 }
348 }
349
350 impl Tile for Self {
351 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
352 Role::ScrollBar {
353 direction: self.direction.as_direction(),
354 value: self.value,
355 max_value: self.max_value,
356 }
357 }
358 }
359
360 impl Events for Self {
361 const REDRAW_ON_MOUSE_OVER: bool = true;
362
363 type Data = ();
364
365 fn probe(&self, coord: Coord) -> Id {
366 if self.invisible && self.max_value == 0 {
367 return self.id();
368 }
369 self.grip.try_probe(coord).unwrap_or_else(|| self.id())
370 }
371
372 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
373 match event {
374 Event::Timer(TIMER_HIDE) => {
375 if !self.is_under_mouse {
376 self.force_visible = false;
377 cx.redraw();
378 }
379 Used
380 }
381 Event::PressStart(press) => {
382 let offset = self.grip.handle_press_on_track(cx, &press);
383 self.apply_grip_offset(cx, offset);
384 Used
385 }
386 Event::MouseOver(true) => {
387 self.is_under_mouse = true;
388 self.force_visible = true;
389 cx.redraw();
390 Used
391 }
392 Event::MouseOver(false) => {
393 self.is_under_mouse = false;
394 let delay = cx.config().event().touch_select_delay();
395 cx.request_timer(self.id(), TIMER_HIDE, delay);
396 Used
397 }
398 _ => Unused,
399 }
400 }
401
402 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
403 if let Some(GripMsg::PressMove(offset)) = cx.try_pop() {
404 self.apply_grip_offset(cx, offset);
405 }
406 }
407 }
408}