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