1#![cfg_attr(not(feature = "experimental"), allow(dead_code, unused_imports))]
4
5use core::fmt::Debug;
6
7use emath::{Align2, Vec2};
8use epaint::{Color32, FontId, Stroke};
9
10use crate::{
11 Context, FontSelection, Frame, Response, Style, UiStack, WidgetAtom,
12 class::{Classes, HasClasses as _},
13 style::{WidgetVisuals, Widgets},
14};
15
16pub trait WidgetStyle: Debug + Clone + Send + Sync + core::any::Any + 'static {}
18
19#[derive(Debug, Clone)]
21pub struct TextVisuals {
22 pub font_id: FontId,
24
25 pub color: Color32,
27}
28
29impl TextVisuals {
30 pub fn new(style: &Style, font: impl Into<FontSelection>, color: Color32) -> Self {
34 Self {
35 color,
36 font_id: style
37 .override_font_id
38 .clone()
39 .unwrap_or_else(|| font.into().resolve(style)),
40 }
41 }
42
43 pub fn from_widget_visuals(
45 style: &Style,
46 font: impl Into<FontSelection>,
47 widget_visuals: &WidgetVisuals,
48 ) -> Self {
49 Self::new(style, font, widget_visuals.text_color())
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct AtomLayoutStyle {
56 pub align2: Option<Align2>,
60
61 pub min_size: Vec2,
63
64 pub gap: f32,
66
67 pub frame: Frame,
69
70 pub text_style: TextVisuals,
72
73 pub image_tint: Color32,
75}
76
77impl Default for AtomLayoutStyle {
78 fn default() -> Self {
79 Self {
80 align2: None,
81 min_size: Vec2::ZERO,
82 gap: 0.0,
83 frame: Frame::default(),
84 text_style: TextVisuals {
85 font_id: FontId::default(),
86 color: Color32::WHITE,
87 },
88 image_tint: Color32::WHITE,
89 }
90 }
91}
92
93impl AtomLayoutStyle {
94 pub fn apply(self, mut layout: WidgetAtom<'_>) -> WidgetAtom<'_> {
99 let Self {
100 align2,
101 min_size,
102 gap,
103 frame,
104 text_style,
105 image_tint,
106 } = self;
107 layout.map_images(|image| {
108 let current_tint = image.image_options().tint;
109 image.tint(current_tint * image_tint)
111 });
112
113 let layout = layout
114 .min_size(min_size)
115 .fallback_gap(gap)
116 .frame(frame)
117 .fallback_font(text_style.font_id)
118 .fallback_text_color(text_style.color);
119
120 if let Some(align2) = align2 {
121 layout.align2(align2)
122 } else {
123 layout
124 }
125 }
126}
127
128#[derive(Debug, Clone)]
130pub struct ButtonStyle {
131 pub atom_layout: AtomLayoutStyle,
132}
133
134impl WidgetStyle for ButtonStyle {}
135
136#[derive(Debug, Clone)]
138pub struct TextEditStyle {
139 pub atom_layout: AtomLayoutStyle,
144
145 pub hint_text_color: Color32,
147
148 pub prefix_suffix_color: Color32,
150}
151
152impl WidgetStyle for TextEditStyle {}
153
154#[derive(Debug, Clone)]
156pub struct CheckboxStyle {
157 pub atom_layout: AtomLayoutStyle,
161
162 pub checkbox_size: f32,
164
165 pub check_size: f32,
167
168 pub checkbox_frame: Frame,
170
171 pub check_stroke: Stroke,
173}
174
175impl WidgetStyle for CheckboxStyle {}
176
177#[derive(Debug, Clone)]
179pub struct SeparatorStyle {
180 pub spacing: f32,
182
183 pub stroke: Stroke,
185}
186
187impl WidgetStyle for SeparatorStyle {}
188
189#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
191pub enum WidgetState {
192 Noninteractive,
193 #[default]
194 Inactive,
195 Hovered,
196 Active,
197}
198
199impl Widgets {
200 pub fn state(&self, state: WidgetState) -> &WidgetVisuals {
202 match state {
203 WidgetState::Noninteractive => &self.noninteractive,
204 WidgetState::Inactive => &self.inactive,
205 WidgetState::Hovered => &self.hovered,
206 WidgetState::Active => &self.active,
207 }
208 }
209}
210
211impl Response {
212 pub fn widget_state(&self) -> WidgetState {
213 if !self.sense.interactive() {
214 WidgetState::Noninteractive
215 } else if self.is_pointer_button_down_on() || self.has_focus() || self.clicked() {
216 WidgetState::Active
217 } else if self.hovered() || self.highlighted() {
218 WidgetState::Hovered
219 } else {
220 WidgetState::Inactive
221 }
222 }
223}
224
225pub struct StyleArgs<'a> {
226 pub classes: &'a Classes,
227 pub state: WidgetState,
228 pub stack: &'a UiStack,
229 pub style: &'a Style,
230 pub ctx: &'a Context,
231}
232
233impl StyleArgs<'_> {
234 pub fn has_class(&self, class: &str) -> bool {
240 self.classes.has_class(class) || self.stack.has_class(class)
241 }
242}