1use makepad_render::*;
2use crate::widgetstyle::*;
3
4#[derive(Clone)]
5pub struct ScrollBar {
6
7 pub bg: Quad,
8 pub bar_size: f32,
9 pub min_handle_size: f32, pub axis: Axis,
11 pub animator: Animator,
12 pub use_vertical_finger_scroll: bool,
13 pub _visible: bool,
14 pub smoothing: Option<f32>,
15 pub _bg_area: Area,
16 pub _bar_side_margin: f32,
17 pub _view_area: Area,
18 pub _view_total: f32, pub _view_visible: f32, pub _scroll_size: f32, pub _scroll_pos: f32, pub _scroll_target: f32,
24 pub _scroll_delta: f32,
25
26 pub _drag_point: Option<f32>, }
28
29#[derive(Clone, PartialEq, Debug)]
30pub enum ScrollBarEvent {
31 None,
32 Scroll {scroll_pos: f32, view_total: f32, view_visible: f32},
33 ScrollDone
34}
35
36impl ScrollBar {
37 pub fn proto(cx: &mut Cx) -> Self {
38 Self {
39 bar_size: 12.0,
40 min_handle_size: 30.0,
41 smoothing: None,
42
43 axis: Axis::Horizontal,
44 animator: Animator::default(),
45 bg: Quad {
46 z: 10.,
47 ..Quad::proto(cx)
48 },
49 use_vertical_finger_scroll: false,
50 _visible: false,
51
52 _view_area: Area::Empty,
53 _view_total: 0.0,
54 _view_visible: 0.0,
55 _bar_side_margin: 6.0,
56 _scroll_size: 0.0,
57 _scroll_pos: 0.0,
58
59 _scroll_target: 0.0,
60 _scroll_delta: 0.0,
61
62 _drag_point: None,
63 _bg_area: Area::Empty,
64 }
65 }
66
67 pub fn bar_size() -> FloatId {uid!()}
68 pub fn instance_is_vertical() -> InstanceFloat {uid!()}
69 pub fn instance_norm_handle() -> InstanceFloat {uid!()}
70 pub fn instance_norm_scroll() -> InstanceFloat {uid!()}
71
72 pub fn anim_default() -> AnimId {uid!()}
73 pub fn anim_over() -> AnimId {uid!()}
74 pub fn anim_down() -> AnimId {uid!()}
75 pub fn shader_bg() -> ShaderId {uid!()}
76
77 pub fn style(cx: &mut Cx, opt: &StyleOptions) {
78 Self::bar_size().set(cx, 12. * opt.scale.powf(0.5));
79
80 Self::anim_default().set(cx, Anim::new(Play::Cut {duration: 0.5}, vec![
81 Track::color(Quad::instance_color(), Ease::Lin, vec![(1.0, Theme::color_scrollbar_base().get(cx))])
82 ]));
83
84 Self::anim_over().set(cx, Anim::new(Play::Cut {duration: 0.05}, vec![
85 Track::color(Quad::instance_color(), Ease::Lin, vec![(1.0, Theme::color_scrollbar_over().get(cx))])
86 ]));
87
88 Self::anim_down().set(cx, Anim::new(Play::Cut {duration: 0.05}, vec![
89 Track::color(Quad::instance_color(), Ease::Lin, vec![(1.0, Theme::color_scrollbar_down().get(cx))])
90 ]));
91
92 Self::shader_bg().set(cx, Quad::def_quad_shader().compose(shader_ast!({
93
94 let is_vertical: Self::instance_is_vertical();
95 let norm_handle: Self::instance_norm_handle();
96 let norm_scroll: Self::instance_norm_scroll();
97
98 const border_radius: float = 1.5;
99
100 fn pixel() -> vec4 {
101 df_viewport(pos * vec2(w, h));
102 if is_vertical > 0.5 {
103 df_box(1., h * norm_scroll, w * 0.5, h * norm_handle, border_radius);
104 }
105 else {
106 df_box(w * norm_scroll, 1., w * norm_handle, h * 0.5, border_radius);
107 }
108 return df_fill_keep(color);
109 }
110 })));
111 }
112 pub fn get_normalized_scroll_pos(&self) -> (f32, f32) {
114 let vy = self._view_visible / self._view_total;
116 if !self._visible {
117 return (0.0, 0.0);
118 }
119 let norm_handle = vy.max(self.min_handle_size / self._scroll_size);
120 let norm_scroll = (1. - norm_handle) * ((self._scroll_pos / self._view_total) / (1. - vy));
121 return (norm_scroll, norm_handle)
122 }
123
124 pub fn set_scroll_pos_from_finger(&mut self, cx: &mut Cx, finger: f32) -> ScrollBarEvent {
126 let vy = self._view_visible / self._view_total;
127 let norm_handle = vy.max(self.min_handle_size / self._scroll_size);
128
129 let new_scroll_pos = (
130 (self._view_total * (1. - vy) * (finger / self._scroll_size)) / (1. - norm_handle)
131 ).max(0.).min(self._view_total - self._view_visible);
132
133 let changed = self._scroll_pos != new_scroll_pos;
135 self._scroll_pos = new_scroll_pos;
136 self._scroll_target = new_scroll_pos;
137 if changed {
138 self.update_shader_scroll_pos(cx);
139 return self.make_scroll_event();
140 }
141 return ScrollBarEvent::None;
142 }
143
144 pub fn update_shader_scroll_pos(&mut self, cx: &mut Cx) {
146 let (norm_scroll, _) = self.get_normalized_scroll_pos();
147 self._bg_area.write_float(cx, Self::instance_norm_scroll(), norm_scroll);
148 }
149
150 pub fn make_scroll_event(&mut self) -> ScrollBarEvent {
152 ScrollBarEvent::Scroll {
153 scroll_pos: self._scroll_pos,
154 view_total: self._view_total,
155 view_visible: self._view_visible
156 }
157 }
158
159 pub fn move_towards_scroll_target(&mut self, cx: &mut Cx) -> bool {
160
161 if self.smoothing.is_none() {
162 return false;
163 }
164 if (self._scroll_target - self._scroll_pos).abs() < 0.01 {
165 return false
166 }
167 if self._scroll_pos > self._scroll_target { self._scroll_pos = self._scroll_pos + (self.smoothing.unwrap() * self._scroll_delta).min(-1.);
169 if self._scroll_pos <= self._scroll_target { self._scroll_pos = self._scroll_target;
171 self.update_shader_scroll_pos(cx);
172 return false;
173 }
174 }
175 else { self._scroll_pos = self._scroll_pos + (self.smoothing.unwrap() * self._scroll_delta).max(1.);
177 if self._scroll_pos > self._scroll_target { self._scroll_pos = self._scroll_target;
179 self.update_shader_scroll_pos(cx);
180 return false;
181 }
182 }
183 self.update_shader_scroll_pos(cx);
184 true
185 }
186
187 pub fn get_scroll_pos(&self) -> f32 {
188 return self._scroll_pos;
189 }
190
191 pub fn set_scroll_pos(&mut self, cx: &mut Cx, scroll_pos: f32) -> bool {
192 let scroll_pos = scroll_pos.min(self._view_total - self._view_visible).max(0.);
194
195 if self._scroll_pos != scroll_pos {
196 self._scroll_pos = scroll_pos;
197 self._scroll_target = scroll_pos;
198 self.update_shader_scroll_pos(cx);
199 cx.next_frame(self._bg_area);
200 return true
201 };
202 return false
203 }
204
205 pub fn get_scroll_target(&mut self) -> f32 {
206 return self._scroll_target
207 }
208
209 pub fn set_scroll_view_total(&mut self, _cx: &mut Cx, view_total: f32) {
210 self._view_total = view_total;
211 }
212
213 pub fn get_scroll_view_total(&self) -> f32 {
214 return self._view_total;
215 }
216
217 pub fn set_scroll_target(&mut self, cx: &mut Cx, scroll_pos_target: f32) -> bool {
218 let new_target = scroll_pos_target.min(self._view_total - self._view_visible).max(0.);
221 if self._scroll_target != new_target {
222 self._scroll_target = new_target;
223 self._scroll_delta = new_target - self._scroll_pos;
224 cx.next_frame(self._bg_area);
225 return true
226 };
227 return false
228 }
229
230 pub fn scroll_into_view(&mut self, cx: &mut Cx, pos: f32, size: f32) {
231 if pos < self._scroll_pos { let scroll_to = pos;
233 if self.smoothing.is_none() {
234 self.set_scroll_pos(cx, scroll_to);
235 }
236 else {
237 self.set_scroll_target(cx, scroll_to);
238 }
239 }
240 else if pos + size > self._scroll_pos + self._view_visible { let scroll_to = (pos + size) - self._view_visible;
242 if pos + size > self._view_total { self._view_total = pos + size;
244 }
245 if self.smoothing.is_none() {
246 self.set_scroll_pos(cx, scroll_to);
247 }
248 else {
249 self.set_scroll_target(cx, scroll_to);
250 }
251 }
252 }
253
254 pub fn handle_scroll_bar(&mut self, cx: &mut Cx, event: &mut Event) -> ScrollBarEvent {
255 match event {
257 Event::FingerScroll(fe) => if !fe.handled {
258 let rect = self._view_area.get_rect(cx);
259 if rect.contains(fe.abs.x, fe.abs.y) { let scroll = match self.axis {
262 Axis::Horizontal => if self.use_vertical_finger_scroll {fe.scroll.y}else {fe.scroll.x},
263 Axis::Vertical => fe.scroll.y
264 };
265 if !self.smoothing.is_none() {
266 let scroll_pos_target = self.get_scroll_target();
267 if self.set_scroll_target(cx, scroll_pos_target + scroll) {
268 fe.handled = true;
269 };
270 self.move_towards_scroll_target(cx); return self.make_scroll_event();
272 }
273 else {
274 let scroll_pos = self.get_scroll_pos();
275 if self.set_scroll_pos(cx, scroll_pos + scroll) {
276 fe.handled = true;
277 }
278 return self.make_scroll_event();
279 }
280 }
281 },
282
283 _ => ()
284 };
285 if self._visible {
286 match event.hits(cx, self._bg_area, HitOpt::default()) {
287 Event::Animate(ae) => {
288 self.animator.calc_area(cx, self._bg_area, ae.time);
289 },
290 Event::AnimEnded(_) => self.animator.end(),
291 Event::Frame(_ae) => {
292
293 if self.move_towards_scroll_target(cx) {
294 cx.next_frame(self._bg_area);
295 }
296 return self.make_scroll_event()
297 },
298 Event::FingerDown(fe) => {
299 self.animator.play_anim(cx, Self::anim_down().get(cx));
300 let rel = match self.axis {
301 Axis::Horizontal => fe.rel.x,
302 Axis::Vertical => fe.rel.y
303 };
304 cx.set_down_mouse_cursor(MouseCursor::Default);
305 let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
306 let bar_start = norm_scroll * self._scroll_size;
307 let bar_size = norm_handle * self._scroll_size;
308 if rel < bar_start || rel > bar_start + bar_size { self._drag_point = Some(bar_size * 0.5);
310 return self.set_scroll_pos_from_finger(cx, rel - self._drag_point.unwrap());
311 }
312 else { self._drag_point = Some(rel - bar_start); }
315 },
316 Event::FingerHover(fe) => {
317 if self._drag_point.is_none() {
318 cx.set_hover_mouse_cursor(MouseCursor::Default);
319 match fe.hover_state {
320 HoverState::In => {
321 self.animator.play_anim(cx, Self::anim_over().get(cx));
322 },
323 HoverState::Out => {
324 self.animator.play_anim(cx, Self::anim_default().get(cx));
325 },
326 _ => ()
327 }
328 }
329 },
330 Event::FingerUp(fe) => {
331 self._drag_point = None;
332 if fe.is_over {
333 if !fe.is_touch {
334 self.animator.play_anim(cx, Self::anim_over().get(cx));
335 }
336 else {
337 self.animator.play_anim(cx, Self::anim_default().get(cx));
338 }
339 }
340 else {
341 self.animator.play_anim(cx, Self::anim_default().get(cx));
342 }
343 return ScrollBarEvent::ScrollDone;
344 },
345 Event::FingerMove(fe) => {
346 if self._drag_point.is_none() {
348 }
351 else {
352 match self.axis {
353 Axis::Horizontal => {
354 return self.set_scroll_pos_from_finger(cx, fe.rel.x - self._drag_point.unwrap());
355 },
356 Axis::Vertical => {
357 return self.set_scroll_pos_from_finger(cx, fe.rel.y - self._drag_point.unwrap());
358 }
359 }
360 }
361 },
362 _ => ()
363 };
364 }
365
366 ScrollBarEvent::None
367 }
368
369 pub fn draw_scroll_bar(&mut self, cx: &mut Cx, axis: Axis, view_area: Area, view_rect: Rect, view_total: Vec2) -> f32 {
370 self.animator.init(cx, | cx | Self::anim_default().get(cx));
371 self.bg.shader = Self::shader_bg().get(cx);
372 self.bg.color = self.animator.last_color(cx, Quad::instance_color());
373 self._bg_area = Area::Empty;
374 self._view_area = view_area;
375 self.axis = axis;
376 self.bar_size = Self::bar_size().get(cx);
377 match self.axis {
378 Axis::Horizontal => {
379 self._visible = view_total.x > view_rect.w + 0.1;
380 self._scroll_size = if view_total.y > view_rect.h + 0.1 {
381 view_rect.w - self.bar_size
382 }
383 else {
384 view_rect.w
385 } -self._bar_side_margin * 2.;
386 self._view_total = view_total.x;
387 self._view_visible = view_rect.w;
388 self._scroll_pos = self._scroll_pos.min(self._view_total - self._view_visible).max(0.);
389
390 if self._visible {
391 let bg_inst = self.bg.draw_quad_rel(
392 cx,
393 Rect {
394 x: self._bar_side_margin,
395 y: view_rect.h - self.bar_size,
396 w: self._scroll_size,
397 h: self.bar_size,
398 }
399 );
400 bg_inst.set_do_scroll(cx, false, false);
401 let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
403 bg_inst.push_float(cx, 0.0);
404 bg_inst.push_float(cx, norm_handle);
405 bg_inst.push_float(cx, norm_scroll);
406 self._bg_area = bg_inst.into();
407 }
408 },
409 Axis::Vertical => {
410 self._visible = view_total.y > view_rect.h + 0.1;
412 self._scroll_size = if view_total.x > view_rect.w + 0.1 {
413 view_rect.h - self.bar_size
414 }
415 else {
416 view_rect.h
417 } -self._bar_side_margin * 2.;
418 self._view_total = view_total.y;
419 self._view_visible = view_rect.h;
420 self._scroll_pos = self._scroll_pos.min(self._view_total - self._view_visible).max(0.);
421 if self._visible {
422 let bg_inst = self.bg.draw_quad_rel(
423 cx,
424 Rect {
425 x: view_rect.w - self.bar_size,
426 y: self._bar_side_margin,
427 w: self.bar_size,
428 h: self._scroll_size
429 }
430 );
431 bg_inst.set_do_scroll(cx, false, false);
432 let (norm_scroll, norm_handle) = self.get_normalized_scroll_pos();
434 bg_inst.push_float(cx, 1.0);
435 bg_inst.push_float(cx, norm_handle);
436 bg_inst.push_float(cx, norm_scroll);
437 self._bg_area = bg_inst.into();
438 }
439 }
440 }
441
442 if self._visible {
444 self.animator.set_area(cx, self._bg_area); }
446
447 let clamped_pos = self._scroll_pos.min(self._view_total - self._view_visible).max(0.);
449 if clamped_pos != self._scroll_pos {
450 self._scroll_pos = clamped_pos;
451 self._scroll_target = clamped_pos;
452 cx.next_frame(self._bg_area);
454 }
455
456 self._scroll_pos
457 }
458}