yakui_widgets/widgets/
scrollable.rs1use std::cell::Cell;
2
3use yakui_core::event::{EventInterest, EventResponse, WidgetEvent};
4use yakui_core::geometry::{Constraints, Vec2};
5use yakui_core::widget::{EventContext, LayoutContext, PaintContext, Widget};
6use yakui_core::Response;
7
8use crate::util::widget_children;
9
10#[derive(Debug)]
11#[non_exhaustive]
12#[must_use = "yakui widgets do nothing if you don't `show` them"]
13pub struct Scrollable {
14 pub direction: Option<ScrollDirection>,
15}
16
17impl Scrollable {
18 pub fn none() -> Self {
19 Scrollable { direction: None }
20 }
21
22 pub fn vertical() -> Self {
23 Scrollable {
24 direction: Some(ScrollDirection::Y),
25 }
26 }
27
28 pub fn show<F: FnOnce()>(self, children: F) -> Response<ScrollableResponse> {
29 widget_children::<ScrollableWidget, F>(children, self)
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ScrollDirection {
35 Y,
36}
37
38#[derive(Debug)]
39#[non_exhaustive]
40pub struct ScrollableWidget {
41 props: Scrollable,
42 scroll_position: Cell<Vec2>,
43 canvas_size: Cell<Vec2>,
44}
45
46pub type ScrollableResponse = ();
47
48impl Widget for ScrollableWidget {
49 type Props<'a> = Scrollable;
50 type Response = ScrollableResponse;
51
52 fn new() -> Self {
53 Self {
54 props: Scrollable::none(),
55 scroll_position: Cell::new(Vec2::ZERO),
56 canvas_size: Cell::new(Vec2::ZERO),
57 }
58 }
59
60 fn update(&mut self, props: Self::Props<'_>) -> Self::Response {
61 self.props = props;
62 }
63
64 fn layout(&self, mut ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2 {
65 ctx.layout.enable_clipping(ctx.dom);
66
67 let node = ctx.dom.get_current();
68 let mut canvas_size = Vec2::ZERO;
69
70 let child_constraints = match self.props.direction {
71 None => constraints,
72 Some(ScrollDirection::Y) => Constraints {
73 min: Vec2::new(constraints.min.x, 0.0),
74 max: Vec2::new(constraints.max.x, f32::INFINITY),
75 },
76 };
77
78 for &child in &node.children {
79 let child_size = ctx.calculate_layout(child, child_constraints);
80 canvas_size = canvas_size.max(child_size);
81 }
82 self.canvas_size.set(canvas_size);
83
84 let size = constraints.constrain(canvas_size);
85
86 let max_scroll_position = (canvas_size - size).max(Vec2::ZERO);
87 let mut scroll_position = self
88 .scroll_position
89 .get()
90 .min(max_scroll_position)
91 .max(Vec2::ZERO);
92
93 match self.props.direction {
94 None => scroll_position = Vec2::ZERO,
95 Some(ScrollDirection::Y) => scroll_position.x = 0.0,
96 }
97
98 self.scroll_position.set(scroll_position);
99
100 for &child in &node.children {
101 ctx.layout.set_pos(child, -scroll_position);
102 }
103
104 size
105 }
106
107 fn paint(&self, mut ctx: PaintContext<'_>) {
108 let node = ctx.dom.get_current();
109
110 for &child in &node.children {
111 ctx.paint(child);
112 }
113 }
114
115 fn event_interest(&self) -> EventInterest {
116 EventInterest::MOUSE_INSIDE
117 }
118
119 fn event(&mut self, _ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse {
120 match *event {
121 WidgetEvent::MouseScroll { delta } => {
122 let pos = self.scroll_position.get();
123 self.scroll_position.set(pos + delta);
124 EventResponse::Sink
125 }
126 _ => EventResponse::Bubble,
127 }
128 }
129}