Skip to main content

cosmic/widget/button/
text.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use super::{Builder, ButtonClass};
5use crate::widget::{icon, row, tooltip};
6use crate::{Apply, Element};
7use iced_core::font::Weight;
8use iced_core::text::LineHeight;
9use iced_core::widget::Id;
10use iced_core::{Alignment, Length, Padding};
11use std::borrow::Cow;
12
13pub type Button<'a, Message> = Builder<'a, Message, Text>;
14
15/// A text button with the destructive style
16pub fn destructive<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
17    Button::new(Text::new())
18        .label(label)
19        .class(ButtonClass::Destructive)
20}
21
22/// A text button with the suggested style
23pub fn suggested<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
24    Button::new(Text::new())
25        .label(label)
26        .class(ButtonClass::Suggested)
27}
28
29/// A text button with the standard style
30pub fn standard<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
31    Button::new(Text::new()).label(label)
32}
33
34/// A text button with the text style
35pub fn text<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
36    Button::new(Text::new())
37        .label(label)
38        .class(ButtonClass::Text)
39}
40
41/// The text variant of a button.
42pub struct Text {
43    pub(super) leading_icon: Option<icon::Handle>,
44    pub(super) trailing_icon: Option<icon::Handle>,
45}
46
47impl Default for Text {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl Text {
54    pub const fn new() -> Self {
55        Self {
56            leading_icon: None,
57            trailing_icon: None,
58        }
59    }
60}
61
62impl<Message> Button<'_, Message> {
63    pub fn new(text: Text) -> Self {
64        let guard = crate::theme::THEME.lock().unwrap();
65        let theme = guard.cosmic();
66        Self {
67            id: Id::unique(),
68            label: Cow::Borrowed(""),
69            #[cfg(feature = "a11y")]
70            name: Cow::Borrowed(""),
71            #[cfg(feature = "a11y")]
72            description: Cow::Borrowed(""),
73            tooltip: Cow::Borrowed(""),
74            on_press: None,
75            width: Length::Shrink,
76            height: Length::Fixed(theme.space_l().into()),
77            padding: Padding::from([0, theme.space_s()]),
78            spacing: theme.space_xxxs(),
79            icon_size: 16,
80            line_height: 20,
81            font_size: 14,
82            font_weight: Weight::Normal,
83            class: ButtonClass::Standard,
84            variant: text,
85        }
86    }
87
88    pub fn leading_icon(mut self, icon: impl Into<icon::Handle>) -> Self {
89        self.variant.leading_icon = Some(icon.into());
90        self
91    }
92
93    pub fn trailing_icon(mut self, icon: impl Into<icon::Handle>) -> Self {
94        self.variant.trailing_icon = Some(icon.into());
95        self
96    }
97}
98
99impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Message> {
100    fn from(mut builder: Button<'a, Message>) -> Element<'a, Message> {
101        let trailing_icon = builder
102            .variant
103            .trailing_icon
104            .map(crate::widget::icon::Handle::icon);
105
106        let leading_icon = builder
107            .variant
108            .leading_icon
109            .map(crate::widget::icon::Handle::icon);
110
111        let label: Option<Element<'_, _>> = (!builder.label.is_empty()).then(|| {
112            let font = crate::font::Font {
113                weight: builder.font_weight,
114                ..crate::font::default()
115            };
116
117            // TODO: Avoid allocation
118            crate::widget::text(builder.label.to_string())
119                .size(builder.font_size)
120                .line_height(LineHeight::Absolute(builder.line_height.into()))
121                .font(font)
122                .into()
123        });
124
125        let mut button: super::Button<'a, Message> = row::with_capacity(3)
126            // Optional icon to place before label.
127            .push_maybe(leading_icon)
128            // Optional label between icons.
129            .push_maybe(label)
130            // Optional icon to place behind the label.
131            .push_maybe(trailing_icon)
132            .padding(builder.padding)
133            .width(builder.width)
134            .height(builder.height)
135            .spacing(builder.spacing)
136            .align_y(Alignment::Center)
137            .apply(super::custom)
138            .padding(0)
139            .id(builder.id)
140            .on_press_maybe(builder.on_press.take())
141            .class(builder.class);
142
143        #[cfg(feature = "a11y")]
144        {
145            if !builder.label.is_empty() {
146                button = button.name(builder.label)
147            }
148
149            button = button.description(builder.description);
150        }
151
152        if builder.tooltip.is_empty() {
153            button.into()
154        } else {
155            tooltip(
156                button,
157                crate::widget::text(builder.tooltip)
158                    .size(builder.font_size)
159                    .font(crate::font::Font {
160                        weight: builder.font_weight,
161                        ..crate::font::default()
162                    }),
163                tooltip::Position::Top,
164            )
165            .into()
166        }
167    }
168}