1use crate::widget::container::Catalog;
5use crate::widget::{DndDestination, DndSource, button, column, container, divider, row, space};
6use crate::{Apply, Element, theme};
7use iced::{Length, Padding};
8
9pub struct ListButton<'a, Message> {
11 content: Element<'a, Message>,
12 on_press: Option<Message>,
13 dnd_source_builder: Option<Box<DndSourceBuilder<'a, Message>>>,
14 dnd_destination_builder: Option<Box<DndDestinationBuilder<'a, Message>>>,
15 selected: bool,
16}
17
18pub type DndSourceBuilder<'a, Message> =
20 dyn FnOnce(
21 Element<'a, Message>,
22 ) -> DndSource<'a, Message, Box<dyn iced::clipboard::mime::AsMimeTypes + Send>>;
23pub type DndDestinationBuilder<'a, Message> =
25 dyn FnOnce(Element<'a, Message>) -> DndDestination<'a, Message>;
26
27pub fn button<'a, Message>(content: impl Into<Element<'a, Message>>) -> ListButton<'a, Message> {
29 ListButton {
30 content: content.into(),
31 on_press: None,
32 dnd_source_builder: None,
33 dnd_destination_builder: None,
34 selected: false,
35 }
36}
37
38impl<'a, Message: 'static> ListButton<'a, Message> {
39 pub fn on_press(mut self, on_press: Message) -> Self {
40 self.on_press = Some(on_press);
41 self
42 }
43
44 pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
45 self.on_press = on_press;
46 self
47 }
48
49 pub fn selected(mut self, selected: bool) -> Self {
50 self.selected = selected;
51 self
52 }
53
54 pub fn with_dnd_source(mut self, builder: Box<DndSourceBuilder<'a, Message>>) -> Self {
55 self.dnd_source_builder = Some(builder);
56 self
57 }
58
59 pub fn with_dnd_destination(
60 mut self,
61 builder: Box<DndDestinationBuilder<'a, Message>>,
62 ) -> Self {
63 self.dnd_destination_builder = Some(builder);
64 self
65 }
66}
67
68pub enum ListItem<'a, Message> {
69 Element(Element<'a, Message>),
70 Button(ListButton<'a, Message>),
71}
72
73pub trait IntoListItem<'a, Message> {
75 fn into_list_item(self) -> ListItem<'a, Message>;
76}
77
78impl<'a, Message, T> IntoListItem<'a, Message> for T
79where
80 T: Into<Element<'a, Message>>,
81{
82 fn into_list_item(self) -> ListItem<'a, Message> {
83 ListItem::Element(self.into())
84 }
85}
86
87impl<'a, Message> IntoListItem<'a, Message> for ListButton<'a, Message> {
88 fn into_list_item(self) -> ListItem<'a, Message> {
89 ListItem::Button(self)
90 }
91}
92
93struct ListEntry<'a, Message> {
95 item: ListItem<'a, Message>,
96 item_padding: Padding,
97 divider_padding: u16,
98}
99
100#[must_use]
101pub struct ListColumn<'a, Message> {
102 list_item_padding: Padding,
103 divider_padding: u16,
104 style: theme::Container<'a>,
105 children: Vec<ListEntry<'a, Message>>,
106}
107
108#[inline]
109pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
110 ListColumn::default()
111}
112
113pub fn with_capacity<'a, Message: 'static>(capacity: usize) -> ListColumn<'a, Message> {
114 let cosmic_theme::Spacing {
115 space_xxs, space_m, ..
116 } = theme::spacing();
117
118 ListColumn {
119 list_item_padding: [space_xxs, space_m].into(),
120 divider_padding: 0,
121 style: theme::Container::List,
122 children: Vec::with_capacity(capacity),
123 }
124}
125
126impl<Message: 'static> Default for ListColumn<'_, Message> {
127 fn default() -> Self {
128 with_capacity(4)
129 }
130}
131
132impl<'a, Message: Clone + 'static> ListColumn<'a, Message> {
133 #[inline]
134 pub fn new() -> Self {
135 Self::default()
136 }
137
138 #[allow(clippy::should_implement_trait)]
140 pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self {
141 self.children.push(ListEntry {
142 item: item.into_list_item(),
143 item_padding: self.list_item_padding,
144 divider_padding: self.divider_padding,
145 });
146 self
147 }
148
149 #[inline]
151 pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
152 self.style = style;
153 self
154 }
155
156 pub fn list_item_padding(mut self, padding: impl Into<Padding>) -> Self {
157 self.list_item_padding = padding.into();
158 self
159 }
160
161 #[inline]
162 pub fn divider_padding(mut self, padding: u16) -> Self {
163 self.divider_padding = padding;
164 self
165 }
166
167 #[must_use]
168 pub fn into_element(self) -> Element<'a, Message> {
169 let count = self.children.len();
170 let last_index = count.saturating_sub(1);
171 let radius_s = theme::active().cosmic().radius_s();
172 let mut col = column::with_capacity((2 * count).saturating_sub(1));
173
174 let content_row =
176 |content| row![container(content), space().height(32)].align_y(iced::Alignment::Center);
177
178 for (
179 i,
180 ListEntry {
181 item,
182 item_padding,
183 divider_padding,
184 },
185 ) in self.children.into_iter().enumerate()
186 {
187 if i > 0 {
188 col = col
189 .push(container(divider::horizontal::default()).padding([0, divider_padding]));
190 }
191
192 col = match item {
193 ListItem::Element(content) => col.push(
194 content_row(content)
195 .padding(item_padding)
196 .width(Length::Fill),
197 ),
198 ListItem::Button(ListButton {
199 content,
200 on_press,
201 selected,
202 dnd_source_builder,
203 dnd_destination_builder,
204 }) => {
205 let mut button: Element<'a, Message> = content_row(content)
206 .apply(button::custom)
207 .padding(item_padding)
208 .width(Length::Fill)
209 .on_press_maybe(on_press)
210 .selected(selected)
211 .class(theme::Button::ListItem(get_radius(
212 radius_s,
213 i == 0,
214 i == last_index,
215 )))
216 .into();
217 if let Some(builder) = dnd_source_builder {
218 button = builder(button).into();
219 }
220
221 if let Some(builder) = dnd_destination_builder {
222 button = builder(button).into();
223 }
224
225 col.push(button)
226 }
227 };
228 }
229
230 col.width(Length::Fill)
231 .apply(container)
232 .class(self.style)
233 .into()
234 }
235}
236
237impl<'a, Message: Clone + 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
238 fn from(column: ListColumn<'a, Message>) -> Self {
239 column.into_element()
240 }
241}
242
243fn get_radius(radius: [f32; 4], first: bool, last: bool) -> [f32; 4] {
244 match (first, last) {
245 (true, true) => radius,
246 (true, false) => [radius[0], radius[1], 0.0, 0.0],
247 (false, true) => [0.0, 0.0, radius[2], radius[3]],
248 (false, false) => [0.0, 0.0, 0.0, 0.0],
249 }
250}