makepad_widgets/
scroll_bar.rs

1use crate::makepad_draw::*;
2
3live_design!{
4    link widgets;
5    use link::theme::*;
6    use makepad_draw::shader::std::*;
7    
8    DrawScrollBar= {{DrawScrollBar}} {}
9    
10    pub ScrollBarBase= {{ScrollBar}} {}
11    
12    pub ScrollBar = <ScrollBarBase> {
13        bar_size: 10.0,
14        bar_side_margin: 3.0
15        min_handle_size: 30.0
16
17        draw_bg: {
18            instance drag: 0.0
19            instance hover: 0.0
20
21            uniform size: 6.0
22            uniform border_size: (THEME_BEVELING)
23            uniform border_radius: 1.5
24
25            uniform color: (THEME_COLOR_OUTSET)
26            uniform color_hover: (THEME_COLOR_OUTSET_HOVER)
27            uniform color_drag: (THEME_COLOR_OUTSET_DRAG)
28
29            uniform border_color: (THEME_COLOR_U_HIDDEN)
30            uniform border_color_hover: (THEME_COLOR_U_HIDDEN)
31            uniform border_color_drag: (THEME_COLOR_U_HIDDEN)
32
33            fn pixel(self) -> vec4 {
34                let sdf = Sdf2d::viewport(self.pos * self.rect_size);
35                if self.is_vertical > 0.5 {
36                    sdf.box(
37                        1.,
38                        self.rect_size.y * self.norm_scroll,
39                        self.size,
40                        self.rect_size.y * self.norm_handle,
41                        self.border_radius
42                    );
43                }
44                else {
45                    sdf.box(
46                        self.rect_size.x * self.norm_scroll,
47                        1.,
48                        self.rect_size.x * self.norm_handle,
49                        self.size,
50                        self.border_radius
51                    );
52                }
53
54                sdf.fill_keep(mix(
55                    self.color,
56                    mix(
57                        self.color_hover,
58                        self.color_drag,
59                        self.drag
60                    ),
61                    self.hover
62                ));
63
64                sdf.stroke(mix(
65                    self.border_color,
66                    mix(
67                        self.border_color_hover,
68                        self.border_color_drag,
69                        self.drag
70                    ),
71                    self.hover
72                ), self.border_size);
73
74                return sdf.result
75            }
76        }
77
78        animator: {
79            hover = {
80                default: off
81                off = {
82                    from: {all: Forward {duration: 0.1}}
83                    apply: {
84                        draw_bg: {drag: 0.0, hover: 0.0}
85                    }
86                }
87                                
88                on = {
89                    cursor: Default,
90                    from: {
91                        all: Forward {duration: 0.1}
92                        drag: Forward {duration: 0.01}
93                    }
94                    apply: {
95                        draw_bg: {
96                            drag: 0.0,
97                            hover: [{time: 0.0, value: 1.0}],
98                        }
99                    }
100                }
101                                
102                drag = {
103                    cursor: Default,
104                    from: {all: Snap}
105                    apply: {
106                        draw_bg: {
107                            drag: 1.0,
108                            hover: 1.0,
109                        }
110                    }
111                }
112            }
113        }
114    }
115
116    pub ScrollBarTabs = <ScrollBar> {
117        draw_bg: {
118            instance drag: 0.0
119            instance hover: 0.0
120
121            uniform size: 6.0
122            uniform border_size: 1.0
123            uniform border_radius: 1.5
124
125            uniform color: (THEME_COLOR_U_HIDDEN)
126            uniform color_hover: (THEME_COLOR_OUTSET_HOVER)
127            uniform color_drag: (THEME_COLOR_OUTSET_DRAG)
128
129            uniform border_color: (THEME_COLOR_U_HIDDEN)
130            uniform border_color_hover: (THEME_COLOR_U_HIDDEN)
131            uniform border_color_drag: (THEME_COLOR_U_HIDDEN)
132
133            fn pixel(self) -> vec4 {
134                let sdf = Sdf2d::viewport(self.pos * self.rect_size);
135                if self.is_vertical > 0.5 {
136                    sdf.box(
137                        1.,
138                        self.rect_size.y * self.norm_scroll,
139                        self.size,
140                        self.rect_size.y * self.norm_handle,
141                        self.border_radius
142                    );
143                }
144                else {
145                    sdf.box(
146                        self.rect_size.x * self.norm_scroll,
147                        1.,
148                        self.rect_size.x * self.norm_handle,
149                        self.size,
150                        self.border_radius
151                    );
152                }
153
154                sdf.fill_keep(mix(
155                    self.color,
156                    mix(
157                        self.color_hover,
158                        self.color_drag,
159                        self.drag
160                    ),
161                    self.hover
162                ));
163
164                sdf.stroke(mix(
165                    self.border_color,
166                    mix(
167                        self.border_color_hover,
168                        self.border_color_drag,
169                        self.drag
170                    ),
171                    self.hover
172                ), self.border_size);
173
174                return sdf.result
175            }
176        }
177    }
178
179
180}
181
182#[derive(Copy, Clone, Debug, Live, LiveHook)]
183#[live_ignore]
184pub enum ScrollAxis {
185    #[pick] Horizontal,
186    Vertical
187}
188#[derive(Live, LiveHook, LiveRegister)]
189pub struct ScrollBar {
190    #[live] draw_bg: DrawScrollBar,
191    #[live] pub bar_size: f64,
192    #[live] pub min_handle_size: f64, //minimum size of the handle in pixels
193    #[live] bar_side_margin: f64,
194    #[live(ScrollAxis::Horizontal)] pub axis: ScrollAxis,
195    
196    #[live] use_vertical_finger_scroll: bool,
197    #[live] smoothing: Option<f64>,
198    
199    #[animator] animator: Animator,
200    
201    #[rust] next_frame: NextFrame,
202    #[rust(false)] visible: bool,
203    #[rust] view_total: f64, // the total view area
204    #[rust] view_visible: f64, // the visible view area
205    #[rust] scroll_size: f64, // the size of the scrollbar
206    #[rust] scroll_pos: f64, // scrolling position non normalised
207    
208    #[rust] scroll_target: f64,
209    #[rust] scroll_delta: f64,
210    #[rust] drag_point: Option<f64>, // the point in pixels where we are dragging
211}
212
213#[derive(Live, LiveHook, LiveRegister)]
214#[repr(C)]
215pub struct DrawScrollBar {
216    #[deref] draw_super: DrawQuad,
217    #[live] is_vertical: f32,
218    #[live] norm_handle: f32,
219    #[live] norm_scroll: f32
220}
221
222#[derive(Clone, PartialEq, Debug)]
223pub enum ScrollBarAction {
224    None,
225    Scroll {scroll_pos: f64, view_total: f64, view_visible: f64},
226    ScrollDone
227}
228
229impl ScrollBar {
230    /*
231    pub fn with_bar_size(self, bar_size: f32) -> Self {Self {bar_size, ..self}}
232    pub fn with_smoothing(self, s: f32) -> Self {Self {smoothing: Some(s), ..self}}
233    pub fn with_use_vertical_finger_scroll(self, use_vertical_finger_scroll: bool) -> Self {Self {use_vertical_finger_scroll, ..self}}
234    */
235    // reads back normalized scroll position info
236    pub fn get_normalized_scroll_pos(&self) -> (f64, f64) {
237        // computed handle size normalized
238        let vy = self.view_visible / self.view_total;
239        if !self.visible {
240            return (0.0, 0.0);
241        }
242        let norm_handle = vy.max(self.min_handle_size / self.scroll_size);
243        let norm_scroll = (1. - norm_handle) * ((self.scroll_pos / self.view_total) / (1. - vy));
244        return (norm_scroll, norm_handle)
245    }
246    
247    // sets the scroll pos from finger position
248    pub fn set_scroll_pos_from_finger(&mut self,finger: f64) -> bool {
249        let vy = self.view_visible / self.view_total;
250        let norm_handle = vy.max(self.min_handle_size / self.scroll_size);
251        
252        let new_scroll_pos = (
253            (self.view_total * (1. - vy) * (finger / self.scroll_size)) / (1. - norm_handle)
254        ).max(0.).min(self.view_total - self.view_visible);
255        //log!("SCROLL POS {} {}", new_scroll_pos, self.view_total - self.view_visible);
256        // lets snap new_scroll_pos
257        let changed = self.scroll_pos != new_scroll_pos;
258        self.scroll_pos = new_scroll_pos;
259        self.scroll_target = new_scroll_pos;
260        changed
261    }
262    
263    // writes the norm_scroll value into the shader
264    pub fn update_shader_scroll_pos(&mut self, cx: &mut Cx) {
265        let (norm_scroll, _) = self.get_normalized_scroll_pos();
266        self.draw_bg.apply_over(cx, live!{
267            norm_scroll: (norm_scroll)
268        });
269        //self.draw_bg.set_norm_scroll(cx, norm_scroll);
270    }
271    
272    // turns scroll_pos into an event on this.event
273    pub fn make_scroll_action(&mut self) -> ScrollBarAction {
274        ScrollBarAction::Scroll {
275            scroll_pos: self.scroll_pos,
276            view_total: self.view_total,
277            view_visible: self.view_visible
278        }
279    }
280    
281    pub fn move_towards_scroll_target(&mut self, cx: &mut Cx) -> bool {
282        if self.smoothing.is_none() {
283            return false;
284        }
285        if (self.scroll_target - self.scroll_pos).abs() < 0.01 {
286            return false
287        }
288        if self.scroll_pos > self.scroll_target { // go back
289            self.scroll_pos = self.scroll_pos + (self.smoothing.unwrap() * self.scroll_delta).min(-1.);
290            if self.scroll_pos <= self.scroll_target { // hit the target
291                self.scroll_pos = self.scroll_target;
292                self.update_shader_scroll_pos(cx);
293                return false;
294            }
295        }
296        else { // go forward
297            self.scroll_pos = self.scroll_pos + (self.smoothing.unwrap() * self.scroll_delta).max(1.);
298            if self.scroll_pos > self.scroll_target { // hit the target
299                self.scroll_pos = self.scroll_target;
300                self.update_shader_scroll_pos(cx);
301                return false;
302            }
303        }
304        self.update_shader_scroll_pos(cx);
305        true
306    }
307    
308    pub fn get_scroll_pos(&self) -> f64 {
309        return self.scroll_pos;
310    }
311    
312    pub fn set_scroll_pos_no_action(&mut self, cx: &mut Cx, scroll_pos: f64) -> bool {
313        let scroll_pos = scroll_pos.min(self.view_total - self.view_visible).max(0.);
314        if self.scroll_pos != scroll_pos {
315            self.scroll_pos = scroll_pos;
316            self.scroll_target = scroll_pos;
317            self.update_shader_scroll_pos(cx);
318            return true
319        };
320        return false
321    }
322    pub fn set_scroll_pos(&mut self, cx: &mut Cx, scroll_pos: f64) -> bool {
323        let scroll_pos = scroll_pos.min(self.view_total - self.view_visible).max(0.);
324        if self.scroll_pos != scroll_pos {
325            self.scroll_pos = scroll_pos;
326            self.scroll_target = scroll_pos;
327            self.update_shader_scroll_pos(cx);
328            self.next_frame = cx.new_next_frame();
329            return true
330        };
331        return false
332    }
333    
334    
335    pub fn set_scroll_pos_no_clip(&mut self, cx: &mut Cx, scroll_pos: f64) -> bool {
336        if self.scroll_pos != scroll_pos {
337            self.scroll_pos = scroll_pos;
338            self.scroll_target = scroll_pos;
339            self.update_shader_scroll_pos(cx);
340            self.next_frame = cx.new_next_frame();
341            return true
342        };
343        return false
344    }
345    
346    pub fn get_scroll_target(&mut self) -> f64 {
347        return self.scroll_target
348    }
349    
350    pub fn set_scroll_view_total(&mut self, _cx: &mut Cx, view_total: f64) {
351        self.view_total = view_total;
352    }
353    
354    pub fn get_scroll_view_total(&self) -> f64 {
355        return self.view_total;
356    }
357    
358    pub fn get_scroll_view_visible(&self) -> f64 {
359        return self.view_visible;
360    }
361    
362    
363    pub fn set_scroll_target(&mut self, cx: &mut Cx, scroll_pos_target: f64) -> bool {
364        // clamp scroll_pos to
365        
366        let new_target = scroll_pos_target.min(self.view_total - self.view_visible).max(0.);
367        if self.scroll_target != new_target {
368            self.scroll_target = new_target;
369            self.scroll_delta = new_target - self.scroll_pos;
370            self.next_frame = cx.new_next_frame();
371            return true
372        };
373        return false
374    }
375    
376    pub fn scroll_into_view(&mut self, cx: &mut Cx, pos: f64, size: f64, smooth: bool) {
377        if pos < self.scroll_pos { // scroll up
378            let scroll_to = pos;
379            if !smooth || self.smoothing.is_none() {
380                self.set_scroll_pos(cx, scroll_to);
381            }
382            else {
383                self.set_scroll_target(cx, scroll_to);
384            }
385        }
386        else if pos + size > self.scroll_pos + self.view_visible { // scroll down
387            let scroll_to = (pos + size) - self.view_visible;
388            if pos + size > self.view_total { // resize _view_total if need be
389                self.view_total = pos + size;
390            }
391            if !smooth || self.smoothing.is_none() {
392                self.set_scroll_pos(cx, scroll_to);
393            }
394            else {
395                self.set_scroll_target(cx, scroll_to);
396            }
397        }
398    }
399    
400    pub fn handle_scroll_event(&mut self, cx: &mut Cx, event: &Event, scroll_area: Area, dispatch_action: &mut dyn FnMut(&mut Cx, ScrollBarAction)) {
401        if let Event::Scroll(e) = event {
402            if scroll_area.rect(cx).contains(e.abs) {
403                if !match self.axis {
404                    ScrollAxis::Horizontal => e.handled_x.get(),
405                    ScrollAxis::Vertical => e.handled_y.get()
406                } {
407                    let scroll = match self.axis {
408                        ScrollAxis::Horizontal => if self.use_vertical_finger_scroll {e.scroll.y}else {e.scroll.x},
409                        ScrollAxis::Vertical => e.scroll.y
410                    };
411                    if !self.smoothing.is_none() && e.is_mouse {
412                        let scroll_pos_target = self.get_scroll_target();
413                        if self.set_scroll_target(cx, scroll_pos_target + scroll) {
414                            match self.axis {
415                                ScrollAxis::Horizontal => e.handled_x.set(true),
416                                ScrollAxis::Vertical => e.handled_y.set(true)
417                            }
418                        };
419                        self.move_towards_scroll_target(cx); // take the first step now
420                        return dispatch_action(cx, self.make_scroll_action());
421                    }
422                    else {
423                        let scroll_pos = self.get_scroll_pos();
424                        if self.set_scroll_pos(cx, scroll_pos + scroll) {
425                            match self.axis {
426                                ScrollAxis::Horizontal => e.handled_x.set(true),
427                                ScrollAxis::Vertical => e.handled_y.set(true)
428                            }
429                        }
430                        return dispatch_action(cx, self.make_scroll_action());
431                    }
432                }
433            }
434        }
435    }
436    pub fn is_area_captured(&self, cx:&Cx)->bool{
437        cx.fingers.is_area_captured(self.draw_bg.area())
438    }
439    
440    pub fn handle_event_with(&mut self, cx: &mut Cx, event: &Event, dispatch_action: &mut dyn FnMut(&mut Cx, ScrollBarAction)) {
441        if self.visible {
442            self.animator_handle_event(cx, event);
443            if self.next_frame.is_event(event).is_some() {
444                if self.move_towards_scroll_target(cx) {
445                    self.next_frame = cx.new_next_frame();
446                }
447                return dispatch_action(cx, self.make_scroll_action());
448            }
449            
450            match event.hits(cx, self.draw_bg.area()) {
451                Hit::FingerDown(fe) if fe.is_primary_hit() => {
452                    self.animator_play(cx, id!(hover.drag));
453                    let rel = fe.abs - fe.rect.pos;
454                    let rel = match self.axis {
455                        ScrollAxis::Horizontal => rel.x,
456                        ScrollAxis::Vertical => rel.y
457                    };
458                    let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
459                    let bar_start = norm_scroll * self.scroll_size;
460                    let bar_size = norm_handle * self.scroll_size;
461                    if rel < bar_start || rel > bar_start + bar_size { // clicked outside
462                        self.drag_point = Some(bar_size * 0.5);
463                        if self.set_scroll_pos_from_finger(rel - self.drag_point.unwrap()){
464                            dispatch_action(cx, self.make_scroll_action());
465                        }
466                    }
467                    else { // clicked on
468                        self.drag_point = Some(rel - bar_start); // store the drag delta
469                    }
470                },
471                Hit::FingerHoverIn(_) => {
472                    self.animator_play(cx, id!(hover.on));
473                },
474                Hit::FingerHoverOut(_) => {
475                    self.animator_play(cx, id!(hover.off));
476                },
477                Hit::FingerUp(fe) if fe.is_primary_hit() => {
478                    self.drag_point = None;
479                    if fe.is_over && fe.device.has_hovers() {
480                        self.animator_play(cx, id!(hover.on));
481                    }
482                    else {
483                        self.animator_play(cx, id!(hover.off));
484                    }
485                    return;
486                },
487                Hit::FingerMove(fe) => {
488                    let rel = fe.abs - fe.rect.pos;
489                    // helper called by event code to scroll from a finger
490                    if self.drag_point.is_none() {
491                        // state should never occur.
492                        //println!("Invalid state in scrollbar, fingerMove whilst drag_point is none")
493                    }
494                    else {
495                        match self.axis {
496                            ScrollAxis::Horizontal => {
497                                if self.set_scroll_pos_from_finger(rel.x - self.drag_point.unwrap()){
498                                    dispatch_action(cx, self.make_scroll_action());
499                                }
500                            },
501                            ScrollAxis::Vertical => {
502                                if self.set_scroll_pos_from_finger(rel.y - self.drag_point.unwrap()){
503                                    dispatch_action(cx, self.make_scroll_action());
504                                }
505                            }
506                        }
507                    }
508                 },
509                _ => ()
510            };
511        }
512    }
513    
514    pub fn draw_scroll_bar(&mut self, cx: &mut Cx2d, axis: ScrollAxis, view_rect: Rect, view_total: DVec2) -> f64 {
515        
516        self.axis = axis;
517        
518        match self.axis {
519            ScrollAxis::Horizontal => {
520                self.visible = view_total.x > view_rect.size.x + 0.1;
521                self.scroll_size = if view_total.y > view_rect.size.y + 0.1 {
522                    view_rect.size.x - self.bar_size
523                }
524                else {
525                    view_rect.size.x
526                } -self.bar_side_margin * 2.;
527                self.view_total = view_total.x;
528                self.view_visible = view_rect.size.x;
529                self.scroll_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
530                
531                if self.visible {
532                    let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
533                    self.draw_bg.is_vertical = 0.0;
534                    self.draw_bg.norm_scroll = norm_scroll as f32;
535                    self.draw_bg.norm_handle = norm_handle as f32;
536                    let scroll = cx.turtle().scroll();
537                    self.draw_bg.draw_rel(
538                        cx,
539                        Rect {
540                            pos: dvec2(self.bar_side_margin, view_rect.size.y - self.bar_size) + scroll,
541                            size: dvec2(self.scroll_size, self.bar_size),
542                        }
543                    );
544                }
545            },
546            ScrollAxis::Vertical => {
547                // compute if we need a horizontal one
548                self.visible = view_total.y > view_rect.size.y + 0.1;
549                self.scroll_size = if view_total.x > view_rect.size.x + 0.1 {
550                    view_rect.size.y - self.bar_size
551                }
552                else {
553                    view_rect.size.y
554                } -self.bar_side_margin * 2.;
555                self.view_total = view_total.y;
556                self.view_visible = view_rect.size.y;
557                self.scroll_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
558                if self.visible {
559                    let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
560                    self.draw_bg.is_vertical = 1.0;
561                    self.draw_bg.norm_scroll = norm_scroll as f32;
562                    self.draw_bg.norm_handle = norm_handle as f32;
563                    let scroll = cx.turtle().scroll();
564                    self.draw_bg.draw_rel(
565                        cx,
566                        Rect {
567                            pos: dvec2(view_rect.size.x - self.bar_size, self.bar_side_margin) + scroll,
568                            size: dvec2(self.bar_size, self.scroll_size)
569                        }
570                    );
571                }
572            }
573        }
574        
575        
576        // see if we need to clamp
577        let clamped_pos = self.scroll_pos.min(self.view_total - self.view_visible).max(0.);
578        if clamped_pos != self.scroll_pos {
579            self.scroll_pos = clamped_pos;
580            self.scroll_target = clamped_pos;
581            // ok so this means we 'scrolled' this can give a problem for virtual viewport widgets
582            self.next_frame = cx.new_next_frame();
583        }
584        
585        self.scroll_pos
586    }
587}