rg3d_ui/
list_view.rs

1use crate::{
2    border::BorderBuilder,
3    brush::Brush,
4    core::{color::Color, pool::Handle},
5    decorator::{Decorator, DecoratorMessage},
6    define_constructor,
7    draw::{CommandTexture, Draw, DrawingContext},
8    message::{MessageDirection, UiMessage},
9    scroll_viewer::{ScrollViewer, ScrollViewerBuilder},
10    stack_panel::StackPanelBuilder,
11    widget::{Widget, WidgetBuilder, WidgetMessage},
12    BuildContext, Control, NodeHandleMapping, Thickness, UiNode, UserInterface, BRUSH_DARK,
13    BRUSH_LIGHT,
14};
15use std::{
16    any::{Any, TypeId},
17    ops::{Deref, DerefMut},
18};
19
20#[derive(Debug, Clone, PartialEq)]
21pub enum ListViewMessage {
22    SelectionChanged(Option<usize>),
23    Items(Vec<Handle<UiNode>>),
24    AddItem(Handle<UiNode>),
25    RemoveItem(Handle<UiNode>),
26}
27
28impl ListViewMessage {
29    define_constructor!(ListViewMessage:SelectionChanged => fn selection(Option<usize>), layout: false);
30    define_constructor!(ListViewMessage:Items => fn items(Vec<Handle<UiNode >>), layout: false);
31    define_constructor!(ListViewMessage:AddItem => fn add_item(Handle<UiNode>), layout: false);
32    define_constructor!(ListViewMessage:RemoveItem => fn remove_item(Handle<UiNode>), layout: false);
33}
34
35#[derive(Clone)]
36pub struct ListView {
37    widget: Widget,
38    selected_index: Option<usize>,
39    item_containers: Vec<Handle<UiNode>>,
40    panel: Handle<UiNode>,
41    items: Vec<Handle<UiNode>>,
42}
43
44crate::define_widget_deref!(ListView);
45
46impl ListView {
47    pub fn new(widget: Widget, items: Vec<Handle<UiNode>>) -> Self {
48        Self {
49            widget,
50            selected_index: None,
51            item_containers: items,
52            panel: Default::default(),
53            items: Default::default(),
54        }
55    }
56
57    pub fn selected(&self) -> Option<usize> {
58        self.selected_index
59    }
60
61    pub fn item_containers(&self) -> &[Handle<UiNode>] {
62        &self.item_containers
63    }
64
65    pub fn items(&self) -> &[Handle<UiNode>] {
66        &self.items
67    }
68
69    fn fix_selection(&self, ui: &UserInterface) {
70        // Check if current selection is out-of-bounds.
71        if let Some(selected_index) = self.selected_index {
72            if selected_index >= self.items.len() {
73                let new_selection = if self.items.is_empty() {
74                    None
75                } else {
76                    Some(self.items.len() - 1)
77                };
78
79                ui.send_message(ListViewMessage::selection(
80                    self.handle,
81                    MessageDirection::ToWidget,
82                    new_selection,
83                ));
84            }
85        }
86    }
87
88    fn sync_decorators(&self, ui: &UserInterface) {
89        for (i, &container) in self.item_containers.iter().enumerate() {
90            let select = match self.selected_index {
91                None => false,
92                Some(selected_index) => i == selected_index,
93            };
94            if let Some(container) = ui.node(container).cast::<ListViewItem>() {
95                let mut stack = container.children().to_vec();
96                while let Some(handle) = stack.pop() {
97                    let node = ui.node(handle);
98
99                    if node.cast::<ListView>().is_some() {
100                        // Do nothing.
101                    } else if node.cast::<Decorator>().is_some() {
102                        ui.send_message(DecoratorMessage::select(
103                            handle,
104                            MessageDirection::ToWidget,
105                            select,
106                        ));
107                    } else {
108                        stack.extend_from_slice(node.children())
109                    }
110                }
111            }
112        }
113    }
114}
115
116#[derive(Clone)]
117pub struct ListViewItem {
118    widget: Widget,
119}
120
121crate::define_widget_deref!(ListViewItem);
122
123impl Control for ListViewItem {
124    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
125        if type_id == TypeId::of::<Self>() {
126            Some(self)
127        } else {
128            None
129        }
130    }
131
132    fn draw(&self, drawing_context: &mut DrawingContext) {
133        // Emit transparent geometry so item container can be picked by hit test.
134        drawing_context.push_rect_filled(&self.widget.screen_bounds(), None);
135        drawing_context.commit(
136            self.clip_bounds(),
137            Brush::Solid(Color::TRANSPARENT),
138            CommandTexture::None,
139            None,
140        );
141    }
142
143    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
144        self.widget.handle_routed_message(ui, message);
145
146        let parent_list_view =
147            self.find_by_criteria_up(ui, |node| node.cast::<ListView>().is_some());
148
149        if let Some(WidgetMessage::MouseUp { .. }) = message.data::<WidgetMessage>() {
150            if !message.handled() {
151                let self_index = ui
152                    .node(parent_list_view)
153                    .cast::<ListView>()
154                    .expect("Parent of ListViewItem must be ListView!")
155                    .item_containers
156                    .iter()
157                    .position(|c| *c == self.handle)
158                    .expect("ListViewItem must be used as a child of ListView");
159
160                // Explicitly set selection on parent items control. This will send
161                // SelectionChanged message and all items will react.
162                ui.send_message(ListViewMessage::selection(
163                    parent_list_view,
164                    MessageDirection::ToWidget,
165                    Some(self_index),
166                ));
167                message.set_handled(true);
168            }
169        }
170    }
171}
172
173impl Control for ListView {
174    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
175        if type_id == TypeId::of::<Self>() {
176            Some(self)
177        } else {
178            None
179        }
180    }
181
182    fn resolve(&mut self, node_map: &NodeHandleMapping) {
183        node_map.resolve(&mut self.panel);
184        node_map.resolve_slice(&mut self.items);
185        node_map.resolve_slice(&mut self.item_containers);
186    }
187
188    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
189        self.widget.handle_routed_message(ui, message);
190
191        if let Some(msg) = message.data::<ListViewMessage>() {
192            if message.destination() == self.handle()
193                && message.direction() == MessageDirection::ToWidget
194            {
195                match msg {
196                    ListViewMessage::Items(items) => {
197                        // Remove previous items.
198                        for child in ui.node(self.panel).children().to_vec() {
199                            ui.send_message(WidgetMessage::remove(
200                                child,
201                                MessageDirection::ToWidget,
202                            ));
203                        }
204
205                        // Generate new items.
206                        let item_containers = generate_item_containers(&mut ui.build_ctx(), items);
207
208                        for item_container in item_containers.iter() {
209                            ui.send_message(WidgetMessage::link(
210                                *item_container,
211                                MessageDirection::ToWidget,
212                                self.panel,
213                            ));
214                        }
215
216                        self.item_containers = item_containers;
217                        self.items = items.clone();
218
219                        self.fix_selection(ui);
220                        self.sync_decorators(ui);
221                    }
222                    &ListViewMessage::AddItem(item) => {
223                        let item_container = generate_item_container(&mut ui.build_ctx(), item);
224
225                        ui.send_message(WidgetMessage::link(
226                            item_container,
227                            MessageDirection::ToWidget,
228                            self.panel,
229                        ));
230
231                        self.item_containers.push(item_container);
232                        self.items.push(item);
233                    }
234                    &ListViewMessage::SelectionChanged(selection) => {
235                        if self.selected_index != selection {
236                            self.selected_index = selection;
237                            self.sync_decorators(ui);
238                            ui.send_message(message.reverse());
239                        }
240                    }
241                    &ListViewMessage::RemoveItem(item) => {
242                        if let Some(item_position) = self.items.iter().position(|i| *i == item) {
243                            self.items.remove(item_position);
244                            self.item_containers.remove(item_position);
245
246                            let container = ui.node(item).parent();
247
248                            ui.send_message(WidgetMessage::remove(
249                                container,
250                                MessageDirection::ToWidget,
251                            ));
252
253                            self.fix_selection(ui);
254                            self.sync_decorators(ui);
255                        }
256                    }
257                }
258            }
259        }
260    }
261}
262
263pub struct ListViewBuilder {
264    widget_builder: WidgetBuilder,
265    items: Vec<Handle<UiNode>>,
266    panel: Option<Handle<UiNode>>,
267    scroll_viewer: Option<Handle<UiNode>>,
268}
269
270impl ListViewBuilder {
271    pub fn new(widget_builder: WidgetBuilder) -> Self {
272        Self {
273            widget_builder,
274            items: Vec::new(),
275            panel: None,
276            scroll_viewer: None,
277        }
278    }
279
280    pub fn with_items(mut self, items: Vec<Handle<UiNode>>) -> Self {
281        self.items = items;
282        self
283    }
284
285    pub fn with_items_panel(mut self, panel: Handle<UiNode>) -> Self {
286        self.panel = Some(panel);
287        self
288    }
289
290    pub fn with_scroll_viewer(mut self, sv: Handle<UiNode>) -> Self {
291        self.scroll_viewer = Some(sv);
292        self
293    }
294
295    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
296        let item_containers = generate_item_containers(ctx, &self.items);
297
298        let panel = self.panel.unwrap_or_else(|| {
299            StackPanelBuilder::new(
300                WidgetBuilder::new().with_children(item_containers.iter().cloned()),
301            )
302            .build(ctx)
303        });
304
305        let back = BorderBuilder::new(
306            WidgetBuilder::new()
307                .with_background(BRUSH_DARK)
308                .with_foreground(BRUSH_LIGHT),
309        )
310        .with_stroke_thickness(Thickness::uniform(1.0))
311        .build(ctx);
312
313        let scroll_viewer = self.scroll_viewer.unwrap_or_else(|| {
314            ScrollViewerBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(3.0)))
315                .build(ctx)
316        });
317        let scroll_viewer_ref = ctx[scroll_viewer]
318            .cast_mut::<ScrollViewer>()
319            .expect("ListView must have ScrollViewer");
320        scroll_viewer_ref.set_content(panel);
321        let content_presenter = scroll_viewer_ref.scroll_panel;
322        ctx.link(panel, content_presenter);
323
324        ctx.link(scroll_viewer, back);
325
326        let list_box = ListView {
327            widget: self.widget_builder.with_child(back).build(),
328            selected_index: None,
329            item_containers,
330            items: self.items,
331            panel,
332        };
333
334        ctx.add_node(UiNode::new(list_box))
335    }
336}
337
338fn generate_item_container(ctx: &mut BuildContext, item: Handle<UiNode>) -> Handle<UiNode> {
339    let item = ListViewItem {
340        widget: WidgetBuilder::new().with_child(item).build(),
341    };
342
343    ctx.add_node(UiNode::new(item))
344}
345
346fn generate_item_containers(
347    ctx: &mut BuildContext,
348    items: &[Handle<UiNode>],
349) -> Vec<Handle<UiNode>> {
350    items
351        .iter()
352        .map(|&item| generate_item_container(ctx, item))
353        .collect()
354}