1use crate::{
2 brush::Brush,
3 core::{algebra::Vector2, color::Color, math::Rect, pool::Handle, scope_profile},
4 define_constructor,
5 draw::{CommandTexture, Draw, DrawingContext},
6 message::{MessageDirection, UiMessage},
7 widget::{Widget, WidgetBuilder},
8 BuildContext, Control, UiNode, UserInterface,
9};
10use std::{
11 any::{Any, TypeId},
12 ops::{Deref, DerefMut},
13};
14
15#[derive(Debug, Clone, PartialEq)]
16pub enum ScrollPanelMessage {
17 VerticalScroll(f32),
18 HorizontalScroll(f32),
19 BringIntoView(Handle<UiNode>),
22}
23
24impl ScrollPanelMessage {
25 define_constructor!(ScrollPanelMessage:VerticalScroll => fn vertical_scroll(f32), layout: false);
26 define_constructor!(ScrollPanelMessage:HorizontalScroll => fn horizontal_scroll(f32), layout: false);
27 define_constructor!(ScrollPanelMessage:BringIntoView => fn bring_into_view(Handle<UiNode>), layout: true);
28}
29
30#[derive(Clone)]
32pub struct ScrollPanel {
33 widget: Widget,
34 scroll: Vector2<f32>,
35 vertical_scroll_allowed: bool,
36 horizontal_scroll_allowed: bool,
37}
38
39crate::define_widget_deref!(ScrollPanel);
40
41impl Control for ScrollPanel {
42 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
43 if type_id == TypeId::of::<Self>() {
44 Some(self)
45 } else {
46 None
47 }
48 }
49
50 fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
51 scope_profile!();
52
53 let size_for_child = Vector2::new(
54 if self.horizontal_scroll_allowed {
55 f32::INFINITY
56 } else {
57 available_size.x
58 },
59 if self.vertical_scroll_allowed {
60 f32::INFINITY
61 } else {
62 available_size.y
63 },
64 );
65
66 let mut desired_size = Vector2::default();
67
68 for child_handle in self.widget.children() {
69 ui.measure_node(*child_handle, size_for_child);
70
71 let child = ui.nodes.borrow(*child_handle);
72 let child_desired_size = child.desired_size();
73 if child_desired_size.x > desired_size.x {
74 desired_size.x = child_desired_size.x;
75 }
76 if child_desired_size.y > desired_size.y {
77 desired_size.y = child_desired_size.y;
78 }
79 }
80
81 desired_size
82 }
83
84 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
85 scope_profile!();
86
87 let mut children_size = Vector2::<f32>::default();
88 for child_handle in self.widget.children() {
89 let desired_size = ui.node(*child_handle).desired_size();
90 children_size.x = children_size.x.max(desired_size.x);
91 children_size.y = children_size.y.max(desired_size.y);
92 }
93
94 let child_rect = Rect::new(
95 -self.scroll.x,
96 -self.scroll.y,
97 if self.horizontal_scroll_allowed {
98 children_size.x.max(final_size.x)
99 } else {
100 final_size.x
101 },
102 if self.vertical_scroll_allowed {
103 children_size.y.max(final_size.y)
104 } else {
105 final_size.y
106 },
107 );
108
109 for child_handle in self.widget.children() {
110 ui.arrange_node(*child_handle, &child_rect);
111 }
112
113 final_size
114 }
115
116 fn draw(&self, drawing_context: &mut DrawingContext) {
117 drawing_context.push_rect_filled(&self.widget.screen_bounds(), None);
119 drawing_context.commit(
120 self.clip_bounds(),
121 Brush::Solid(Color::TRANSPARENT),
122 CommandTexture::None,
123 None,
124 );
125 }
126
127 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
128 self.widget.handle_routed_message(ui, message);
129
130 if message.destination() == self.handle() {
131 if let Some(msg) = message.data::<ScrollPanelMessage>() {
132 match *msg {
133 ScrollPanelMessage::VerticalScroll(scroll) => {
134 self.scroll.y = scroll;
135 self.invalidate_arrange();
136 }
137 ScrollPanelMessage::HorizontalScroll(scroll) => {
138 self.scroll.x = scroll;
139 self.invalidate_arrange();
140 }
141 ScrollPanelMessage::BringIntoView(handle) => {
142 if let Some(node_to_focus_ref) = ui.try_get_node(handle) {
143 let size = node_to_focus_ref.actual_size();
144 let mut parent = handle;
145 let mut relative_position = Vector2::default();
146 while parent.is_some() && parent != self.handle {
147 let node = ui.node(parent);
148 relative_position += node.actual_local_position();
149 parent = node.parent();
150 }
151 if relative_position.x < 0.0
154 || relative_position.y < 0.0
155 || relative_position.x > self.actual_size().x
156 || relative_position.y > self.actual_size().y
157 || (relative_position.x + size.x) > self.actual_size().x
158 || (relative_position.y + size.y) > self.actual_size().y
159 {
160 relative_position += self.scroll;
161 if parent == self.handle {
164 if self.vertical_scroll_allowed {
165 ui.send_message(ScrollPanelMessage::vertical_scroll(
166 self.handle,
167 MessageDirection::ToWidget,
168 relative_position.y,
169 ));
170 }
171 if self.horizontal_scroll_allowed {
172 ui.send_message(ScrollPanelMessage::horizontal_scroll(
173 self.handle,
174 MessageDirection::ToWidget,
175 relative_position.x,
176 ));
177 }
178 }
179 }
180 }
181 }
182 }
183 }
184 }
185 }
186}
187
188impl ScrollPanel {
189 pub fn new(widget: Widget) -> Self {
190 Self {
191 widget,
192 scroll: Default::default(),
193 vertical_scroll_allowed: true,
194 horizontal_scroll_allowed: false,
195 }
196 }
197}
198
199pub struct ScrollPanelBuilder {
200 widget_builder: WidgetBuilder,
201 vertical_scroll_allowed: Option<bool>,
202 horizontal_scroll_allowed: Option<bool>,
203}
204
205impl ScrollPanelBuilder {
206 pub fn new(widget_builder: WidgetBuilder) -> Self {
207 Self {
208 widget_builder,
209 vertical_scroll_allowed: None,
210 horizontal_scroll_allowed: None,
211 }
212 }
213
214 pub fn with_vertical_scroll_allowed(mut self, value: bool) -> Self {
215 self.vertical_scroll_allowed = Some(value);
216 self
217 }
218
219 pub fn with_horizontal_scroll_allowed(mut self, value: bool) -> Self {
220 self.horizontal_scroll_allowed = Some(value);
221 self
222 }
223
224 pub fn build(self, ui: &mut BuildContext) -> Handle<UiNode> {
225 ui.add_node(UiNode::new(ScrollPanel {
226 widget: self.widget_builder.build(),
227 scroll: Vector2::default(),
228 vertical_scroll_allowed: self.vertical_scroll_allowed.unwrap_or(true),
229 horizontal_scroll_allowed: self.horizontal_scroll_allowed.unwrap_or(false),
230 }))
231 }
232}