1use super::{GripMsg, GripPart, ScrollRegion};
9use kas::event::{Scroll, TimerHandle};
10use kas::prelude::*;
11use kas::theme::Feature;
12use std::fmt::Debug;
13
14#[kas_macros::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 ScrollMsg(pub i32);
39
40const TIMER_HIDE: TimerHandle = TimerHandle::new(0, false);
41
42#[impl_self]
43mod ScrollBar {
44 #[derive(Clone, 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]
135 pub fn set_invisible(&mut self, invisible: bool) {
136 self.invisible = invisible;
137 }
138
139 #[inline]
143 pub fn with_invisible(mut self, invisible: bool) -> Self {
144 self.invisible = invisible;
145 self
146 }
147
148 #[inline]
152 #[must_use]
153 pub fn with_limits(mut self, max_value: i32, grip_size: i32) -> Self {
154 self.grip_size = grip_size.max(1);
156
157 self.max_value = max_value.max(0);
158 self.value = self.value.clamp(0, self.max_value);
159 self
160 }
161
162 #[inline]
164 #[must_use]
165 pub fn with_value(mut self, value: i32) -> Self {
166 self.value = value.clamp(0, self.max_value);
167 self
168 }
169
170 pub fn set_limits(&mut self, cx: &mut EventState, max_value: i32, grip_size: i32) {
187 self.grip_size = grip_size.max(1);
189
190 self.max_value = max_value.max(0);
191 self.value = self.value.clamp(0, self.max_value);
192 self.update_widgets(cx);
193 }
194
195 #[inline]
199 pub fn max_value(&self) -> i32 {
200 self.max_value
201 }
202
203 #[inline]
207 pub fn grip_size(&self) -> i32 {
208 self.grip_size
209 }
210
211 #[inline]
213 pub fn value(&self) -> i32 {
214 self.value
215 }
216
217 pub fn set_value(&mut self, cx: &mut EventState, value: i32) -> bool {
221 let value = value.clamp(0, self.max_value);
222 let changed = value != self.value;
223 if changed {
224 self.value = value;
225 self.grip.set_offset(cx, self.offset());
226 }
227 if !self.is_under_mouse {
228 self.force_visible = true;
229 let delay = cx.config().event().touch_select_delay();
230 cx.request_timer(self.id(), TIMER_HIDE, delay);
231 }
232 changed
233 }
234
235 #[inline]
236 fn bar_len(&self) -> i32 {
237 match self.direction.is_vertical() {
238 false => self.rect().size.0,
239 true => self.rect().size.1,
240 }
241 }
242
243 fn update_widgets(&mut self, cx: &mut EventState) {
244 let len = self.bar_len();
245 let total = 1i64.max(i64::from(self.max_value) + i64::from(self.grip_size));
246 let grip_len = i64::from(self.grip_size) * i64::conv(len) / total;
247 self.grip_len = i32::conv(grip_len).max(self.min_grip_len).min(len);
248 let mut size = self.rect().size;
249 size.set_component(self.direction, self.grip_len);
250 self.grip.set_size(size);
251 self.grip.set_offset(cx, self.offset());
252 }
253
254 fn offset(&self) -> Offset {
256 let len = self.bar_len() - self.grip_len;
257 let lhs = i64::from(self.value) * i64::conv(len);
258 let rhs = i64::from(self.max_value);
259 let mut pos = if rhs == 0 {
260 0
261 } else {
262 i32::conv((lhs + (rhs / 2)) / rhs).min(len)
263 };
264 if self.direction.is_reversed() {
265 pos = len - pos;
266 }
267 match self.direction.is_vertical() {
268 false => Offset(pos, 0),
269 true => Offset(0, pos),
270 }
271 }
272
273 fn apply_grip_offset(&mut self, cx: &mut EventCx, offset: Offset) {
275 let offset = self.grip.set_offset(cx, offset);
276
277 let len = self.bar_len() - self.grip_len;
278 let mut offset = match self.direction.is_vertical() {
279 false => offset.0,
280 true => offset.1,
281 };
282 if self.direction.is_reversed() {
283 offset = len - offset;
284 }
285
286 let lhs = i64::from(offset) * i64::from(self.max_value);
287 let rhs = i64::conv(len);
288 if rhs == 0 {
289 debug_assert_eq!(self.value, 0);
290 return;
291 }
292 let value = i32::conv((lhs + (rhs / 2)) / rhs);
293 if self.set_value(cx, value) {
294 cx.push(ScrollMsg(value));
295 }
296 }
297
298 #[inline]
305 pub fn currently_visible(&self, ev_state: &EventState) -> bool {
306 !self.invisible
307 || (self.max_value != 0 && self.force_visible)
308 || ev_state.is_depressed(self.grip.id_ref())
309 }
310 }
311
312 impl Layout for Self {
313 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
314 let _ = self.grip.size_rules(sizer.re(), axis);
315 sizer.feature(Feature::ScrollBar(self.direction()), axis)
316 }
317
318 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
319 let align = match self.direction.is_vertical() {
320 false => AlignPair::new(Align::Stretch, hints.vert.unwrap_or(Align::Center)),
321 true => AlignPair::new(hints.horiz.unwrap_or(Align::Center), Align::Stretch),
322 };
323 let rect = cx.align_feature(Feature::ScrollBar(self.direction()), rect, align);
324 widget_set_rect!(rect);
325 self.grip.set_track(rect);
326
327 self.grip.set_rect(cx, Rect::ZERO, AlignHints::NONE);
329
330 self.min_grip_len = cx.size_cx().grip_len();
331 self.update_widgets(cx);
332 }
333
334 #[inline]
335 fn draw(&self, mut draw: DrawCx) {
336 if self.currently_visible(draw.ev_state()) {
337 let dir = self.direction.as_direction();
338 draw.scroll_bar(self.rect(), &self.grip, dir);
339 }
340 }
341 }
342
343 impl Tile for Self {
344 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
345 Role::ScrollBar {
346 direction: self.direction.as_direction(),
347 value: self.value,
348 max_value: self.max_value,
349 }
350 }
351
352 fn probe(&self, coord: Coord) -> Id {
353 if self.invisible && self.max_value == 0 {
354 return self.id();
355 }
356 self.grip.try_probe(coord).unwrap_or_else(|| self.id())
357 }
358 }
359
360 impl Events for Self {
361 const REDRAW_ON_MOUSE_OVER: bool = true;
362
363 type Data = ();
364
365 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
366 match event {
367 Event::Timer(TIMER_HIDE) => {
368 if !self.is_under_mouse {
369 self.force_visible = false;
370 cx.redraw(self);
371 }
372 Used
373 }
374 Event::PressStart(press) => {
375 let offset = self.grip.handle_press_on_track(cx, &press);
376 self.apply_grip_offset(cx, offset);
377 Used
378 }
379 Event::MouseOver(true) => {
380 self.is_under_mouse = true;
381 self.force_visible = true;
382 cx.redraw(self);
383 Used
384 }
385 Event::MouseOver(false) => {
386 self.is_under_mouse = false;
387 let delay = cx.config().event().touch_select_delay();
388 cx.request_timer(self.id(), TIMER_HIDE, delay);
389 Used
390 }
391 _ => Unused,
392 }
393 }
394
395 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
396 if let Some(GripMsg::PressMove(offset)) = cx.try_pop() {
397 self.apply_grip_offset(cx, offset);
398 }
399 }
400 }
401}
402
403#[impl_self]
404mod ScrollBars {
405 #[derive(Clone, Debug, Default)]
415 #[widget]
416 pub struct ScrollBars<W: Scrollable + Widget> {
417 core: widget_core!(),
418 mode: ScrollBarMode,
419 show_bars: (bool, bool), #[widget(&())]
421 horiz_bar: ScrollBar<kas::dir::Right>,
422 #[widget(&())]
423 vert_bar: ScrollBar<kas::dir::Down>,
424 #[widget]
425 inner: W,
426 }
427
428 impl Self {
429 #[inline]
433 pub fn new(inner: W) -> Self {
434 ScrollBars {
435 core: Default::default(),
436 mode: ScrollBarMode::Auto,
437 show_bars: (false, false),
438 horiz_bar: ScrollBar::new(),
439 vert_bar: ScrollBar::new(),
440 inner,
441 }
442 }
443
444 #[inline]
446 pub fn with_fixed_bars(mut self, horiz: bool, vert: bool) -> Self
447 where
448 Self: Sized,
449 {
450 self.mode = ScrollBarMode::Fixed(horiz, vert);
451 self.horiz_bar.set_invisible(false);
452 self.vert_bar.set_invisible(false);
453 self.show_bars = (horiz, vert);
454 self
455 }
456
457 #[inline]
462 pub fn with_invisible_bars(mut self, horiz: bool, vert: bool) -> Self
463 where
464 Self: Sized,
465 {
466 self.mode = ScrollBarMode::Invisible(horiz, vert);
467 self.horiz_bar.set_invisible(true);
468 self.vert_bar.set_invisible(true);
469 self.show_bars = (horiz, vert);
470 self
471 }
472
473 #[inline]
475 pub fn scroll_bar_mode(&self) -> ScrollBarMode {
476 self.mode
477 }
478
479 pub fn set_scroll_bar_mode(&mut self, cx: &mut EventState, mode: ScrollBarMode) {
481 if mode != self.mode {
482 self.mode = mode;
483 let (invis_horiz, invis_vert) = match mode {
484 ScrollBarMode::Auto => (false, false),
485 ScrollBarMode::Fixed(horiz, vert) => {
486 self.show_bars = (horiz, vert);
487 (false, false)
488 }
489 ScrollBarMode::Invisible(horiz, vert) => {
490 self.show_bars = (horiz, vert);
491 (horiz, vert)
492 }
493 };
494 self.horiz_bar.set_invisible(invis_horiz);
495 self.vert_bar.set_invisible(invis_vert);
496 cx.resize(self);
497 }
498 }
499
500 #[inline]
502 pub fn inner(&self) -> &W {
503 &self.inner
504 }
505
506 #[inline]
508 pub fn inner_mut(&mut self) -> &mut W {
509 &mut self.inner
510 }
511 }
512
513 impl Scrollable for Self {
514 #[inline]
515 fn content_size(&self) -> Size {
516 self.inner.content_size()
517 }
518 #[inline]
519 fn max_scroll_offset(&self) -> Offset {
520 self.inner.max_scroll_offset()
521 }
522 #[inline]
523 fn scroll_offset(&self) -> Offset {
524 self.inner.scroll_offset()
525 }
526 fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset {
527 let offset = self.inner.set_scroll_offset(cx, offset);
528 self.horiz_bar.set_value(cx, offset.0);
529 self.vert_bar.set_value(cx, offset.1);
530 offset
531 }
532 }
533
534 impl Layout for Self {
535 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
536 let mut rules = self.inner.size_rules(sizer.re(), axis);
537 let vert_rules = self.vert_bar.size_rules(sizer.re(), axis);
538 let horiz_rules = self.horiz_bar.size_rules(sizer.re(), axis);
539 let (use_horiz, use_vert) = match self.mode {
540 ScrollBarMode::Fixed(horiz, vert) => (horiz, vert),
541 ScrollBarMode::Auto => (true, true),
542 ScrollBarMode::Invisible(_, _) => (false, false),
543 };
544 if axis.is_horizontal() && use_horiz {
545 rules.append(vert_rules);
546 } else if axis.is_vertical() && use_vert {
547 rules.append(horiz_rules);
548 }
549 rules
550 }
551
552 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
553 widget_set_rect!(rect);
554 let pos = rect.pos;
555 let mut child_size = rect.size;
556
557 let bar_width = cx.size_cx().scroll_bar_width();
558 if self.mode == ScrollBarMode::Auto {
559 let max_offset = self.inner.max_scroll_offset();
560 self.show_bars = (max_offset.0 > 0, max_offset.1 > 0);
561 }
562 if self.show_bars.0 && !self.horiz_bar.invisible {
563 child_size.1 -= bar_width;
564 }
565 if self.show_bars.1 && !self.vert_bar.invisible {
566 child_size.0 -= bar_width;
567 }
568
569 let child_rect = Rect::new(pos, child_size);
570 self.inner.set_rect(cx, child_rect, hints);
571 let max_scroll_offset = self.inner.max_scroll_offset();
572
573 if self.show_bars.0 {
574 let pos = Coord(pos.0, rect.pos2().1 - bar_width);
575 let size = Size::new(child_size.0, bar_width);
576 self.horiz_bar
577 .set_rect(cx, Rect { pos, size }, AlignHints::NONE);
578 self.horiz_bar
579 .set_limits(cx, max_scroll_offset.0, rect.size.0);
580 } else {
581 self.horiz_bar.set_rect(cx, Rect::ZERO, AlignHints::NONE);
582 }
583
584 if self.show_bars.1 {
585 let pos = Coord(rect.pos2().0 - bar_width, pos.1);
586 let size = Size::new(bar_width, self.rect().size.1);
587 self.vert_bar
588 .set_rect(cx, Rect { pos, size }, AlignHints::NONE);
589 self.vert_bar
590 .set_limits(cx, max_scroll_offset.1, rect.size.1);
591 } else {
592 self.vert_bar.set_rect(cx, Rect::ZERO, AlignHints::NONE);
593 }
594 }
595
596 fn draw(&self, mut draw: DrawCx) {
597 self.inner.draw(draw.re());
598 if self.show_bars == (false, false) {
599 return;
600 }
601
602 let ev_state = draw.ev_state();
605 if matches!(self.mode, ScrollBarMode::Invisible(_, _))
606 && (self.horiz_bar.currently_visible(ev_state)
607 || self.vert_bar.currently_visible(ev_state))
608 {
609 draw.with_pass(|mut draw| {
610 if self.show_bars.0 {
611 self.horiz_bar.draw(draw.re());
612 }
613 if self.show_bars.1 {
614 self.vert_bar.draw(draw.re());
615 }
616 });
617 } else {
618 if self.show_bars.0 {
619 self.horiz_bar.draw(draw.re());
620 }
621 if self.show_bars.1 {
622 self.vert_bar.draw(draw.re());
623 }
624 }
625 }
626 }
627
628 impl Tile for Self {
629 fn probe(&self, coord: Coord) -> Id {
630 self.vert_bar
631 .try_probe(coord)
632 .or_else(|| self.horiz_bar.try_probe(coord))
633 .or_else(|| self.inner.try_probe(coord))
634 .unwrap_or_else(|| self.id())
635 }
636 }
637
638 impl Events for Self {
639 type Data = W::Data;
640
641 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
642 let index = cx.last_child();
643 if index == Some(widget_index![self.horiz_bar]) {
644 if let Some(ScrollMsg(x)) = cx.try_pop() {
645 let offset = Offset(x, self.inner.scroll_offset().1);
646 self.inner.set_scroll_offset(cx, offset);
647 }
648 } else if index == Some(widget_index![self.vert_bar]) {
649 if let Some(ScrollMsg(y)) = cx.try_pop() {
650 let offset = Offset(self.inner.scroll_offset().0, y);
651 self.inner.set_scroll_offset(cx, offset);
652 }
653 }
654 }
655
656 fn handle_scroll(&mut self, cx: &mut EventCx, _: &Self::Data, _: Scroll) {
657 let offset = self.inner.scroll_offset();
659 self.horiz_bar.set_value(cx, offset.0);
660 self.vert_bar.set_value(cx, offset.1);
661 }
662 }
663}
664
665#[impl_self]
666mod ScrollBarRegion {
667 #[autoimpl(Deref, DerefMut, Scrollable using self.0)]
673 #[derive(Clone, Debug, Default)]
674 #[derive_widget]
675 pub struct ScrollBarRegion<W: Widget>(#[widget] ScrollBars<ScrollRegion<W>>);
676
677 impl Self {
678 #[inline]
680 pub fn new(inner: W) -> Self {
681 ScrollBarRegion(ScrollBars::new(ScrollRegion::new(inner)))
682 }
683
684 #[inline]
686 pub fn with_fixed_bars(self, horiz: bool, vert: bool) -> Self
687 where
688 Self: Sized,
689 {
690 ScrollBarRegion(self.0.with_fixed_bars(horiz, vert))
691 }
692
693 #[inline]
698 pub fn with_invisible_bars(self, horiz: bool, vert: bool) -> Self
699 where
700 Self: Sized,
701 {
702 ScrollBarRegion(self.0.with_invisible_bars(horiz, vert))
703 }
704
705 #[inline]
707 pub fn scroll_bar_mode(&self) -> ScrollBarMode {
708 self.0.scroll_bar_mode()
709 }
710
711 #[inline]
713 pub fn set_scroll_bar_mode(&mut self, cx: &mut EventState, mode: ScrollBarMode) {
714 self.0.set_scroll_bar_mode(cx, mode);
715 }
716
717 #[inline]
719 pub fn inner(&self) -> &W {
720 self.0.inner.inner()
721 }
722
723 #[inline]
725 pub fn inner_mut(&mut self) -> &mut W {
726 self.0.inner.inner_mut()
727 }
728 }
729}