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(theme.transparent).on.into());
130 appearance.text_color = Some(cosmic.background(theme.transparent).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(theme.transparent).on.into());
138 appearance.text_color = Some(cosmic.background(theme.transparent).on.into());
139 }
140 Button::MenuFolder => {
141 let component = &cosmic.background(theme.transparent).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 = Some(Background::Color(
155 cosmic.primary(theme.transparent).component.hover.into(),
156 ));
157 appearance.icon_color = Some(cosmic.accent.base.into());
158 appearance.text_color = Some(cosmic.accent_text_color().into());
159 } else {
160 appearance.background = Some(Background::Color(background));
161 appearance.icon_color = icon;
162 appearance.text_color = text;
163 }
164 }
165 Button::MenuItem => {
166 let (background, text, icon) = color(&cosmic.background(theme.transparent).component);
167 appearance.background = Some(Background::Color(background));
168 appearance.icon_color = icon;
169 appearance.text_color = text;
170 corner_radii = &cosmic.corner_radii.radius_s;
171 }
172 Button::MenuRoot => {
173 appearance.background = None;
174 appearance.icon_color = None;
175 appearance.text_color = None;
176 }
177 }
178
179 appearance.border_radius = (*corner_radii).into();
180
181 if focused {
182 appearance.outline_width = 1.0;
183 appearance.outline_color = cosmic.accent.base.into();
184 appearance.border_width = 2.0;
185 appearance.border_color = Color::TRANSPARENT;
186 }
187
188 appearance
189}
190
191impl Catalog for crate::Theme {
192 type Class = Button;
193
194 fn active(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
195 if let Button::Custom { active, .. } = style {
196 return active(focused, self);
197 }
198
199 appearance(self, focused, selected, false, style, move |component| {
200 let text_color = if matches!(
201 style,
202 Button::Icon | Button::IconVertical | Button::HeaderBar
203 ) && selected
204 {
205 Some(self.cosmic().accent_text_color().into())
206 } else {
207 Some(component.on.into())
208 };
209
210 (component.base.into(), text_color, text_color)
211 })
212 }
213
214 fn disabled(&self, style: &Self::Class) -> Style {
215 if let Button::Custom { disabled, .. } = style {
216 return disabled(self);
217 }
218
219 appearance(self, false, false, true, style, |component| {
220 let mut background = Color::from(component.base);
221 if !matches!(
222 style,
223 Button::MenuFolder | Button::MenuItem | Button::MenuRoot
224 ) {
225 background.a *= 0.5;
226 }
227 (
228 background,
229 Some(component.on_disabled.into()),
230 Some(component.on_disabled.into()),
231 )
232 })
233 }
234
235 fn drop_target(&self, style: &Self::Class) -> Style {
236 self.active(false, false, style)
237 }
238
239 fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
240 if let Button::Custom { hovered, .. } = style {
241 return hovered(focused, self);
242 }
243
244 appearance(
245 self,
246 focused || matches!(style, Button::Image),
247 selected,
248 false,
249 style,
250 |component| {
251 let text_color = if matches!(
252 style,
253 Button::Icon | Button::IconVertical | Button::HeaderBar
254 ) && selected
255 {
256 Some(self.cosmic().accent_text_color().into())
257 } else {
258 Some(component.on.into())
259 };
260
261 (component.hover.into(), text_color, text_color)
262 },
263 )
264 }
265
266 fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
267 if let Button::Custom { pressed, .. } = style {
268 return pressed(focused, self);
269 }
270
271 appearance(self, focused, selected, false, style, |component| {
272 let text_color = if matches!(
273 style,
274 Button::Icon | Button::IconVertical | Button::HeaderBar
275 ) && selected
276 {
277 Some(self.cosmic().accent_text_color().into())
278 } else {
279 Some(component.on.into())
280 };
281
282 (component.pressed.into(), text_color, text_color)
283 })
284 }
285
286 fn selection_background(&self) -> Background {
287 Background::Color(self.cosmic().primary(self.transparent).base.into())
288 }
289}