kas_widgets/
scroll.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Scroll region
7
8use kas::event::{CursorIcon, Scroll, components::ScrollComponent};
9use kas::prelude::*;
10use std::fmt::Debug;
11
12#[impl_self]
13mod ScrollRegion {
14    /// A scrollable region
15    ///
16    /// This region supports scrolling via mouse wheel and click/touch drag.
17    ///
18    /// The ideal size of a `ScrollRegion` is the ideal size of its content:
19    /// that is, all content may be shown at ideal size without scrolling.
20    /// The minimum size of a `ScrollRegion` is somewhat arbitrary (currently,
21    /// fixed at the height of three lines of standard text). The inner size
22    /// (content size) is `max(content_min_size, outer_size - content_margin)`.
23    ///
24    /// Scroll bars are not included; use [`ScrollBarRegion`] if you want those.
25    ///
26    /// ### Messages
27    ///
28    /// [`kas::messages::SetScrollOffset`] may be used to set the scroll offset.
29    ///
30    /// [`ScrollBarRegion`]: crate::ScrollBarRegion
31    #[derive(Clone, Debug, Default)]
32    #[widget]
33    pub struct ScrollRegion<W: Widget> {
34        core: widget_core!(),
35        min_child_size: Size,
36        offset: Offset,
37        frame_size: Size,
38        scroll: ScrollComponent,
39        #[widget]
40        inner: W,
41    }
42
43    impl Self {
44        /// Construct a new scroll region around an inner widget
45        #[inline]
46        pub fn new(inner: W) -> Self {
47            ScrollRegion {
48                core: Default::default(),
49                min_child_size: Size::ZERO,
50                offset: Default::default(),
51                frame_size: Default::default(),
52                scroll: Default::default(),
53                inner,
54            }
55        }
56
57        /// Access inner widget directly
58        #[inline]
59        pub fn inner(&self) -> &W {
60            &self.inner
61        }
62
63        /// Access inner widget directly
64        #[inline]
65        pub fn inner_mut(&mut self) -> &mut W {
66            &mut self.inner
67        }
68    }
69
70    impl Scrollable for Self {
71        #[inline]
72        fn content_size(&self) -> Size {
73            self.min_child_size
74        }
75
76        #[inline]
77        fn max_scroll_offset(&self) -> Offset {
78            self.scroll.max_offset()
79        }
80
81        #[inline]
82        fn scroll_offset(&self) -> Offset {
83            self.scroll.offset()
84        }
85
86        #[inline]
87        fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset {
88            let action = self.scroll.set_offset(offset);
89            cx.action(&self, action);
90            self.scroll.offset()
91        }
92    }
93
94    impl Layout for Self {
95        fn size_rules(&mut self, sizer: SizeCx, mut axis: AxisInfo) -> SizeRules {
96            axis.sub_other(self.frame_size.extract(axis.flipped()));
97
98            let mut rules = self.inner.size_rules(sizer.re(), axis);
99            self.min_child_size.set_component(axis, rules.min_size());
100            rules.reduce_min_to(sizer.min_scroll_size(axis));
101
102            // We use a frame to contain the content margin within the scrollable area.
103            let frame = kas::layout::FrameRules::ZERO;
104            let (rules, offset, size) = frame.surround(rules);
105            self.offset.set_component(axis, offset);
106            self.frame_size.set_component(axis, size);
107            rules
108        }
109
110        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
111            widget_set_rect!(rect);
112            let child_size = (rect.size - self.frame_size).max(self.min_child_size);
113            let child_rect = Rect::new(rect.pos, child_size);
114            self.inner.set_rect(cx, child_rect, hints);
115            let _ = self
116                .scroll
117                .set_sizes(rect.size, child_size + self.frame_size);
118        }
119
120        fn draw(&self, mut draw: DrawCx) {
121            // We use a new pass to clip and offset scrolled content:
122            draw.with_clip_region(self.rect(), self.scroll_offset(), |mut draw| {
123                self.inner.draw(draw.re());
124            });
125        }
126    }
127
128    impl Tile for Self {
129        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
130            Role::ScrollRegion {
131                offset: self.scroll_offset(),
132                max_offset: self.max_scroll_offset(),
133            }
134        }
135
136        #[inline]
137        fn translation(&self, _: usize) -> Offset {
138            self.scroll_offset()
139        }
140
141        fn probe(&self, coord: Coord) -> Id {
142            if self.scroll.is_kinetic_scrolling() {
143                return self.id();
144            }
145
146            self.inner
147                .try_probe(coord + self.scroll_offset())
148                .unwrap_or_else(|| self.id())
149        }
150    }
151
152    impl Events for Self {
153        type Data = W::Data;
154
155        fn mouse_over_icon(&self) -> Option<CursorIcon> {
156            self.scroll
157                .is_kinetic_scrolling()
158                .then_some(CursorIcon::AllScroll)
159        }
160
161        fn configure(&mut self, cx: &mut ConfigCx) {
162            cx.register_nav_fallback(self.id());
163        }
164
165        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
166            self.scroll
167                .scroll_by_event(cx, event, self.id(), self.rect())
168        }
169
170        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
171            if let Some(kas::messages::SetScrollOffset(offset)) = cx.try_pop() {
172                self.set_scroll_offset(cx, offset);
173            }
174        }
175
176        fn handle_scroll(&mut self, cx: &mut EventCx, _: &Self::Data, scroll: Scroll) {
177            self.scroll.scroll(cx, self.id(), self.rect(), scroll);
178        }
179    }
180}