widgetry/style/
button_style.rs

1use geom::CornerRadii;
2
3use crate::{
4    include_labeled_bytes, ButtonBuilder, Color, ControlState, EventCtx, Key, OutlineStyle,
5    ScreenDims, Style, Widget,
6};
7
8#[derive(Clone)]
9pub struct ButtonStyle {
10    pub fg: Color,
11    pub fg_disabled: Color,
12    pub outline: OutlineStyle,
13    pub bg: Color,
14    pub bg_hover: Color,
15    pub bg_disabled: Color,
16}
17
18impl<'a, 'c> ButtonStyle {
19    pub fn btn(&self) -> ButtonBuilder<'a, 'c> {
20        self.apply(ButtonBuilder::new())
21    }
22
23    pub fn apply(&self, builder: ButtonBuilder<'a, 'c>) -> ButtonBuilder<'a, 'c> {
24        let builder = builder
25            .label_color(self.fg, ControlState::Default)
26            .label_color(self.fg_disabled, ControlState::Disabled)
27            .image_color(self.fg, ControlState::Default)
28            .image_color(self.fg_disabled, ControlState::Disabled)
29            .bg_color(self.bg, ControlState::Default)
30            .bg_color(self.bg_hover, ControlState::Hovered)
31            .bg_color(self.bg_disabled, ControlState::Disabled);
32
33        if self.outline.0 > 0.0 {
34            builder.outline(self.outline, ControlState::Default)
35        } else {
36            builder
37        }
38    }
39
40    pub fn text<I: Into<String>>(&self, text: I) -> ButtonBuilder<'a, 'c> {
41        self.btn().label_text(text)
42    }
43
44    /// Note this usually removes color from the icon. Use `image_color(RewriteColor::NoOp,
45    /// ControlState::Default)` or similar to retain color.
46    pub fn icon(&self, image_path: &'a str) -> ButtonBuilder<'a, 'c> {
47        icon_button(self.btn().image_path(image_path))
48    }
49
50    pub fn icon_bytes(&self, labeled_bytes: (&'a str, &'a [u8])) -> ButtonBuilder<'a, 'c> {
51        icon_button(self.btn().image_bytes(labeled_bytes))
52    }
53
54    pub fn icon_text<I: Into<String>>(
55        &self,
56        image_path: &'a str,
57        text: I,
58    ) -> ButtonBuilder<'a, 'c> {
59        self.btn()
60            .label_text(text)
61            .image_path(image_path)
62            .image_dims(ScreenDims::square(18.0))
63    }
64
65    pub fn dropdown(&self) -> ButtonBuilder<'a, 'c> {
66        self.icon_bytes(include_labeled_bytes!("../../icons/arrow_drop_down.svg"))
67            .image_dims(12.0)
68            .stack_spacing(12.0)
69            .label_first()
70    }
71
72    pub fn popup(&self, text: &'a str) -> ButtonBuilder<'a, 'c> {
73        self.dropdown().label_text(text)
74    }
75}
76
77impl<'a, 'c> Style {
78    /// title: name of previous screen, which you'll return to
79    pub fn btn_back(&self, title: &'a str) -> ButtonBuilder<'a, 'c> {
80        self.btn_outline
81            .icon_bytes(include_labeled_bytes!("../../icons/nav_back.svg"))
82            .label_text(title)
83            .padding_left(8.0)
84    }
85
86    /// A right facing caret, like ">", suitable for paging to the "next" set of results
87    pub fn btn_next(&self) -> ButtonBuilder<'a, 'c> {
88        self.btn_plain
89            .icon_bytes(include_labeled_bytes!("../../icons/next.svg"))
90    }
91
92    /// A left facing caret, like "<", suitable for paging to the "next" set of results
93    pub fn btn_prev(&self) -> ButtonBuilder<'a, 'c> {
94        self.btn_plain
95            .icon_bytes(include_labeled_bytes!("../../icons/prev.svg"))
96    }
97
98    /// An "X" button
99    pub fn btn_close(&self) -> ButtonBuilder<'a, 'c> {
100        self.btn_plain
101            .icon_bytes(include_labeled_bytes!("../../icons/close.svg"))
102    }
103
104    /// An "X" button to close the current state. Bound to the escape key and aligned to the right,
105    /// usually after a title.
106    pub fn btn_close_widget(&self, ctx: &EventCtx) -> Widget {
107        self.btn_close()
108            .hotkey(Key::Escape)
109            .build_widget(ctx, "close")
110            .align_right()
111    }
112
113    pub fn btn_popup_icon_text(&self, icon_path: &'a str, text: &'a str) -> ButtonBuilder<'a, 'c> {
114        // The text is styled like an "outline" button, while the image is styled like a "solid"
115        // button.
116        self.btn_outline
117            .btn()
118            .label_text(text)
119            .image_path(icon_path)
120            .image_dims(25.0)
121            .image_color(self.btn_solid.fg, ControlState::Default)
122            .image_bg_color(self.btn_solid.bg, ControlState::Default)
123            .image_bg_color(self.btn_solid.bg_hover, ControlState::Hovered)
124            // Move the padding from the *entire button* to just the image, so we get a colored
125            // padded area around the image.
126            .padding(0)
127            .image_padding(8.0)
128            // ...though we still need to pad between the text and button edge
129            .padding_right(8.0)
130            // Round the button's image's exterior corners so they don't protrude past the button's
131            // corners. However, per design, we want the images interior corners to be
132            // unrounded.
133            .image_corner_rounding(CornerRadii {
134                top_left: 2.0,
135                top_right: 0.0,
136                bottom_right: 0.0,
137                bottom_left: 2.0,
138            })
139    }
140}
141
142// Captures some constants for uniform styling of icon-only buttons
143fn icon_button<'a, 'c>(builder: ButtonBuilder<'a, 'c>) -> ButtonBuilder<'a, 'c> {
144    builder.padding(8.0).image_dims(25.0)
145}