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