rg3d_ui/
scroll_viewer.rs

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    /// Adjusts vertical and horizontal scroll values so given node will be in "view box"
20    /// of scroll viewer.
21    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                // handle flag here is raised to prevent infinite message loop with the branch down below (ScrollBar::value).
145                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                        // Re-cast message to inner panel.
223                        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}