1use std::{
2 cell::RefCell,
3 rc::{Rc, Weak},
4};
5
6use super::*;
7use crate::canvas::GlyphArray;
8
9pub trait Scrollable: Window {
11 fn is_scrollable(&self, axis: Orientation) -> bool;
13
14 fn scroll(&mut self, direction: Direction, kind: ScrollKind);
16
17 fn set_offset(&mut self, direction: Orientation, offset: usize);
19
20 fn offset(&self, axis: Orientation) -> usize;
22
23 fn viewport(&self, axis: Orientation) -> usize;
25
26 fn set_scroll_viewer(&mut self, scroll_viewer: Weak<RefCell<ScrollViewer>>);
28}
29
30#[derive(Clone, Debug, Default)]
33pub struct ScrollBase {
34 pub offset: Vector2D<usize>,
36 pub viewport: Vector2D<usize>,
38 pub render_size: TPoint,
40 pub scroll_viewer: Weak<RefCell<ScrollViewer>>,
42}
43
44impl ScrollBase {
45 pub fn is_scrollable(&self, axis: Orientation) -> bool {
46 let idx = axis as usize;
47 self.scroll_viewer.strong_count() > usize::MIN
48 && ((self.offset[idx] == usize::MIN
49 && self.viewport[idx] > self.render_size[idx] as usize)
50 || self.offset[idx] > usize::MIN)
51 }
52
53 pub fn scroll(&mut self, direction: Direction, kind: ScrollKind) {
54 let delta = match kind {
55 ScrollKind::Line => 1,
56 ScrollKind::Page => match direction {
57 Direction::Up | Direction::Down => self.render_size.y as usize,
58 Direction::Left | Direction::Right => self.render_size.x as usize,
59 },
60 };
61 match direction {
62 Direction::Up => {
63 if let Some(offset) = self.offset.y.checked_sub(delta) {
64 self.offset.y = offset;
65 }
66 else {
67 self.offset.y = usize::MIN;
68 }
69 }
70 Direction::Down => {
71 let offset = self.offset.y + delta;
72 if offset <= self.viewport.y - delta {
73 self.offset.y += delta;
74 }
75 else {
76 self.offset.y = self.viewport.y - delta;
77 }
78 }
79 Direction::Left => {
80 if let Some(offset) = self.offset.x.checked_sub(delta) {
81 self.offset.x -= offset;
82 }
83 else {
84 self.offset.x = usize::MIN
85 }
86 }
87 Direction::Right => {
88 let offset = self.offset.x + delta;
89 if offset <= self.viewport.x - delta {
90 self.offset.x += delta;
91 }
92 else {
93 self.offset.x = self.viewport.x - delta;
94 }
95 }
96 }
97 }
98
99 pub fn set_offset(&mut self, direction: Orientation, offset: usize) {
100 match direction {
101 Orientation::Horizontal => {
102 if offset < self.viewport.x {
103 self.offset.x = offset;
104 }
105 else {
106 self.offset.x = self.viewport.x.checked_sub(1).unwrap_or_default();
107 }
108 }
109 Orientation::Vertical => {
110 if offset < self.viewport.y {
111 self.offset.y = offset;
112 }
113 else {
114 self.offset.y = self.viewport.y.checked_sub(1).unwrap_or_default();
115 }
116 }
117 }
118 }
119
120 pub fn offset(&self, axis: Orientation) -> usize {
121 match axis {
122 Orientation::Horizontal => self.offset.x,
123 Orientation::Vertical => self.offset.y,
124 }
125 }
126
127 pub fn viewport(&self, axis: Orientation) -> usize {
128 match axis {
129 Orientation::Horizontal => self.viewport.x,
130 Orientation::Vertical => self.viewport.y,
131 }
132 }
133
134 pub fn set_scroll_viewer(&mut self, scroll_viewer: Weak<RefCell<ScrollViewer>>) {
135 self.scroll_viewer = scroll_viewer;
136 }
137}
138
139#[derive(Clone, Copy, Debug)]
140pub enum Direction {
141 Up,
142 Down,
143 Left,
144 Right,
145}
146
147#[derive(Copy, Clone, Debug)]
148pub enum ScrollKind {
149 Line,
150 Page,
151}
152
153#[derive(Default)]
167pub struct ScrollViewer {
168 base: WidgetBase,
169 draw_bars: Vector2D<bool>,
170 content: Option<Rc<RefCell<dyn Scrollable>>>,
171 show_scrollbar: bool,
172 size: Vector2D<TSize>,
173}
174
175impl ScrollViewer {
176 pub const ARROW_UP: Grapheme = Grapheme::new_unchecked("▲", GlyphWidth::Half);
177 pub const ARROW_UP_DISABLED: Grapheme = Grapheme::new_unchecked("△", GlyphWidth::Half);
178 pub const BAR_MARKER_V: Grapheme = Grapheme::new_unchecked("┃", GlyphWidth::Half);
179 pub const ARROW_DOWN: Grapheme = Grapheme::new_unchecked("▼", GlyphWidth::Half);
180 pub const ARROW_DOWN_DISABLED: Grapheme = Grapheme::new_unchecked("▽", GlyphWidth::Half);
181
182 pub const ARROW_LEFT: Grapheme = Grapheme::new_unchecked("◀", GlyphWidth::Half);
183 pub const ARROW_LEFT_DISABLED: Grapheme = Grapheme::new_unchecked("◁", GlyphWidth::Half);
184 pub const BAR_MARKER_H: Grapheme = Grapheme::new_unchecked("━", GlyphWidth::Half);
185 pub const ARROW_RIGHT: Grapheme = Grapheme::new_unchecked("▶", GlyphWidth::Half);
186 pub const ARROW_RIGHT_DISABLED: Grapheme = Grapheme::new_unchecked("▷", GlyphWidth::Half);
187
188 pub fn set_content(rc: &Rc<RefCell<Self>>, mut content: Option<&Rc<RefCell<dyn Scrollable>>>) {
189 if let Some(old) = rc.borrow_mut().content.as_mut() {
190 old.borrow_mut().set_scroll_viewer(Weak::new());
191 }
192
193 if let Some(cnt) = content.as_mut() {
194 cnt.borrow_mut().set_scroll_viewer(Rc::downgrade(rc));
195 }
196 let brw = &mut rc.borrow_mut();
197 brw.content = content.cloned();
198 brw.provoke_changed_property(WindowProperty::Children);
199 }
200
201 pub fn set_scrollbar_visibility(&mut self, show: bool) {
202 self.show_scrollbar = show;
203 self.reevaluate_showbars();
204 }
205
206 fn reevaluate_showbars(&mut self) {
207 if let Some(cnt) = self.content.as_ref() {
208 let brw = cnt.borrow();
209 let old = self.draw_bars;
210 self.draw_bars = Vector2D::new(
211 self.size.x > 2 && brw.is_scrollable(Orientation::Vertical) && self.show_scrollbar,
212 self.size.y > 2
213 && brw.is_scrollable(Orientation::Horizontal)
214 && self.show_scrollbar,
215 );
216 if old != self.draw_bars {
217 self.provoke_changed_property(WindowProperty::Children);
218 }
219 }
220 }
221
222 fn render_scroller(
223 &self,
224 array: &mut dyn GlyphArray,
225 size: TPoint,
226 content: &dyn Scrollable,
227 orientation: Orientation,
228 other_bar: bool,
229 ) {
230 let (arrow_prev, arrow_next, bar_marker);
231 let (arrow_prev_dis, arrow_next_dis);
232 let max_size;
233
234 match orientation {
235 Orientation::Horizontal => {
236 arrow_prev = Self::ARROW_LEFT;
237 arrow_prev_dis = Self::ARROW_LEFT_DISABLED;
238 arrow_next = Self::ARROW_RIGHT;
239 arrow_next_dis = Self::ARROW_RIGHT_DISABLED;
240 bar_marker = Self::BAR_MARKER_H;
241 max_size = size.x;
242 }
243 Orientation::Vertical => {
244 arrow_prev = Self::ARROW_UP;
245 arrow_prev_dis = Self::ARROW_UP_DISABLED;
246 arrow_next = Self::ARROW_DOWN;
247 arrow_next_dis = Self::ARROW_DOWN_DISABLED;
248 bar_marker = Self::BAR_MARKER_V;
249 max_size = size.y;
250 }
251 }
252
253 if max_size > 2 && content.is_scrollable(orientation) {
254 let offset = content.offset(orientation);
255 let viewport = content.viewport(orientation);
256
257 let mut arrow_up = array.get(TSize::MIN).unwrap();
258 if !self.base.enabled || offset == usize::MIN {
259 arrow_up.set_grapheme(arrow_prev_dis).ok();
260 if let Some(color) = self.base.colors.disabled {
261 arrow_up.set_fg(color);
262 }
263 }
264 else {
265 arrow_up.set_grapheme(arrow_prev).ok();
266 }
267 std::mem::drop(arrow_up);
268
269 let render_size = max_size - other_bar as TSize;
270 if max_size >= 2 {
271 let length = size[orientation as usize] - 2 - other_bar as TSize;
272 let start = (offset * length as usize + 1).div_ceil(viewport) as TSize;
273 let end = if offset + render_size as usize > viewport {
274 length + 1
275 }
276 else {
277 ((offset + render_size as usize + 1) * length as usize).div_ceil(viewport)
278 as TSize
279 };
280
281 for idx in start..end + (start == end) as TSize {
282 let mut g = array.get(idx).unwrap();
283 g.set_grapheme(bar_marker).ok();
284 if let Some(color) = self.base.colors.disabled
285 && !self.base.enabled
286 {
287 g.set_fg(color);
288 }
289 }
290 }
291
292 let mut arrow_down = array.get(max_size - 1 - other_bar as TSize).unwrap();
293 if !self.base.enabled || offset + render_size as usize >= viewport {
294 arrow_down.set_grapheme(arrow_next_dis).ok();
295 if let Some(color) = self.base.colors.disabled {
296 arrow_down.set_fg(color);
297 }
298 }
299 else {
300 arrow_down.set_grapheme(arrow_next).ok();
301 }
302 }
303 }
304
305
306 pub fn is_scrollable(&self, axis: Orientation) -> bool {
308 match self.content.as_ref() {
309 Some(sc) => sc.borrow().is_scrollable(axis),
310 None => false,
311 }
312 }
313
314 pub fn scroll(&mut self, direction: Direction, kind: ScrollKind) {
316 if let Some(sc) = self.content.as_ref() {
317 sc.borrow_mut().scroll(direction, kind);
318 }
319 }
320
321 pub fn set_offset(&mut self, direction: Orientation, offset: usize) {
323 if let Some(sc) = self.content.as_ref() {
324 sc.borrow_mut().set_offset(direction, offset);
325 }
326 }
327
328 pub fn offset(&self, axis: Orientation) -> usize {
330 match self.content.as_ref() {
331 Some(sc) => sc.borrow().offset(axis),
332 None => usize::MIN,
333 }
334 }
335
336 pub fn viewport(&self, axis: Orientation) -> usize {
338 match self.content.as_ref() {
339 Some(sc) => sc.borrow().viewport(axis),
340 None => usize::MIN,
341 }
342 }
343}
344
345impl Window for ScrollViewer {
346 fn render(&self, canvas: &mut crate::canvas::Canvas) {
347 if let Some(content) = self.content.as_ref() {
348 let brw = content.borrow();
349 let size = canvas.size();
350 if self.draw_bars.x {
351 self.render_scroller(
352 &mut canvas.get_column(size.x - 1, GlyphWidth::Half).unwrap(),
353 size,
354 &*brw,
355 Orientation::Vertical,
356 self.draw_bars.y,
357 );
358 }
359 if self.draw_bars.y {
360 self.render_scroller(
361 &mut canvas.get_row(size.y - 1, GlyphWidth::Half).unwrap(),
362 size,
363 &*brw,
364 Orientation::Horizontal,
365 self.draw_bars.x,
366 );
367 }
368 }
369 }
370
371 fn handle_event(&mut self, event: &mut WindowEvent) {
372 if let Event::Resize(w, h) = event.raw() {
373 self.size = Vector2D::new(*w, *h);
374 self.reevaluate_showbars();
375 }
376 }
377
378 fn children(&mut self, mut builder: SubWindowBuilder) -> super::SubWindows {
379 if let Some(ch) = self.content.clone() {
380 let rect = builder.base_rect();
381 builder
382 .add_pair(
383 ch,
384 Some(
385 rect.subrect(
386 0,
387 0,
388 rect.size.x - self.draw_bars.x as TSize,
389 rect.size.y - self.draw_bars.y as TSize,
390 )
391 .unwrap(),
392 ),
393 )
394 .unwrap();
395 }
396 builder.build().unwrap()
397 }
398
399 fn focus(&self) -> Option<WindowRef> {
400 self.content.clone().map(|x| x as Rc<RefCell<dyn Window>>)
401 }
402
403 fn is_enabled(&self) -> bool {
404 self.base.enabled
405 }
406}
407
408impl HasWindowUID for ScrollViewer {
409 fn uid(&self) -> super::WindowUID {
410 self.base.uid
411 }
412}
413
414impl WindowLayout for ScrollViewer {
415 fn desired_size(&self, available_size: TPoint) -> TPoint {
416 self.base.desired_size(available_size)
417 }
418
419 fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
420 self.base.alignment
421 }
422
423 fn border(&self) -> BorderStyle {
424 self.base.border
425 }
426
427 fn is_visible(&self) -> bool {
428 self.base.visibility
429 }
430
431 fn margin(&self) -> Thickness {
432 self.base.margin
433 }
434}
435
436impl Widget for ScrollViewer {
437 fn set_visibility(&mut self, visibility: bool) {
438 self.base.visibility = visibility;
439 self.provoke_changed_property(WindowProperty::IsVisible);
440 }
441
442 fn set_width(&mut self, width: Size) {
443 self.base.size.x = width;
444 self.provoke_changed_property(WindowProperty::Size);
445 }
446
447 fn set_height(&mut self, height: Size) {
448 self.base.size.y = height;
449 self.provoke_changed_property(WindowProperty::Size);
450 }
451
452 fn set_margin(&mut self, margin: Thickness) {
453 self.base.margin = margin;
454 self.provoke_changed_property(WindowProperty::Margin);
455 }
456
457 fn set_border(&mut self, border: BorderStyle) {
458 self.base.border = border;
459 self.provoke_changed_property(WindowProperty::Border);
460 }
461
462 fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
463 self.base.alignment = (horizontal, vertical);
464 self.provoke_changed_property(WindowProperty::Alignment);
465 }
466
467 fn set_enabled_state(&mut self, is_enabled: bool) {
468 self.base.enabled = is_enabled;
469 self.provoke_changed_property(WindowProperty::Children);
470 }
471
472 fn set_width_constraint(&mut self, width: SizeConstraint) {
473 self.base.constraints.x = width;
474 self.provoke_changed_property(WindowProperty::Size);
475 }
476
477 fn set_height_constraint(&mut self, height: SizeConstraint) {
478 self.base.constraints.y = height;
479 self.provoke_changed_property(WindowProperty::Size);
480 }
481}
482
483impl WidgetColors for ScrollViewer {
484 fn set_disabled_color(&mut self, color: Option<Color>) {
485 self.base.colors.disabled = color;
486 }
487
488 fn set_base_fg_color(&mut self, color: Option<Color>) {
489 self.base.colors.base_fg = color;
490 }
491
492 fn set_base_bg_color(&mut self, color: Option<Color>) {
493 self.base.colors.base_bg = color;
494 }
495}