Skip to main content

sequoia_view/
scroll_view.rs

1use std::rc::Rc;
2
3use sequoia_core::{Color, Constraints, Length, Point, Rect, Size, Vec2};
4use sequoia_input::{Event, EventPhase, Key, Modifiers, PointerEvent};
5use sequoia_layout::{LayoutCtx, LayoutStyle};
6
7use crate::any::AnyView;
8use crate::runtime::{ElementId, Runtime};
9use crate::view::{EventCtx, PaintCtx, View};
10
11const DEFAULT_SCROLLBAR_W: f32 = 8.0;
12
13const SCROLLBAR_PAD: f32 = 2.0;
14
15const THUMB_MIN: f32 = 24.0;
16
17const DEFAULT_STEP: f32 = 40.0;
18
19const HOVER_EXTRA: f32 = 4.0;
20
21const TOUCH_PAN_THRESHOLD_SQ: f32 = 64.0;
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
24#[non_exhaustive]
25pub enum ScrollAxis {
26    #[default]
27    Vertical,
28
29    Horizontal,
30
31    Both,
32}
33
34impl ScrollAxis {
35    pub fn scrolls_x(self) -> bool {
36        matches!(self, Self::Horizontal | Self::Both)
37    }
38
39    pub fn scrolls_y(self) -> bool {
40        matches!(self, Self::Vertical | Self::Both)
41    }
42}
43
44#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum ScrollbarVisibility {
47    #[default]
48    Auto,
49
50    Always,
51
52    Never,
53
54    OnHover,
55
56    Overlay,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq)]
60enum Drag {
61    V {
62        grab: f32,
63    },
64
65    H {
66        grab: f32,
67    },
68
69    Touch {
70        id: u64,
71        start: Point,
72        last: Point,
73        moved: bool,
74    },
75}
76
77struct Bars {
78    v_track: Option<Rect>,
79    v_thumb: Option<Rect>,
80    h_track: Option<Rect>,
81    h_thumb: Option<Rect>,
82}
83
84#[derive(Clone, Copy, Default)]
85struct ScrollState {
86    offset: Vec2,
87    content: Size,
88    viewport: Size,
89    drag: Option<Drag>,
90    arranged: Vec2,
91
92    arranged_epoch: u64,
93    frame: Rect,
94
95    bar_hover: bool,
96}
97
98#[derive(Clone)]
99pub struct ScrollView {
100    child: AnyView,
101    axis: ScrollAxis,
102    scrollbar: ScrollbarVisibility,
103    bar_width: f32,
104    track_color: Option<Color>,
105    thumb_color: Option<Color>,
106    step: f32,
107    on_scroll: Option<Rc<dyn Fn(Vec2)>>,
108}
109
110impl ScrollView {
111    pub fn new(child: impl View + 'static) -> Self {
112        Self {
113            child: AnyView::new(child),
114            axis: ScrollAxis::Vertical,
115            scrollbar: ScrollbarVisibility::Auto,
116            bar_width: DEFAULT_SCROLLBAR_W,
117            track_color: None,
118            thumb_color: None,
119            step: DEFAULT_STEP,
120            on_scroll: None,
121        }
122    }
123
124    pub fn with_child(&self, child: impl View + 'static) -> Self {
125        let mut out = self.clone();
126        out.child = AnyView::new(child);
127        out
128    }
129
130    pub fn axis(mut self, axis: ScrollAxis) -> Self {
131        self.axis = axis;
132        self
133    }
134
135    pub fn vertical(mut self) -> Self {
136        self.axis = ScrollAxis::Vertical;
137        self
138    }
139
140    pub fn horizontal(mut self) -> Self {
141        self.axis = ScrollAxis::Horizontal;
142        self
143    }
144
145    pub fn both(mut self) -> Self {
146        self.axis = ScrollAxis::Both;
147        self
148    }
149
150    pub fn scrollbar_visibility(mut self, visibility: ScrollbarVisibility) -> Self {
151        self.scrollbar = visibility;
152        self
153    }
154
155    pub fn show_scrollbar(mut self, show: bool) -> Self {
156        self.scrollbar = if show {
157            ScrollbarVisibility::Always
158        } else {
159            ScrollbarVisibility::Never
160        };
161        self
162    }
163
164    pub fn scrollbar_width(mut self, width: f32) -> Self {
165        self.bar_width = width;
166        self
167    }
168
169    pub fn scrollbar_track(mut self, color: Color) -> Self {
170        self.track_color = Some(color);
171        self
172    }
173
174    pub fn scrollbar_thumb(mut self, color: Color) -> Self {
175        self.thumb_color = Some(color);
176        self
177    }
178
179    pub fn scroll_step(mut self, step: f32) -> Self {
180        self.step = step.max(0.0);
181        self
182    }
183
184    pub fn on_scroll(mut self, handler: impl Fn(Vec2) + 'static) -> Self {
185        self.on_scroll = Some(Rc::new(handler));
186        self
187    }
188
189    pub fn scroll_axis(&self) -> ScrollAxis {
190        self.axis
191    }
192
193    fn load(rt: &Runtime, el: ElementId) -> ScrollState {
194        rt.read_state::<ScrollState, ScrollState>(el, |s| *s)
195    }
196
197    pub fn offset(&self, rt: &Runtime, el: ElementId) -> f32 {
198        Self::load(rt, el).offset.y
199    }
200
201    pub fn offset_x(&self, rt: &Runtime, el: ElementId) -> f32 {
202        Self::load(rt, el).offset.x
203    }
204
205    pub fn offset_y(&self, rt: &Runtime, el: ElementId) -> f32 {
206        Self::load(rt, el).offset.y
207    }
208
209    pub fn offset_vec(&self, rt: &Runtime, el: ElementId) -> Vec2 {
210        Self::load(rt, el).offset
211    }
212
213    pub fn content_height(&self, rt: &Runtime, el: ElementId) -> f32 {
214        Self::load(rt, el).content.height
215    }
216
217    pub fn content_width(&self, rt: &Runtime, el: ElementId) -> f32 {
218        Self::load(rt, el).content.width
219    }
220
221    pub fn content_size(&self, rt: &Runtime, el: ElementId) -> Size {
222        Self::load(rt, el).content
223    }
224
225    pub fn viewport_size(&self, rt: &Runtime, el: ElementId) -> Size {
226        Self::load(rt, el).viewport
227    }
228
229    pub fn viewport_width(&self, rt: &Runtime, el: ElementId) -> f32 {
230        Self::load(rt, el).viewport.width
231    }
232
233    pub fn viewport_height(&self, rt: &Runtime, el: ElementId) -> f32 {
234        Self::load(rt, el).viewport.height
235    }
236
237    pub fn max_offset(&self, rt: &Runtime, el: ElementId) -> f32 {
238        let st = Self::load(rt, el);
239        (st.content.height - st.viewport.height).max(0.0)
240    }
241
242    pub fn max_offset_x(&self, rt: &Runtime, el: ElementId) -> f32 {
243        let st = Self::load(rt, el);
244        (st.content.width - st.viewport.width).max(0.0)
245    }
246
247    pub fn max_offset_y(&self, rt: &Runtime, el: ElementId) -> f32 {
248        self.max_offset(rt, el)
249    }
250
251    pub fn max_offset_vec(&self, rt: &Runtime, el: ElementId) -> Vec2 {
252        Vec2::new(self.max_offset_x(rt, el), self.max_offset(rt, el))
253    }
254
255    pub fn can_scroll_x(&self, rt: &Runtime, el: ElementId) -> bool {
256        self.axis.scrolls_x() && self.max_offset_x(rt, el) > 0.0
257    }
258
259    pub fn can_scroll_y(&self, rt: &Runtime, el: ElementId) -> bool {
260        self.axis.scrolls_y() && self.max_offset(rt, el) > 0.0
261    }
262
263    pub fn scroll_fraction(&self, rt: &Runtime, el: ElementId) -> Vec2 {
264        Vec2::new(
265            self.scroll_fraction_x(rt, el),
266            self.scroll_fraction_y(rt, el),
267        )
268    }
269
270    pub fn scroll_fraction_x(&self, rt: &Runtime, el: ElementId) -> f32 {
271        let max = self.max_offset_x(rt, el);
272        if max > 0.0 {
273            (Self::load(rt, el).offset.x / max).clamp(0.0, 1.0)
274        } else {
275            0.0
276        }
277    }
278
279    pub fn scroll_fraction_y(&self, rt: &Runtime, el: ElementId) -> f32 {
280        let max = self.max_offset(rt, el);
281        if max > 0.0 {
282            (Self::load(rt, el).offset.y / max).clamp(0.0, 1.0)
283        } else {
284            0.0
285        }
286    }
287
288    pub fn scroll_to(&self, rt: &Runtime, el: ElementId, offset: Vec2) {
289        let max = self.max_offset_vec(rt, el);
290        let o = Self::load(rt, el).offset;
291        let next = Vec2::new(
292            if self.axis.scrolls_x() {
293                offset.x.clamp(0.0, max.x)
294            } else {
295                o.x.clamp(0.0, max.x)
296            },
297            if self.axis.scrolls_y() {
298                offset.y.clamp(0.0, max.y)
299            } else {
300                o.y.clamp(0.0, max.y)
301            },
302        );
303        self.apply(rt, el, next);
304    }
305
306    pub fn scroll_to_xy(&self, rt: &Runtime, el: ElementId, x: f32, y: f32) {
307        self.scroll_to(rt, el, Vec2::new(x, y));
308    }
309
310    pub fn scroll_to_x(&self, rt: &Runtime, el: ElementId, x: f32) {
311        let o = Self::load(rt, el).offset;
312        self.scroll_to(rt, el, Vec2::new(x, o.y));
313    }
314
315    pub fn scroll_to_y(&self, rt: &Runtime, el: ElementId, y: f32) {
316        let o = Self::load(rt, el).offset;
317        self.scroll_to(rt, el, Vec2::new(o.x, y));
318    }
319
320    pub fn scroll_by(&self, rt: &Runtime, el: ElementId, delta: Vec2) {
321        let o = Self::load(rt, el).offset;
322        self.scroll_to(rt, el, Vec2::new(o.x + delta.x, o.y + delta.y));
323    }
324
325    pub fn scroll_to_top(&self, rt: &Runtime, el: ElementId) {
326        self.scroll_to_y(rt, el, 0.0);
327    }
328
329    pub fn scroll_to_bottom(&self, rt: &Runtime, el: ElementId) {
330        self.scroll_to_y(rt, el, self.max_offset(rt, el));
331    }
332
333    pub fn scroll_into_view(&self, rt: &Runtime, el: ElementId, rect: Rect) {
334        let st = Self::load(rt, el);
335        let mut next = st.offset;
336        if self.axis.scrolls_y() {
337            let vh = st.viewport.height;
338            if rect.top() < next.y {
339                next.y = rect.top();
340            } else if rect.bottom() > next.y + vh {
341                next.y = rect.bottom() - vh;
342            }
343        }
344        if self.axis.scrolls_x() {
345            let vw = st.viewport.width;
346            if rect.left() < next.x {
347                next.x = rect.left();
348            } else if rect.right() > next.x + vw {
349                next.x = rect.right() - vw;
350            }
351        }
352        self.scroll_to(rt, el, next);
353    }
354
355    pub fn scroll_element_into_view(&self, rt: &Runtime, el: ElementId, child: ElementId) {
356        let Some(r) = rt.rect_of(child) else {
357            return;
358        };
359        let (frame, off) = rt.read_state(el, |s: &ScrollState| (s.frame, s.offset));
360        let content = Rect::from_xywh(
361            r.left() - frame.left() + off.x,
362            r.top() - frame.top() + off.y,
363            r.size.width,
364            r.size.height,
365        );
366        self.scroll_into_view(rt, el, content);
367    }
368
369    pub fn scroll_to_left(&self, rt: &Runtime, el: ElementId) {
370        self.scroll_to_x(rt, el, 0.0);
371    }
372
373    pub fn scroll_to_right(&self, rt: &Runtime, el: ElementId) {
374        self.scroll_to_x(rt, el, self.max_offset_x(rt, el));
375    }
376
377    pub fn page_up(&self, rt: &Runtime, el: ElementId) {
378        let st = Self::load(rt, el);
379        self.scroll_to(
380            rt,
381            el,
382            Vec2::new(st.offset.x, st.offset.y - st.viewport.height),
383        );
384    }
385
386    pub fn page_down(&self, rt: &Runtime, el: ElementId) {
387        let st = Self::load(rt, el);
388        self.scroll_to(
389            rt,
390            el,
391            Vec2::new(st.offset.x, st.offset.y + st.viewport.height),
392        );
393    }
394
395    pub fn page_left(&self, rt: &Runtime, el: ElementId) {
396        let st = Self::load(rt, el);
397        self.scroll_to(
398            rt,
399            el,
400            Vec2::new(st.offset.x - st.viewport.width, st.offset.y),
401        );
402    }
403
404    pub fn page_right(&self, rt: &Runtime, el: ElementId) {
405        let st = Self::load(rt, el);
406        self.scroll_to(
407            rt,
408            el,
409            Vec2::new(st.offset.x + st.viewport.width, st.offset.y),
410        );
411    }
412
413    pub fn ensure_visible(&self, rt: &Runtime, el: ElementId, rect: Rect) -> bool {
414        let st = Self::load(rt, el);
415        let vp = st.viewport;
416        let o = st.offset;
417        let max = self.max_offset_vec(rt, el);
418        let mut next = o;
419        if self.axis.scrolls_x() && vp.width > 0.0 {
420            if rect.size.width >= vp.width || rect.left() < o.x {
421                next.x = rect.left();
422            } else if rect.right() > o.x + vp.width {
423                next.x = rect.right() - vp.width;
424            }
425            next.x = next.x.clamp(0.0, max.x);
426        }
427        if self.axis.scrolls_y() && vp.height > 0.0 {
428            if rect.size.height >= vp.height || rect.top() < o.y {
429                next.y = rect.top();
430            } else if rect.bottom() > o.y + vp.height {
431                next.y = rect.bottom() - vp.height;
432            }
433            next.y = next.y.clamp(0.0, max.y);
434        }
435        if next != o {
436            self.apply(rt, el, next);
437            true
438        } else {
439            false
440        }
441    }
442
443    fn apply(&self, rt: &Runtime, el: ElementId, next: Vec2) {
444        let prev = rt.with_state::<ScrollState, Vec2>(el, |s| {
445            let prev = s.offset;
446            s.offset = next;
447            prev
448        });
449        if next != prev
450            && let Some(cb) = &self.on_scroll
451        {
452            cb(next);
453        }
454    }
455
456    fn shows_v(&self, st: &ScrollState) -> bool {
457        if !self.axis.scrolls_y() || self.bar_width <= 0.0 {
458            return false;
459        }
460        match self.scrollbar {
461            ScrollbarVisibility::Never => false,
462            ScrollbarVisibility::Always => true,
463            ScrollbarVisibility::Auto => st.content.height > st.viewport.height,
464            ScrollbarVisibility::OnHover | ScrollbarVisibility::Overlay => {
465                (st.bar_hover || st.drag.is_some()) && st.content.height > st.viewport.height
466            }
467        }
468    }
469
470    fn shows_h(&self, st: &ScrollState) -> bool {
471        if !self.axis.scrolls_x() || self.bar_width <= 0.0 {
472            return false;
473        }
474        match self.scrollbar {
475            ScrollbarVisibility::Never => false,
476            ScrollbarVisibility::Always => true,
477            ScrollbarVisibility::Auto => st.content.width > st.viewport.width,
478            ScrollbarVisibility::OnHover | ScrollbarVisibility::Overlay => {
479                (st.bar_hover || st.drag.is_some()) && st.content.width > st.viewport.width
480            }
481        }
482    }
483
484    fn gutter(&self) -> f32 {
485        (self.bar_width + SCROLLBAR_PAD * 2.0 + HOVER_EXTRA).max(0.0)
486    }
487
488    fn in_gutter(&self, rect: Rect, pos: Point) -> bool {
489        let g = self.gutter();
490        if g <= 0.0 {
491            return false;
492        }
493        let v = self.axis.scrolls_y()
494            && pos.x >= rect.right() - g
495            && pos.x <= rect.right()
496            && pos.y >= rect.top()
497            && pos.y <= rect.bottom();
498        let h = self.axis.scrolls_x()
499            && pos.y >= rect.bottom() - g
500            && pos.y <= rect.bottom()
501            && pos.x >= rect.left()
502            && pos.x <= rect.right();
503        v || h
504    }
505
506    fn track_hover(&self, rt: &Runtime, el: ElementId, rect: Rect, pos: Point) -> bool {
507        if !matches!(
508            self.scrollbar,
509            ScrollbarVisibility::OnHover | ScrollbarVisibility::Overlay
510        ) {
511            return false;
512        }
513        let hov = self.in_gutter(rect, pos);
514        rt.with_state(el, |s: &mut ScrollState| {
515            let old = s.bar_hover;
516            s.bar_hover = hov;
517            old != hov
518        })
519    }
520
521    fn v_bar(&self, st: &ScrollState, viewport: Rect) -> Option<(Rect, Rect)> {
522        if !self.shows_v(st) {
523            return None;
524        }
525        let w = self.bar_width;
526        let track = Rect::from_xywh(
527            viewport.right() - w - SCROLLBAR_PAD,
528            viewport.top() + SCROLLBAR_PAD,
529            w,
530            (viewport.size.height - SCROLLBAR_PAD * 2.0).max(0.0),
531        );
532        if track.is_empty() {
533            return None;
534        }
535        Some((track, self.v_thumb(st, track)))
536    }
537
538    fn v_thumb(&self, st: &ScrollState, track: Rect) -> Rect {
539        let content = st.content.height;
540        let vh = st.viewport.height;
541        let max_off = (content - vh).max(0.0);
542        if max_off <= 0.0 || content <= 0.0 {
543            return track;
544        }
545        let thumb_h = (track.size.height * vh / content)
546            .max(THUMB_MIN)
547            .min(track.size.height);
548        let travel = (track.size.height - thumb_h).max(0.0);
549        let t = (st.offset.y / max_off).clamp(0.0, 1.0);
550        Rect::from_xywh(
551            track.left(),
552            track.top() + t * travel,
553            track.size.width,
554            thumb_h,
555        )
556    }
557
558    fn h_bar(&self, st: &ScrollState, viewport: Rect) -> Option<(Rect, Rect)> {
559        if !self.shows_h(st) {
560            return None;
561        }
562        let h = self.bar_width;
563        let track = Rect::from_xywh(
564            viewport.left() + SCROLLBAR_PAD,
565            viewport.bottom() - h - SCROLLBAR_PAD,
566            (viewport.size.width - SCROLLBAR_PAD * 2.0).max(0.0),
567            h,
568        );
569        if track.is_empty() {
570            return None;
571        }
572        Some((track, self.h_thumb(st, track)))
573    }
574
575    fn h_thumb(&self, st: &ScrollState, track: Rect) -> Rect {
576        let content = st.content.width;
577        let vw = st.viewport.width;
578        let max_off = (content - vw).max(0.0);
579        if max_off <= 0.0 || content <= 0.0 {
580            return track;
581        }
582        let thumb_w = (track.size.width * vw / content)
583            .max(THUMB_MIN)
584            .min(track.size.width);
585        let travel = (track.size.width - thumb_w).max(0.0);
586        let t = (st.offset.x / max_off).clamp(0.0, 1.0);
587        Rect::from_xywh(
588            track.left() + t * travel,
589            track.top(),
590            thumb_w,
591            track.size.height,
592        )
593    }
594
595    fn bars(&self, st: &ScrollState, viewport: Rect) -> Bars {
596        let (v_track, v_thumb) = self.v_bar(st, viewport).unzip();
597        let (h_track, h_thumb) = self.h_bar(st, viewport).unzip();
598        Bars {
599            v_track,
600            v_thumb,
601            h_track,
602            h_thumb,
603        }
604    }
605
606    fn drag_offset(
607        &self,
608        st: &ScrollState,
609        bars: &Bars,
610        drag: Drag,
611        x: f32,
612        y: f32,
613    ) -> Option<Vec2> {
614        let o = st.offset;
615        match drag {
616            Drag::Touch { .. } => None,
617            Drag::V { grab } => {
618                let (track, thumb) = bars.v_track.zip(bars.v_thumb)?;
619                let travel = (track.size.height - thumb.size.height).max(0.0);
620                let max_off = (st.content.height - st.viewport.height).max(0.0);
621                if travel <= 0.0 || max_off <= 0.0 {
622                    return None;
623                }
624                let top = (y - grab - track.top()).clamp(0.0, travel);
625                Some(Vec2::new(o.x, top / travel * max_off))
626            }
627            Drag::H { grab } => {
628                let (track, thumb) = bars.h_track.zip(bars.h_thumb)?;
629                let travel = (track.size.width - thumb.size.width).max(0.0);
630                let max_off = (st.content.width - st.viewport.width).max(0.0);
631                if travel <= 0.0 || max_off <= 0.0 {
632                    return None;
633                }
634                let left = (x - grab - track.left()).clamp(0.0, travel);
635                Some(Vec2::new(left / travel * max_off, o.y))
636            }
637        }
638    }
639
640    fn on_wheel(
641        &self,
642        rt: &Runtime,
643        el: ElementId,
644        delta: Vec2,
645        modifiers: Modifiers,
646        ctx: &mut EventCtx,
647    ) {
648        let (mut dx, mut dy) = (delta.x, delta.y);
649        if modifiers.contains(Modifiers::SHIFT) && self.axis.scrolls_x() {
650            dx = dy;
651            dy = 0.0;
652        }
653        let st = Self::load(rt, el);
654        let o = st.offset;
655        let max = Vec2::new(
656            (st.content.width - st.viewport.width).max(0.0),
657            (st.content.height - st.viewport.height).max(0.0),
658        );
659        let nx = if self.axis.scrolls_x() {
660            (o.x - dx).clamp(0.0, max.x)
661        } else {
662            o.x.clamp(0.0, max.x)
663        };
664        let ny = if self.axis.scrolls_y() {
665            (o.y - dy).clamp(0.0, max.y)
666        } else {
667            o.y.clamp(0.0, max.y)
668        };
669        let next = Vec2::new(nx, ny);
670        if next != o {
671            self.apply(rt, el, next);
672            ctx.request_frame();
673            ctx.handled();
674        }
675    }
676
677    fn on_key(
678        &self,
679        rt: &Runtime,
680        el: ElementId,
681        key: Key,
682        modifiers: Modifiers,
683        ctx: &mut EventCtx,
684    ) {
685        let st = Self::load(rt, el);
686        let o = st.offset;
687        let max = Vec2::new(
688            (st.content.width - st.viewport.width).max(0.0),
689            (st.content.height - st.viewport.height).max(0.0),
690        );
691        let vp = st.viewport;
692        let step = if self.step.is_finite() {
693            self.step.max(0.0)
694        } else {
695            0.0
696        };
697        let next = match (key, modifiers) {
698            (Key::ARROW_UP, Modifiers::NONE) if self.axis.scrolls_y() => Vec2::new(o.x, o.y - step),
699            (Key::ARROW_DOWN, Modifiers::NONE) if self.axis.scrolls_y() => {
700                Vec2::new(o.x, o.y + step)
701            }
702            (Key::ARROW_LEFT, Modifiers::NONE) if self.axis.scrolls_x() => {
703                Vec2::new(o.x - step, o.y)
704            }
705            (Key::ARROW_RIGHT, Modifiers::NONE) if self.axis.scrolls_x() => {
706                Vec2::new(o.x + step, o.y)
707            }
708            (Key::PAGE_UP, Modifiers::NONE) if self.axis.scrolls_y() => {
709                Vec2::new(o.x, o.y - vp.height)
710            }
711            (Key::PAGE_DOWN, Modifiers::NONE) if self.axis.scrolls_y() => {
712                Vec2::new(o.x, o.y + vp.height)
713            }
714            (Key::HOME, Modifiers::NONE) if self.axis.scrolls_y() => Vec2::new(o.x, 0.0),
715            (Key::END, Modifiers::NONE) if self.axis.scrolls_y() => Vec2::new(o.x, max.y),
716            (Key::HOME, m) if m == Modifiers::CONTROL => Vec2::new(0.0, 0.0),
717            (Key::END, m) if m == Modifiers::CONTROL => Vec2::new(max.x, max.y),
718            _ => return,
719        };
720        let next = Vec2::new(next.x.clamp(0.0, max.x), next.y.clamp(0.0, max.y));
721        if next != o {
722            self.apply(rt, el, next);
723            ctx.request_frame();
724            ctx.handled();
725        }
726    }
727}
728
729impl View for ScrollView {
730    fn body(&self) -> impl View + 'static {
731        crate::views::Unit
732    }
733
734    fn children_override(&self) -> Option<Vec<AnyView>> {
735        Some(vec![self.child.clone()])
736    }
737
738    fn layout_style(&self) -> LayoutStyle {
739        LayoutStyle {
740            width: Length::Fill(1.0),
741            height: Length::Fill(1.0),
742            ..LayoutStyle::default()
743        }
744    }
745
746    fn intrinsic_size(&self, _ctx: &mut LayoutCtx) -> Option<Size> {
747        None
748    }
749
750    fn contain_size(&self) -> bool {
751        true
752    }
753
754    fn arrange_override(
755        &self,
756        rt: &Runtime,
757        el: ElementId,
758        rect: Rect,
759        ctx: &mut LayoutCtx,
760    ) -> bool {
761        let children = rt.children(el);
762        let Some(&child) = children.first() else {
763            rt.with_state(el, |s: &mut ScrollState| {
764                s.viewport = rect.size;
765                s.content = Size::ZERO;
766                s.frame = rect;
767            });
768            return true;
769        };
770
771        let gx = if self.scrollbar == ScrollbarVisibility::OnHover && self.axis.scrolls_y() {
772            self.gutter()
773        } else {
774            0.0
775        };
776        let gy = if self.scrollbar == ScrollbarVisibility::OnHover && self.axis.scrolls_x() {
777            self.gutter()
778        } else {
779            0.0
780        };
781        let max_w = if self.axis.scrolls_x() {
782            f32::INFINITY
783        } else {
784            (rect.size.width - gx).max(0.0)
785        };
786        let max_h = if self.axis.scrolls_y() {
787            f32::INFINITY
788        } else {
789            (rect.size.height - gy).max(0.0)
790        };
791        let unconstrained = Constraints::loose(Size::new(max_w, max_h));
792
793        let content_size = rt.measure_node(child, unconstrained, ctx);
794
795        let max = Vec2::new(
796            (content_size.width - rect.size.width).max(0.0),
797            (content_size.height - rect.size.height).max(0.0),
798        );
799        let off = rt.with_state(el, |s: &mut ScrollState| {
800            s.offset = Vec2::new(s.offset.x.clamp(0.0, max.x), s.offset.y.clamp(0.0, max.y));
801            s.offset
802        });
803
804        let (prev_frame, prev_content, prev_off, prev_epoch) = rt
805            .read_state(el, |s: &ScrollState| {
806                (s.frame, s.content, s.arranged, s.arranged_epoch)
807            });
808        let prev_content_w = if self.axis.scrolls_x() {
809            prev_content.width
810        } else {
811            prev_content.width.max(prev_frame.size.width)
812        };
813        let prev_content_h = if self.axis.scrolls_y() {
814            prev_content.height
815        } else {
816            prev_content.height.max(prev_frame.size.height)
817        };
818        let new_w = if self.axis.scrolls_x() {
819            content_size.width
820        } else {
821            content_size.width.max(rect.size.width - gx)
822        };
823        let new_h = if self.axis.scrolls_y() {
824            content_size.height
825        } else {
826            content_size.height.max(rect.size.height - gy)
827        };
828
829        rt.with_state(el, |s: &mut ScrollState| {
830            s.viewport = rect.size;
831            s.content = content_size;
832        });
833
834        if !prev_frame.size.is_empty()
835            && prev_content_w == new_w
836            && prev_content_h == new_h
837            && prev_epoch == rt.layout_epoch()
838        {
839            let dx = (rect.left() - prev_frame.left()) - (off.x - prev_off.x);
840            let dy = (rect.top() - prev_frame.top()) - (off.y - prev_off.y);
841            if dx != 0.0 || dy != 0.0 {
842                rt.translate_subtree(child, Vec2::new(dx, dy));
843            }
844            rt.with_state(el, |s: &mut ScrollState| {
845                s.arranged = off;
846                s.frame = rect;
847            });
848            return true;
849        }
850
851        let w = new_w;
852        let h = new_h;
853        let child_rect = Rect::from_xywh(rect.left() - off.x, rect.top() - off.y, w, h);
854        let epoch = rt.layout_epoch();
855        rt.arrange_node(child, child_rect, ctx);
856        rt.with_state(el, |s: &mut ScrollState| {
857            s.arranged = off;
858            s.arranged_epoch = epoch;
859            s.frame = rect;
860        });
861        true
862    }
863
864    fn clip_self(&self) -> bool {
865        true
866    }
867
868    fn captures_pointer(&self) -> bool {
869        false
870    }
871
872    fn wants_hover_updates(&self) -> bool {
873        matches!(
874            self.scrollbar,
875            ScrollbarVisibility::OnHover | ScrollbarVisibility::Overlay
876        )
877    }
878
879    fn captures_pointer_ctx(&self, rt: &Runtime, el: ElementId) -> bool {
880        rt.read_state(el, |s: &ScrollState| s.drag.is_some())
881    }
882
883    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
884        let rt = ctx.runtime().clone();
885        let el = ctx.element();
886        match event {
887            Event::Pointer(PointerEvent::Wheel {
888                delta, modifiers, ..
889            }) => {
890                if matches!(ctx.phase, EventPhase::Target | EventPhase::Bubble) {
891                    self.on_wheel(&rt, el, *delta, *modifiers, ctx);
892                }
893            }
894            Event::Pointer(PointerEvent::Down { pos, .. }) => {
895                if !matches!(ctx.phase, EventPhase::Target | EventPhase::Bubble) {
896                    return;
897                }
898
899                if self.track_hover(&rt, el, ctx.rect, *pos) {
900                    ctx.request_frame();
901                }
902                let st = Self::load(&rt, el);
903                if st.drag.is_some() {
904                    return;
905                }
906                let bars = self.bars(&st, ctx.rect);
907
908                if let Some(thumb) = bars.v_thumb
909                    && thumb.contains(*pos)
910                {
911                    rt.with_state(el, |s: &mut ScrollState| {
912                        s.drag = Some(Drag::V {
913                            grab: pos.y - thumb.top(),
914                        });
915                    });
916                    ctx.request_frame();
917                    ctx.handled();
918                    return;
919                }
920                if let Some(thumb) = bars.h_thumb
921                    && thumb.contains(*pos)
922                {
923                    rt.with_state(el, |s: &mut ScrollState| {
924                        s.drag = Some(Drag::H {
925                            grab: pos.x - thumb.left(),
926                        });
927                    });
928                    ctx.request_frame();
929                    ctx.handled();
930                    return;
931                }
932                if let Some(track) = bars.v_track
933                    && track.contains(*pos)
934                    && let Some(thumb) = bars.v_thumb
935                {
936                    let travel = (track.size.height - thumb.size.height).max(0.0);
937                    let max_off = (st.content.height - st.viewport.height).max(0.0);
938                    if travel > 0.0 && max_off > 0.0 {
939                        let top =
940                            (pos.y - thumb.size.height * 0.5 - track.top()).clamp(0.0, travel);
941                        let o = st.offset;
942                        self.apply(&rt, el, Vec2::new(o.x, top / travel * max_off));
943                    }
944                    rt.with_state(el, |s: &mut ScrollState| {
945                        s.drag = Some(Drag::V {
946                            grab: thumb.size.height * 0.5,
947                        });
948                    });
949                    ctx.request_frame();
950                    ctx.handled();
951                    return;
952                }
953                if let Some(track) = bars.h_track
954                    && track.contains(*pos)
955                    && let Some(thumb) = bars.h_thumb
956                {
957                    let travel = (track.size.width - thumb.size.width).max(0.0);
958                    let max_off = (st.content.width - st.viewport.width).max(0.0);
959                    if travel > 0.0 && max_off > 0.0 {
960                        let left =
961                            (pos.x - thumb.size.width * 0.5 - track.left()).clamp(0.0, travel);
962                        let o = st.offset;
963                        self.apply(&rt, el, Vec2::new(left / travel * max_off, o.y));
964                    }
965                    rt.with_state(el, |s: &mut ScrollState| {
966                        s.drag = Some(Drag::H {
967                            grab: thumb.size.width * 0.5,
968                        });
969                    });
970                    ctx.request_frame();
971                    ctx.handled();
972                }
973            }
974            Event::Pointer(PointerEvent::Move { pos, .. }) => {
975                if ctx.phase != EventPhase::Target {
976                    return;
977                }
978                let st = Self::load(&rt, el);
979                if let Some(drag) = st.drag {
980                    let bars = self.bars(&st, ctx.rect);
981                    if let Some(next) = self.drag_offset(&st, &bars, drag, pos.x, pos.y) {
982                        let max = Vec2::new(
983                            (st.content.width - st.viewport.width).max(0.0),
984                            (st.content.height - st.viewport.height).max(0.0),
985                        );
986                        let next = Vec2::new(next.x.clamp(0.0, max.x), next.y.clamp(0.0, max.y));
987                        if next != st.offset {
988                            self.apply(&rt, el, next);
989                            ctx.request_frame();
990                        }
991                        ctx.handled();
992                    }
993                }
994            }
995            Event::Pointer(PointerEvent::Up { pos, .. }) => {
996                if ctx.phase != EventPhase::Target {
997                    return;
998                }
999
1000                let mut reframed = false;
1001                if matches!(
1002                    self.scrollbar,
1003                    ScrollbarVisibility::OnHover | ScrollbarVisibility::Overlay
1004                ) && self.track_hover(&rt, el, ctx.rect, *pos)
1005                {
1006                    ctx.request_frame();
1007                    reframed = true;
1008                }
1009                let had = rt.with_state(el, |s: &mut ScrollState| s.drag.take().is_some());
1010                if had && !reframed {
1011                    ctx.request_frame();
1012                }
1013                if had {
1014                    ctx.handled();
1015                }
1016            }
1017            Event::Pointer(PointerEvent::TouchDown { id, pos, .. }) => {
1018                if !matches!(ctx.phase, EventPhase::Target | EventPhase::Bubble) {
1019                    return;
1020                }
1021                let st = Self::load(&rt, el);
1022
1023                if st.drag.is_some() {
1024                    return;
1025                }
1026
1027                rt.with_state(el, |s: &mut ScrollState| {
1028                    s.drag = Some(Drag::Touch {
1029                        id: *id,
1030                        start: *pos,
1031                        last: *pos,
1032                        moved: false,
1033                    });
1034                });
1035            }
1036            Event::Pointer(PointerEvent::TouchMove { id, pos, .. }) => {
1037                if !matches!(ctx.phase, EventPhase::Target | EventPhase::Bubble) {
1038                    return;
1039                }
1040                let st = Self::load(&rt, el);
1041                let Some(Drag::Touch {
1042                    id: did,
1043                    start,
1044                    last,
1045                    moved,
1046                }) = st.drag
1047                else {
1048                    return;
1049                };
1050                if did != *id {
1051                    return;
1052                }
1053                let dx_total = pos.x - start.x;
1054                let dy_total = pos.y - start.y;
1055                if !moved && dx_total * dx_total + dy_total * dy_total < TOUCH_PAN_THRESHOLD_SQ {
1056                    return;
1057                }
1058                if !moved {
1059                    ctx.capture_pointer();
1060                }
1061
1062                let max = Vec2::new(
1063                    (st.content.width - st.viewport.width).max(0.0),
1064                    (st.content.height - st.viewport.height).max(0.0),
1065                );
1066                let next = Vec2::new(
1067                    if self.axis.scrolls_x() {
1068                        (st.offset.x - (pos.x - last.x)).clamp(0.0, max.x)
1069                    } else {
1070                        st.offset.x
1071                    },
1072                    if self.axis.scrolls_y() {
1073                        (st.offset.y - (pos.y - last.y)).clamp(0.0, max.y)
1074                    } else {
1075                        st.offset.y
1076                    },
1077                );
1078                rt.with_state(el, |s: &mut ScrollState| {
1079                    s.drag = Some(Drag::Touch {
1080                        id: *id,
1081                        start,
1082                        last: *pos,
1083                        moved: true,
1084                    });
1085                });
1086                if next != st.offset {
1087                    self.apply(&rt, el, next);
1088                    ctx.request_frame();
1089                }
1090                ctx.handled();
1091            }
1092            Event::Pointer(PointerEvent::TouchUp { id, .. }) => {
1093                if !matches!(ctx.phase, EventPhase::Target | EventPhase::Bubble) {
1094                    return;
1095                }
1096
1097                let is_touch = matches!(
1098                    Self::load(&rt, el).drag,
1099                    Some(Drag::Touch { id: did, .. }) if did == *id
1100                );
1101                if !is_touch {
1102                    return;
1103                }
1104                let moved = rt.with_state(el, |s: &mut ScrollState| {
1105                    let moved = matches!(s.drag, Some(Drag::Touch { moved: true, .. }));
1106                    s.drag.take();
1107                    moved
1108                });
1109                if moved {
1110                    ctx.release_pointer();
1111                    ctx.request_frame();
1112                    ctx.handled();
1113                }
1114            }
1115            Event::Pointer(PointerEvent::Cancel) => {
1116                if ctx.phase != EventPhase::Target {
1117                    return;
1118                }
1119                let had = rt.with_state(el, |s: &mut ScrollState| s.drag.take().is_some());
1120                let unhov = rt.with_state(el, |s: &mut ScrollState| {
1121                    let old = s.bar_hover;
1122                    s.bar_hover = false;
1123                    old
1124                });
1125                if had || unhov {
1126                    ctx.request_frame();
1127                }
1128                if had {
1129                    ctx.handled();
1130                }
1131            }
1132            Event::Key(key) => {
1133                if matches!(ctx.phase, EventPhase::Target | EventPhase::Bubble) {
1134                    self.on_key(&rt, el, key.key, key.modifiers, ctx);
1135                }
1136            }
1137            Event::Ime(_) => {}
1138            Event::FilesHovered { .. } | Event::FilesDropped { .. } | Event::FilesCancelled => {}
1139
1140            _ => {}
1141        }
1142    }
1143
1144    fn paint_self(&self, ctx: &mut PaintCtx) {
1145        let rt = ctx.runtime().clone();
1146        let el = ctx.element();
1147        let viewport = ctx.rect;
1148        rt.with_state(el, |s: &mut ScrollState| {
1149            s.viewport = viewport.size;
1150        });
1151    }
1152
1153    fn paint_foreground(&self, ctx: &mut PaintCtx) {
1154        let rt = ctx.runtime().clone();
1155        let el = ctx.element();
1156        let viewport = ctx.rect;
1157
1158        let hov = matches!(
1159            self.scrollbar,
1160            ScrollbarVisibility::OnHover | ScrollbarVisibility::Overlay
1161        ) && rt
1162            .pointer_pos()
1163            .is_some_and(|p| self.in_gutter(viewport, p));
1164        rt.with_state(el, |s: &mut ScrollState| {
1165            s.viewport = viewport.size;
1166            if matches!(
1167                self.scrollbar,
1168                ScrollbarVisibility::OnHover | ScrollbarVisibility::Overlay
1169            ) {
1170                s.bar_hover = hov;
1171            }
1172        });
1173        let st = Self::load(&rt, el);
1174
1175        let bars = self.bars(&st, viewport);
1176        if bars.v_track.is_none() && bars.h_track.is_none() {
1177            return;
1178        }
1179
1180        let theme = ctx.theme();
1181        let track_color = self.track_color.unwrap_or(theme.border);
1182        let thumb_color = self.thumb_color.unwrap_or(theme.text_secondary);
1183        let radius = self.bar_width * 0.375;
1184        if let (Some(track), Some(thumb)) = (bars.v_track, bars.v_thumb) {
1185            ctx.quad(track, track_color, radius);
1186            ctx.quad(thumb, thumb_color, radius);
1187        }
1188        if let (Some(track), Some(thumb)) = (bars.h_track, bars.h_thumb) {
1189            ctx.quad(track, track_color, radius);
1190            ctx.quad(thumb, thumb_color, radius);
1191        }
1192
1193        if let (Some(v), Some(h)) = (bars.v_track, bars.h_track) {
1194            let corner = Rect::from_xywh(v.left(), h.top(), v.size.width, h.size.height);
1195            if !corner.is_empty() {
1196                ctx.quad(corner, track_color, 0.0);
1197            }
1198        }
1199    }
1200}