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