egui/
widget_style.rs

1use emath::Vec2;
2use epaint::{Color32, FontId, Shadow, Stroke, text::TextWrapMode};
3
4use crate::{
5    Frame, Response, Style, TextStyle,
6    style::{WidgetVisuals, Widgets},
7};
8
9/// General text style
10pub struct TextVisuals {
11    /// Font used
12    pub font_id: FontId,
13
14    /// Font color
15    pub color: Color32,
16
17    /// Text decoration
18    pub underline: Stroke,
19    pub strikethrough: Stroke,
20}
21
22/// General widget style
23pub struct WidgetStyle {
24    pub frame: Frame,
25
26    pub text: TextVisuals,
27
28    pub stroke: Stroke,
29}
30
31pub struct ButtonStyle {
32    pub frame: Frame,
33    pub text_style: TextVisuals,
34}
35
36pub struct CheckboxStyle {
37    /// Frame around
38    pub frame: Frame,
39
40    /// Text next to it
41    pub text_style: TextVisuals,
42
43    /// Checkbox size
44    pub checkbox_size: f32,
45
46    /// Checkmark size
47    pub check_size: f32,
48
49    /// Frame of the checkbox itself
50    pub checkbox_frame: Frame,
51
52    /// Checkmark stroke
53    pub check_stroke: Stroke,
54}
55
56pub struct LabelStyle {
57    /// Frame around
58    pub frame: Frame,
59
60    /// Text style
61    pub text: TextVisuals,
62
63    /// Wrap mode used
64    pub wrap_mode: TextWrapMode,
65}
66
67pub struct SeparatorStyle {
68    /// How much space is allocated in the layout direction
69    pub spacing: f32,
70
71    /// How to paint it
72    pub stroke: Stroke,
73}
74
75#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
76pub enum WidgetState {
77    Noninteractive,
78    #[default]
79    Inactive,
80    Hovered,
81    Active,
82}
83
84impl Widgets {
85    pub fn state(&self, state: WidgetState) -> &WidgetVisuals {
86        match state {
87            WidgetState::Noninteractive => &self.noninteractive,
88            WidgetState::Inactive => &self.inactive,
89            WidgetState::Hovered => &self.hovered,
90            WidgetState::Active => &self.active,
91        }
92    }
93}
94
95impl Response {
96    pub fn widget_state(&self) -> WidgetState {
97        if !self.sense.interactive() {
98            WidgetState::Noninteractive
99        } else if self.is_pointer_button_down_on() || self.has_focus() || self.clicked() {
100            WidgetState::Active
101        } else if self.hovered() || self.highlighted() {
102            WidgetState::Hovered
103        } else {
104            WidgetState::Inactive
105        }
106    }
107}
108
109impl Style {
110    pub fn widget_style(&self, state: WidgetState) -> WidgetStyle {
111        let visuals = self.visuals.widgets.state(state);
112        let font_id = self.override_font_id.clone();
113        WidgetStyle {
114            frame: Frame {
115                fill: visuals.bg_fill,
116                stroke: visuals.bg_stroke,
117                corner_radius: visuals.corner_radius,
118                inner_margin: self.spacing.button_padding.into(),
119                ..Default::default()
120            },
121            stroke: visuals.fg_stroke,
122            text: TextVisuals {
123                color: self
124                    .visuals
125                    .override_text_color
126                    .unwrap_or_else(|| visuals.text_color()),
127                font_id: font_id.unwrap_or_else(|| TextStyle::Body.resolve(self)),
128                strikethrough: Stroke::NONE,
129                underline: Stroke::NONE,
130            },
131        }
132    }
133
134    pub fn button_style(&self, state: WidgetState, selected: bool) -> ButtonStyle {
135        let mut visuals = *self.visuals.widgets.state(state);
136        let mut ws = self.widget_style(state);
137
138        if selected {
139            visuals.weak_bg_fill = self.visuals.selection.bg_fill;
140            visuals.bg_fill = self.visuals.selection.bg_fill;
141            visuals.fg_stroke = self.visuals.selection.stroke;
142            ws.text.color = self.visuals.selection.stroke.color;
143        }
144
145        ButtonStyle {
146            frame: Frame {
147                fill: visuals.weak_bg_fill,
148                stroke: visuals.bg_stroke,
149                corner_radius: visuals.corner_radius,
150                outer_margin: (-Vec2::splat(visuals.expansion)).into(),
151                inner_margin: (self.spacing.button_padding + Vec2::splat(visuals.expansion)
152                    - Vec2::splat(visuals.bg_stroke.width))
153                .into(),
154                ..Default::default()
155            },
156            text_style: ws.text,
157        }
158    }
159
160    pub fn checkbox_style(&self, state: WidgetState) -> CheckboxStyle {
161        let visuals = self.visuals.widgets.state(state);
162        let ws = self.widget_style(state);
163        CheckboxStyle {
164            frame: Frame::new(),
165            checkbox_size: self.spacing.icon_width,
166            check_size: self.spacing.icon_width_inner,
167            checkbox_frame: Frame {
168                fill: visuals.bg_fill,
169                corner_radius: visuals.corner_radius,
170                stroke: visuals.bg_stroke,
171                // Use the inner_margin for the expansion
172                inner_margin: visuals.expansion.into(),
173                ..Default::default()
174            },
175            text_style: ws.text,
176            check_stroke: ws.stroke,
177        }
178    }
179
180    pub fn label_style(&self, state: WidgetState) -> LabelStyle {
181        let ws = self.widget_style(state);
182        LabelStyle {
183            frame: Frame {
184                fill: ws.frame.fill,
185                inner_margin: 0.0.into(),
186                outer_margin: 0.0.into(),
187                stroke: Stroke::NONE,
188                shadow: Shadow::NONE,
189                corner_radius: 0.into(),
190            },
191            text: ws.text,
192            wrap_mode: TextWrapMode::Wrap,
193        }
194    }
195
196    pub fn separator_style(&self, _state: WidgetState) -> SeparatorStyle {
197        let visuals = self.visuals.noninteractive();
198        SeparatorStyle {
199            spacing: 6.0,
200            stroke: visuals.bg_stroke,
201        }
202    }
203}