Skip to main content

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