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,
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.base.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.base.into());
122 appearance.text_color = Some(cosmic.accent.base.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 => {
152 corner_radii = &[0.0; 4];
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.base.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 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_color().into())
207 } else {
208 Some(component.on.into())
209 };
210
211 (component.base.into(), text_color, text_color)
212 })
213 }
214
215 fn disabled(&self, style: &Self::Class) -> Style {
216 if let Button::Custom { disabled, .. } = style {
217 return disabled(self);
218 }
219
220 appearance(self, false, false, true, style, |component| {
221 let mut background = Color::from(component.base);
222 background.a *= 0.5;
223 (
224 background,
225 Some(component.on_disabled.into()),
226 Some(component.on_disabled.into()),
227 )
228 })
229 }
230
231 fn drop_target(&self, style: &Self::Class) -> Style {
232 self.active(false, false, style)
233 }
234
235 fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
236 if let Button::Custom { hovered, .. } = style {
237 return hovered(focused, self);
238 }
239
240 appearance(
241 self,
242 focused || matches!(style, Button::Image),
243 selected,
244 false,
245 style,
246 |component| {
247 let text_color = if matches!(
248 style,
249 Button::Icon | Button::IconVertical | Button::HeaderBar
250 ) && selected
251 {
252 Some(self.cosmic().accent_color().into())
253 } else {
254 Some(component.on.into())
255 };
256
257 (component.hover.into(), text_color, text_color)
258 },
259 )
260 }
261
262 fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
263 if let Button::Custom { pressed, .. } = style {
264 return pressed(focused, self);
265 }
266
267 appearance(self, focused, selected, false, style, |component| {
268 let text_color = if matches!(
269 style,
270 Button::Icon | Button::IconVertical | Button::HeaderBar
271 ) && selected
272 {
273 Some(self.cosmic().accent_color().into())
274 } else {
275 Some(component.on.into())
276 };
277
278 (component.pressed.into(), text_color, text_color)
279 })
280 }
281
282 fn selection_background(&self) -> Background {
283 Background::Color(self.cosmic().primary.base.into())
284 }
285}