1#![warn(missing_docs)]
26
27use crate::{
28 core::{
29 algebra::Vector2, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
30 uuid_provider, visitor::prelude::*,
31 },
32 grid::{Column, GridBuilder, Row},
33 message::{MessageDirection, UiMessage},
34 scroll_bar::{ScrollBar, ScrollBarBuilder, ScrollBarMessage},
35 scroll_panel::{ScrollPanelBuilder, ScrollPanelMessage},
36 widget::{Widget, WidgetBuilder, WidgetMessage},
37 BuildContext, Control, Orientation, UiNode, UserInterface,
38};
39use fyrox_core::pool::ObjectOrVariant;
40
41use crate::message::MessageData;
42use crate::scroll_panel::ScrollPanel;
43use fyrox_graph::{
44 constructor::{ConstructorProvider, GraphNodeConstructor},
45 SceneGraph,
46};
47
48#[derive(Debug, Clone, PartialEq)]
50pub enum ScrollViewerMessage {
51 Content(Handle<UiNode>),
53 BringIntoView(Handle<UiNode>),
55 VScrollSpeed(f32),
57 HScrollSpeed(f32),
59 ScrollToEnd,
61 VerticalScroll(f32),
63 HorizontalScroll(f32),
65}
66
67impl MessageData for ScrollViewerMessage {
68 fn need_perform_layout(&self) -> bool {
69 matches!(
70 self,
71 Self::BringIntoView(_)
72 | Self::VScrollSpeed(_)
73 | Self::HScrollSpeed(_)
74 | Self::ScrollToEnd
75 )
76 }
77}
78
79impl ScrollViewerMessage {
80 pub fn content(handle: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
82 Self::Content(handle.to_base())
83 }
84
85 pub fn bring_into_view(handle: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
87 Self::BringIntoView(handle.to_base())
88 }
89}
90
91#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
173#[reflect(derived_type = "UiNode")]
174pub struct ScrollViewer {
175 pub widget: Widget,
177 pub content: Handle<UiNode>,
179 pub scroll_panel: Handle<ScrollPanel>,
181 pub v_scroll_bar: Handle<ScrollBar>,
183 pub h_scroll_bar: Handle<ScrollBar>,
185 pub v_scroll_speed: f32,
187 pub h_scroll_speed: f32,
189}
190
191impl ConstructorProvider<UiNode, UserInterface> for ScrollViewer {
192 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
193 GraphNodeConstructor::new::<Self>()
194 .with_variant("Scroll Viewer", |ui| {
195 ScrollViewerBuilder::new(WidgetBuilder::new().with_name("Scroll Viewer"))
196 .build(&mut ui.build_ctx())
197 .to_base()
198 .into()
199 })
200 .with_group("Layout")
201 }
202}
203
204crate::define_widget_deref!(ScrollViewer);
205
206uuid_provider!(ScrollViewer = "173e869f-7da0-4ae2-915a-5d545d8150cc");
207
208impl Control for ScrollViewer {
209 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
210 let size = self.widget.arrange_override(ui, final_size);
211
212 if self.content.is_some() {
213 let content_size = ui.node(self.content).desired_size();
214 let available_size_for_content = ui[self.scroll_panel].desired_size();
215
216 let x_max = (content_size.x - available_size_for_content.x).max(0.0);
217 let x_size_ratio = if content_size.x > f32::EPSILON {
218 (available_size_for_content.x / content_size.x).min(1.0)
219 } else {
220 1.0
221 };
222 ui.send(self.h_scroll_bar, ScrollBarMessage::MaxValue(x_max));
223 ui.send(self.h_scroll_bar, ScrollBarMessage::SizeRatio(x_size_ratio));
224
225 let y_max = (content_size.y - available_size_for_content.y).max(0.0);
226 let y_size_ratio = if content_size.y > f32::EPSILON {
227 (available_size_for_content.y / content_size.y).min(1.0)
228 } else {
229 1.0
230 };
231 ui.send(self.v_scroll_bar, ScrollBarMessage::MaxValue(y_max));
232 ui.send(self.v_scroll_bar, ScrollBarMessage::SizeRatio(y_size_ratio));
233 }
234
235 size
236 }
237
238 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
239 self.widget.handle_routed_message(ui, message);
240
241 if let Some(WidgetMessage::MouseWheel { amount, .. }) = message.data::<WidgetMessage>() {
242 if !message.handled() {
243 let (scroll_bar, scroll_speed) = if ui.keyboard_modifiers().shift {
244 (self.h_scroll_bar, self.h_scroll_speed)
245 } else {
246 (self.v_scroll_bar, self.v_scroll_speed)
247 };
248
249 if let Ok(scroll_bar) = ui.try_get(scroll_bar) {
250 let old_value = *scroll_bar.value;
251 let new_value = old_value - amount * scroll_speed;
252 if (old_value - new_value).abs() > f32::EPSILON {
253 message.set_handled(true);
254 }
255 ui.send(scroll_bar.handle, ScrollBarMessage::Value(new_value));
256 }
257 }
258 } else if let Some(msg) = message.data::<ScrollPanelMessage>() {
259 if message.destination() == self.scroll_panel {
260 let msg = match *msg {
261 ScrollPanelMessage::VerticalScroll(value) => {
262 UiMessage::for_widget(self.v_scroll_bar, ScrollBarMessage::Value(value))
263 }
264 ScrollPanelMessage::HorizontalScroll(value) => {
265 UiMessage::for_widget(self.h_scroll_bar, ScrollBarMessage::Value(value))
266 }
267 _ => return,
268 };
269 msg.set_handled(true);
271 ui.send_message(msg);
272 }
273 } else if let Some(msg) = message.data::<ScrollBarMessage>() {
274 if message.direction() == MessageDirection::FromWidget {
275 match msg {
276 ScrollBarMessage::Value(new_value) => {
277 if !message.handled() {
278 if message.destination() == self.v_scroll_bar
279 && self.v_scroll_bar.is_some()
280 {
281 ui.send(
282 self.scroll_panel,
283 ScrollPanelMessage::VerticalScroll(*new_value),
284 );
285 } else if message.destination() == self.h_scroll_bar
286 && self.h_scroll_bar.is_some()
287 {
288 ui.send(
289 self.scroll_panel,
290 ScrollPanelMessage::HorizontalScroll(*new_value),
291 );
292 }
293 }
294 }
295 &ScrollBarMessage::MaxValue(_) => {
296 if message.destination() == self.v_scroll_bar && self.v_scroll_bar.is_some()
297 {
298 if let Ok(scroll_bar) = ui.try_get(self.v_scroll_bar) {
299 let visibility =
300 (*scroll_bar.max - *scroll_bar.min).abs() >= f32::EPSILON;
301 ui.send(self.v_scroll_bar, WidgetMessage::Visibility(visibility));
302 }
303 } else if message.destination() == self.h_scroll_bar
304 && self.h_scroll_bar.is_some()
305 {
306 if let Ok(scroll_bar) = ui.try_get(self.h_scroll_bar) {
307 let visibility =
308 (*scroll_bar.max - *scroll_bar.min).abs() >= f32::EPSILON;
309 ui.send(self.h_scroll_bar, WidgetMessage::Visibility(visibility));
310 }
311 }
312 }
313 _ => (),
314 }
315 }
316 } else if let Some(msg) = message.data::<ScrollViewerMessage>() {
317 if message.destination() == self.handle() {
318 match msg {
319 ScrollViewerMessage::Content(content) => {
320 for child in ui[self.scroll_panel].children() {
321 ui.send(*child, WidgetMessage::Remove);
322 }
323 ui.send(*content, WidgetMessage::link_with(self.scroll_panel));
324 }
325 &ScrollViewerMessage::BringIntoView(handle) => {
326 ui.send(self.scroll_panel, ScrollPanelMessage::BringIntoView(handle));
328 }
329 &ScrollViewerMessage::HScrollSpeed(speed) => {
330 if self.h_scroll_speed != speed
331 && message.direction() == MessageDirection::ToWidget
332 {
333 self.h_scroll_speed = speed;
334
335 ui.send_message(message.reverse());
336 }
337 }
338 &ScrollViewerMessage::VScrollSpeed(speed) => {
339 if self.v_scroll_speed != speed
340 && message.direction() == MessageDirection::ToWidget
341 {
342 self.v_scroll_speed = speed;
343
344 ui.send_message(message.reverse());
345 }
346 }
347 ScrollViewerMessage::ScrollToEnd => {
348 ui.send(self.scroll_panel, ScrollPanelMessage::ScrollToEnd);
350 }
351 ScrollViewerMessage::HorizontalScroll(value) => {
352 ui.send(self.h_scroll_bar, ScrollBarMessage::Value(*value));
353 }
354 ScrollViewerMessage::VerticalScroll(value) => {
355 ui.send(self.v_scroll_bar, ScrollBarMessage::Value(*value));
356 }
357 }
358 }
359 }
360 }
361}
362
363pub struct ScrollViewerBuilder {
365 widget_builder: WidgetBuilder,
366 content: Handle<UiNode>,
367 h_scroll_bar: Option<Handle<ScrollBar>>,
368 v_scroll_bar: Option<Handle<ScrollBar>>,
369 horizontal_scroll_allowed: bool,
370 vertical_scroll_allowed: bool,
371 v_scroll_speed: f32,
372 h_scroll_speed: f32,
373}
374
375impl ScrollViewerBuilder {
376 pub fn new(widget_builder: WidgetBuilder) -> Self {
378 Self {
379 widget_builder,
380 content: Handle::NONE,
381 h_scroll_bar: None,
382 v_scroll_bar: None,
383 horizontal_scroll_allowed: false,
384 vertical_scroll_allowed: true,
385 v_scroll_speed: 30.0,
386 h_scroll_speed: 30.0,
387 }
388 }
389
390 pub fn with_content(mut self, content: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
392 self.content = content.to_base();
393 self
394 }
395
396 pub fn with_vertical_scroll_bar(mut self, v_scroll_bar: Handle<ScrollBar>) -> Self {
398 self.v_scroll_bar = Some(v_scroll_bar);
399 self
400 }
401
402 pub fn with_horizontal_scroll_bar(mut self, h_scroll_bar: Handle<ScrollBar>) -> Self {
404 self.h_scroll_bar = Some(h_scroll_bar);
405 self
406 }
407
408 pub fn with_vertical_scroll_allowed(mut self, value: bool) -> Self {
410 self.vertical_scroll_allowed = value;
411 self
412 }
413
414 pub fn with_horizontal_scroll_allowed(mut self, value: bool) -> Self {
416 self.horizontal_scroll_allowed = value;
417 self
418 }
419
420 pub fn with_v_scroll_speed(mut self, speed: f32) -> Self {
422 self.v_scroll_speed = speed;
423 self
424 }
425
426 pub fn with_h_scroll_speed(mut self, speed: f32) -> Self {
428 self.h_scroll_speed = speed;
429 self
430 }
431
432 pub fn build(self, ctx: &mut BuildContext) -> Handle<ScrollViewer> {
434 let scroll_panel = ScrollPanelBuilder::new(
435 WidgetBuilder::new()
436 .with_child(self.content)
437 .on_row(0)
438 .on_column(0),
439 )
440 .with_horizontal_scroll_allowed(self.horizontal_scroll_allowed)
441 .with_vertical_scroll_allowed(self.vertical_scroll_allowed)
442 .build(ctx);
443
444 let v_scroll_bar = self.v_scroll_bar.unwrap_or_else(|| {
445 ScrollBarBuilder::new(WidgetBuilder::new().with_width(16.0))
446 .with_step(30.0)
447 .with_orientation(Orientation::Vertical)
448 .build(ctx)
449 });
450 ctx[v_scroll_bar].set_row(0).set_column(1);
451
452 let h_scroll_bar = self.h_scroll_bar.unwrap_or_else(|| {
453 ScrollBarBuilder::new(WidgetBuilder::new().with_height(16.0))
454 .with_step(30.0)
455 .with_orientation(Orientation::Horizontal)
456 .build(ctx)
457 });
458 ctx[h_scroll_bar].set_row(1).set_column(0);
459
460 let sv = ScrollViewer {
461 widget: self
462 .widget_builder
463 .with_child(
464 GridBuilder::new(
465 WidgetBuilder::new()
466 .with_child(scroll_panel)
467 .with_child(h_scroll_bar)
468 .with_child(v_scroll_bar),
469 )
470 .add_row(Row::stretch())
471 .add_row(Row::auto())
472 .add_column(Column::stretch())
473 .add_column(Column::auto())
474 .build(ctx),
475 )
476 .build(ctx),
477 content: self.content,
478 v_scroll_bar,
479 h_scroll_bar,
480 scroll_panel,
481 v_scroll_speed: self.v_scroll_speed,
482 h_scroll_speed: self.h_scroll_speed,
483 };
484 ctx.add(sv)
485 }
486}
487
488#[cfg(test)]
489mod test {
490 use crate::scroll_viewer::ScrollViewerBuilder;
491 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
492
493 #[test]
494 fn test_deletion() {
495 test_widget_deletion(|ctx| ScrollViewerBuilder::new(WidgetBuilder::new()).build(ctx));
496 }
497}