1use std::rc::Rc;
2
3use fltk::group::Scroll;
4use fltk::prelude::{GroupExt, WidgetBase};
5
6use crate::{LayoutElement, Size};
7
8pub struct Scrollable<G: GroupExt + Clone = Scroll> {
9 props: ScrollableProperties<G>,
10 child: Rc<dyn LayoutElement>,
11}
12
13pub struct ScrollableBuilder<G: GroupExt + Clone = Scroll> {
14 props: ScrollableProperties<G>,
15}
16
17struct ScrollableProperties<G: GroupExt + Clone> {
18 group: G,
19 mode: ScrollMode,
20 horz_gap: i32,
21 vert_gap: i32,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ScrollMode {
26 Vertical,
27 Horizontal,
28 Both,
29}
30
31impl<G: GroupExt + Clone> LayoutElement for Scrollable<G> {
32 fn min_size(&self) -> Size {
33 let scrollbar_size = fltk::app::scrollbar_size();
34 let mut min_size = self.child.min_size();
35 min_size.width += scrollbar_size + self.props.horz_gap;
36 min_size.height += scrollbar_size + self.props.vert_gap;
37 if self.props.mode != ScrollMode::Vertical {
38 min_size.width = scrollbar_size + self.props.horz_gap;
39 }
40 if self.props.mode != ScrollMode::Horizontal {
41 min_size.height = scrollbar_size + self.props.vert_gap;
42 }
43 min_size
44 }
45
46 fn layout(&self, x: i32, y: i32, width: i32, height: i32) {
47 self.props.group.clone().resize(x, y, width, height);
48 self.layout_children();
49 }
50}
51
52impl Scrollable {
53 pub fn builder() -> ScrollableBuilder {
54 ScrollableBuilder::new(Scroll::default_fill())
55 }
56}
57
58impl<G: GroupExt + Clone> Scrollable<G> {
59 pub fn group(&self) -> G {
60 self.props.group.clone()
61 }
62
63 pub fn layout_children(&self) {
64 let x = self.props.group.x();
65 let y = self.props.group.y();
66 let mut width = self.props.group.width();
67 let mut height = self.props.group.height();
68
69 let scrollbar_size = fltk::app::scrollbar_size();
70 let child_min_size = self.child.min_size();
71
72 let horz_scroll = width < child_min_size.width;
73 let vert_scroll = height < child_min_size.height;
74
75 if horz_scroll {
76 height -= scrollbar_size + self.props.vert_gap;
77 }
78 if vert_scroll {
79 width -= scrollbar_size + self.props.horz_gap;
80 }
81
82 width = std::cmp::max(width, child_min_size.width);
83 height = std::cmp::max(height, child_min_size.height);
84
85 self.child.layout(x, y, width, height);
86 }
87
88 fn new(props: ScrollableProperties<G>, child: Rc<dyn LayoutElement>) -> Self {
89 Self { props, child }
90 }
91}
92impl<G: GroupExt + Clone> ScrollableBuilder<G> {
93 pub fn new(group: G) -> Self {
94 Self {
95 props: ScrollableProperties {
96 group,
97 mode: ScrollMode::Vertical,
98 horz_gap: 0,
99 vert_gap: 0,
100 },
101 }
102 }
103
104 pub fn with_mode(mut self, mode: ScrollMode) -> Self {
105 self.props.mode = mode;
106 self
107 }
108
109 pub fn with_horz_gap(mut self, gap: i32) -> Self {
110 self.props.horz_gap = gap;
111 self
112 }
113
114 pub fn with_vert_gap(mut self, gap: i32) -> Self {
115 self.props.vert_gap = gap;
116 self
117 }
118
119 pub fn with_gap(mut self, horz: i32, vert: i32) -> Self {
120 self.props.horz_gap = horz;
121 self.props.vert_gap = vert;
122 self
123 }
124
125 pub fn add<E: LayoutElement + 'static>(self, element: E) -> Scrollable<G> {
126 self.add_shared(Rc::new(element))
127 }
128
129 pub fn add_shared(self, element: Rc<dyn LayoutElement>) -> Scrollable<G> {
130 self.props.group.end();
131 Scrollable::new(self.props, element)
132 }
133}