1use super::*;
2use crate::canvas::*;
3
4use std::{
5 cell::RefCell,
6 rc::{Rc, Weak},
7};
8
9use crossterm::event::{KeyCode, KeyModifiers};
10
11pub type StackpanelSelectionChangedCallback = dyn Fn(&mut Stackpanel);
12pub type StackpanelKeyHandler = fn(&mut Stackpanel, event: &mut WindowEvent);
13
14#[derive(Clone)]
15struct Child {
16 rc: WindowRef,
17 offset: usize,
19 len: usize,
20}
21
22impl Child {
23 fn new(rc: WindowRef) -> Self {
24 Self {
25 rc,
26 offset: usize::MIN,
27 len: usize::MIN,
28 }
29 }
30}
31
32struct Children {
33 items: Vec<Child>,
34 focus: usize,
35 alignment: Alignment,
36}
37
38impl Default for Children {
39 fn default() -> Self {
40 Self {
41 items: Vec::default(),
42 focus: usize::MIN,
43 alignment: Alignment::Center,
44 }
45 }
46}
47
48pub struct Stackpanel {
69 orientation: Orientation,
70 margin: u8,
71 children: Children,
72 base: WidgetBase,
73 scroll: ScrollBase,
74 callback: Rc<StackpanelSelectionChangedCallback>,
75 key_handler: StackpanelKeyHandler,
76}
77
78impl Default for Stackpanel {
79 fn default() -> Self {
80 Self::new(Orientation::Vertical)
81 }
82}
83
84impl Stackpanel {
85 pub fn default_callback(_: &mut Self) {}
86
87 pub fn default_key_handler(&mut self, event: &mut WindowEvent) {
88 if let Event::Key(k) = event.raw() {
89 let scroll_mode =
90 k.modifiers.contains(KeyModifiers::CONTROL) && self.has_scroll_viewer();
91 match self.orientation {
92 Orientation::Horizontal => match k.code {
93 KeyCode::Left => {
94 self.scroll(Direction::Left, ScrollKind::Line, scroll_mode);
95 event.handled = true;
96 }
97 KeyCode::Right => {
98 self.scroll(Direction::Right, ScrollKind::Line, scroll_mode);
99 event.handled = true;
100 }
101 KeyCode::PageUp => {
102 self.scroll(Direction::Left, ScrollKind::Page, scroll_mode);
103 event.handled = true;
104 }
105 KeyCode::PageDown => {
106 self.scroll(Direction::Right, ScrollKind::Page, scroll_mode);
107 event.handled = true;
108 }
109 _ => {}
110 },
111 Orientation::Vertical => match k.code {
112 KeyCode::Up => {
113 self.scroll(Direction::Up, ScrollKind::Line, scroll_mode);
114 event.handled = true;
115 }
116 KeyCode::Down => {
117 self.scroll(Direction::Down, ScrollKind::Line, scroll_mode);
118 event.handled = true;
119 }
120 KeyCode::PageUp => {
121 self.scroll(Direction::Up, ScrollKind::Page, scroll_mode);
122 event.handled = true;
123 }
124 KeyCode::PageDown => {
125 self.scroll(Direction::Down, ScrollKind::Page, scroll_mode);
126 event.handled = true;
127 }
128 _ => {}
129 },
130 }
131 }
132 }
133
134 pub fn new(orientation: Orientation) -> Self {
136 Self {
137 orientation,
138 margin: 1,
139 children: Children::default(),
140 base: WidgetBase::default(),
141 scroll: ScrollBase::default(),
142 callback: Rc::new(Self::default_callback),
143 key_handler: Self::default_key_handler,
144 }
145 }
146
147 pub fn set_selection_changed_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
148 self.callback = Rc::new(callback);
149 }
150
151 pub fn set_key_handler(&mut self, f: StackpanelKeyHandler) {
152 self.key_handler = f;
153 }
154
155 pub fn set_item_alignment(&mut self, alignment: Alignment) {
157 self.children.alignment = alignment;
158 self.provoke_changed_property(WindowProperty::Children);
159 }
160
161 pub fn set_orientation(&mut self, orientation: Orientation) {
162 self.orientation = orientation;
163 self.provoke_changed_property(WindowProperty::Children);
164 }
165
166 pub fn set_focus(&mut self, item: &Rc<RefCell<dyn Window>>) {
168 let uid = item.borrow().uid();
169 self.children.focus = self
170 .children
171 .items
172 .iter()
173 .position(|x| x.rc.borrow().uid() == uid)
174 .unwrap_or_default();
175 self.provoke_changed_property(WindowProperty::Focus);
176 }
177
178 pub fn set_child_margin(&mut self, margin: u8) {
180 self.margin = margin;
181 }
182
183 fn is_selection_visible(&self) -> bool {
184 let idx = self.orientation as usize;
185 let focus = &self.children.items[self.children.focus];
186 focus.offset >= self.scroll.offset[idx]
187 && (focus.offset + focus.len)
188 < (self.scroll.offset[idx] + self.scroll.render_size[idx] as usize)
189 }
190
191 fn make_selection_visible(&mut self) {
192 let child = &self.children.items[self.children.focus];
193 let render_size = self.scroll.render_size[self.orientation as usize];
194 if let Some(new_offset) = self
195 .children
196 .items
197 .iter()
198 .map(|x| x.offset)
199 .find(|x| x + render_size as usize >= child.offset + child.len)
200 {
201 self.scroll.set_offset(self.orientation, new_offset);
202 self.provoke_changed_property(WindowProperty::Children);
203 }
204 }
205
206 pub fn scroll(&mut self, direction: Direction, kind: ScrollKind, scroll_only: bool) {
208 match kind {
209 ScrollKind::Line => match direction {
210 Direction::Left | Direction::Up => self.move_previous(scroll_only),
211 Direction::Right | Direction::Down => self.move_next(scroll_only),
212 },
213 ScrollKind::Page => match scroll_only {
214 true => self.scroll.scroll(direction, kind),
215 false => {
216 let delta = self.scroll.render_size[self.orientation as usize] as usize;
217 let min_offset = match direction {
218 Direction::Left | Direction::Up => self.scroll.offset
219 [self.orientation as usize]
220 .checked_sub(delta)
221 .unwrap_or_default(),
222 Direction::Right | Direction::Down => {
223 self.scroll.offset[self.orientation as usize] + delta
224 }
225 };
226 if let Some(index) = self
227 .children
228 .items
229 .iter()
230 .position(|x| x.offset >= min_offset)
231 {
232 self.children.focus = index;
233 self.provoke_changed_property(WindowProperty::Focus);
234
235 self.selection_changed();
236
237 self.scroll.set_offset(
238 self.orientation,
239 self.children.items[self.children.focus].offset,
240 );
241 self.provoke_changed_property(WindowProperty::Children);
242 }
243 }
244 },
245 }
246 }
247
248 fn move_next(&mut self, scroll_only: bool) {
249 match scroll_only {
250 true => {
251 if let Some(new_offset) = self
252 .children
253 .items
254 .iter()
255 .map(|x| x.offset)
256 .find(|x| *x > self.scroll.offset[self.orientation as usize])
257 {
258 self.scroll.set_offset(self.orientation, new_offset);
259 self.provoke_changed_property(WindowProperty::Children);
260 }
261 }
262 false => {
263 if self.children.focus < self.children.items.len() - 1 {
264 if let Some(idx) = self.children.items[self.children.focus + 1..]
265 .iter()
266 .position(|x| x.rc.borrow_mut().is_enabled())
267 {
268 if !self.is_selection_visible() {
269 self.make_selection_visible();
270 }
271
272 self.children.focus = self.children.focus + 1 + idx;
273 self.provoke_changed_property(WindowProperty::Focus);
274
275 self.selection_changed();
276
277 if self.has_scroll_viewer() {
278 let idx = self.orientation as usize;
279 let max =
280 self.scroll.offset[idx] + self.scroll.render_size[idx] as usize;
281 let child = &self.children.items[self.children.focus];
282 if child.offset >= max || child.offset + child.len >= max {
283 let render_size =
284 self.scroll.render_size[self.orientation as usize];
285 if let Some(new_offset) =
286 self.children.items.iter().map(|x| x.offset).find(|x| {
287 x + render_size as usize >= child.offset + child.len
288 }) {
289 self.scroll.set_offset(self.orientation, new_offset);
290 self.provoke_changed_property(WindowProperty::Children);
291 }
292 }
293 }
294 }
295 }
296 }
297 }
298 }
299
300 fn move_previous(&mut self, scroll_only: bool) {
301 match scroll_only {
302 true => {
303 if let Some(new_offset) = self
304 .children
305 .items
306 .iter()
307 .rev()
308 .map(|x| x.offset)
309 .find(|x| *x < self.scroll.offset[self.orientation as usize])
310 {
311 self.scroll.set_offset(self.orientation, new_offset);
312 self.provoke_changed_property(WindowProperty::Children);
313 }
314 }
315 false => {
316 if self.children.focus > usize::MIN {
317 if let Some(idx) = self.children.items[..self.children.focus]
318 .iter()
319 .rev()
320 .position(|x| x.rc.borrow_mut().is_enabled())
321 {
322 if !self.is_selection_visible() {
323 self.make_selection_visible();
324 }
325
326 self.children.focus = (self.children.focus - 1) - idx;
327 self.provoke_changed_property(WindowProperty::Focus);
328
329 self.selection_changed();
330
331 if self.has_scroll_viewer() {
332 let idx = self.orientation as usize;
333 let min = self.scroll.offset[idx];
334 let offset = self.children.items[self.children.focus].offset;
335 if offset < min {
336 self.scroll.set_offset(self.orientation, offset);
337 self.provoke_changed_property(WindowProperty::Children);
338 }
339 }
340 }
341 }
342 }
343 }
344 }
345
346 pub fn has_scroll_viewer(&self) -> bool {
347 self.scroll.scroll_viewer.strong_count() > usize::MIN
348 }
349
350 fn retrieve_children(&mut self, builder: &mut SubWindowBuilder) {
351 let has_sv = self.has_scroll_viewer();
352 let base_rect = builder.base_rect();
353 self.scroll.viewport = Default::default();
354 self.scroll.render_size = base_rect.size;
355
356 let full_size = match self.orientation {
357 Orientation::Horizontal => base_rect
358 .size
359 .x
360 .checked_sub(self.children.items.len() as TSize - 1)
361 .unwrap_or(base_rect.size.x),
362 Orientation::Vertical => base_rect
363 .size
364 .y
365 .checked_sub(self.children.items.len() as TSize - 1)
366 .unwrap_or(base_rect.size.y),
367 };
368 let fixed_size = match full_size
369 .checked_div(self.children.items.len() as TSize)
370 .unwrap_or_default()
371 {
372 TSize::MIN => 1,
373 v => v,
374 };
375
376 let item_count = self.children.items.len();
377
378 match self.orientation {
379 Orientation::Horizontal => {
380 for (n, item) in self.children.items.iter_mut().enumerate() {
381 let brw = item.rc.borrow();
382 let tn = brw.border().thickness();
383 let size = brw.desired_size(Vector2D::new(base_rect.size.x, base_rect.size.y));
384 let len = match has_sv {
385 true => size.x + tn.width(),
386 false => fixed_size,
387 } as usize;
388 let start = match self.children.alignment {
389 Alignment::LowerBound => TSize::MIN,
390 Alignment::Center => (base_rect.size.y - size.y) / 2,
391 Alignment::HigherBound => base_rect.size.y - size.y,
392 };
393 if self.scroll.viewport.x >= self.scroll.offset.x {
394 match base_rect.subrect(
395 (self.scroll.viewport.x - self.scroll.offset.x) as TSize,
396 start,
397 len as TSize,
398 size.y,
399 ) {
400 Ok(rect) => {
401 builder.add_rect(rect).unwrap();
402 builder.add_child(item.rc.clone());
403 }
404 Err(_) => {
405 if !has_sv {
406 self.scroll.viewport.x += len;
407 break;
408 }
409 }
410 }
411 }
412 item.offset = self.scroll.viewport.y;
413 item.len = len;
414 self.scroll.viewport.x += len;
415 if n < item_count - 1 {
416 self.scroll.viewport.x += self.margin as usize;
417 }
418 }
419 self.scroll.viewport.y = base_rect.size.y as usize;
420 }
421 Orientation::Vertical => {
422 for (n, item) in self.children.items.iter_mut().enumerate() {
423 let brw = item.rc.borrow();
424 let tn = brw.border().thickness();
425 let size = brw.desired_size(Vector2D::new(base_rect.size.x, base_rect.size.y));
426 let len = match has_sv {
427 true => size.y + tn.height(),
428 false => fixed_size,
429 } as usize;
430 let start = match self.children.alignment {
431 Alignment::LowerBound => TSize::MIN,
432 Alignment::Center => (base_rect.size.x - size.x) / 2,
433 Alignment::HigherBound => base_rect.size.x - size.x,
434 };
435 if self.scroll.viewport.y >= self.scroll.offset.y {
436 match base_rect.subrect(
437 start,
438 (self.scroll.viewport.y - self.scroll.offset.y) as TSize,
439 size.x,
440 len as TSize,
441 ) {
442 Ok(rect) => {
443 builder.add_rect(rect).unwrap();
444 builder.add_child(item.rc.clone());
445 }
446 Err(_) => {
447 if !has_sv {
448 self.scroll.viewport.y += len;
449 break;
450 }
451 }
452 }
453 }
454 item.offset = self.scroll.viewport.y;
455 item.len = len;
456 self.scroll.viewport.y += len;
457 if n < item_count - 1 {
459 self.scroll.viewport.y += self.margin as usize;
460 }
461 }
462 self.scroll.viewport.x = base_rect.size.x as usize;
463 }
464 }
465 }
466
467 fn selection_changed(&mut self) {
468 let cb = self.callback.clone();
469 cb(self);
470 }
471}
472
473impl Scrollable for Stackpanel {
474 fn is_scrollable(&self, axis: Orientation) -> bool {
475 self.scroll.is_scrollable(axis)
476 }
477
478 fn scroll(&mut self, direction: Direction, kind: ScrollKind) {
479 self.scroll(direction, kind, false);
480 }
481
482 fn set_offset(&mut self, direction: Orientation, offset: usize) {
483 self.scroll.set_offset(direction, offset);
484 }
485
486 fn offset(&self, axis: Orientation) -> usize {
487 self.scroll.offset[axis as usize]
488 }
489
490 fn viewport(&self, axis: Orientation) -> usize {
491 self.scroll.viewport[axis as usize]
492 }
493
494 fn set_scroll_viewer(&mut self, scroll_viewer: Weak<RefCell<ScrollViewer>>) {
495 self.scroll.set_scroll_viewer(scroll_viewer);
496 self.provoke_changed_property(WindowProperty::Children);
497 }
498}
499
500impl ItemCollection<WindowRef> for Stackpanel {
501 type Index = usize;
502
503 fn add_item(&mut self, item: WindowRef) {
505 self.children.items.push(Child::new(item));
506 self.provoke_changed_property(WindowProperty::Children);
507 }
508
509 fn remove_item(&mut self, item: &WindowRef) {
511 let uid = item.borrow().uid();
512 if let Some(index) = self
513 .children
514 .items
515 .iter()
516 .position(|x| x.rc.borrow().uid() == uid)
517 {
518 self.children.items.remove(index);
519 if self.children.focus == index {
520 self.children.focus = usize::MIN;
521 self.selection_changed();
522 self.provoke_changed_property(WindowProperty::Focus);
523 }
524 self.provoke_changed_property(WindowProperty::Children);
525 }
526 }
527
528 fn remove_at(&mut self, index: Self::Index) -> WindowRef {
529 self.children.items.remove(index).rc
530 }
531
532 fn get_item(&mut self, index: Self::Index) -> Option<&WindowRef> {
534 match self.children.items.get(index) {
535 Some(item) => Some(&item.rc),
536 None => None,
537 }
538 }
539
540 fn clear_items(&mut self) {
542 if !self.children.items.is_empty() {
543 self.children.items.clear();
544 self.provoke_changed_property(WindowProperty::Children);
545 self.provoke_changed_property(WindowProperty::Focus);
546 self.selection_changed();
547 }
548 }
549
550 fn items(&self) -> Self::Index {
552 self.children.items.len()
553 }
554}
555
556impl Window for Stackpanel {
557 fn render(&self, canvas: &mut Canvas) {
558 if let Some(color) = self.base.colors.disabled
559 && !self.base.enabled
560 {
561 canvas.fill_foreground(color);
562 }
563 }
564
565 fn children(&mut self, mut builder: SubWindowBuilder) -> SubWindows {
566 if self.children.items.is_empty() {
567 return SubWindows::default();
568 }
569
570 builder.disable_overlapping();
571 self.retrieve_children(&mut builder);
572 builder.build().unwrap()
573 }
574
575 fn handle_event(&mut self, event: &mut WindowEvent) {
576 (self.key_handler)(self, event);
577 }
578
579 fn focus(&self) -> Option<WindowRef> {
580 match self.base.enabled {
581 true => self
582 .children
583 .items
584 .get(self.children.focus)
585 .cloned()
586 .map(|x| x.rc),
587 false => None,
588 }
589 }
590
591 fn is_enabled(&self) -> bool {
592 self.base.enabled
593 }
594}
595
596impl WindowLayout for Stackpanel {
597 fn desired_size(&self, available_size: TPoint) -> TPoint {
598 self.base.desired_size(available_size)
599 }
600
601 fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
602 self.base.alignment
603 }
604
605 fn border(&self) -> BorderStyle {
606 self.base.border
607 }
608
609 fn is_visible(&self) -> bool {
610 self.base.visibility
611 }
612
613 fn margin(&self) -> Thickness {
614 self.base.margin
615 }
616}
617
618impl HasWindowUID for Stackpanel {
619 fn uid(&self) -> WindowUID {
620 self.base.uid
621 }
622}
623
624impl Widget for Stackpanel {
625 fn set_visibility(&mut self, visibility: bool) {
626 self.provoke_changed_property(WindowProperty::IsVisible);
627 self.base.visibility = visibility;
628 }
629
630 fn set_width(&mut self, width: Size) {
631 self.base.size.x = width;
632 self.provoke_changed_property(WindowProperty::Size);
633 }
634
635 fn set_height(&mut self, height: Size) {
636 self.base.size.y = height;
637 self.provoke_changed_property(WindowProperty::Size);
638 }
639
640 fn set_margin(&mut self, margin: Thickness) {
641 self.base.margin = margin;
642 self.provoke_changed_property(WindowProperty::Margin);
643 }
644
645 fn set_border(&mut self, border: BorderStyle) {
646 self.base.border = border;
647 self.provoke_changed_property(WindowProperty::Border);
648 }
649
650 fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
651 self.base.alignment = (horizontal, vertical);
652 self.provoke_changed_property(WindowProperty::Alignment);
653 }
654
655 fn set_enabled_state(&mut self, is_enabled: bool) {
656 self.base.enabled = is_enabled;
657 self.provoke_changed_property(WindowProperty::Children);
658 }
659
660 fn set_width_constraint(&mut self, width: SizeConstraint) {
661 self.base.constraints.x = width;
662 self.provoke_changed_property(WindowProperty::Size);
663 }
664
665 fn set_height_constraint(&mut self, height: SizeConstraint) {
666 self.base.constraints.y = height;
667 self.provoke_changed_property(WindowProperty::Size);
668 }
669}
670
671impl WidgetColors for Stackpanel {
672 fn set_disabled_color(&mut self, color: Option<Color>) {
673 self.base.colors.disabled = color;
674 }
675
676 fn set_base_fg_color(&mut self, color: Option<Color>) {
677 self.base.colors.base_fg = color;
678 }
679
680 fn set_base_bg_color(&mut self, color: Option<Color>) {
681 self.base.colors.base_bg = color;
682 }
683}