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