1use crate::{
2 core::{algebra::Vector2, pool::Handle},
3 define_constructor,
4 grid::{Column, GridBuilder, Row},
5 message::{MessageDirection, UiMessage},
6 scroll_bar::{ScrollBar, ScrollBarBuilder, ScrollBarMessage},
7 scroll_panel::{ScrollPanelBuilder, ScrollPanelMessage},
8 widget::{Widget, WidgetBuilder, WidgetMessage},
9 BuildContext, Control, NodeHandleMapping, Orientation, UiNode, UserInterface,
10};
11use std::{
12 any::{Any, TypeId},
13 ops::{Deref, DerefMut},
14};
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum ScrollViewerMessage {
18 Content(Handle<UiNode>),
19 BringIntoView(Handle<UiNode>),
22}
23
24impl ScrollViewerMessage {
25 define_constructor!(ScrollViewerMessage:Content => fn content(Handle<UiNode>), layout: false);
26 define_constructor!(ScrollViewerMessage:BringIntoView=> fn bring_into_view(Handle<UiNode>), layout: true);
27}
28
29#[derive(Clone)]
30pub struct ScrollViewer {
31 pub widget: Widget,
32 pub content: Handle<UiNode>,
33 pub scroll_panel: Handle<UiNode>,
34 pub v_scroll_bar: Handle<UiNode>,
35 pub h_scroll_bar: Handle<UiNode>,
36}
37
38crate::define_widget_deref!(ScrollViewer);
39
40impl ScrollViewer {
41 pub fn new(
42 widget: Widget,
43 content: Handle<UiNode>,
44 content_presenter: Handle<UiNode>,
45 v_scroll_bar: Handle<UiNode>,
46 h_scroll_bar: Handle<UiNode>,
47 ) -> Self {
48 Self {
49 widget,
50 content,
51 scroll_panel: content_presenter,
52 v_scroll_bar,
53 h_scroll_bar,
54 }
55 }
56
57 pub fn content_presenter(&self) -> Handle<UiNode> {
58 self.scroll_panel
59 }
60
61 pub fn content(&self) -> Handle<UiNode> {
62 self.content
63 }
64
65 pub fn set_content(&mut self, content: Handle<UiNode>) {
66 self.content = content;
67 }
68}
69
70impl Control for ScrollViewer {
71 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
72 if type_id == TypeId::of::<Self>() {
73 Some(self)
74 } else {
75 None
76 }
77 }
78
79 fn resolve(&mut self, node_map: &NodeHandleMapping) {
80 node_map.resolve(&mut self.content);
81 node_map.resolve(&mut self.scroll_panel);
82 node_map.resolve(&mut self.v_scroll_bar);
83 node_map.resolve(&mut self.h_scroll_bar);
84 }
85
86 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
87 let size = self.widget.arrange_override(ui, final_size);
88
89 if self.content.is_some() {
90 let content_size = ui.node(self.content).desired_size();
91 let available_size_for_content = ui.node(self.scroll_panel).desired_size();
92
93 let x_max = (content_size.x - available_size_for_content.x).max(0.0);
94 ui.send_message(ScrollBarMessage::max_value(
95 self.h_scroll_bar,
96 MessageDirection::ToWidget,
97 x_max,
98 ));
99
100 let y_max = (content_size.y - available_size_for_content.y).max(0.0);
101 ui.send_message(ScrollBarMessage::max_value(
102 self.v_scroll_bar,
103 MessageDirection::ToWidget,
104 y_max,
105 ));
106 }
107
108 size
109 }
110
111 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
112 self.widget.handle_routed_message(ui, message);
113
114 if let Some(WidgetMessage::MouseWheel { amount, .. }) = message.data::<WidgetMessage>() {
115 if self.v_scroll_bar.is_some() && !message.handled() {
116 if let Some(v_scroll_bar) = ui.node(self.v_scroll_bar).cast::<ScrollBar>() {
117 let old_value = v_scroll_bar.value();
118 let new_value = old_value - amount * 17.0;
119 if (old_value - new_value).abs() > f32::EPSILON {
120 message.set_handled(true);
121 }
122 ui.send_message(ScrollBarMessage::value(
123 self.v_scroll_bar,
124 MessageDirection::ToWidget,
125 new_value,
126 ));
127 }
128 }
129 } else if let Some(msg) = message.data::<ScrollPanelMessage>() {
130 if message.destination() == self.scroll_panel {
131 let msg = match *msg {
132 ScrollPanelMessage::VerticalScroll(value) => ScrollBarMessage::value(
133 self.v_scroll_bar,
134 MessageDirection::ToWidget,
135 value,
136 ),
137 ScrollPanelMessage::HorizontalScroll(value) => ScrollBarMessage::value(
138 self.h_scroll_bar,
139 MessageDirection::ToWidget,
140 value,
141 ),
142 _ => return,
143 };
144 msg.set_handled(true);
146 ui.send_message(msg);
147 }
148 } else if let Some(msg) = message.data::<ScrollBarMessage>() {
149 if message.direction() == MessageDirection::FromWidget {
150 match msg {
151 ScrollBarMessage::Value(new_value) => {
152 if !message.handled() {
153 if message.destination() == self.v_scroll_bar
154 && self.v_scroll_bar.is_some()
155 {
156 ui.send_message(ScrollPanelMessage::vertical_scroll(
157 self.scroll_panel,
158 MessageDirection::ToWidget,
159 *new_value,
160 ));
161 } else if message.destination() == self.h_scroll_bar
162 && self.h_scroll_bar.is_some()
163 {
164 ui.send_message(ScrollPanelMessage::horizontal_scroll(
165 self.scroll_panel,
166 MessageDirection::ToWidget,
167 *new_value,
168 ));
169 }
170 }
171 }
172 &ScrollBarMessage::MaxValue(_) => {
173 if message.destination() == self.v_scroll_bar && self.v_scroll_bar.is_some()
174 {
175 if let Some(scroll_bar) = ui.node(self.v_scroll_bar).cast::<ScrollBar>()
176 {
177 let visibility = (scroll_bar.max_value() - scroll_bar.min_value())
178 .abs()
179 >= f32::EPSILON;
180 ui.send_message(WidgetMessage::visibility(
181 self.v_scroll_bar,
182 MessageDirection::ToWidget,
183 visibility,
184 ));
185 }
186 } else if message.destination() == self.h_scroll_bar
187 && self.h_scroll_bar.is_some()
188 {
189 if let Some(scroll_bar) = ui.node(self.h_scroll_bar).cast::<ScrollBar>()
190 {
191 let visibility = (scroll_bar.max_value() - scroll_bar.min_value())
192 .abs()
193 >= f32::EPSILON;
194 ui.send_message(WidgetMessage::visibility(
195 self.h_scroll_bar,
196 MessageDirection::ToWidget,
197 visibility,
198 ));
199 }
200 }
201 }
202 _ => (),
203 }
204 }
205 } else if let Some(msg) = message.data::<ScrollViewerMessage>() {
206 if message.destination() == self.handle() {
207 match msg {
208 ScrollViewerMessage::Content(content) => {
209 for child in ui.node(self.scroll_panel).children().to_vec() {
210 ui.send_message(WidgetMessage::remove(
211 child,
212 MessageDirection::ToWidget,
213 ));
214 }
215 ui.send_message(WidgetMessage::link(
216 *content,
217 MessageDirection::ToWidget,
218 self.scroll_panel,
219 ));
220 }
221 &ScrollViewerMessage::BringIntoView(handle) => {
222 ui.send_message(ScrollPanelMessage::bring_into_view(
224 self.scroll_panel,
225 MessageDirection::ToWidget,
226 handle,
227 ));
228 }
229 }
230 }
231 }
232 }
233}
234
235pub struct ScrollViewerBuilder {
236 widget_builder: WidgetBuilder,
237 content: Handle<UiNode>,
238 h_scroll_bar: Option<Handle<UiNode>>,
239 v_scroll_bar: Option<Handle<UiNode>>,
240 horizontal_scroll_allowed: bool,
241 vertical_scroll_allowed: bool,
242}
243
244impl ScrollViewerBuilder {
245 pub fn new(widget_builder: WidgetBuilder) -> Self {
246 Self {
247 widget_builder,
248 content: Handle::NONE,
249 h_scroll_bar: None,
250 v_scroll_bar: None,
251 horizontal_scroll_allowed: false,
252 vertical_scroll_allowed: true,
253 }
254 }
255
256 pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
257 self.content = content;
258 self
259 }
260
261 pub fn with_vertical_scroll_bar(mut self, v_scroll_bar: Handle<UiNode>) -> Self {
262 self.v_scroll_bar = Some(v_scroll_bar);
263 self
264 }
265
266 pub fn with_horizontal_scroll_bar(mut self, h_scroll_bar: Handle<UiNode>) -> Self {
267 self.h_scroll_bar = Some(h_scroll_bar);
268 self
269 }
270
271 pub fn with_vertical_scroll_allowed(mut self, value: bool) -> Self {
272 self.vertical_scroll_allowed = value;
273 self
274 }
275
276 pub fn with_horizontal_scroll_allowed(mut self, value: bool) -> Self {
277 self.horizontal_scroll_allowed = value;
278 self
279 }
280
281 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
282 let content_presenter = ScrollPanelBuilder::new(
283 WidgetBuilder::new()
284 .with_child(self.content)
285 .on_row(0)
286 .on_column(0),
287 )
288 .with_horizontal_scroll_allowed(self.horizontal_scroll_allowed)
289 .with_vertical_scroll_allowed(self.vertical_scroll_allowed)
290 .build(ctx);
291
292 let v_scroll_bar = self.v_scroll_bar.unwrap_or_else(|| {
293 ScrollBarBuilder::new(WidgetBuilder::new().with_width(22.0))
294 .with_orientation(Orientation::Vertical)
295 .build(ctx)
296 });
297 ctx[v_scroll_bar].set_row(0).set_column(1);
298
299 let h_scroll_bar = self.h_scroll_bar.unwrap_or_else(|| {
300 ScrollBarBuilder::new(WidgetBuilder::new().with_height(22.0))
301 .with_orientation(Orientation::Horizontal)
302 .build(ctx)
303 });
304 ctx[h_scroll_bar].set_row(1).set_column(0);
305
306 let sv = ScrollViewer {
307 widget: self
308 .widget_builder
309 .with_child(
310 GridBuilder::new(
311 WidgetBuilder::new()
312 .with_child(content_presenter)
313 .with_child(h_scroll_bar)
314 .with_child(v_scroll_bar),
315 )
316 .add_row(Row::stretch())
317 .add_row(Row::auto())
318 .add_column(Column::stretch())
319 .add_column(Column::auto())
320 .build(ctx),
321 )
322 .build(),
323 content: self.content,
324 v_scroll_bar,
325 h_scroll_bar,
326 scroll_panel: content_presenter,
327 };
328 ctx.add_node(UiNode::new(sv))
329 }
330}