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