1use crate::draw::color::{Rgba, Rgba8Srgb};
9use crate::event::EventState;
10use crate::theme::Background;
11use crate::Id;
12
13const MULT_DEPRESS: f32 = 0.75;
14const MULT_HIGHLIGHT: f32 = 1.25;
15const MIN_HIGHLIGHT: f32 = 0.2;
16
17bitflags::bitflags! {
18 #[derive(Copy, Clone, Default)]
23 pub struct InputState: u8 {
24 const DISABLED = 1 << 0;
28 const HOVER = 1 << 2;
30 const DEPRESS = 1 << 3;
35 const NAV_FOCUS = 1 << 4;
37 const KEY_FOCUS = 1 << 5;
40 const SEL_FOCUS = 1 << 6;
43 }
44}
45
46impl InputState {
47 pub fn new_all(ev: &EventState, id: &Id) -> Self {
49 let mut state = Self::new_except_depress(ev, id);
50 if ev.is_depressed(id) {
51 state |= InputState::DEPRESS;
52 }
53 state
54 }
55
56 pub fn new_except_depress(ev: &EventState, id: &Id) -> Self {
58 let (key_focus, sel_focus) = ev.has_key_focus(id);
59 let mut state = InputState::empty();
60 if ev.is_disabled(id) {
61 state |= InputState::DISABLED;
62 }
63 if ev.is_hovered(id) {
64 state |= InputState::HOVER;
65 }
66 if ev.has_nav_focus(id) {
67 state |= InputState::NAV_FOCUS;
68 }
69 if key_focus {
70 state |= InputState::KEY_FOCUS;
71 }
72 if sel_focus {
73 state |= InputState::SEL_FOCUS;
74 }
75 state
76 }
77
78 pub fn new2(ev: &EventState, id: &Id, id2: &Id) -> Self {
80 let mut state = Self::new_all(ev, id);
81 if ev.is_hovered(id2) {
82 state |= InputState::HOVER;
83 }
84 state
85 }
86
87 #[inline]
89 pub fn disabled(self) -> bool {
90 self.contains(InputState::DISABLED)
91 }
92
93 #[inline]
95 pub fn hover(self) -> bool {
96 self.contains(InputState::HOVER)
97 }
98
99 #[inline]
101 pub fn depress(self) -> bool {
102 self.contains(InputState::DEPRESS)
103 }
104
105 #[inline]
107 pub fn nav_focus(self) -> bool {
108 self.contains(InputState::NAV_FOCUS)
109 }
110
111 #[inline]
113 pub fn key_focus(self) -> bool {
114 self.contains(InputState::KEY_FOCUS)
115 }
116
117 #[inline]
119 pub fn sel_focus(self) -> bool {
120 self.contains(InputState::SEL_FOCUS)
121 }
122}
123
124#[allow(clippy::derive_partial_eq_without_eq)]
126#[derive(Clone, Debug, PartialEq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct Colors<C> {
129 pub is_dark: bool,
131 pub background: C,
133 pub frame: C,
135 pub edit_bg: C,
137 pub edit_bg_disabled: C,
139 pub edit_bg_error: C,
141 pub accent: C,
145 pub accent_soft: C,
149 pub nav_focus: C,
155 pub text: C,
157 pub text_invert: C,
159 pub text_disabled: C,
161 pub text_sel_bg: C,
165}
166
167pub type ColorsSrgb = Colors<Rgba8Srgb>;
169
170pub type ColorsLinear = Colors<Rgba>;
172
173impl From<&ColorsSrgb> for ColorsLinear {
174 fn from(col: &ColorsSrgb) -> Self {
175 Colors {
176 is_dark: col.is_dark,
177 background: col.background.into(),
178 frame: col.frame.into(),
179 accent: col.accent.into(),
180 accent_soft: col.accent_soft.into(),
181 nav_focus: col.nav_focus.into(),
182 edit_bg: col.edit_bg.into(),
183 edit_bg_disabled: col.edit_bg_disabled.into(),
184 edit_bg_error: col.edit_bg_error.into(),
185 text: col.text.into(),
186 text_invert: col.text_invert.into(),
187 text_disabled: col.text_disabled.into(),
188 text_sel_bg: col.text_sel_bg.into(),
189 }
190 }
191}
192
193impl From<ColorsSrgb> for ColorsLinear {
194 fn from(col: ColorsSrgb) -> Self {
195 Colors::from(&col)
196 }
197}
198
199impl From<&ColorsLinear> for ColorsSrgb {
200 fn from(col: &ColorsLinear) -> Self {
201 Colors {
202 is_dark: col.is_dark,
203 background: col.background.into(),
204 frame: col.frame.into(),
205 accent: col.accent.into(),
206 accent_soft: col.accent_soft.into(),
207 nav_focus: col.nav_focus.into(),
208 edit_bg: col.edit_bg.into(),
209 edit_bg_disabled: col.edit_bg_disabled.into(),
210 edit_bg_error: col.edit_bg_error.into(),
211 text: col.text.into(),
212 text_invert: col.text_invert.into(),
213 text_disabled: col.text_disabled.into(),
214 text_sel_bg: col.text_sel_bg.into(),
215 }
216 }
217}
218
219impl From<ColorsLinear> for ColorsSrgb {
220 fn from(col: ColorsLinear) -> Self {
221 Colors::from(&col)
222 }
223}
224
225impl ColorsSrgb {
226 pub const LIGHT: ColorsSrgb = Colors {
228 is_dark: false,
229 background: Rgba8Srgb::parse("FAFAFA"),
230 frame: Rgba8Srgb::parse("BCBCBC"),
231 accent: Rgba8Srgb::parse("8347f2"),
232 accent_soft: Rgba8Srgb::parse("B38DF9"),
233 nav_focus: Rgba8Srgb::parse("7E3FF2"),
234 edit_bg: Rgba8Srgb::parse("FAFAFA"),
235 edit_bg_disabled: Rgba8Srgb::parse("DCDCDC"),
236 edit_bg_error: Rgba8Srgb::parse("FFBCBC"),
237 text: Rgba8Srgb::parse("000000"),
238 text_invert: Rgba8Srgb::parse("FFFFFF"),
239 text_disabled: Rgba8Srgb::parse("AAAAAA"),
240 text_sel_bg: Rgba8Srgb::parse("A172FA"),
241 };
242
243 pub const DARK: ColorsSrgb = Colors {
245 is_dark: true,
246 background: Rgba8Srgb::parse("404040"),
247 frame: Rgba8Srgb::parse("AAAAAA"),
248 accent: Rgba8Srgb::parse("F74C00"),
249 accent_soft: Rgba8Srgb::parse("E77346"),
250 nav_focus: Rgba8Srgb::parse("D03E00"),
251 edit_bg: Rgba8Srgb::parse("303030"),
252 edit_bg_disabled: Rgba8Srgb::parse("606060"),
253 edit_bg_error: Rgba8Srgb::parse("a06868"),
254 text: Rgba8Srgb::parse("FFFFFF"),
255 text_invert: Rgba8Srgb::parse("000000"),
256 text_disabled: Rgba8Srgb::parse("CBCBCB"),
257 text_sel_bg: Rgba8Srgb::parse("E77346"),
258 };
259
260 pub const BLUE: ColorsSrgb = Colors {
262 is_dark: false,
263 background: Rgba8Srgb::parse("FFFFFF"),
264 frame: Rgba8Srgb::parse("DADADA"),
265 accent: Rgba8Srgb::parse("3fafd7"),
266 accent_soft: Rgba8Srgb::parse("7CDAFF"),
267 nav_focus: Rgba8Srgb::parse("3B697A"),
268 edit_bg: Rgba8Srgb::parse("FFFFFF"),
269 edit_bg_disabled: Rgba8Srgb::parse("DCDCDC"),
270 edit_bg_error: Rgba8Srgb::parse("FFBCBC"),
271 text: Rgba8Srgb::parse("000000"),
272 text_invert: Rgba8Srgb::parse("FFFFFF"),
273 text_disabled: Rgba8Srgb::parse("AAAAAA"),
274 text_sel_bg: Rgba8Srgb::parse("6CC0E1"),
275 };
276}
277
278impl ColorsLinear {
279 pub fn adjust_for_state(col: Rgba, state: InputState) -> Rgba {
281 if state.disabled() {
282 col.average()
283 } else if state.depress() {
284 col.multiply(MULT_DEPRESS)
285 } else if state.hover() || state.key_focus() {
286 col.multiply(MULT_HIGHLIGHT).max(MIN_HIGHLIGHT)
287 } else {
288 col
289 }
290 }
291
292 pub fn from_bg(&self, bg: Background, state: InputState, force_accent: bool) -> Rgba {
294 let use_accent = force_accent || state.depress() || state.nav_focus();
295 let col = match bg {
296 _ if state.disabled() => self.edit_bg_disabled,
297 Background::Default if use_accent => self.accent_soft,
298 Background::Default => self.background,
299 Background::Error => self.edit_bg_error,
300 Background::Rgb(rgb) => rgb.into(),
301 };
302 Self::adjust_for_state(col, state)
303 }
304
305 pub fn from_edit_bg(&self, bg: Background, state: InputState) -> Rgba {
307 let mut col = match bg {
308 _ if state.disabled() => self.edit_bg_disabled,
309 Background::Default => self.edit_bg,
310 Background::Error => self.edit_bg_error,
311 Background::Rgb(rgb) => rgb.into(),
312 };
313 if state.depress() {
314 col = col.multiply(MULT_DEPRESS);
315 }
316 col
317 }
318
319 pub fn nav_region(&self, state: InputState) -> Option<Rgba> {
321 if state.nav_focus() && !state.disabled() {
322 Some(self.nav_focus)
323 } else {
324 None
325 }
326 }
327
328 #[inline]
330 pub fn accent_state(&self, state: InputState) -> Rgba {
331 Self::adjust_for_state(self.accent, state)
332 }
333
334 #[inline]
336 pub fn accent_soft_state(&self, state: InputState) -> Rgba {
337 Self::adjust_for_state(self.accent_soft, state)
338 }
339
340 #[inline]
342 pub fn check_mark_state(&self, state: InputState) -> Rgba {
343 Self::adjust_for_state(self.accent, state)
344 }
345
346 pub fn menu_entry(&self, state: InputState) -> Option<Rgba> {
348 if state.depress() || state.nav_focus() {
349 Some(self.accent_soft.multiply(MULT_DEPRESS))
350 } else {
351 None
352 }
353 }
354
355 pub fn text_over(&self, bg: Rgba) -> Rgba {
357 let bg_sum = bg.sum();
358 if (bg_sum - self.text_invert.sum()).abs() > (bg_sum - self.text.sum()).abs() {
359 self.text_invert
360 } else {
361 self.text
362 }
363 }
364}