1use egui::{
2 Align, Button, CollapsingHeader, Color32, ComboBox, CornerRadius, DragValue, Frame, Layout,
3 Margin, Order, Popup, PopupCloseBehavior, Rect, Response, RichText, ScrollArea, Sense,
4 SetOpenCommand, Shadow, Slider, Stroke, StrokeKind, TextEdit, Ui, Vec2, Window,
5 color_picker::{Alpha, color_edit_button_srgba},
6 ecolor::HexColor,
7 vec2,
8};
9
10use super::{Theme, hsla::Hsla, utils};
11use crate::{ButtonVisuals, ComboBoxVisuals, TextEditVisuals, ThemeColors};
12
13#[derive(Clone, PartialEq)]
15pub enum WidgetState {
16 NonInteractive,
17 Inactive,
18 Hovered,
19 Active,
20 Open,
21}
22
23#[derive(Clone, PartialEq)]
24pub enum Color {
25 Bg(Color32),
26 WidgetBG(Color32),
27 Hover(Color32),
28 Text(Color32),
29 TextMuted(Color32),
30 Highlight(Color32),
31 Border(Color32),
32 Accent(Color32),
33 Error(Color32),
34 Warning(Color32),
35 Success(Color32),
36 Info(Color32),
37}
38
39impl Color {
40 pub fn all_colors_from(theme: &ThemeColors) -> Vec<Color> {
41 vec![
42 Color::Bg(theme.bg),
43 Color::WidgetBG(theme.widget_bg),
44 Color::Hover(theme.hover),
45 Color::Text(theme.text),
46 Color::TextMuted(theme.text_muted),
47 Color::Highlight(theme.highlight),
48 Color::Border(theme.border),
49 Color::Accent(theme.accent),
50 Color::Error(theme.error),
51 Color::Warning(theme.warning),
52 Color::Success(theme.success),
53 Color::Info(theme.info),
54 ]
55 }
56
57 pub fn to_str(&self) -> &'static str {
58 match self {
59 Color::Bg(_) => "Bg",
60 Color::WidgetBG(_) => "WidgetBG",
61 Color::Hover(_) => "Hover",
62 Color::Text(_) => "Text",
63 Color::TextMuted(_) => "Text Muted",
64 Color::Highlight(_) => "Highlight",
65 Color::Border(_) => "Border",
66 Color::Accent(_) => "Accent",
67 Color::Error(_) => "Error",
68 Color::Warning(_) => "Warning",
69 Color::Success(_) => "Success",
70 Color::Info(_) => "Info",
71 }
72 }
73
74 pub fn color32(&self) -> Color32 {
75 match self {
76 Color::Bg(color) => *color,
77 Color::WidgetBG(color) => *color,
78 Color::Hover(color) => *color,
79 Color::Text(color) => *color,
80 Color::TextMuted(color) => *color,
81 Color::Highlight(color) => *color,
82 Color::Border(color) => *color,
83 Color::Accent(color) => *color,
84 Color::Error(color) => *color,
85 Color::Warning(color) => *color,
86 Color::Success(color) => *color,
87 Color::Info(color) => *color,
88 }
89 }
90
91 pub fn name_from(color: Color32, theme_colors: &ThemeColors) -> &'static str {
92 if color == theme_colors.bg {
93 "Bg"
94 } else if color == theme_colors.widget_bg {
95 "WidgetBG"
96 } else if color == theme_colors.hover {
97 "Hover"
98 } else if color == theme_colors.text {
99 "Text"
100 } else if color == theme_colors.text_muted {
101 "Text Muted"
102 } else if color == theme_colors.highlight {
103 "Highlight"
104 } else if color == theme_colors.border {
105 "Border"
106 } else if color == theme_colors.accent {
107 "Accent"
108 } else if color == theme_colors.error {
109 "Error"
110 } else if color == theme_colors.warning {
111 "Warning"
112 } else if color == theme_colors.success {
113 "Success"
114 } else if color == theme_colors.info {
115 "Info"
116 } else {
117 "Unknown"
118 }
119 }
120}
121
122impl WidgetState {
123 pub fn to_str(&self) -> &'static str {
125 match self {
126 WidgetState::NonInteractive => "Non-interactive",
127 WidgetState::Inactive => "Inactive",
128 WidgetState::Hovered => "Hovered",
129 WidgetState::Active => "Active",
130 WidgetState::Open => "Open",
131 }
132 }
133
134 pub fn to_vec(&self) -> Vec<WidgetState> {
136 let non_interactive = Self::NonInteractive;
137 let inactive = Self::Inactive;
138 let hovered = Self::Hovered;
139 let active = Self::Active;
140 let open = Self::Open;
141
142 vec![non_interactive, inactive, hovered, active, open]
143 }
144}
145
146#[derive(Clone)]
147pub struct ThemeEditor {
148 pub open: bool,
149 pub widget_state: WidgetState,
151 pub hsla_edit_button: HslaEditButton,
152 pub color: Color,
153 pub bg_color: Color32,
154 pub size: (f32, f32),
155}
156
157impl ThemeEditor {
158 pub fn new() -> Self {
159 Self {
160 open: false,
161 widget_state: WidgetState::NonInteractive,
162 hsla_edit_button: HslaEditButton::new(),
163 color: Color::Bg(Color32::TRANSPARENT),
164 bg_color: Color32::from_rgba_premultiplied(32, 45, 70, 255),
165 size: (300.0, 300.0),
166 }
167 }
168
169 pub fn show(&mut self, theme: &mut Theme, ui: &mut Ui) -> Option<Theme> {
173 if !self.open {
174 return None;
175 }
176
177 let mut open = self.open;
178 let mut new_theme = None;
179 let frame = Frame::window(ui.style()).fill(self.bg_color);
180
181 Window::new("Theme Editor")
182 .open(&mut open)
183 .resizable([true, true])
184 .frame(frame)
185 .show(ui.ctx(), |ui| {
186 ui.set_min_width(self.size.0);
187 ui.set_min_height(self.size.1);
188 ui.spacing_mut().button_padding = vec2(10.0, 8.0);
189 new_theme = utils::change_theme(theme, ui);
192
193 ui.add_space(20.0);
194
195 ScrollArea::vertical().show(ui, |ui| {
196 ui.set_width(self.size.0);
197 ui.set_height(self.size.1);
198 self.ui(theme, ui);
199 });
200 });
201 self.open = open;
202 new_theme
203 }
204
205 pub fn ui(&mut self, theme: &mut Theme, ui: &mut Ui) {
207 ui.vertical_centered(|ui| {
208 ui.spacing_mut().item_spacing.y = 10.0;
209 let colors = theme.colors.clone();
210
211 CollapsingHeader::new("Theme Frames").show(ui, |ui| {
212 CollapsingHeader::new("Window Frame").show(ui, |ui| {
213 self.frame_settings(
214 "window_frame",
215 &mut theme.window_frame,
216 &colors,
217 ui,
218 );
219 });
220
221 CollapsingHeader::new("Frame 1").show(ui, |ui| {
222 self.frame_settings("frame1", &mut theme.frame1, &colors, ui);
223 });
224
225 CollapsingHeader::new("Frame 2").show(ui, |ui| {
226 self.frame_settings("frame2", &mut theme.frame2, &colors, ui);
227 });
228 });
229
230 CollapsingHeader::new("Custom Widgets Visuals").show(ui, |ui| {
231 CollapsingHeader::new("Button").show(ui, |ui| {
232 CollapsingHeader::new("Button Visuals 1").show(ui, |ui| {
233 self.button_visuals(colors, &mut theme.colors.button_visuals, ui);
234 });
235 });
236
237 CollapsingHeader::new("Label").show(ui, |ui| {
238 CollapsingHeader::new("Label Visuals 1").show(ui, |ui| {
239 self.button_visuals(colors, &mut theme.colors.label_visuals, ui);
240 });
241 });
242
243 CollapsingHeader::new("Combo Box").show(ui, |ui| {
244 CollapsingHeader::new("Combo Box Visuals 1").show(ui, |ui| {
245 self.combo_box_visuals(colors, &mut theme.colors.combo_box_visuals, ui);
246 });
247 });
248
249 CollapsingHeader::new("Text Edit").show(ui, |ui| {
250 CollapsingHeader::new("Text Edit Visuals 1").show(ui, |ui| {
251 self.text_edit_visuals(colors, &mut theme.colors.text_edit_visuals, ui);
252 });
253 });
254 });
255
256 CollapsingHeader::new("Theme Colors").show(ui, |ui| {
257 let old_colors = theme.colors;
258
259 ui.label("BG");
260 self.hsla_edit_button.show("bg", ui, &mut theme.colors.bg);
261
262 ui.label("WidgetBG");
263 self.hsla_edit_button.show("widgetbg", ui, &mut theme.colors.widget_bg);
264
265 ui.label("Hover");
266 self.hsla_edit_button.show("hover", ui, &mut theme.colors.hover);
267
268 ui.label("Text");
269 self.hsla_edit_button.show("text1", ui, &mut theme.colors.text);
270
271 ui.label("Text Muted");
272 self.hsla_edit_button.show("text_muted1", ui, &mut theme.colors.text_muted);
273
274 ui.label("Highlight");
275 self.hsla_edit_button.show("highlight1", ui, &mut theme.colors.highlight);
276
277 ui.label("Border");
278 self.hsla_edit_button.show("border1", ui, &mut theme.colors.border);
279
280 ui.label("Accent");
281 self.hsla_edit_button.show("accent", ui, &mut theme.colors.accent);
282
283 ui.label("Error");
284 self.hsla_edit_button.show("error1", ui, &mut theme.colors.error);
285
286 ui.label("Warning");
287 self.hsla_edit_button.show("warning1", ui, &mut theme.colors.warning);
288
289 ui.label("Success");
290 self.hsla_edit_button.show("success1", ui, &mut theme.colors.success);
291
292 ui.label("Info");
293 self.hsla_edit_button.show("info1", ui, &mut theme.colors.info);
294
295 theme.remap_derived_frames(&old_colors);
296 });
297
298 CollapsingHeader::new("Text Sizes").show(ui, |ui| {
299 ui.label("Very Small");
300 ui.add(Slider::new(&mut theme.text_sizes.very_small, 0.0..=100.0).text("Size"));
301
302 ui.label("Small");
303 ui.add(Slider::new(&mut theme.text_sizes.small, 0.0..=100.0).text("Size"));
304
305 ui.label("Normal");
306 ui.add(Slider::new(&mut theme.text_sizes.normal, 0.0..=100.0).text("Size"));
307
308 ui.label("Large");
309 ui.add(Slider::new(&mut theme.text_sizes.large, 0.0..=100.0).text("Size"));
310
311 ui.label("Very Large");
312 ui.add(Slider::new(&mut theme.text_sizes.very_large, 0.0..=100.0).text("Size"));
313
314 ui.label("Heading");
315 ui.add(Slider::new(&mut theme.text_sizes.heading, 0.0..=100.0).text("Size"));
316 });
317
318 CollapsingHeader::new("Other Colors").show(ui, |ui| {
319 ui.label("Selection Stroke");
320 ui.add(
321 Slider::new(
322 &mut theme.style.visuals.selection.stroke.width,
323 0.0..=10.0,
324 )
325 .text("Stroke Width"),
326 );
327 ui.label("Selection Stroke Color");
328 self.hsla_edit_button.show(
329 "selection_stroke_color1",
330 ui,
331 &mut theme.style.visuals.selection.stroke.color,
332 );
333
334 ui.label("Selection Bg Fill");
335 self.hsla_edit_button.show(
336 "selection_bg_fill1",
337 ui,
338 &mut theme.style.visuals.selection.bg_fill,
339 );
340
341 ui.label("Hyperlink Color");
342 self.hsla_edit_button.show(
343 "hyperlink_color1",
344 ui,
345 &mut theme.style.visuals.hyperlink_color,
346 );
347
348 ui.label("Faint Background Color");
349 self.hsla_edit_button.show(
350 "faint_bg_color1",
351 ui,
352 &mut theme.style.visuals.faint_bg_color,
353 );
354
355 ui.label("Extreme Background Color");
356 self.hsla_edit_button.show(
357 "extreme_bg_color1",
358 ui,
359 &mut theme.style.visuals.extreme_bg_color,
360 );
361
362 ui.label("Code Background Color");
363 self.hsla_edit_button.show(
364 "code_bg_color1",
365 ui,
366 &mut theme.style.visuals.code_bg_color,
367 );
368
369 ui.label("Warning Text Color");
370 self.hsla_edit_button.show(
371 "warn_fg_color1",
372 ui,
373 &mut theme.style.visuals.warn_fg_color,
374 );
375
376 ui.label("Error Text Color");
377 self.hsla_edit_button.show(
378 "error_fg_color1",
379 ui,
380 &mut theme.style.visuals.error_fg_color,
381 );
382
383 ui.label("Panel Fill Color");
384 self.hsla_edit_button.show(
385 "panel_fill1",
386 ui,
387 &mut theme.style.visuals.panel_fill,
388 );
389 });
390
391 CollapsingHeader::new("Window Visuals").show(ui, |ui| {
392 ui.label("Window Rounding");
393 edit_corner_radius(&mut theme.style.visuals.window_corner_radius, ui);
394
395 ui.label("Window Shadow");
396 edit_shadow(&mut theme.style.visuals.window_shadow, ui);
397
398 ui.label("Window Fill Color");
399 self.hsla_edit_button.show(
400 "window_fill1",
401 ui,
402 &mut theme.style.visuals.window_fill,
403 );
404
405 ui.label("Window Stroke");
406 self.edit_stroke(
407 "window_stroke",
408 &colors,
409 &mut theme.style.visuals.window_stroke,
410 ui,
411 );
412
413 ui.label("Window Highlight Topmost");
414 ui.checkbox(
415 &mut theme.style.visuals.window_highlight_topmost,
416 "Highlight Topmost",
417 );
418 });
419
420 CollapsingHeader::new("Popup Shadow").show(ui, |ui| {
421 edit_shadow(&mut theme.style.visuals.popup_shadow, ui);
422 });
423
424 CollapsingHeader::new("Menu Rounding").show(ui, |ui| {
425 edit_corner_radius(&mut theme.style.visuals.menu_corner_radius, ui);
426 });
427
428 CollapsingHeader::new("Widget Visuals").show(ui, |ui| {
429 self.widget_settings(theme, ui);
430 });
431
432 CollapsingHeader::new("Other Settings").show(ui, |ui| {
433 ui.label("Resize Corner Size");
434 ui.add(
435 Slider::new(
436 &mut theme.style.visuals.resize_corner_size,
437 0.0..=100.0,
438 )
439 .text("Corner Size"),
440 );
441
442 ui.label("Button Frame");
443 ui.checkbox(
444 &mut theme.style.visuals.button_frame,
445 "Button Frame",
446 );
447 });
448
449 CollapsingHeader::new("Tessellation").show(ui, |ui| {
450 self.tesellation_settings(theme, ui);
451 });
452 });
453 }
454
455 fn tesellation_settings(&mut self, theme: &Theme, ui: &mut Ui) {
456 let text_size = theme.text_sizes.normal;
457
458 let mut options = ui.ctx().tessellation_options(|options| options.clone());
459
460 let text = RichText::new("Feathering").size(text_size);
461
462 ui.checkbox(&mut options.feathering, text);
463
464 ui.add(
465 DragValue::new(&mut options.feathering_size_in_pixels)
466 .speed(0.1)
467 .range(0.0..=100.0),
468 );
469
470 let text = RichText::new("Coarse tessellation culling").size(text_size);
471 ui.checkbox(&mut options.coarse_tessellation_culling, text);
472
473 let text = RichText::new("Precomputed discs").size(text_size);
474 ui.checkbox(&mut options.prerasterized_discs, text);
475
476 let text = RichText::new("Round text to pixels").size(text_size);
477 ui.checkbox(&mut options.round_text_to_pixels, text);
478
479 let text = RichText::new("Round line segments to pixels").size(text_size);
480 ui.checkbox(&mut options.round_line_segments_to_pixels, text);
481
482 let text = RichText::new("Round rects to pixels").size(text_size);
483 ui.checkbox(&mut options.round_rects_to_pixels, text);
484
485 let text = RichText::new("Debug paint text rects").size(text_size);
486 ui.checkbox(&mut options.debug_paint_text_rects, text);
487
488 let text = RichText::new("Debug paint clip rects").size(text_size);
489 ui.checkbox(&mut options.debug_paint_clip_rects, text);
490
491 let text = RichText::new("Debug ignore clip rects").size(text_size);
492 ui.checkbox(&mut options.debug_ignore_clip_rects, text);
493
494 let text = RichText::new("Bezier tolerance").size(text_size);
495 ui.label(text);
496 ui.add(DragValue::new(&mut options.bezier_tolerance).speed(0.1).range(0.0..=1.0));
497
498 let text = RichText::new("Epsilon").size(text_size);
499 ui.label(text);
500 ui.add(DragValue::new(&mut options.epsilon).speed(0.1).range(0.0..=1.0));
501
502 let text = RichText::new("Parallel tessellation").size(text_size);
503 ui.checkbox(&mut options.parallel_tessellation, text);
504
505 let text = RichText::new("Validate meshes").size(text_size);
506 ui.checkbox(&mut options.validate_meshes, text);
507
508 ui.ctx().tessellation_options_mut(|options_mut| {
509 *options_mut = options;
510 });
511 }
512
513 fn button_visuals(&mut self, colors: ThemeColors, visuals: &mut ButtonVisuals, ui: &mut Ui) {
514 let text = RichText::new("Button Visuals");
515 ui.label(text);
516
517 ui.label("Text Color");
518 ui.horizontal(|ui| {
519 let color = self.color_select("1", visuals.text, &colors, ui);
520 if let Some(color) = color {
521 visuals.text = color.color32();
522 }
523
524 self.hsla_edit_button.show("text1", ui, &mut visuals.text);
525 });
526
527 ui.label("Background Color");
528 ui.horizontal(|ui| {
529 let color = self.color_select("2", visuals.bg, &colors, ui);
530 if let Some(color) = color {
531 visuals.bg = color.color32();
532 }
533
534 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
535 });
536
537 ui.label("Background Hover Color");
538 ui.horizontal(|ui| {
539 let color = self.color_select("3", visuals.bg_hover, &colors, ui);
540 if let Some(color) = color {
541 visuals.bg_hover = color.color32();
542 }
543
544 self.hsla_edit_button.show("bg_hover1", ui, &mut visuals.bg_hover);
545 });
546
547 ui.label("Background Click Color");
548 ui.horizontal(|ui| {
549 let color = self.color_select("4", visuals.bg_click, &colors, ui);
550 if let Some(color) = color {
551 visuals.bg_click = color.color32();
552 }
553
554 self.hsla_edit_button.show("bg_click1", ui, &mut visuals.bg_click);
555 });
556
557 ui.label("Background Selected");
558 ui.horizontal(|ui| {
559 let color = self.color_select("5", visuals.bg_selected, &colors, ui);
560
561 if let Some(color) = color {
562 visuals.bg_selected = color.color32();
563 }
564
565 self.hsla_edit_button.show("bg_selected1", ui, &mut visuals.bg_selected);
566 });
567
568 ui.label("Border Color");
569 ui.horizontal(|ui| {
570 let color = self.color_select("6", visuals.border.color, &colors, ui);
571
572 if let Some(color) = color {
573 visuals.border.color = color.color32();
574 }
575
576 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
577 });
578
579 ui.label("Border Hover Color");
580 ui.horizontal(|ui| {
581 let color = self.color_select("7", visuals.border_hover.color, &colors, ui);
582 if let Some(color) = color {
583 visuals.border_hover.color = color.color32();
584 }
585
586 self.hsla_edit_button.show(
587 "border_hover1",
588 ui,
589 &mut visuals.border_hover.color,
590 );
591 });
592
593 ui.label("Border Click Color");
594 ui.horizontal(|ui| {
595 let color = self.color_select("8", visuals.border_click.color, &colors, ui);
596 if let Some(color) = color {
597 visuals.border_click.color = color.color32();
598 }
599
600 self.hsla_edit_button.show(
601 "border_click1",
602 ui,
603 &mut visuals.border_click.color,
604 );
605 });
606
607 ui.label("Corner Radius");
608 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
609 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
610 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
611 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
612
613 ui.label("Shadow");
614 ui.horizontal(|ui| {
615 let color = self.color_select("9", visuals.shadow.color, &colors, ui);
616 if let Some(color) = color {
617 visuals.shadow.color = color.color32();
618 }
619
620 color_edit_button_srgba(
629 ui,
630 &mut visuals.shadow.color,
631 Alpha::BlendOrAdditive,
632 );
633 });
634
635 ui.label("Shadow Offset");
636 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
637 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
638
639 ui.label("Shadow Blur");
640 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
641
642 ui.label("Shadow Spread");
643 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
644 }
645
646 fn combo_box_visuals(
647 &mut self,
648 colors: ThemeColors,
649 visuals: &mut ComboBoxVisuals,
650 ui: &mut Ui,
651 ) {
652 ui.label("Background Color");
653 ui.horizontal(|ui| {
654 let color = self.color_select("2", visuals.bg, &colors, ui);
655 if let Some(color) = color {
656 visuals.bg = color.color32();
657 }
658
659 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
660 });
661
662 ui.label("Background Hover Color");
663 ui.horizontal(|ui| {
664 let color = self.color_select("3", visuals.bg_hover, &colors, ui);
665 if let Some(color) = color {
666 visuals.bg_hover = color.color32();
667 }
668
669 self.hsla_edit_button.show("bg_hover1", ui, &mut visuals.bg_hover);
670 });
671
672 ui.label("Border Color");
673 ui.horizontal(|ui| {
674 let color = self.color_select("4", visuals.border.color, &colors, ui);
675
676 if let Some(color) = color {
677 visuals.border.color = color.color32();
678 }
679
680 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
681 });
682
683 ui.label("Border Hover Color");
684 ui.horizontal(|ui| {
685 let color = self.color_select("5", visuals.border_hover.color, &colors, ui);
686 if let Some(color) = color {
687 visuals.border_hover.color = color.color32();
688 }
689
690 self.hsla_edit_button.show(
691 "border_hover1",
692 ui,
693 &mut visuals.border_hover.color,
694 );
695 });
696
697 ui.label("Border Open Color");
698 ui.horizontal(|ui| {
699 let color = self.color_select("6", visuals.border_open.color, &colors, ui);
700 if let Some(color) = color {
701 visuals.border_open.color = color.color32();
702 }
703
704 self.hsla_edit_button.show("border_open1", ui, &mut visuals.border_open.color);
705 });
706
707 ui.label("Corner Radius");
708 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
709 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
710 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
711 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
712
713 ui.label("Shadow");
714 ui.horizontal(|ui| {
715 let color = self.color_select("9", visuals.shadow.color, &colors, ui);
716 if let Some(color) = color {
717 visuals.shadow.color = color.color32();
718 }
719
720 color_edit_button_srgba(
721 ui,
722 &mut visuals.shadow.color,
723 Alpha::BlendOrAdditive,
724 );
725 });
726
727 ui.label("Shadow Offset");
728 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
729 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
730
731 ui.label("Shadow Blur");
732 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
733
734 ui.label("Shadow Spread");
735 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
736 }
737
738 fn text_edit_visuals(
739 &mut self,
740 colors: ThemeColors,
741 visuals: &mut TextEditVisuals,
742 ui: &mut Ui,
743 ) {
744 ui.label("Text Color");
745 ui.horizontal(|ui| {
746 let color = self.color_select("1", visuals.text, &colors, ui);
747 if let Some(color) = color {
748 visuals.text = color.color32();
749 }
750
751 self.hsla_edit_button.show("text1", ui, &mut visuals.text);
752 });
753
754 ui.label("Background Color");
755 ui.horizontal(|ui| {
756 let color = self.color_select("2", visuals.bg, &colors, ui);
757 if let Some(color) = color {
758 visuals.bg = color.color32();
759 }
760
761 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
762 });
763
764 ui.label("Border Color");
765 ui.horizontal(|ui| {
766 let color = self.color_select("3", visuals.border.color, &colors, ui);
767
768 if let Some(color) = color {
769 visuals.border.color = color.color32();
770 }
771
772 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
773 });
774
775 ui.label("Border Hover Color");
776 ui.horizontal(|ui| {
777 let color = self.color_select("4", visuals.border_hover.color, &colors, ui);
778 if let Some(color) = color {
779 visuals.border_hover.color = color.color32();
780 }
781
782 self.hsla_edit_button.show(
783 "border_hover1",
784 ui,
785 &mut visuals.border_hover.color,
786 );
787 });
788
789 ui.label("Border Open Color");
790 ui.horizontal(|ui| {
791 let color = self.color_select("5", visuals.border_open.color, &colors, ui);
792 if let Some(color) = color {
793 visuals.border_open.color = color.color32();
794 }
795
796 self.hsla_edit_button.show("border_open1", ui, &mut visuals.border_open.color);
797 });
798
799 ui.label("Corner Radius");
800 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
801 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
802 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
803 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
804
805 ui.label("Shadow");
806 ui.horizontal(|ui| {
807 let color = self.color_select("6", visuals.shadow.color, &colors, ui);
808 if let Some(color) = color {
809 visuals.shadow.color = color.color32();
810 }
811
812 color_edit_button_srgba(
813 ui,
814 &mut visuals.shadow.color,
815 Alpha::BlendOrAdditive,
816 );
817 });
818
819 ui.label("Shadow Offset");
820 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
821 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
822
823 ui.label("Shadow Blur");
824 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
825
826 ui.label("Shadow Spread");
827 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
828 }
829
830 fn widget_settings(&mut self, theme: &mut Theme, ui: &mut Ui) {
831 self.select_widget_state(ui);
832
833 let widget_visuals = match self.widget_state {
834 WidgetState::NonInteractive => &mut theme.style.visuals.widgets.noninteractive,
835 WidgetState::Inactive => &mut theme.style.visuals.widgets.inactive,
836 WidgetState::Hovered => &mut theme.style.visuals.widgets.hovered,
837 WidgetState::Active => &mut theme.style.visuals.widgets.active,
838 WidgetState::Open => &mut theme.style.visuals.widgets.open,
839 };
840
841 ui.label("Background Fill Color");
842
843 ui.horizontal(|ui| {
844 let color = self.color_select("1", widget_visuals.bg_fill, &theme.colors, ui);
845 if let Some(color) = color {
846 widget_visuals.bg_fill = color.color32();
847 }
848
849 self.hsla_edit_button.show("bg_fill1", ui, &mut widget_visuals.bg_fill);
850 });
851
852 ui.label("Weak Background Fill Color");
853
854 ui.horizontal(|ui| {
855 let color = self.color_select(
856 "2",
857 widget_visuals.weak_bg_fill,
858 &theme.colors,
859 ui,
860 );
861 if let Some(color) = color {
862 widget_visuals.weak_bg_fill = color.color32();
863 }
864
865 self.hsla_edit_button.show(
866 "weak_bg_fill1",
867 ui,
868 &mut widget_visuals.weak_bg_fill,
869 );
870 });
871
872 ui.label("Background Stroke Width");
873 ui.add(Slider::new(
874 &mut widget_visuals.bg_stroke.width,
875 0.0..=10.0,
876 ));
877
878 ui.label("Background Stroke Color");
879 ui.horizontal(|ui| {
880 let color = self.color_select(
881 "3",
882 widget_visuals.bg_stroke.color,
883 &theme.colors,
884 ui,
885 );
886 if let Some(color) = color {
887 widget_visuals.bg_stroke.color = color.color32();
888 }
889
890 self.hsla_edit_button.show(
891 "bg_stroke_color1",
892 ui,
893 &mut widget_visuals.bg_stroke.color,
894 );
895 });
896
897 ui.label("Rounding");
898 edit_corner_radius(&mut widget_visuals.corner_radius, ui);
899
900 ui.label("Foreground Stroke Width");
901 ui.add(Slider::new(
902 &mut widget_visuals.fg_stroke.width,
903 0.0..=10.0,
904 ));
905
906 ui.label("Foreground Stroke Color");
907 ui.horizontal(|ui| {
908 let color = self.color_select(
909 "4",
910 widget_visuals.fg_stroke.color,
911 &theme.colors,
912 ui,
913 );
914
915 if let Some(color) = color {
916 widget_visuals.fg_stroke.color = color.color32();
917 }
918
919 self.hsla_edit_button.show(
920 "fg_stroke_color1",
921 ui,
922 &mut widget_visuals.fg_stroke.color,
923 );
924 });
925
926 ui.label("Expansion");
927 ui.add(Slider::new(&mut widget_visuals.expansion, 0.0..=100.0).text("Expansion"));
928 }
929
930 fn frame_settings(&mut self, id: &str, frame: &mut Frame, colors: &ThemeColors, ui: &mut Ui) {
931 CollapsingHeader::new("Inner & Outter Margin").show(ui, |ui| {
932 ui.label("Inner Margin");
933 edit_margin(&mut frame.inner_margin, ui);
934
935 ui.label("Outter Margin");
936 edit_margin(&mut frame.outer_margin, ui);
937 });
938
939 ui.label("Rounding");
940 edit_corner_radius(&mut frame.corner_radius, ui);
941
942 ui.label("Shadow");
943 edit_shadow(&mut frame.shadow, ui);
944
945 ui.label("Fill Color");
946 self.hsla_edit_button.show(&format!("{id}_fill"), ui, &mut frame.fill);
947
948 ui.label("Stroke Width & Color");
949 self.edit_stroke(id, colors, &mut frame.stroke, ui);
950 }
951
952 fn select_widget_state(&mut self, ui: &mut Ui) {
953 ComboBox::from_label("")
954 .selected_text(self.widget_state.to_str())
955 .show_ui(ui, |ui| {
956 for widget in self.widget_state.to_vec() {
957 let value = ui.selectable_value(
958 &mut self.widget_state,
959 widget.clone(),
960 widget.to_str(),
961 );
962
963 if value.clicked() {
964 self.widget_state = widget;
965 }
966 }
967 });
968 }
969
970 fn color_select(
971 &mut self,
972 id: &str,
973 current_color: Color32,
974 colors: &ThemeColors,
975 ui: &mut Ui,
976 ) -> Option<Color> {
977 let all_colors = Color::all_colors_from(colors);
978
979 let mut selected_color = None;
980 let current_color_name = Color::name_from(current_color, colors);
981
982 ComboBox::from_id_salt(id).selected_text(current_color_name).show_ui(ui, |ui| {
983 for color in all_colors {
984 let value = ui.selectable_value(&mut self.color, color.clone(), color.to_str());
985
986 if value.clicked() {
987 selected_color = Some(color);
988 }
989 }
990 });
991 selected_color
992 }
993
994 fn edit_stroke(&mut self, id: &str, colors: &ThemeColors, stroke: &mut Stroke, ui: &mut Ui) {
995 ui.add(Slider::new(&mut stroke.width, 0.0..=100.0).text("Stroke Width"));
996
997 ui.label("Stroke Color");
998
999 ui.horizontal(|ui| {
1000 let color = self.color_select(
1001 &format!("{id}_stroke_pal"),
1002 stroke.color,
1003 colors,
1004 ui,
1005 );
1006 if let Some(color) = color {
1007 stroke.color = color.color32();
1008 }
1009
1010 color_edit_button_srgba(ui, &mut stroke.color, Alpha::BlendOrAdditive);
1011
1012 self.hsla_edit_button.show(&format!("{id}_stroke"), ui, &mut stroke.color);
1013 });
1014 }
1015}
1016
1017fn edit_margin(margin: &mut Margin, ui: &mut Ui) {
1018 ui.add(Slider::new(&mut margin.top, 0..=127).text("Top"));
1019 ui.add(Slider::new(&mut margin.bottom, 0..=127).text("Bottom"));
1020 ui.add(Slider::new(&mut margin.left, 0..=127).text("Left"));
1021 ui.add(Slider::new(&mut margin.right, 0..=127).text("Right"));
1022}
1023
1024fn edit_corner_radius(corner_radius: &mut CornerRadius, ui: &mut Ui) {
1025 ui.add(Slider::new(&mut corner_radius.nw, 0..=255).text("Top Left"));
1026 ui.add(Slider::new(&mut corner_radius.ne, 0..=255).text("Top Right"));
1027 ui.add(Slider::new(&mut corner_radius.sw, 0..=255).text("Bottom Left"));
1028 ui.add(Slider::new(&mut corner_radius.se, 0..=255).text("Bottom Right"));
1029}
1030
1031fn edit_shadow(shadow: &mut Shadow, ui: &mut Ui) {
1032 ui.add(Slider::new(&mut shadow.offset[0], -128..=127).text("Offset X"));
1033 ui.add(Slider::new(&mut shadow.offset[1], -128..=127).text("Offset Y"));
1034 ui.add(Slider::new(&mut shadow.blur, 0..=255).text("Blur"));
1035 ui.add(Slider::new(&mut shadow.spread, 0..=255).text("Spread"));
1036
1037 ui.label("Shadow Color");
1038 color_edit_button_srgba(ui, &mut shadow.color, Alpha::BlendOrAdditive);
1039}
1040
1041#[derive(Clone)]
1042pub struct HslaEditButton {
1043 from_hex_text: String,
1044}
1045
1046impl HslaEditButton {
1047 pub fn new() -> Self {
1048 Self {
1049 from_hex_text: String::new(),
1050 }
1051 }
1052
1053 pub fn show(&mut self, id: &str, ui: &mut Ui, color32: &mut Color32) -> Response {
1054 let stroke = Stroke::new(1.0, Color32::GRAY);
1055 let button_size = Vec2::new(50.0, 20.0);
1056 let (rect, mut response) = ui.allocate_exact_size(button_size, Sense::click());
1057 ui.painter().rect_filled(rect, 4.0, *color32);
1058 ui.painter().rect_stroke(rect, 4.0, stroke, StrokeKind::Inside);
1059
1060 let popup_id = ui.make_persistent_id(id);
1061
1062 let set_command = if response.clicked() {
1063 Some(SetOpenCommand::Toggle)
1064 } else {
1065 None
1066 };
1067
1068 let close_behavior = PopupCloseBehavior::CloseOnClickOutside;
1069 response.layer_id.order = Order::Debug;
1070
1071 let popup = Popup::from_response(&response)
1072 .close_behavior(close_behavior)
1073 .open_memory(set_command);
1074
1075 let working_id = popup_id.with("working_hsla");
1076 let mut working_hsla = ui
1077 .memory(|mem| mem.data.get_temp(working_id))
1078 .unwrap_or_else(|| Hsla::from_color32(*color32));
1079
1080 let popup_res = popup.show(|ui| self.hsla_picker_ui(ui, &mut working_hsla));
1081
1082 if let Some(inner) = popup_res {
1083 if inner.inner {
1085 ui.memory_mut(|mem| mem.data.insert_temp(working_id, working_hsla));
1086 *color32 = working_hsla.to_color32();
1087 response.mark_changed();
1088 }
1089 } else {
1090 ui.memory_mut(|mem| mem.data.remove::<Hsla>(working_id));
1091 }
1092
1093 response
1094 }
1095
1096 fn hsla_picker_ui(&mut self, ui: &mut Ui, hsla: &mut Hsla) -> bool {
1098 let mut changed = false;
1099 let stroke = Stroke::new(1.0, Color32::GRAY);
1100
1101 ui.horizontal(|ui| {
1102 ui.set_width(200.0);
1103
1104 ui.vertical(|ui| {
1106 changed |= sl_2d_picker(ui, hsla);
1107 changed |= hue_slider(ui, hsla);
1108 changed |= alpha_slider(ui, hsla);
1109 });
1110
1111 ui.vertical(|ui| {
1113 let preview_size = Vec2::new(80.0, 80.0);
1115 let (rect, _) = ui.allocate_exact_size(preview_size, Sense::hover());
1116 ui.painter().rect_filled(rect, 4.0, hsla.to_color32());
1117 ui.painter().rect_stroke(rect, 4.0, stroke, StrokeKind::Inside);
1118
1119 ui.label(RichText::new("Preview").strong());
1120
1121 ui.add_space(10.0);
1123 changed |= ui.add(Slider::new(&mut hsla.h, 0.0..=360.0).text("Hue")).changed();
1124 changed |= ui.add(Slider::new(&mut hsla.s, 0.0..=100.0).text("Saturation")).changed();
1125 changed |= ui.add(Slider::new(&mut hsla.l, 0.0..=100.0).text("Lightness")).changed();
1126 changed |= ui.add(Slider::new(&mut hsla.a, 0.0..=1.0).text("Alpha")).changed();
1127 });
1128
1129 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1130 ui.vertical(|ui| {
1131 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1133 let (r, g, b, a) = hsla.to_rgba_components();
1134 let text = RichText::new(format!("RGBA ({r}, {g}, {b}, {a})"));
1135 let button = Button::new(text).min_size(vec2(160.0, 15.0));
1136 if ui.add(button).clicked() {
1137 ui.ctx().copy_text(format!("({r}, {g}, {b}, {a})"));
1138 }
1139 });
1140
1141 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1143 let hex_color = HexColor::Hex6(hsla.to_color32());
1144 let text = RichText::new(format!("HEX {}", hex_color));
1145 let button = Button::new(text).min_size(vec2(160.0, 15.0));
1146 if ui.add(button).clicked() {
1147 ui.ctx().copy_text(format!("{}", hex_color));
1148 }
1149 });
1150
1151 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1153 let text = RichText::new("Convert From HEX");
1154 let button = Button::new(text).small();
1155 ui.add(TextEdit::singleline(&mut self.from_hex_text));
1156 if ui.add(button).clicked() {
1157 let new_color = Hsla::from_hex(&self.from_hex_text);
1158 if let Some(new_color) = new_color {
1159 *hsla = new_color;
1160 changed = true;
1161 }
1162 }
1163 });
1164 });
1165 });
1166 });
1167
1168 changed
1169 }
1170}
1171
1172fn sl_2d_picker(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1174 let size = Vec2::new(150.0, 150.0);
1175 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1176
1177 let mut changed = false;
1178
1179 if response.dragged() {
1180 if let Some(pos) = response.hover_pos() {
1181 let relative = pos - rect.min;
1182 hsla.s = (relative.x / size.x).clamp(0.0, 1.0) * 100.0;
1183 hsla.l = (1.0 - (relative.y / size.y)).clamp(0.0, 1.0) * 100.0; changed = true;
1185 }
1186 }
1187
1188 let painter = ui.painter();
1190 const RES: usize = 64; let cell_size = size / RES as f32;
1192 for i in 0..RES {
1193 for j in 0..RES {
1194 let s = (i as f32 / (RES - 1) as f32) * 100.0;
1195 let l = (1.0 - (j as f32 / (RES - 1) as f32)) * 100.0; let temp_hsla = Hsla {
1197 h: hsla.h,
1198 s,
1199 l,
1200 a: 1.0,
1201 };
1202 let color = temp_hsla.to_color32();
1203
1204 let min = rect.min + Vec2::new(i as f32 * cell_size.x, j as f32 * cell_size.y);
1205 let cell_rect = Rect::from_min_size(min, cell_size);
1206 painter.rect_filled(cell_rect, 0.0, color);
1207 }
1208 }
1209
1210 let x = (hsla.s / 100.0) * size.x;
1212 let y = (1.0 - hsla.l / 100.0) * size.y;
1213 let cursor_pos = rect.min + Vec2::new(x, y);
1214 painter.circle_stroke(cursor_pos, 5.0, Stroke::new(1.0, Color32::WHITE));
1215 painter.circle_stroke(cursor_pos, 5.0, Stroke::new(1.0, Color32::BLACK));
1216
1217 painter.rect_stroke(
1219 rect,
1220 0.0,
1221 Stroke::new(1.0, Color32::GRAY),
1222 StrokeKind::Inside,
1223 );
1224
1225 changed
1226}
1227
1228fn hue_slider(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1230 let size = Vec2::new(150.0, 20.0);
1231 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1232
1233 let mut changed = false;
1234
1235 if response.dragged() {
1236 if let Some(pos) = response.hover_pos() {
1237 let relative_x = (pos.x - rect.min.x) / size.x;
1238 hsla.h = relative_x.clamp(0.0, 1.0) * 360.0;
1239 changed = true;
1240 }
1241 }
1242
1243 let painter = ui.painter();
1245 const RES: usize = 128; let cell_width = size.x / RES as f32;
1247 for i in 0..RES {
1248 let h = (i as f32 / (RES - 1) as f32) * 360.0;
1249 let temp_hsla = Hsla {
1250 h,
1251 s: 100.0,
1252 l: 50.0,
1253 a: 1.0,
1254 }; let color = temp_hsla.to_color32();
1256
1257 let min = rect.min + Vec2::new(i as f32 * cell_width, 0.0);
1258 let cell_rect = Rect::from_min_size(min, Vec2::new(cell_width, size.y));
1259 painter.rect_filled(cell_rect, 0.0, color);
1260 }
1261
1262 let x = (hsla.h / 360.0) * size.x;
1264 let line_start = rect.min + Vec2::new(x, 0.0);
1265 let line_end = rect.min + Vec2::new(x, size.y);
1266 painter.line_segment(
1267 [line_start, line_end],
1268 Stroke::new(2.0, Color32::WHITE),
1269 );
1270
1271 painter.rect_stroke(
1273 rect,
1274 4.0,
1275 Stroke::new(1.0, Color32::GRAY),
1276 StrokeKind::Inside,
1277 );
1278
1279 changed
1280}
1281
1282fn alpha_slider(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1283 let size = Vec2::new(150.0, 20.0);
1284 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1285 let mut changed = false;
1286
1287 if response.dragged() {
1288 if let Some(pos) = response.hover_pos() {
1289 let relative_x = (pos.x - rect.min.x) / size.x;
1290 hsla.a = relative_x.clamp(0.0, 1.0);
1291 changed = true;
1292 }
1293 }
1294
1295 let painter = ui.painter();
1296 let checker_size = 5.0;
1298
1299 for x in (0..=((size.x / checker_size) as usize)).step_by(1) {
1300 for y in (0..=((size.y / checker_size) as usize)).step_by(1) {
1301 let color = if (x + y) % 2 == 0 {
1302 Color32::GRAY
1303 } else {
1304 Color32::LIGHT_GRAY
1305 };
1306 let min = rect.min + Vec2::new(x as f32 * checker_size, y as f32 * checker_size);
1307 let cell_rect = Rect::from_min_size(min, Vec2::splat(checker_size)).intersect(rect);
1308 painter.rect_filled(cell_rect, 0.0, color);
1309 }
1310 }
1311
1312 const RES: usize = 64;
1314 let cell_width = size.x / RES as f32;
1315
1316 for i in 0..RES {
1317 let a = i as f32 / (RES - 1) as f32;
1318 let temp_hsla = Hsla {
1319 h: hsla.h,
1320 s: hsla.s,
1321 l: hsla.l,
1322 a,
1323 };
1324
1325 let color = temp_hsla.to_color32();
1326 let min = rect.min + Vec2::new(i as f32 * cell_width, 0.0);
1327 let cell_rect = Rect::from_min_size(min, Vec2::new(cell_width, size.y));
1328 painter.rect_filled(cell_rect, 0.0, color);
1329 }
1330
1331 let x = hsla.a * size.x;
1333 let line_start = rect.min + Vec2::new(x, 0.0);
1334 let line_end = rect.min + Vec2::new(x, size.y);
1335
1336 painter.line_segment(
1337 [line_start, line_end],
1338 Stroke::new(2.0, Color32::WHITE),
1339 );
1340
1341 painter.rect_stroke(
1343 rect,
1344 4.0,
1345 Stroke::new(1.0, Color32::GRAY),
1346 StrokeKind::Inside,
1347 );
1348 changed
1349}