1use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
2use crate::event::Event;
3use crate::geom::{Pos, Rect, Size};
4use crate::layout::Constraint;
5use crate::node::Node;
6use crate::render::RenderCx;
7use crate::style::{Color, Style};
8
9pub struct Scroll {
14 child: Option<Node>,
15 scroll_y: u16,
16 scroll_x: u16,
17 content_height: u16,
18 content_width: u16,
19 rect: Rect,
20}
21
22impl Scroll {
23 pub fn new(child: impl Component + 'static) -> Self {
24 Self {
25 child: Some(Node::new(child)),
26 scroll_y: 0,
27 scroll_x: 0,
28 content_height: 0,
29 content_width: 0,
30 rect: Rect::default(),
31 }
32 }
33}
34
35impl Component for Scroll {
36 fn render(&self, cx: &mut RenderCx) {
37 if let Some(child) = &self.child {
38 let viewport = cx.rect;
39
40 let max_scroll_y = self.content_height.saturating_sub(viewport.height);
42 let max_scroll_x = self.content_width.saturating_sub(viewport.width);
43 let sy = self.scroll_y.min(max_scroll_y);
44 let sx = self.scroll_x.min(max_scroll_x);
45
46 fn offset_all(node: &Node, dx: i16, dy: i16) {
48 let mut r = node.rect();
49 if dx >= 0 { r.x = r.x.wrapping_add(dx as u16); }
50 else { r.x = r.x.wrapping_sub((-dx) as u16); }
51 if dy >= 0 { r.y = r.y.wrapping_add(dy as u16); }
52 else { r.y = r.y.wrapping_sub((-dy) as u16); }
53 node.set_rect(r);
54 node.component.for_each_child(&mut |c: &Node| offset_all(c, dx, dy));
55 }
56
57 offset_all(child, -(sx as i16), -(sy as i16));
58 child.render_with_clip(cx.buffer, cx.focused_id, Some(viewport));
59 offset_all(child, sx as i16, sy as i16);
60
61 self.render_scrollbar(cx, viewport, sy, max_scroll_y, sx, max_scroll_x);
63 }
64 }
65
66 fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
67 Size { width: constraint.max.width, height: constraint.max.height }
68 }
69
70 fn focusable(&self) -> bool { false }
71
72 fn event(&mut self, event: &Event, cx: &mut EventCx) {
73 if matches!(event, Event::Focus | Event::Blur) { return; }
74
75 if let Event::Key(key_event) = event {
76 let old_y = self.scroll_y;
77 let old_x = self.scroll_x;
78 match &key_event.key {
79 crate::event::Key::Up => { self.scroll_y = self.scroll_y.saturating_sub(1); }
80 crate::event::Key::Down => { self.scroll_y = self.scroll_y.saturating_add(1); }
81 crate::event::Key::Left => { self.scroll_x = self.scroll_x.saturating_sub(1); }
82 crate::event::Key::Right => { self.scroll_x = self.scroll_x.saturating_add(1); }
83 crate::event::Key::PageUp => {
84 let page = self.rect.height.max(1).saturating_sub(1);
85 self.scroll_y = self.scroll_y.saturating_sub(page);
86 }
87 crate::event::Key::PageDown => {
88 let page = self.rect.height.max(1).saturating_sub(1);
89 self.scroll_y = self.scroll_y.saturating_add(page);
90 }
91 _ => {}
92 }
93 if self.scroll_y != old_y || self.scroll_x != old_x {
94 cx.invalidate_paint();
95 return;
96 }
97 }
98
99 if let Event::Mouse(mouse_event) = event {
100 match mouse_event.kind {
101 crate::event::MouseKind::ScrollUp => {
102 self.scroll_y = self.scroll_y.saturating_sub(1);
103 cx.invalidate_paint();
104 return;
105 }
106 crate::event::MouseKind::ScrollDown => {
107 self.scroll_y = self.scroll_y.saturating_add(1);
108 cx.invalidate_paint();
109 return;
110 }
111 _ => {}
112 }
113 }
114
115 if cx.phase() == crate::event::EventPhase::Capture {
117 if let Some(child) = &mut self.child {
118 let mut child_cx = EventCx::with_task_sender(
119 &mut child.dirty, cx.global_dirty, cx.quit,
120 cx.phase, cx.propagation_stopped, cx.task_sender.clone(),
121 );
122 child.component.event(event, &mut child_cx);
123 }
124 }
125 }
126
127 fn layout(&mut self, rect: Rect, _cx: &mut LayoutCx) {
128 self.rect = rect;
129 if let Some(child) = &mut self.child {
130 let child_size = child.measure(Constraint::loose(65535, 65535));
131 self.content_height = child_size.height;
132 self.content_width = child_size.width;
133 let child_rect = Rect {
134 x: rect.x, y: rect.y,
135 width: child_size.width.max(rect.width),
136 height: child_size.height.max(rect.height),
137 };
138 child.layout(child_rect);
139 }
140 }
141
142 fn for_each_child(&self, f: &mut dyn FnMut(&Node)) {
143 if let Some(child) = &self.child { f(child); }
144 }
145 fn for_each_child_mut(&mut self, f: &mut dyn FnMut(&mut Node)) {
146 if let Some(child) = &mut self.child { f(child); }
147 }
148 fn style(&self) -> Style { Style::default() }
149}
150
151impl Scroll {
152 fn render_scrollbar(&self, cx: &mut RenderCx, vp: Rect, sy: u16, max_y: u16, sx: u16, max_x: u16) {
154 let bar_style = Style::default().bg(Color::Gray);
155
156 if max_y > 0 {
158 let thumb_h = ((vp.height as u64 * vp.height as u64) / (vp.height as u64 + max_y as u64)) as u16;
159 let thumb_h = thumb_h.max(1);
160 let thumb_y = if max_y > 0 {
161 vp.y.saturating_add((sy as u64 * (vp.height.saturating_sub(thumb_h)) as u64 / max_y as u64) as u16)
162 } else {
163 vp.y
164 };
165
166 for y in vp.y..vp.y.saturating_add(vp.height) {
167 let x = vp.x.saturating_add(vp.width.saturating_sub(1));
168 if y >= thumb_y && y < thumb_y.saturating_add(thumb_h) {
169 cx.buffer.write_text(Pos { x, y }, vp, "█", &bar_style);
170 } else if let Some(cell) = cx.buffer.get_mut(x, y) {
171 cell.style.bg = Some(Color::Black);
172 }
173 }
174 }
175
176 if max_x > 0 {
178 let thumb_w = ((vp.width as u64 * vp.width as u64) / (vp.width as u64 + max_x as u64)) as u16;
179 let thumb_w = thumb_w.max(1);
180 let thumb_x = if max_x > 0 {
181 vp.x.saturating_add((sx as u64 * (vp.width.saturating_sub(thumb_w)) as u64 / max_x as u64) as u16)
182 } else {
183 vp.x
184 };
185
186 for x in vp.x..vp.x.saturating_add(vp.width) {
187 let y = vp.y.saturating_add(vp.height.saturating_sub(1));
188 if x >= thumb_x && x < thumb_x.saturating_add(thumb_w) {
189 cx.buffer.write_text(Pos { x, y }, vp, "█", &bar_style);
190 } else if let Some(cell) = cx.buffer.get_mut(x, y) {
191 cell.style.bg = Some(Color::Black);
192 }
193 }
194 }
195 }
196}