cosmic/widget/table/widget/
compact.rs

1use 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, menu},
11};
12use iced::{Alignment, Border, Padding};
13
14#[derive(Setters)]
15#[must_use]
16pub struct CompactTableView<'a, SelectionMode, Item, Category, Message>
17where
18    Category: ItemCategory,
19    Item: ItemInterface<Category>,
20    Model<SelectionMode, Item, Category>: Selectable,
21    SelectionMode: Default,
22    Message: Clone + 'static,
23{
24    pub(super) model: &'a Model<SelectionMode, Item, Category>,
25
26    #[setters(into)]
27    pub(super) element_padding: Padding,
28
29    #[setters(into)]
30    pub(super) item_padding: Padding,
31    pub(super) item_spacing: u16,
32    pub(super) icon_size: u16,
33
34    #[setters(into)]
35    pub(super) divider_padding: Padding,
36
37    // === Item Interaction ===
38    #[setters(skip)]
39    pub(super) on_item_mb_left: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
40    #[setters(skip)]
41    pub(super) on_item_mb_double: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
42    #[setters(skip)]
43    pub(super) on_item_mb_mid: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
44    #[setters(skip)]
45    pub(super) on_item_mb_right: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
46    #[setters(skip)]
47    pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<Message>>>>,
48}
49
50impl<'a, SelectionMode, Item, Category, Message>
51    From<CompactTableView<'a, SelectionMode, Item, Category, Message>> for Element<'a, Message>
52where
53    Category: ItemCategory,
54    Item: ItemInterface<Category>,
55    Model<SelectionMode, Item, Category>: Selectable,
56    SelectionMode: Default,
57    Message: Clone + 'static,
58{
59    fn from(val: CompactTableView<'a, SelectionMode, Item, Category, Message>) -> Self {
60        let cosmic_theme::Spacing { space_xxxs, .. } = theme::spacing();
61        val.model
62            .iter()
63            .map(|entity| {
64                let item = val.model.item(entity).unwrap();
65                let selected = val.model.is_active(entity);
66                let context_menu = (val.item_context_builder)(item);
67
68                widget::column::with_capacity(2)
69                    .spacing(val.item_spacing)
70                    .push(
71                        widget::divider::horizontal::default()
72                            .apply(container)
73                            .padding(val.divider_padding),
74                    )
75                    .push(
76                        widget::row::with_capacity(2)
77                            .spacing(space_xxxs)
78                            .align_y(Alignment::Center)
79                            .push_maybe(
80                                item.get_icon(Category::default())
81                                    .map(|icon| icon.size(val.icon_size)),
82                            )
83                            .push(
84                                widget::column::with_capacity(2)
85                                    .push(widget::text::body(item.get_text(Category::default())))
86                                    .push({
87                                        let mut elements = val
88                                            .model
89                                            .categories
90                                            .iter()
91                                            .skip_while(|cat| **cat != Category::default())
92                                            .flat_map(|category| {
93                                                [
94                                                    widget::text::caption(item.get_text(*category))
95                                                        .apply(Element::from),
96                                                    widget::text::caption("-").apply(Element::from),
97                                                ]
98                                            })
99                                            .collect::<Vec<Element<'static, Message>>>();
100                                        elements.pop();
101                                        elements
102                                            .apply(widget::row::with_children)
103                                            .spacing(space_xxxs)
104                                            .wrap()
105                                    }),
106                            )
107                            .apply(container)
108                            .padding(val.item_padding)
109                            .width(iced::Length::Fill)
110                            .class(theme::Container::custom(move |theme| {
111                                widget::container::Style {
112                                    icon_color: if selected {
113                                        Some(theme.cosmic().on_accent_color().into())
114                                    } else {
115                                        None
116                                    },
117                                    text_color: if selected {
118                                        Some(theme.cosmic().on_accent_color().into())
119                                    } else {
120                                        None
121                                    },
122                                    background: if selected {
123                                        Some(iced::Background::Color(
124                                            theme.cosmic().accent_color().into(),
125                                        ))
126                                    } else {
127                                        None
128                                    },
129                                    border: Border {
130                                        radius: theme.cosmic().radius_xs().into(),
131                                        ..Default::default()
132                                    },
133                                    shadow: Default::default(),
134                                    snap: true,
135                                }
136                            }))
137                            .apply(widget::mouse_area)
138                            // Left click
139                            .apply(|mouse_area| {
140                                if let Some(ref on_item_mb) = val.on_item_mb_left {
141                                    mouse_area.on_press((on_item_mb)(entity))
142                                } else {
143                                    mouse_area
144                                }
145                            })
146                            // Double click
147                            .apply(|mouse_area| {
148                                if let Some(ref on_item_mb) = val.on_item_mb_double {
149                                    mouse_area.on_double_click((on_item_mb)(entity))
150                                } else {
151                                    mouse_area
152                                }
153                            })
154                            // Middle click
155                            .apply(|mouse_area| {
156                                if let Some(ref on_item_mb) = val.on_item_mb_mid {
157                                    mouse_area.on_middle_press((on_item_mb)(entity))
158                                } else {
159                                    mouse_area
160                                }
161                            })
162                            // Right click
163                            .apply(|mouse_area| {
164                                if let Some(ref on_item_mb) = val.on_item_mb_right {
165                                    mouse_area.on_right_press((on_item_mb)(entity))
166                                } else {
167                                    mouse_area
168                                }
169                            })
170                            .apply(|ma| widget::context_menu(ma, context_menu)),
171                    )
172                    .apply(Element::from)
173            })
174            .apply(widget::column::with_children)
175            .spacing(val.item_spacing)
176            .padding(val.element_padding)
177            .apply(Element::from)
178    }
179}
180
181impl<'a, SelectionMode, Item, Category, Message>
182    CompactTableView<'a, SelectionMode, Item, Category, Message>
183where
184    SelectionMode: Default,
185    Model<SelectionMode, Item, Category>: Selectable,
186    Category: ItemCategory,
187    Item: ItemInterface<Category>,
188    Message: Clone + 'static,
189{
190    pub fn new(model: &'a Model<SelectionMode, Item, Category>) -> Self {
191        let cosmic_theme::Spacing {
192            space_xxxs,
193            space_xxs,
194            ..
195        } = theme::spacing();
196
197        Self {
198            model,
199            element_padding: Padding::from(0),
200
201            divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
202
203            item_padding: Padding::from(space_xxs),
204            item_spacing: 0,
205            icon_size: 48,
206
207            on_item_mb_left: None,
208            on_item_mb_double: None,
209            on_item_mb_mid: None,
210            on_item_mb_right: None,
211            item_context_builder: Box::new(|_| None),
212        }
213    }
214
215    pub fn on_item_left_click<F>(mut self, on_click: F) -> Self
216    where
217        F: Fn(Entity) -> Message + 'static,
218    {
219        self.on_item_mb_left = Some(Box::new(on_click));
220        self
221    }
222
223    pub fn on_item_double_click<F>(mut self, on_click: F) -> Self
224    where
225        F: Fn(Entity) -> Message + 'static,
226    {
227        self.on_item_mb_double = Some(Box::new(on_click));
228        self
229    }
230
231    pub fn on_item_middle_click<F>(mut self, on_click: F) -> Self
232    where
233        F: Fn(Entity) -> Message + 'static,
234    {
235        self.on_item_mb_mid = Some(Box::new(on_click));
236        self
237    }
238
239    pub fn on_item_right_click<F>(mut self, on_click: F) -> Self
240    where
241        F: Fn(Entity) -> Message + 'static,
242    {
243        self.on_item_mb_right = Some(Box::new(on_click));
244        self
245    }
246
247    pub fn item_context<F>(mut self, context_menu_builder: F) -> Self
248    where
249        F: Fn(&Item) -> Option<Vec<menu::Tree<Message>>> + 'static,
250        Message: 'static,
251    {
252        self.item_context_builder = Box::new(context_menu_builder);
253        self
254    }
255}