material_egui/widgets/
button.rs

1use egui::{Align2, Color32, FontId, Id, LayerId, Order, Response, Stroke, Ui};
2use crate::MaterialColors;
3#[derive(Clone, Default, Debug)]
4pub struct Button {
5    style: MaterialColors
6}
7impl Button {
8    pub fn new(style: &MaterialColors) -> Self {
9        Self {
10            style: style.clone(),
11        }
12    }
13    //
14    pub fn elevated(&self, ui: &mut Ui, text: impl Into<String>) -> Response {
15        Self::m3_button(
16            ui,
17            text,
18            self.style.surface_container_low,
19            self.style.primary,
20            Self::stroke_null(),
21        )
22    }
23    pub fn filled(&self, ui: &mut Ui, text: impl Into<String>) -> Response {
24        Self::m3_button(
25            ui,
26            text,
27            self.style.primary,
28            self.style.on_primary,
29            Self::stroke_null(),
30        )
31    }
32    pub fn filled_tonal(&self, ui: &mut Ui, text: impl Into<String>) -> Response {
33        Self::m3_button(
34            ui,
35            text,
36            self.style.primary_container,
37            self.style.on_primary_container,
38            Self::stroke_null(),
39        )
40    }
41    pub fn outlined(&self, ui: &mut Ui, text: impl Into<String>) -> Response {
42        Self::m3_button(
43            ui,
44            text,
45            self.style.surface,
46            self.style.primary,
47            Stroke {
48                width: 1.5,
49                color: self.style.outline,
50            },
51        )
52    }
53
54    fn m3_button(
55        ui: &mut egui::Ui,
56        text: impl Into<String>,
57        container_style: Color32,
58        text_style: Color32,
59        stroke: Stroke,
60    ) -> egui::Response {
61        let text = text.into().replace('\n', "");
62
63        // initial rectangle
64        let scale = 1.5;
65
66        let desired_size = ui.spacing().interact_size.y * egui::vec2(scale * 2., scale);
67        let (mut rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
68
69        // styling
70        let mut container_style = container_style;
71        if response.hovered() {
72            container_style = Self::delta(container_style, 10);
73        };
74
75        let mut width = 0.;
76        let mut height = 0.;
77        let mut position = rect.left_center();
78        position.x += 10.;
79
80        ui.with_layer_id(
81            LayerId {
82                order: Order::Foreground,
83                id: Id::new("foreground"),
84            },
85            |ui| {
86                let ui = ui.painter().text(
87                    position,
88                    Align2::LEFT_CENTER,
89                    text,
90                    FontId::proportional(rect.width() / 4.5),
91                    text_style,
92                );
93                width = ui.width();
94                height = ui.height();
95            },
96        );
97
98        // box
99        let radius = 0.5 * rect.height();
100        rect.set_width(width + 20.);
101
102        ui.with_layer_id(LayerId::background(), |ui| {
103            ui.painter().rect(rect, radius, container_style, stroke);
104        });
105
106        response
107    }
108
109
110    fn stroke_null() -> Stroke {
111        Stroke {
112            width: 0.0,
113            color: Default::default(),
114        }
115    }
116
117    // makes the color lighter or darker depending on the colors brightness
118    fn delta(i: Color32, change: u8) -> Color32 {
119        let f = change;
120        let (mut r, mut g, mut b, a) = (i.r(), i.g(), i.b(), i.a());
121        if (r as u32 + g as u32 + b as u32) > 384 {
122            r -= f;
123            g -= f;
124            b -= f;
125        } else {
126            r += f;
127            g += f;
128            b += f;
129        }
130        Color32::from_rgba_premultiplied(r, g, b, a)
131    }
132}