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