1use cosmic_theme::Component;
7use iced_core::{Background, Color};
8
9use crate::{
10 theme::TRANSPARENT_COMPONENT,
11 widget::button::{Catalog, Style},
12};
13
14#[derive(Default)]
15pub enum Button {
16 AppletIcon,
17 AppletMenu,
18 Custom {
19 active: Box<dyn Fn(bool, &crate::Theme) -> Style>,
20 disabled: Box<dyn Fn(&crate::Theme) -> Style>,
21 hovered: Box<dyn Fn(bool, &crate::Theme) -> Style>,
22 pressed: Box<dyn Fn(bool, &crate::Theme) -> Style>,
23 },
24 Destructive,
25 HeaderBar,
26 Icon,
27 IconVertical,
28 Image,
29 Link,
30 ListItem([f32; 4]),
31 MenuFolder,
32 MenuItem,
33 MenuRoot,
34 NavToggle,
35 #[default]
36 Standard,
37 Suggested,
38 Text,
39 Transparent,
40}
41
42pub fn appearance(
43 theme: &crate::Theme,
44 focused: bool,
45 selected: bool,
46 disabled: bool,
47 style: &Button,
48 color: impl Fn(&Component) -> (Color, Option<Color>, Option<Color>),
49) -> Style {
50 let cosmic = theme.cosmic();
51 let mut corner_radii = &cosmic.corner_radii.radius_xl;
52 let mut appearance = Style::new();
53 let hc = theme.theme_type.is_high_contrast();
54 match style {
55 Button::Standard
56 | Button::Text
57 | Button::Suggested
58 | Button::Destructive
59 | Button::Transparent => {
60 let style_component = match style {
61 Button::Standard => &cosmic.button,
62 Button::Text => &cosmic.text_button,
63 Button::Suggested => &cosmic.accent_button,
64 Button::Destructive => &cosmic.destructive_button,
65 Button::Transparent => &TRANSPARENT_COMPONENT,
66 _ => return appearance,
67 };
68
69 let (background, text, icon) = color(style_component);
70 appearance.background = Some(Background::Color(background));
71 if !matches!(style, Button::Standard) {
72 appearance.text_color = text;
73 appearance.icon_color = icon;
74 } else if hc {
75 appearance.border_color = style_component.border.into();
76 appearance.border_width = 1.;
77 }
78 }
79
80 Button::Icon | Button::IconVertical | Button::HeaderBar | Button::NavToggle => {
81 if matches!(style, Button::IconVertical) {
82 corner_radii = &cosmic.corner_radii.radius_m;
83 if selected {
84 appearance.overlay = Some(Background::Color(Color::from(
85 cosmic.icon_button.selected_state_color(),
86 )));
87 }
88 }
89 if matches!(style, Button::NavToggle) {
90 corner_radii = &cosmic.corner_radii.radius_s;
91 }
92
93 let (background, text, icon) = color(&cosmic.icon_button);
94 appearance.background = Some(Background::Color(background));
95 appearance.icon_color = if disabled { icon } else { None };
97 appearance.text_color = if disabled { text } else { None };
98 }
99
100 Button::Image => {
101 appearance.background = None;
102 appearance.text_color = Some(cosmic.accent_text_color().into());
103 appearance.icon_color = Some(cosmic.accent.base.into());
104
105 corner_radii = &cosmic.corner_radii.radius_s;
106 appearance.border_radius = (*corner_radii).into();
107
108 if focused || selected {
109 appearance.border_width = 2.0;
110 appearance.border_color = cosmic.accent.base.into();
111 } else if hc {
112 appearance.border_color = theme.current_container().component.divider.into();
113 appearance.border_width = 1.;
114 }
115
116 return appearance;
117 }
118
119 Button::Link => {
120 appearance.background = None;
121 appearance.icon_color = Some(cosmic.accent_text_color().into());
122 appearance.text_color = Some(cosmic.accent_text_color().into());
123 corner_radii = &cosmic.corner_radii.radius_0;
124 }
125
126 Button::Custom { .. } => (),
127 Button::AppletMenu => {
128 let (background, _, _) = color(&cosmic.text_button);
129 appearance.background = Some(Background::Color(background));
130
131 appearance.icon_color = Some(cosmic.background.on.into());
132 appearance.text_color = Some(cosmic.background.on.into());
133 corner_radii = &cosmic.corner_radii.radius_0;
134 }
135 Button::AppletIcon => {
136 let (background, _, _) = color(&cosmic.text_button);
137 appearance.background = Some(Background::Color(background));
138
139 appearance.icon_color = Some(cosmic.background.on.into());
140 appearance.text_color = Some(cosmic.background.on.into());
141 }
142 Button::MenuFolder => {
143 let component = &cosmic.background.component;
145 let (background, _, _) = color(component);
146 appearance.background = Some(Background::Color(background));
147 appearance.icon_color = Some(component.on.into());
148 appearance.text_color = Some(component.on.into());
149 corner_radii = &cosmic.corner_radii.radius_s;
150 }
151 Button::ListItem(radii) => {
152 corner_radii = radii;
153 let (background, text, icon) = color(&cosmic.background.component);
154
155 if selected {
156 appearance.background =
157 Some(Background::Color(cosmic.primary.component.hover.into()));
158 appearance.icon_color = Some(cosmic.accent.base.into());
159 appearance.text_color = Some(cosmic.accent_text_color().into());
160 } else {
161 appearance.background = Some(Background::Color(background));
162 appearance.icon_color = icon;
163 appearance.text_color = text;
164 }
165 }
166 Button::MenuItem => {
167 let (background, text, icon) = color(&cosmic.background.component);
168 appearance.background = Some(Background::Color(background));
169 appearance.icon_color = icon;
170 appearance.text_color = text;
171 corner_radii = &cosmic.corner_radii.radius_s;
172 }
173 Button::MenuRoot => {
174 appearance.background = None;
175 appearance.icon_color = None;
176 appearance.text_color = None;
177 }
178 }
179
180 appearance.border_radius = (*corner_radii).into();
181
182 if focused {
183 appearance.outline_width = 1.0;
184 appearance.outline_color = cosmic.accent.base.into();
185 appearance.border_width = 2.0;
186 appearance.border_color = Color::TRANSPARENT;
187 }
188
189 appearance
190}
191
192impl Catalog for crate::Theme {
193 type Class = Button;
194
195 fn active(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
196 if let Button::Custom { active, .. } = style {
197 return active(focused, self);
198 }
199
200 let mut s = appearance(self, focused, selected, false, style, move |component| {
201 let text_color = if matches!(
202 style,
203 Button::Icon | Button::IconVertical | Button::HeaderBar
204 ) && selected
205 {
206 Some(self.cosmic().accent_text_color().into())
207 } else {
208 Some(component.on.into())
209 };
210
211 (component.base.into(), text_color, text_color)
212 });
213
214 if let Button::ListItem(_) = style {
215 if !selected {
216 s.background = None;
217 }
218 }
219
220 s
221 }
222
223 fn disabled(&self, style: &Self::Class) -> Style {
224 if let Button::Custom { disabled, .. } = style {
225 return disabled(self);
226 }
227
228 appearance(self, false, false, true, style, |component| {
229 let mut background = Color::from(component.base);
230 background.a *= 0.5;
231 (
232 background,
233 Some(component.on_disabled.into()),
234 Some(component.on_disabled.into()),
235 )
236 })
237 }
238
239 fn drop_target(&self, style: &Self::Class) -> Style {
240 self.active(false, false, style)
241 }
242
243 fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
244 if let Button::Custom { hovered, .. } = style {
245 return hovered(focused, self);
246 }
247
248 let mut s = appearance(
249 self,
250 focused || matches!(style, Button::Image),
251 selected,
252 false,
253 style,
254 |component| {
255 let text_color = if matches!(
256 style,
257 Button::Icon | Button::IconVertical | Button::HeaderBar
258 ) && selected
259 {
260 Some(self.cosmic().accent_text_color().into())
261 } else {
262 Some(component.on.into())
263 };
264
265 (component.hover.into(), text_color, text_color)
266 },
267 );
268
269 if let Button::ListItem(_) = style {
270 if !selected {
271 s.background = None;
272 }
273 }
274
275 s
276 }
277
278 fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
279 if let Button::Custom { pressed, .. } = style {
280 return pressed(focused, self);
281 }
282
283 appearance(self, focused, selected, false, style, |component| {
284 let text_color = if matches!(
285 style,
286 Button::Icon | Button::IconVertical | Button::HeaderBar
287 ) && selected
288 {
289 Some(self.cosmic().accent_text_color().into())
290 } else {
291 Some(component.on.into())
292 };
293
294 (component.pressed.into(), text_color, text_color)
295 })
296 }
297
298 fn selection_background(&self) -> Background {
299 Background::Color(self.cosmic().primary.base.into())
300 }
301}