Skip to main content

cosmic/widget/button/
icon.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use super::{Builder, ButtonClass};
5use crate::Element;
6use crate::widget::icon::Handle;
7use crate::widget::tooltip;
8use apply::Apply;
9use iced_core::font::Weight;
10use iced_core::text::LineHeight;
11use iced_core::widget::Id;
12use iced_core::{Alignment, Length, Padding};
13use std::borrow::Cow;
14
15pub type Button<'a, Message> = Builder<'a, Message, Icon>;
16
17/// The icon variant of a button.
18pub struct Icon {
19    handle: Handle,
20    vertical: bool,
21    selected: bool,
22}
23
24/// A button constructed from an icon handle, using icon button styling.
25pub fn icon<'a, Message>(handle: impl Into<Handle>) -> Button<'a, Message> {
26    Button::new(Icon {
27        handle: handle.into(),
28        vertical: false,
29        selected: false,
30    })
31}
32
33impl<Message> Button<'_, Message> {
34    pub fn new(icon: Icon) -> Self {
35        let guard = crate::theme::THEME.lock().unwrap();
36        let theme = guard.cosmic();
37        let padding = theme.space_xxs();
38
39        Self {
40            id: Id::unique(),
41            label: Cow::Borrowed(""),
42            #[cfg(feature = "a11y")]
43            name: Cow::Borrowed(""),
44            #[cfg(feature = "a11y")]
45            description: Cow::Borrowed(""),
46            tooltip: Cow::Borrowed(""),
47            on_press: None,
48            width: Length::Shrink,
49            height: Length::Shrink,
50            padding: Padding::from(padding),
51            spacing: theme.space_xxxs(),
52            icon_size: if icon.handle.symbolic { 16 } else { 24 },
53            line_height: 20,
54            font_size: 14,
55            font_weight: Weight::Normal,
56            class: ButtonClass::Icon,
57            variant: icon,
58        }
59    }
60
61    /// Applies the **Extra Small** button size preset.
62    pub fn extra_small(mut self) -> Self {
63        let guard = crate::theme::THEME.lock().unwrap();
64        let theme = guard.cosmic();
65
66        self.font_size = 14;
67        self.font_weight = Weight::Normal;
68        self.icon_size = 16;
69        self.line_height = 20;
70        self.padding = Padding::from(theme.space_xxs());
71        self.spacing = theme.space_xxxs();
72
73        self
74    }
75
76    /// Applies the **Medium** button size preset.
77    pub fn medium(mut self) -> Self {
78        let guard = crate::theme::THEME.lock().unwrap();
79        let theme = guard.cosmic();
80
81        self.font_size = 24;
82        self.font_weight = Weight::Normal;
83        self.icon_size = 32;
84        self.line_height = 32;
85        self.padding = Padding::from(theme.space_xs());
86        self.spacing = theme.space_xxs();
87
88        self
89    }
90
91    /// Applies the **Large** button size preset.
92    pub fn large(mut self) -> Self {
93        let guard = crate::theme::THEME.lock().unwrap();
94        let theme = guard.cosmic();
95
96        self.font_size = 28;
97        self.font_weight = Weight::Normal;
98        self.icon_size = 40;
99        self.line_height = 36;
100        self.padding = Padding::from(theme.space_xs());
101        self.spacing = theme.space_xxs();
102
103        self
104    }
105
106    /// Applies the **Extra Large** button size preset.
107    pub fn extra_large(mut self) -> Self {
108        let guard = crate::theme::THEME.lock().unwrap();
109        let theme = guard.cosmic();
110        let padding = theme.space_xs();
111
112        self.font_size = 32;
113        self.font_weight = Weight::Light;
114        self.icon_size = 56;
115        self.line_height = 44;
116        self.padding = Padding::from(padding);
117        self.spacing = theme.space_xxs();
118
119        self
120    }
121
122    #[inline]
123    pub fn selected(mut self, selected: bool) -> Self {
124        self.variant.selected = selected;
125        self
126    }
127
128    #[inline]
129    pub fn vertical(mut self, vertical: bool) -> Self {
130        self.variant.vertical = vertical;
131        self.class = ButtonClass::IconVertical;
132        self
133    }
134}
135
136impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
137    fn from(builder: Button<'a, Message>) -> Element<'a, Message> {
138        let mut content = Vec::with_capacity(2);
139
140        content.push(
141            crate::widget::icon(builder.variant.handle.clone())
142                .size(builder.icon_size)
143                .into(),
144        );
145
146        if !builder.label.is_empty() {
147            content.push(
148                crate::widget::text(builder.label)
149                    .size(builder.font_size)
150                    .line_height(LineHeight::Absolute(builder.line_height.into()))
151                    .font(crate::font::Font {
152                        weight: builder.font_weight,
153                        ..crate::font::default()
154                    })
155                    .into(),
156            );
157        }
158
159        let mut button = if builder.variant.vertical {
160            crate::widget::column::with_children(content)
161                .padding(builder.padding)
162                .spacing(builder.spacing)
163                .align_x(Alignment::Center)
164                .apply(super::custom)
165        } else {
166            crate::widget::row::with_children(content)
167                .padding(builder.padding)
168                .width(builder.width)
169                .height(builder.height)
170                .spacing(builder.spacing)
171                .align_y(Alignment::Center)
172                .apply(super::custom)
173        };
174
175        #[cfg(feature = "a11y")]
176        {
177            button = button.name(builder.name).description(builder.description);
178        }
179
180        let button = button
181            .padding(0)
182            .id(builder.id)
183            .on_press_maybe(builder.on_press)
184            .selected(builder.variant.selected)
185            .class(builder.class);
186
187        if builder.tooltip.is_empty() {
188            button.into()
189        } else {
190            tooltip(
191                button,
192                crate::widget::text(builder.tooltip)
193                    .size(builder.font_size)
194                    .font(crate::font::Font {
195                        weight: builder.font_weight,
196                        ..crate::font::default()
197                    }),
198                tooltip::Position::Top,
199            )
200            .into()
201        }
202    }
203}