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