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