cosmic/widget/table/widget/
standard.rs1use derive_setters::Setters;
2
3use crate::widget::table::model::{
4 Entity, Model,
5 category::{ItemCategory, ItemInterface},
6 selection::Selectable,
7};
8use crate::{
9 Apply, Element, theme,
10 widget::{self, container, divider, menu},
11};
12use iced::{Alignment, Border, Length, Padding};
13
14#[derive(Setters)]
17#[must_use]
18pub struct TableView<'a, SelectionMode, Item, Category, Message>
19where
20 Category: ItemCategory,
21 Item: ItemInterface<Category>,
22 Model<SelectionMode, Item, Category>: Selectable,
23 SelectionMode: Default,
24 Message: Clone + 'static,
25{
26 pub(super) model: &'a Model<SelectionMode, Item, Category>,
27
28 #[setters(into)]
29 pub(super) element_padding: Padding,
30 #[setters(into)]
31 pub(super) width: Length,
32 #[setters(into)]
33 pub(super) height: Length,
34
35 #[setters(into)]
36 pub(super) item_padding: Padding,
37 pub(super) item_spacing: u16,
38 pub(super) icon_spacing: u16,
39 pub(super) icon_size: u16,
40
41 #[setters(into)]
42 pub(super) divider_padding: Padding,
43
44 #[setters(skip)]
46 pub(super) on_item_mb_left: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
47 #[setters(skip)]
48 pub(super) on_item_mb_double: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
49 #[setters(skip)]
50 pub(super) on_item_mb_mid: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
51 #[setters(skip)]
52 pub(super) on_item_mb_right: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
53 #[setters(skip)]
54 pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<Message>>>>,
55 #[setters(skip)]
59 pub(super) on_category_mb_left: Option<Box<dyn Fn(Category) -> Message + 'static>>,
60 #[setters(skip)]
61 pub(super) on_category_mb_double: Option<Box<dyn Fn(Category) -> Message + 'static>>,
62 #[setters(skip)]
63 pub(super) on_category_mb_mid: Option<Box<dyn Fn(Category) -> Message + 'static>>,
64 #[setters(skip)]
65 pub(super) on_category_mb_right: Option<Box<dyn Fn(Category) -> Message + 'static>>,
66 #[setters(skip)]
67 pub(super) category_context_builder: Box<dyn Fn(Category) -> Option<Vec<menu::Tree<Message>>>>,
68}
69
70impl<'a, SelectionMode, Item, Category, Message>
71 From<TableView<'a, SelectionMode, Item, Category, Message>> for Element<'a, Message>
72where
73 Category: ItemCategory,
74 Item: ItemInterface<Category>,
75 Model<SelectionMode, Item, Category>: Selectable,
76 SelectionMode: Default,
77 Message: Clone + 'static,
78{
79 fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self {
80 let header_row = val
82 .model
83 .categories
84 .iter()
85 .copied()
86 .map(|category| {
87 let cat_context_tree = (val.category_context_builder)(category);
88
89 let mut sort_state = 0;
90
91 if let Some(sort) = val.model.sort {
92 if sort.0 == category {
93 if sort.1 {
94 sort_state = 1;
95 } else {
96 sort_state = 2;
97 }
98 }
99 };
100
101 widget::row::with_capacity(2)
103 .spacing(val.icon_spacing)
104 .push(widget::text::heading(category.to_string()))
105 .push_maybe(match sort_state {
106 1 => Some(widget::icon::from_name("pan-up-symbolic").icon()),
107 2 => Some(widget::icon::from_name("pan-down-symbolic").icon()),
108 _ => None,
109 })
110 .apply(container)
111 .padding(
112 Padding::default()
113 .left(val.item_padding.left)
114 .right(val.item_padding.right),
115 )
116 .width(category.width())
117 .apply(widget::mouse_area)
118 .apply(|mouse_area| {
119 if let Some(ref on_category_select) = val.on_category_mb_left {
120 mouse_area.on_press((on_category_select)(category))
121 } else {
122 mouse_area
123 }
124 })
125 .apply(|mouse_area| widget::context_menu(mouse_area, cat_context_tree))
126 .apply(Element::from)
127 })
128 .apply(widget::row::with_children)
129 .apply(Element::from);
130 let items_full = if val.model.items.is_empty() {
132 vec![
133 divider::horizontal::default()
134 .apply(container)
135 .padding(val.divider_padding)
136 .apply(Element::from),
137 ]
138 } else {
139 val.model
140 .iter()
141 .flat_map(move |entity| {
142 let item = val.model.item(entity).unwrap();
143 let categories = &val.model.categories;
144 let selected = val.model.is_active(entity);
145 let item_context = (val.item_context_builder)(item);
146
147 [
148 divider::horizontal::default()
149 .apply(container)
150 .padding(val.divider_padding)
151 .apply(Element::from),
152 categories
153 .iter()
154 .map(|category| {
155 widget::row::with_capacity(2)
156 .spacing(val.icon_spacing)
157 .push_maybe(
158 item.get_icon(*category)
159 .map(|icon| icon.size(val.icon_size)),
160 )
161 .push(widget::text::body(item.get_text(*category)))
162 .align_y(Alignment::Center)
163 .apply(container)
164 .width(category.width())
165 .align_y(Alignment::Center)
166 .apply(Element::from)
167 })
168 .apply(widget::row::with_children)
169 .apply(container)
170 .padding(val.item_padding)
171 .class(theme::Container::custom(move |theme| {
172 widget::container::Style {
173 icon_color: if selected {
174 Some(theme.cosmic().on_accent_color().into())
175 } else {
176 None
177 },
178 text_color: if selected {
179 Some(theme.cosmic().on_accent_color().into())
180 } else {
181 None
182 },
183 background: if selected {
184 Some(iced::Background::Color(
185 theme.cosmic().accent_color().into(),
186 ))
187 } else {
188 None
189 },
190 border: Border {
191 radius: theme.cosmic().radius_xs().into(),
192 ..Default::default()
193 },
194 shadow: Default::default(),
195 snap: true,
196 }
197 }))
198 .apply(widget::mouse_area)
199 .apply(|mouse_area| {
201 if let Some(ref on_item_mb) = val.on_item_mb_left {
202 mouse_area.on_press((on_item_mb)(entity))
203 } else {
204 mouse_area
205 }
206 })
207 .apply(|mouse_area| {
209 if let Some(ref on_item_mb) = val.on_item_mb_double {
210 mouse_area.on_double_click((on_item_mb)(entity))
211 } else {
212 mouse_area
213 }
214 })
215 .apply(|mouse_area| {
217 if let Some(ref on_item_mb) = val.on_item_mb_mid {
218 mouse_area.on_middle_press((on_item_mb)(entity))
219 } else {
220 mouse_area
221 }
222 })
223 .apply(|mouse_area| {
225 if let Some(ref on_item_mb) = val.on_item_mb_right {
226 mouse_area.on_right_press((on_item_mb)(entity))
227 } else {
228 mouse_area
229 }
230 })
231 .apply(|mouse_area| widget::context_menu(mouse_area, item_context))
232 .apply(Element::from),
233 ]
234 })
235 .collect::<Vec<Element<'a, Message>>>()
236 };
237 let mut elements = items_full;
238 elements.insert(0, header_row);
239 elements
240 .apply(widget::column::with_children)
241 .width(val.width)
242 .height(val.height)
243 .spacing(val.item_spacing)
244 .padding(val.element_padding)
245 .apply(Element::from)
246 }
247}
248
249impl<'a, SelectionMode, Item, Category, Message>
250 TableView<'a, SelectionMode, Item, Category, Message>
251where
252 SelectionMode: Default,
253 Model<SelectionMode, Item, Category>: Selectable,
254 Category: ItemCategory,
255 Item: ItemInterface<Category>,
256 Message: Clone + 'static,
257{
258 pub fn new(model: &'a Model<SelectionMode, Item, Category>) -> Self {
259 let cosmic_theme::Spacing {
260 space_xxxs,
261 space_xxs,
262 ..
263 } = theme::spacing();
264
265 Self {
266 model,
267
268 element_padding: Padding::from(0),
269 width: Length::Fill,
270 height: Length::Shrink,
271
272 item_padding: Padding::from(space_xxs),
273 item_spacing: 0,
274 icon_spacing: space_xxxs,
275 icon_size: 24,
276
277 divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
278
279 on_item_mb_left: None,
280 on_item_mb_double: None,
281 on_item_mb_mid: None,
282 on_item_mb_right: None,
283 item_context_builder: Box::new(|_| None),
284
285 on_category_mb_left: None,
286 on_category_mb_double: None,
287 on_category_mb_mid: None,
288 on_category_mb_right: None,
289 category_context_builder: Box::new(|_| None),
290 }
291 }
292
293 pub fn on_item_left_click<F>(mut self, on_click: F) -> Self
294 where
295 F: Fn(Entity) -> Message + 'static,
296 {
297 self.on_item_mb_left = Some(Box::new(on_click));
298 self
299 }
300
301 pub fn on_item_double_click<F>(mut self, on_click: F) -> Self
302 where
303 F: Fn(Entity) -> Message + 'static,
304 {
305 self.on_item_mb_double = Some(Box::new(on_click));
306 self
307 }
308
309 pub fn on_item_middle_click<F>(mut self, on_click: F) -> Self
310 where
311 F: Fn(Entity) -> Message + 'static,
312 {
313 self.on_item_mb_mid = Some(Box::new(on_click));
314 self
315 }
316
317 pub fn on_item_right_click<F>(mut self, on_click: F) -> Self
318 where
319 F: Fn(Entity) -> Message + 'static,
320 {
321 self.on_item_mb_right = Some(Box::new(on_click));
322 self
323 }
324
325 pub fn item_context<F>(mut self, context_menu_builder: F) -> Self
326 where
327 F: Fn(&Item) -> Option<Vec<menu::Tree<Message>>> + 'static,
328 Message: 'static,
329 {
330 self.item_context_builder = Box::new(context_menu_builder);
331 self
332 }
333
334 pub fn on_category_left_click<F>(mut self, on_select: F) -> Self
335 where
336 F: Fn(Category) -> Message + 'static,
337 {
338 self.on_category_mb_left = Some(Box::new(on_select));
339 self
340 }
341 pub fn on_category_double_click<F>(mut self, on_select: F) -> Self
342 where
343 F: Fn(Category) -> Message + 'static,
344 {
345 self.on_category_mb_double = Some(Box::new(on_select));
346 self
347 }
348 pub fn on_category_middle_click<F>(mut self, on_select: F) -> Self
349 where
350 F: Fn(Category) -> Message + 'static,
351 {
352 self.on_category_mb_mid = Some(Box::new(on_select));
353 self
354 }
355
356 pub fn on_category_right_click<F>(mut self, on_select: F) -> Self
357 where
358 F: Fn(Category) -> Message + 'static,
359 {
360 self.on_category_mb_right = Some(Box::new(on_select));
361 self
362 }
363
364 pub fn category_context<F>(mut self, context_menu_builder: F) -> Self
365 where
366 F: Fn(Category) -> Option<Vec<menu::Tree<Message>>> + 'static,
367 Message: 'static,
368 {
369 self.category_context_builder = Box::new(context_menu_builder);
370 self
371 }
372}