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 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 } 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 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 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 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 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}