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
9pub struct TextVisuals {
11 pub font_id: FontId,
13
14 pub color: Color32,
16
17 pub underline: Stroke,
19 pub strikethrough: Stroke,
20}
21
22pub 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 pub frame: Frame,
39
40 pub text_style: TextVisuals,
42
43 pub checkbox_size: f32,
45
46 pub check_size: f32,
48
49 pub checkbox_frame: Frame,
51
52 pub check_stroke: Stroke,
54}
55
56pub struct LabelStyle {
57 pub frame: Frame,
59
60 pub text: TextVisuals,
62
63 pub wrap_mode: TextWrapMode,
65}
66
67pub struct SeparatorStyle {
68 pub spacing: f32,
70
71 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 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}