makepad_widgets/
scroll_bar.rs

1use crate::makepad_draw::*;
2
3live_design!{
4    DrawScrollBar= {{DrawScrollBar}} {}
5    ScrollBarBase= {{ScrollBar}} {}
6}
7
8#[derive(Live, LiveHook)]
9pub struct ScrollBar {
10    #[live] draw_bar: DrawScrollBar,
11    #[live] pub bar_size: f64,
12    #[live] pub min_handle_size: f64, //minimum size of the handle in pixels
13    #[live] bar_side_margin: f64,
14    #[live(Axis::Horizontal)] pub axis: Axis,
15    
16    #[live] use_vertical_finger_scroll: bool,
17    #[live] smoothing: Option<f64>,
18    
19    #[animator] animator: Animator,
20    
21    #[rust] next_frame: NextFrame,
22    #[rust(false)] visible: bool,
23    #[rust] view_total: f64, // the total view area
24    #[rust] view_visible: f64, // the visible view area
25    #[rust] scroll_size: f64, // the size of the scrollbar
26    #[rust] scroll_pos: f64, // scrolling position non normalised
27    
28    #[rust] scroll_target: f64,
29    #[rust] scroll_delta: f64,
30    #[rust] drag_point: Option<f64>, // the point in pixels where we are dragging
31}
32
33#[derive(Live, LiveHook)]
34#[repr(C)]
35pub struct DrawScrollBar {
36    #[deref] draw_super: DrawQuad,
37    #[live] is_vertical: f32,
38    #[live] norm_handle: f32,
39    #[live] norm_scroll: f32
40}
41
42#[derive(Clone, PartialEq, Debug)]
43pub enum ScrollBarAction {
44    None,
45    Scroll {scroll_pos: f64, view_total: f64, view_visible: f64},
46    ScrollDone
47}
48
49impl ScrollBar {
50    /*
51    pub fn with_bar_size(self, bar_size: f32) -> Self {Self {bar_size, ..self}}
52    pub fn with_smoothing(self, s: f32) -> Self {Self {smoothing: Some(s), ..self}}
53    pub fn with_use_vertical_finger_scroll(self, use_vertical_finger_scroll: bool) -> Self {Self {use_vertical_finger_scroll, ..self}}
54    */
55    // reads back normalized scroll position info
56    pub fn get_normalized_scroll_pos(&self) -> (f64, f64) {
57        // computed handle size normalized
58        let vy = self.view_visible / self.view_total;
59        if !self.visible {
60            return (0.0, 0.0);
61        }
62        let norm_handle = vy.max(self.min_handle_size / self.scroll_size);
63        let norm_scroll = (1. - norm_handle) * ((self.scroll_pos / self.view_total) / (1. - vy));
64        return (norm_scroll, norm_handle)
65    }
66    
67    // sets the scroll pos from finger position
68    pub fn set_scroll_pos_from_finger(&mut self,finger: f64) -> bool {
69        let vy = self.view_visible / self.view_total;
70        let norm_handle = vy.max(self.min_handle_size / self.scroll_size);
71        
72        let new_scroll_pos = (
73            (self.view_total * (1. - vy) * (finger / self.scroll_size)) / (1. - norm_handle)
74        ).max(0.).min(self.view_total - self.view_visible);
75        //log!("SCROLL POS {} {}", new_scroll_pos, self.view_total - self.view_visible);
76        // lets snap new_scroll_pos
77        let changed = self.scroll_pos != new_scroll_pos;
78        self.scroll_pos = new_scroll_pos;
79        self.scroll_target = new_scroll_pos;
80        changed
81    }
82    
83    // writes the norm_scroll value into the shader
84    pub fn update_shader_scroll_pos(&mut self, cx: &mut Cx) {
85        let (norm_scroll, _) = self.get_normalized_scroll_pos();
86        self.draw_bar.apply_over(cx, live!{
87            norm_scroll: (norm_scroll)
88        });
89        //self.draw_bg.set_norm_scroll(cx, norm_scroll);
90    }
91    
92    // turns scroll_pos into an event on this.event
93    pub fn make_scroll_action(&mut self) -> ScrollBarAction {
94        ScrollBarAction::Scroll {
95            scroll_pos: self.scroll_pos,
96            view_total: self.view_total,
97            view_visible: self.view_visible
98        }
99    }
100    
101    pub fn move_towards_scroll_target(&mut self, cx: &mut Cx) -> bool {
102        if self.smoothing.is_none() {
103            return false;
104        }
105        if (self.scroll_target - self.scroll_pos).abs() < 0.01 {
106            return false
107        }
108        if self.scroll_pos > self.scroll_target { // go back
109            self.scroll_pos = self.scroll_pos + (self.smoothing.unwrap() * self.scroll_delta).min(-1.);
110            if self.scroll_pos <= self.scroll_target { // hit the target
111                self.scroll_pos = self.scroll_target;
112                self.update_shader_scroll_pos(cx);
113                return false;
114            }
115        }
116        else { // go forward
117            self.scroll_pos = self.scroll_pos + (self.smoothing.unwrap() * self.scroll_delta).max(1.);
118            if self.scroll_pos > self.scroll_target { // hit the target
119                self.scroll_pos = self.scroll_target;
120                self.update_shader_scroll_pos(cx);
121                return false;
122            }
123        }
124        self.update_shader_scroll_pos(cx);
125        true
126    }
127    
128    pub fn get_scroll_pos(&self) -> f64 {
129        return self.scroll_pos;
130    }
131    
132    pub fn set_scroll_pos_no_action(&mut self, cx: &mut Cx, scroll_pos: f64) -> bool {
133        let scroll_pos = scroll_pos.min(self.view_total - self.view_visible).max(0.);
134        if self.scroll_pos != scroll_pos {
135            self.scroll_pos = scroll_pos;
136            self.scroll_target = scroll_pos;
137            self.update_shader_scroll_pos(cx);
138            return true
139        };
140        return false
141    }
142    pub fn set_scroll_pos(&mut self, cx: &mut Cx, scroll_pos: f64) -> bool {
143        let scroll_pos = scroll_pos.min(self.view_total - self.view_visible).max(0.);
144        if self.scroll_pos != scroll_pos {
145            self.scroll_pos = scroll_pos;
146            self.scroll_target = scroll_pos;
147            self.update_shader_scroll_pos(cx);
148            self.next_frame = cx.new_next_frame();
149            return true
150        };
151        return false
152    }
153    
154    
155    pub fn set_scroll_pos_no_clip(&mut self, cx: &mut Cx, scroll_pos: f64) -> bool {
156        if self.scroll_pos != scroll_pos {
157            self.scroll_pos = scroll_pos;
158            self.scroll_target = scroll_pos;
159            self.update_shader_scroll_pos(cx);
160            self.next_frame = cx.new_next_frame();
161            return true
162        };
163        return false
164    }
165    
166    pub fn get_scroll_target(&mut self) -> f64 {
167        return self.scroll_target
168    }
169    
170    pub fn set_scroll_view_total(&mut self, _cx: &mut Cx, view_total: f64) {
171        self.view_total = view_total;
172    }
173    
174    pub fn get_scroll_view_total(&self) -> f64 {
175        return self.view_total;
176    }
177    
178    pub fn get_scroll_view_visible(&self) -> f64 {
179        return self.view_visible;
180    }
181    
182    
183    pub fn set_scroll_target(&mut self, cx: &mut Cx, scroll_pos_target: f64) -> bool {
184        // clamp scroll_pos to
185        
186        let new_target = scroll_pos_target.min(self.view_total - self.view_visible).max(0.);
187        if self.scroll_target != new_target {
188            self.scroll_target = new_target;
189            self.scroll_delta = new_target - self.scroll_pos;
190            self.next_frame = cx.new_next_frame();
191            return true
192        };
193        return false
194    }
195    
196    pub fn scroll_into_view(&mut self, cx: &mut Cx, pos: f64, size: f64, smooth: bool) {
197        if pos < self.scroll_pos { // scroll up
198            let scroll_to = pos;
199            if !smooth || self.smoothing.is_none() {
200                self.set_scroll_pos(cx, scroll_to);
201            }
202            else {
203                self.set_scroll_target(cx, scroll_to);
204            }
205        }
206        else if pos + size > self.scroll_pos + self.view_visible { // scroll down
207            let scroll_to = (pos + size) - self.view_visible;
208            if pos + size > self.view_total { // resize _view_total if need be
209                self.view_total = pos + size;
210            }
211            if !smooth || self.smoothing.is_none() {
212                self.set_scroll_pos(cx, scroll_to);
213            }
214            else {
215                self.set_scroll_target(cx, scroll_to);
216            }
217        }
218    }
219    
220    pub fn handle_scroll_event(&mut self, cx: &mut Cx, event: &Event, scroll_area: Area, dispatch_action: &mut dyn FnMut(&mut Cx, ScrollBarAction)) {
221        if let Event::Scroll(e) = event {
222            if scroll_area.get_rect(cx).contains(e.abs) {
223                if !match self.axis {
224                    Axis::Horizontal => e.handled_x.get(),
225                    Axis::Vertical => e.handled_y.get()
226                } {
227                    let scroll = match self.axis {
228                        Axis::Horizontal => if self.use_vertical_finger_scroll {e.scroll.y}else {e.scroll.x},
229                        Axis::Vertical => e.scroll.y
230                    };
231                    if !self.smoothing.is_none() && e.is_mouse {
232                        let scroll_pos_target = self.get_scroll_target();
233                        if self.set_scroll_target(cx, scroll_pos_target + scroll) {
234                            match self.axis {
235                                Axis::Horizontal => e.handled_x.set(true),
236                                Axis::Vertical => e.handled_y.set(true)
237                            }
238                        };
239                        self.move_towards_scroll_target(cx); // take the first step now
240                        return dispatch_action(cx, self.make_scroll_action());
241                    }
242                    else {
243                        let scroll_pos = self.get_scroll_pos();
244                        if self.set_scroll_pos(cx, scroll_pos + scroll) {
245                            match self.axis {
246                                Axis::Horizontal => e.handled_x.set(true),
247                                Axis::Vertical => e.handled_y.set(true)
248                            }
249                        }
250                        return dispatch_action(cx, self.make_scroll_action());
251                    }
252                }
253            }
254        }
255    }
256    pub fn is_area_captured(&self, cx:&Cx)->bool{
257        cx.fingers.is_area_captured(self.draw_bar.area())
258    }
259    
260    pub fn handle_event_with(&mut self, cx: &mut Cx, event: &Event, dispatch_action: &mut dyn FnMut(&mut Cx, ScrollBarAction)) {
261        if self.visible {
262            self.animator_handle_event(cx, event);
263            if self.next_frame.is_event(event).is_some() {
264                if self.move_towards_scroll_target(cx) {
265                    self.next_frame = cx.new_next_frame();
266                }
267                return dispatch_action(cx, self.make_scroll_action());
268            }
269            
270            match event.hits(cx, self.draw_bar.area()) {
271                Hit::FingerDown(fe) => {
272                    self.animator_play(cx, id!(hover.pressed));
273                    let rel = fe.abs - fe.rect.pos;
274                    let rel = match self.axis {
275                        Axis::Horizontal => rel.x,
276                        Axis::Vertical => rel.y
277                    };
278                    let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
279                    let bar_start = norm_scroll * self.scroll_size;
280                    let bar_size = norm_handle * self.scroll_size;
281                    if rel < bar_start || rel > bar_start + bar_size { // clicked outside
282                        self.drag_point = Some(bar_size * 0.5);
283                        if self.set_scroll_pos_from_finger(rel - self.drag_point.unwrap()){
284                            dispatch_action(cx, self.make_scroll_action());
285                        }
286                    }
287                    else { // clicked on
288                        self.drag_point = Some(rel - bar_start); // store the drag delta
289                    }
290                },
291                Hit::FingerHoverIn(_) => {
292                    self.animator_play(cx, id!(hover.on));
293                },
294                Hit::FingerHoverOut(_) => {
295                    self.animator_play(cx, id!(hover.off));
296                },
297                Hit::FingerUp(fe) => {
298                    self.drag_point = None;
299                    if fe.is_over && fe.device.has_hovers() {
300                        self.animator_play(cx, id!(hover.on));
301                    }
302                    else {
303                        self.animator_play(cx, id!(hover.off));
304                    }
305                    return;
306                },
307                Hit::FingerMove(fe) => {
308                    let rel = fe.abs - fe.rect.pos;
309                    // helper called by event code to scroll from a finger
310                    if self.drag_point.is_none() {
311                        // state should never occur.
312                        //println!("Invalid state in scrollbar, fingerMove whilst drag_point is none")
313                    }
314                    else {
315                        match self.axis {
316                            Axis::Horizontal => {
317                                if self.set_scroll_pos_from_finger(rel.x - self.drag_point.unwrap()){
318                                    dispatch_action(cx, self.make_scroll_action());
319                                }
320                            },
321                            Axis::Vertical => {
322                                if self.set_scroll_pos_from_finger(rel.y - self.drag_point.unwrap()){
323                                    dispatch_action(cx, self.make_scroll_action());
324                                }
325                            }
326                        }
327                    }
328                 },
329                _ => ()
330            };
331        }
332    }
333    
334    pub fn draw_scroll_bar(&mut self, cx: &mut Cx2d, axis: Axis, view_rect: Rect, view_total: DVec2) -> f64 {
335        
336        self.axis = axis;
337        
338        match self.axis {
339            Axis::Horizontal => {
340                self.visible = view_total.x > view_rect.size.x + 0.1;
341                self.scroll_size = if view_total.y > view_rect.size.y + 0.1 {
342                    view_rect.size.x - self.bar_size
343                }
344                else {
345                    view_rect.size.x
346                } -self.bar_side_margin * 2.;
347                self.view_total = view_total.x;
348                self.view_visible = view_rect.size.x;
349                self.scroll_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
350                
351                if self.visible {
352                    let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
353                    self.draw_bar.is_vertical = 0.0;
354                    self.draw_bar.norm_scroll = norm_scroll as f32;
355                    self.draw_bar.norm_handle = norm_handle as f32;
356                    let scroll = cx.turtle().scroll();
357                    self.draw_bar.draw_rel(
358                        cx,
359                        Rect {
360                            pos: dvec2(self.bar_side_margin, view_rect.size.y - self.bar_size) + scroll,
361                            size: dvec2(self.scroll_size, self.bar_size),
362                        }
363                    );
364                }
365            },
366            Axis::Vertical => {
367                // compute if we need a horizontal one
368                self.visible = view_total.y > view_rect.size.y + 0.1;
369                self.scroll_size = if view_total.x > view_rect.size.x + 0.1 {
370                    view_rect.size.y - self.bar_size
371                }
372                else {
373                    view_rect.size.y
374                } -self.bar_side_margin * 2.;
375                self.view_total = view_total.y;
376                self.view_visible = view_rect.size.y;
377                self.scroll_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
378                if self.visible {
379                    let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
380                    self.draw_bar.is_vertical = 1.0;
381                    self.draw_bar.norm_scroll = norm_scroll as f32;
382                    self.draw_bar.norm_handle = norm_handle as f32;
383                    let scroll = cx.turtle().scroll();
384                    self.draw_bar.draw_rel(
385                        cx,
386                        Rect {
387                            pos: dvec2(view_rect.size.x - self.bar_size, self.bar_side_margin) + scroll,
388                            size: dvec2(self.bar_size, self.scroll_size)
389                        }
390                    );
391                }
392            }
393        }
394        
395        
396        // see if we need to clamp
397        let clamped_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
398        if clamped_pos != self.scroll_pos {
399            self.scroll_pos = clamped_pos;
400            self.scroll_target = clamped_pos;
401            // ok so this means we 'scrolled' this can give a problem for virtual viewport widgets
402            self.next_frame = cx.new_next_frame();
403        }
404        
405        self.scroll_pos
406    }
407}