1use std::cell::Cell;
21use std::rc::Rc;
22
23use crate::color::Color;
24use crate::draw_ctx::DrawCtx;
25use crate::event::{Event, EventResult, MouseButton};
26use crate::geometry::{Point, Rect, Size};
27use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
28use crate::widget::Widget;
29
30use super::scrollbar::{
31 paint_prepared_scrollbar, ScrollbarAxis, ScrollbarGeometry, ScrollbarOrientation,
32 DEFAULT_GRAB_MARGIN,
33};
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum ScrollBarVisibility {
43 AlwaysVisible,
45 VisibleWhenNeeded,
48 AlwaysHidden,
50}
51
52impl Default for ScrollBarVisibility {
53 fn default() -> Self {
54 Self::VisibleWhenNeeded
55 }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub enum ScrollBarKind {
61 Solid,
62 Floating,
63}
64
65impl Default for ScrollBarKind {
66 fn default() -> Self {
67 Self::Floating
68 }
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub enum ScrollBarColor {
74 Background,
76 Foreground,
78}
79
80impl Default for ScrollBarColor {
81 fn default() -> Self {
82 Self::Background
83 }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq)]
88pub struct ScrollBarStyle {
89 pub bar_width: f64,
92 pub floating_width: f64,
97 pub handle_min_length: f64,
99 pub outer_margin: f64,
101 pub inner_margin: f64,
103 pub content_margin: f64,
106 pub margin_same: bool,
109 pub kind: ScrollBarKind,
111 pub color: ScrollBarColor,
113 pub fade_strength: f64,
115 pub fade_size: f64,
117}
118
119impl ScrollBarStyle {
120 pub fn bar_width_at(&self, t: f64) -> f64 {
129 if self.kind == ScrollBarKind::Solid {
130 return self.bar_width;
131 }
132 let from = self.floating_width.min(self.bar_width);
133 let t = t.clamp(0.0, 1.0);
134 from + (self.bar_width - from) * t
135 }
136}
137
138impl Default for ScrollBarStyle {
139 fn default() -> Self {
140 Self {
141 bar_width: 10.0,
142 floating_width: 2.0,
143 handle_min_length: 12.0,
144 outer_margin: 0.0,
145 inner_margin: 4.0,
146 content_margin: 0.0,
147 margin_same: true,
148 kind: ScrollBarKind::default(),
149 color: ScrollBarColor::Foreground,
150 fade_strength: 0.5,
151 fade_size: 20.0,
152 }
153 }
154}
155
156impl ScrollBarStyle {
157 pub fn solid() -> Self {
161 Self {
162 bar_width: 6.0,
163 floating_width: 2.0,
164 handle_min_length: 12.0,
165 outer_margin: 0.0,
166 inner_margin: 4.0,
167 content_margin: 0.0,
168 margin_same: true,
169 kind: ScrollBarKind::Solid,
170 color: ScrollBarColor::Background,
171 fade_strength: 0.5,
172 fade_size: 20.0,
173 }
174 }
175 pub fn thin() -> Self {
182 Self {
183 bar_width: 10.0,
184 floating_width: 2.0,
185 handle_min_length: 12.0,
186 outer_margin: 0.0,
187 inner_margin: 4.0,
188 content_margin: 0.0,
189 margin_same: true,
190 kind: ScrollBarKind::Floating,
191 color: ScrollBarColor::Background,
192 fade_strength: 0.5,
193 fade_size: 20.0,
194 }
195 }
196 pub fn floating() -> Self {
199 Self::default()
200 }
201}
202
203std::thread_local! {
211 static CURRENT_SCROLL_STYLE: Cell<ScrollBarStyle> = Cell::new(ScrollBarStyle::default());
212 static CURRENT_SCROLL_VISIBILITY: Cell<ScrollBarVisibility> = Cell::new(ScrollBarVisibility::VisibleWhenNeeded);
213 static SCROLL_STYLE_EPOCH: Cell<u64> = Cell::new(1);
214}
215
216pub fn current_scroll_style() -> ScrollBarStyle {
218 CURRENT_SCROLL_STYLE.with(|c| c.get())
219}
220
221pub fn set_scroll_style(s: ScrollBarStyle) {
224 CURRENT_SCROLL_STYLE.with(|c| c.set(s));
225 SCROLL_STYLE_EPOCH.with(|c| c.set(c.get().wrapping_add(1)));
226 crate::animation::request_draw();
227}
228
229pub fn current_scroll_visibility() -> ScrollBarVisibility {
231 CURRENT_SCROLL_VISIBILITY.with(|c| c.get())
232}
233
234pub fn set_scroll_visibility(v: ScrollBarVisibility) {
238 CURRENT_SCROLL_VISIBILITY.with(|c| c.set(v));
239 SCROLL_STYLE_EPOCH.with(|c| c.set(c.get().wrapping_add(1)));
240 crate::animation::request_draw();
241}
242
243fn current_scroll_style_epoch() -> u64 {
244 SCROLL_STYLE_EPOCH.with(|c| c.get())
245}
246
247const RIGHT_EDGE_GUARD: f64 = 4.0;
253const BOTTOM_EDGE_GUARD: f64 = 4.0;
255
256pub struct ScrollView {
262 bounds: Rect,
263 children: Vec<Box<dyn Widget>>, base: WidgetBase,
265
266 v: ScrollbarAxis,
267 h: ScrollbarAxis,
268
269 stick_to_bottom: bool,
272 was_at_bottom: bool,
273
274 bar_visibility: ScrollBarVisibility,
276 visibility_explicit: bool,
281 style: ScrollBarStyle,
282 style_explicit: bool,
286
287 offset_cell: Option<Rc<Cell<f64>>>,
289 max_scroll_cell: Option<Rc<Cell<f64>>>,
290 h_offset_cell: Option<Rc<Cell<f64>>>,
291 h_max_scroll_cell: Option<Rc<Cell<f64>>>,
292 visibility_cell: Option<Rc<Cell<ScrollBarVisibility>>>,
293 style_cell: Option<Rc<Cell<ScrollBarStyle>>>,
294 viewport_cell: Option<Rc<Cell<Rect>>>,
297 painted_style_epoch: Cell<u64>,
298
299 middle_dragging: bool,
300 middle_start_world: Point,
301 middle_start_v_offset: f64,
302 middle_start_h_offset: f64,
303}
304
305impl ScrollView {
306 pub fn new(content: Box<dyn Widget>) -> Self {
307 Self {
308 bounds: Rect::default(),
309 children: vec![content],
310 base: WidgetBase::new(),
311 v: ScrollbarAxis {
312 enabled: true,
313 ..ScrollbarAxis::default()
314 },
315 h: ScrollbarAxis::default(),
316 stick_to_bottom: false,
317 was_at_bottom: false,
318 bar_visibility: current_scroll_visibility(),
319 visibility_explicit: false,
320 style: current_scroll_style(),
321 style_explicit: false,
322 offset_cell: None,
323 max_scroll_cell: None,
324 h_offset_cell: None,
325 h_max_scroll_cell: None,
326 visibility_cell: None,
327 style_cell: None,
328 viewport_cell: None,
329 painted_style_epoch: Cell::new(0),
330 middle_dragging: false,
331 middle_start_world: Point::ORIGIN,
332 middle_start_v_offset: 0.0,
333 middle_start_h_offset: 0.0,
334 }
335 }
336
337 pub fn horizontal(mut self, enabled: bool) -> Self {
340 self.h.enabled = enabled;
341 self
342 }
343 pub fn vertical(mut self, enabled: bool) -> Self {
344 self.v.enabled = enabled;
345 self
346 }
347
348 pub fn scroll_offset(&self) -> f64 {
351 self.v.offset
352 }
353
354 pub fn set_scroll_offset(&mut self, offset: f64) {
355 self.v.offset = offset;
356 if let Some(c) = &self.offset_cell {
357 c.set(offset);
358 }
359 }
360
361 pub fn max_scroll_value(&self) -> f64 {
362 self.v.max_scroll(self.bounds.height)
363 }
364
365 pub fn with_offset_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
366 self.offset_cell = Some(cell);
367 self
368 }
369
370 pub fn with_max_scroll_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
371 self.max_scroll_cell = Some(cell);
372 self
373 }
374
375 pub fn with_h_offset_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
376 self.h_offset_cell = Some(cell);
377 self
378 }
379
380 pub fn with_h_max_scroll_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
381 self.h_max_scroll_cell = Some(cell);
382 self
383 }
384
385 pub fn with_stick_to_bottom(mut self, stick: bool) -> Self {
386 self.stick_to_bottom = stick;
387 self
388 }
389
390 pub fn with_bar_visibility(mut self, v: ScrollBarVisibility) -> Self {
391 self.bar_visibility = v;
392 self.visibility_explicit = true;
393 self
394 }
395
396 pub fn set_bar_visibility(&mut self, v: ScrollBarVisibility) {
397 self.bar_visibility = v;
398 self.visibility_explicit = true;
399 }
400
401 pub fn with_bar_visibility_cell(mut self, cell: Rc<Cell<ScrollBarVisibility>>) -> Self {
402 self.visibility_cell = Some(cell);
403 self
404 }
405
406 pub fn with_style(mut self, s: ScrollBarStyle) -> Self {
407 self.style = s;
408 self.style_explicit = true;
409 self
410 }
411
412 pub fn with_style_cell(mut self, cell: Rc<Cell<ScrollBarStyle>>) -> Self {
413 self.style_cell = Some(cell);
414 self
415 }
416
417 pub fn with_viewport_cell(mut self, cell: Rc<Cell<Rect>>) -> Self {
419 self.viewport_cell = Some(cell);
420 self
421 }
422
423 fn viewport(&self) -> (f64, f64) {
426 let (reserve_x, reserve_y) = self.bar_reserve();
428 let w = (self.bounds.width - reserve_x).max(0.0);
429 let h = (self.bounds.height - reserve_y).max(0.0);
430 (w, h)
431 }
432
433 fn bar_reserve(&self) -> (f64, f64) {
435 if self.style.kind != ScrollBarKind::Solid {
436 return (0.0, 0.0);
437 }
438 let span = self.style.bar_width + self.style.outer_margin + self.style.inner_margin;
439 let rx = if self.h.enabled && self.h.content > self.bounds.width {
440 0.0
441 } else {
442 0.0
443 };
444 let need_v = self.v.enabled && self.v.content > self.bounds.height - self.h_bar_thickness();
447 let need_h = self.h.enabled && self.h.content > self.bounds.width - self.v_bar_thickness();
448 let rx = rx + if need_v { span } else { 0.0 };
449 let ry = if need_h { span } else { 0.0 };
450 (rx, ry)
451 }
452
453 fn v_bar_thickness(&self) -> f64 {
456 self.style.bar_width + self.style.outer_margin + self.style.inner_margin
457 }
458 fn h_bar_thickness(&self) -> f64 {
459 self.style.bar_width + self.style.outer_margin + self.style.inner_margin
460 }
461
462 fn v_bar_right(&self) -> f64 {
464 self.bounds.width - RIGHT_EDGE_GUARD - self.style.outer_margin
465 }
466 fn h_bar_bottom(&self) -> f64 {
469 BOTTOM_EDGE_GUARD + self.style.outer_margin
470 }
471
472 fn v_track_range(&self) -> (f64, f64) {
475 let (_, reserve_y) = self.bar_reserve();
476 let lo = self.style.inner_margin + reserve_y;
477 let hi = (self.bounds.height - self.style.inner_margin).max(lo);
478 (lo, hi)
479 }
480
481 fn h_track_range(&self) -> (f64, f64) {
482 let (reserve_x, _) = self.bar_reserve();
483 let lo = self.style.inner_margin;
484 let hi = (self.bounds.width - self.style.inner_margin - reserve_x).max(lo);
485 (lo, hi)
486 }
487
488 fn v_scrollbar_geometry(&self) -> ScrollbarGeometry {
489 let (lo, hi) = self.v_track_range();
490 ScrollbarGeometry {
491 orientation: ScrollbarOrientation::Vertical,
492 track_start: lo,
493 track_end: hi,
494 cross_end: self.v_bar_right(),
495 hit_margin: DEFAULT_GRAB_MARGIN,
496 }
497 }
498
499 fn h_scrollbar_geometry(&self) -> ScrollbarGeometry {
500 let (lo, hi) = self.h_track_range();
501 ScrollbarGeometry {
502 orientation: ScrollbarOrientation::Horizontal,
503 track_start: lo,
504 track_end: hi,
505 cross_end: self.h_bar_bottom(),
506 hit_margin: DEFAULT_GRAB_MARGIN,
507 }
508 }
509
510 fn pos_in_v_hover(&self, pos: Point) -> bool {
511 self.v
512 .pos_in_hover(pos, self.style, self.v_scrollbar_geometry())
513 }
514
515 fn pos_in_h_hover(&self, pos: Point) -> bool {
516 self.h
517 .pos_in_hover(pos, self.style, self.h_scrollbar_geometry())
518 }
519
520 fn clamp_offsets(&mut self) {
521 let (vw, vh) = self.viewport();
522 self.v.clamp_offset(vh);
523 self.h.clamp_offset(vw);
524 }
525
526 fn publish_offsets(&self) {
527 if let Some(c) = &self.offset_cell {
528 c.set(self.v.offset);
529 }
530 if let Some(c) = &self.h_offset_cell {
531 c.set(self.h.offset);
532 }
533 }
534
535 pub fn with_margin(mut self, m: Insets) -> Self {
538 self.base.margin = m;
539 self
540 }
541 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
542 self.base.h_anchor = h;
543 self
544 }
545 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
546 self.base.v_anchor = v;
547 self
548 }
549 pub fn with_min_size(mut self, s: Size) -> Self {
550 self.base.min_size = s;
551 self
552 }
553 pub fn with_max_size(mut self, s: Size) -> Self {
554 self.base.max_size = s;
555 self
556 }
557
558 fn scrollbar_animation_active(&self) -> bool {
561 self.v.animation_active() || self.h.animation_active()
562 }
563}
564
565mod widget_impl;