cosmic/theme/style/
segmented_button.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Contains stylesheet implementation for [`crate::widget::segmented_button`].
5
6use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet};
7use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance};
8use iced::Border;
9use iced_core::{Background, border::Radius};
10use palette::WithAlpha;
11
12#[derive(Default)]
13pub enum SegmentedButton {
14    /// A tabbed widget for switching between views in an interface.
15    #[default]
16    TabBar,
17    /// A widget for multiple choice selection.
18    Control,
19    /// Navigation bar style
20    NavBar,
21    /// File browser
22    FileNav,
23    /// Or implement any custom theme of your liking.
24    Custom(Box<dyn Fn(&Theme) -> Appearance>),
25}
26
27impl StyleSheet for Theme {
28    type Style = SegmentedButton;
29
30    #[allow(clippy::too_many_lines)]
31    fn horizontal(&self, style: &Self::Style) -> Appearance {
32        let cosmic = self.cosmic();
33        let container = self.current_container();
34        match style {
35            SegmentedButton::Control => {
36                let rad_xl = cosmic.corner_radii.radius_xl;
37                let rad_0 = cosmic.corner_radii.radius_0;
38                let active = horizontal::selection_active(cosmic, &container.component);
39                Appearance {
40                    background: Some(Background::Color(container.component.base.into())),
41                    border: Border {
42                        radius: rad_xl.into(),
43                        ..Default::default()
44                    },
45                    inactive: ItemStatusAppearance {
46                        background: None,
47                        first: ItemAppearance {
48                            border: Border {
49                                radius: Radius::from([rad_xl[0], rad_0[1], rad_0[2], rad_xl[3]]),
50                                ..Default::default()
51                            },
52                        },
53                        middle: ItemAppearance {
54                            border: Border {
55                                radius: cosmic.corner_radii.radius_0.into(),
56                                ..Default::default()
57                            },
58                        },
59                        last: ItemAppearance {
60                            border: Border {
61                                radius: Radius::from([rad_0[0], rad_xl[1], rad_xl[2], rad_0[3]]),
62                                ..Default::default()
63                            },
64                        },
65                        text_color: container.component.on.into(),
66                    },
67                    hover: hover(cosmic, &active, 0.2),
68                    pressed: hover(cosmic, &active, 0.15),
69                    active,
70                    ..Default::default()
71                }
72            }
73
74            SegmentedButton::NavBar | SegmentedButton::FileNav => Appearance {
75                active_width: 0.0,
76                ..horizontal::tab_bar(cosmic, container)
77            },
78
79            SegmentedButton::TabBar => horizontal::tab_bar(cosmic, container),
80
81            SegmentedButton::Custom(func) => func(self),
82        }
83    }
84
85    #[allow(clippy::too_many_lines)]
86    fn vertical(&self, style: &Self::Style) -> Appearance {
87        let cosmic = self.cosmic();
88        let container = self.current_container();
89        match style {
90            SegmentedButton::Control => {
91                let rad_xl = cosmic.corner_radii.radius_xl;
92                let rad_0 = cosmic.corner_radii.radius_0;
93                let active = vertical::selection_active(cosmic, &container.component);
94                Appearance {
95                    background: Some(Background::Color(container.component.base.into())),
96                    border: Border {
97                        radius: rad_xl.into(),
98                        ..Default::default()
99                    },
100                    inactive: ItemStatusAppearance {
101                        background: None,
102                        first: ItemAppearance {
103                            border: Border {
104                                radius: Radius::from([rad_xl[0], rad_xl[1], rad_0[0], rad_0[0]]),
105                                ..Default::default()
106                            },
107                        },
108                        middle: ItemAppearance {
109                            border: Border {
110                                radius: cosmic.corner_radii.radius_0.into(),
111                                ..Default::default()
112                            },
113                        },
114                        last: ItemAppearance {
115                            border: Border {
116                                radius: Radius::from([rad_0[0], rad_0[1], rad_xl[2], rad_xl[3]]),
117                                ..Default::default()
118                            },
119                        },
120                        text_color: container.component.on.into(),
121                    },
122                    hover: hover(cosmic, &active, 0.2),
123                    pressed: hover(cosmic, &active, 0.15),
124                    active,
125                    ..Default::default()
126                }
127            }
128
129            SegmentedButton::NavBar | SegmentedButton::FileNav => Appearance {
130                active_width: 0.0,
131                ..vertical::tab_bar(cosmic, container)
132            },
133
134            SegmentedButton::TabBar => vertical::tab_bar(cosmic, container),
135
136            SegmentedButton::Custom(func) => func(self),
137        }
138    }
139}
140
141mod horizontal {
142    use super::Appearance;
143    use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
144    use cosmic_theme::{Component, Container};
145    use iced::Border;
146    use iced_core::{Background, border::Radius};
147    use palette::WithAlpha;
148
149    pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance {
150        let active = tab_bar_active(cosmic);
151        let hc = cosmic.is_high_contrast;
152        let border = if hc {
153            Border {
154                color: container.component.border.into(),
155                radius: cosmic.corner_radii.radius_0.into(),
156                width: 1.0,
157            }
158        } else {
159            Border::default()
160        };
161
162        Appearance {
163            active_width: 4.0,
164            border: Border {
165                radius: cosmic.corner_radii.radius_0.into(),
166                ..Default::default()
167            },
168            inactive: ItemStatusAppearance {
169                background: None,
170                first: ItemAppearance { border },
171                middle: ItemAppearance { border },
172                last: ItemAppearance { border },
173                text_color: container.component.on.into(),
174            },
175            hover: super::hover(cosmic, &active, 0.3),
176            pressed: super::hover(cosmic, &active, 0.25),
177            active,
178            ..Default::default()
179        }
180    }
181
182    pub fn selection_active(
183        cosmic: &cosmic_theme::Theme,
184        component: &Component,
185    ) -> ItemStatusAppearance {
186        let rad_xl = cosmic.corner_radii.radius_xl;
187        let rad_0 = cosmic.corner_radii.radius_0;
188
189        ItemStatusAppearance {
190            background: Some(Background::Color(
191                cosmic.palette.neutral_5.with_alpha(0.1).into(),
192            )),
193            first: ItemAppearance {
194                border: Border {
195                    radius: Radius::from([rad_xl[0], rad_0[1], rad_0[2], rad_xl[3]]),
196                    ..Default::default()
197                },
198            },
199            middle: ItemAppearance {
200                border: Border {
201                    radius: cosmic.corner_radii.radius_0.into(),
202                    ..Default::default()
203                },
204            },
205            last: ItemAppearance {
206                border: Border {
207                    radius: Radius::from([rad_0[0], rad_xl[1], rad_xl[2], rad_0[3]]),
208                    ..Default::default()
209                },
210            },
211            text_color: cosmic.accent_text_color().into(),
212        }
213    }
214
215    pub fn tab_bar_active(cosmic: &cosmic_theme::Theme) -> ItemStatusAppearance {
216        let rad_s = cosmic.corner_radii.radius_s;
217        let rad_0 = cosmic.corner_radii.radius_0;
218        ItemStatusAppearance {
219            background: Some(Background::Color(
220                cosmic.palette.neutral_5.with_alpha(0.2).into(),
221            )),
222            first: ItemAppearance {
223                border: Border {
224                    color: cosmic.accent.base.into(),
225                    radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]),
226                    width: 0.0,
227                },
228            },
229            middle: ItemAppearance {
230                border: Border {
231                    color: cosmic.accent.base.into(),
232                    radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]),
233                    width: 0.0,
234                },
235            },
236            last: ItemAppearance {
237                border: Border {
238                    color: cosmic.accent.base.into(),
239                    radius: Radius::from([rad_s[0], rad_s[1], rad_0[2], rad_0[3]]),
240                    width: 0.0,
241                },
242            },
243            text_color: cosmic.accent_text_color().into(),
244        }
245    }
246}
247
248mod vertical {
249    use super::Appearance;
250    use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
251    use cosmic_theme::{Component, Container};
252    use iced::Border;
253    use iced_core::{Background, border::Radius};
254    use palette::WithAlpha;
255
256    pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance {
257        let active = tab_bar_active(cosmic);
258        Appearance {
259            active_width: 4.0,
260            border: Border {
261                radius: cosmic.corner_radii.radius_0.into(),
262                ..Default::default()
263            },
264            inactive: ItemStatusAppearance {
265                background: None,
266                text_color: container.component.on.into(),
267                ..active
268            },
269            hover: super::hover(cosmic, &active, 0.3),
270            pressed: super::hover(cosmic, &active, 0.25),
271            active,
272            ..Default::default()
273        }
274    }
275
276    pub fn selection_active(
277        cosmic: &cosmic_theme::Theme,
278        component: &Component,
279    ) -> ItemStatusAppearance {
280        let rad_0 = cosmic.corner_radii.radius_0;
281        let rad_xl = cosmic.corner_radii.radius_xl;
282
283        ItemStatusAppearance {
284            background: Some(Background::Color(
285                cosmic.palette.neutral_5.with_alpha(0.1).into(),
286            )),
287            first: ItemAppearance {
288                border: Border {
289                    radius: Radius::from([rad_xl[0], rad_xl[1], rad_0[2], rad_0[3]]),
290                    ..Default::default()
291                },
292            },
293            middle: ItemAppearance {
294                border: Border {
295                    radius: cosmic.corner_radii.radius_0.into(),
296                    ..Default::default()
297                },
298            },
299            last: ItemAppearance {
300                border: Border {
301                    radius: Radius::from([rad_0[0], rad_0[1], rad_xl[2], rad_xl[3]]),
302                    ..Default::default()
303                },
304            },
305            text_color: cosmic.accent_text_color().into(),
306        }
307    }
308
309    pub fn tab_bar_active(cosmic: &cosmic_theme::Theme) -> ItemStatusAppearance {
310        ItemStatusAppearance {
311            background: Some(Background::Color(
312                cosmic.palette.neutral_5.with_alpha(0.2).into(),
313            )),
314            first: ItemAppearance {
315                border: Border {
316                    radius: cosmic.corner_radii.radius_m.into(),
317                    width: 0.0,
318                    ..Default::default()
319                },
320            },
321            middle: ItemAppearance {
322                border: Border {
323                    radius: cosmic.corner_radii.radius_m.into(),
324                    width: 0.0,
325                    ..Default::default()
326                },
327            },
328            last: ItemAppearance {
329                border: Border {
330                    radius: cosmic.corner_radii.radius_m.into(),
331                    width: 0.0,
332                    ..Default::default()
333                },
334            },
335            text_color: cosmic.accent_text_color().into(),
336        }
337    }
338}
339
340pub fn hover(
341    cosmic: &cosmic_theme::Theme,
342    default: &ItemStatusAppearance,
343    alpha: f32,
344) -> ItemStatusAppearance {
345    ItemStatusAppearance {
346        background: Some(Background::Color(
347            cosmic.palette.neutral_5.with_alpha(alpha).into(),
348        )),
349        text_color: cosmic.accent_text_color().into(),
350        ..*default
351    }
352}