1use egui::{
8 Align, Button, CollapsingHeader, Color32, ComboBox, CornerRadius, DragValue, Frame, Layout,
9 Margin, Order, Popup, PopupCloseBehavior, Rect, Response, RichText, ScrollArea, Sense,
10 SetOpenCommand, Shadow, Slider, Stroke, StrokeKind, TextEdit, Ui, Vec2,
11 color_picker::{Alpha, color_edit_button_srgba},
12 ecolor::HexColor,
13 vec2,
14};
15
16use crate::theme::{Theme, ThemeColors};
17use crate::utils::{self, Hsla};
18use crate::visuals::*;
19use crate::widgets::Window;
20
21const DARK: Color32 = Color32::from_rgba_premultiplied(22, 22, 30, 255);
22
23#[derive(Clone, PartialEq)]
25pub enum WidgetState {
26 NonInteractive,
28 Inactive,
30 Hovered,
32 Active,
34 Open,
36}
37
38#[derive(Clone, PartialEq)]
40pub enum Color {
41 Bg(Color32),
42 WidgetBG(Color32),
43 Hover(Color32),
44 Text(Color32),
45 TextMuted(Color32),
46 Highlight(Color32),
47 Border(Color32),
48 Accent(Color32),
49 Error(Color32),
50 Warning(Color32),
51 Success(Color32),
52 Info(Color32),
53}
54
55impl Color {
56 pub fn all_colors_from(theme: &ThemeColors) -> Vec<Color> {
58 vec![
59 Color::Bg(theme.bg),
60 Color::WidgetBG(theme.widget_bg),
61 Color::Hover(theme.hover),
62 Color::Text(theme.text),
63 Color::TextMuted(theme.text_muted),
64 Color::Highlight(theme.highlight),
65 Color::Border(theme.border),
66 Color::Accent(theme.accent),
67 Color::Error(theme.error),
68 Color::Warning(theme.warning),
69 Color::Success(theme.success),
70 Color::Info(theme.info),
71 ]
72 }
73
74 pub fn to_str(&self) -> &'static str {
76 match self {
77 Color::Bg(_) => "Bg",
78 Color::WidgetBG(_) => "WidgetBG",
79 Color::Hover(_) => "Hover",
80 Color::Text(_) => "Text",
81 Color::TextMuted(_) => "Text Muted",
82 Color::Highlight(_) => "Highlight",
83 Color::Border(_) => "Border",
84 Color::Accent(_) => "Accent",
85 Color::Error(_) => "Error",
86 Color::Warning(_) => "Warning",
87 Color::Success(_) => "Success",
88 Color::Info(_) => "Info",
89 }
90 }
91
92 pub fn color32(&self) -> Color32 {
94 match self {
95 Color::Bg(color) => *color,
96 Color::WidgetBG(color) => *color,
97 Color::Hover(color) => *color,
98 Color::Text(color) => *color,
99 Color::TextMuted(color) => *color,
100 Color::Highlight(color) => *color,
101 Color::Border(color) => *color,
102 Color::Accent(color) => *color,
103 Color::Error(color) => *color,
104 Color::Warning(color) => *color,
105 Color::Success(color) => *color,
106 Color::Info(color) => *color,
107 }
108 }
109
110 pub fn name_from(color: Color32, theme_colors: &ThemeColors) -> &'static str {
112 if color == theme_colors.bg {
113 "Bg"
114 } else if color == theme_colors.widget_bg {
115 "WidgetBG"
116 } else if color == theme_colors.hover {
117 "Hover"
118 } else if color == theme_colors.text {
119 "Text"
120 } else if color == theme_colors.text_muted {
121 "Text Muted"
122 } else if color == theme_colors.highlight {
123 "Highlight"
124 } else if color == theme_colors.border {
125 "Border"
126 } else if color == theme_colors.accent {
127 "Accent"
128 } else if color == theme_colors.error {
129 "Error"
130 } else if color == theme_colors.warning {
131 "Warning"
132 } else if color == theme_colors.success {
133 "Success"
134 } else if color == theme_colors.info {
135 "Info"
136 } else {
137 "Unknown"
138 }
139 }
140}
141
142impl WidgetState {
143 pub fn to_str(&self) -> &'static str {
145 match self {
146 WidgetState::NonInteractive => "Non-interactive",
147 WidgetState::Inactive => "Inactive",
148 WidgetState::Hovered => "Hovered",
149 WidgetState::Active => "Active",
150 WidgetState::Open => "Open",
151 }
152 }
153
154 pub fn to_vec(&self) -> Vec<WidgetState> {
156 let non_interactive = Self::NonInteractive;
157 let inactive = Self::Inactive;
158 let hovered = Self::Hovered;
159 let active = Self::Active;
160 let open = Self::Open;
161
162 vec![non_interactive, inactive, hovered, active, open]
163 }
164}
165
166#[derive(Clone)]
171pub struct ThemeEditor {
172 pub open: bool,
174 pub widget_state: WidgetState,
176 pub hsla_edit_button: HslaEditButton,
178 pub color: Color,
180 pub bg_color: Color32,
182 pub size: (f32, f32),
184}
185
186impl ThemeEditor {
187 pub fn new() -> Self {
189 Self {
190 open: false,
191 widget_state: WidgetState::NonInteractive,
192 hsla_edit_button: HslaEditButton::new(),
193 color: Color::Bg(Color32::TRANSPARENT),
194 bg_color: DARK,
195 size: (300.0, 300.0),
196 }
197 }
198
199 pub fn show(&mut self, theme: &mut Theme, ui: &mut Ui) -> Option<Theme> {
203 if !self.open {
204 return None;
205 }
206
207 let mut open = self.open;
208 let mut new_theme = None;
209 let frame = Frame::window(ui.style()).fill(self.bg_color);
210 let title_frame = frame.stroke(Stroke::NONE);
211
212 Window::new("Theme Editor")
213 .open(&mut open)
214 .resizable([true, true])
215 .title_frame(title_frame)
216 .frame(frame)
217 .show(ui.ctx(), |ui| {
218 ui.set_min_width(self.size.0);
219 ui.set_min_height(self.size.1);
220 ui.spacing_mut().button_padding = vec2(10.0, 8.0);
221
222 new_theme = utils::theme_switcher(theme, ui);
223
224 ui.add_space(20.0);
225
226 ScrollArea::vertical().show(ui, |ui| {
227 ui.set_width(self.size.0);
228 ui.set_height(self.size.1);
229 self.ui(theme, ui);
230 });
231 });
232 self.open = open;
233 new_theme
234 }
235
236 pub fn ui(&mut self, theme: &mut Theme, ui: &mut Ui) {
238 ui.vertical_centered(|ui| {
239 ui.spacing_mut().item_spacing.y = 10.0;
240 let colors = theme.colors.clone();
241
242 CollapsingHeader::new("Theme Frames").show(ui, |ui| {
243 CollapsingHeader::new("Window Frame").show(ui, |ui| {
244 self.frame_settings(
245 "window_frame",
246 &mut theme.window_frame,
247 &colors,
248 ui,
249 );
250 });
251
252 CollapsingHeader::new("Frame 1").show(ui, |ui| {
253 self.frame_settings("frame1", &mut theme.frame1, &colors, ui);
254 });
255
256 CollapsingHeader::new("Frame 2").show(ui, |ui| {
257 self.frame_settings("frame2", &mut theme.frame2, &colors, ui);
258 });
259 });
260
261 CollapsingHeader::new("Custom Widgets Visuals").show(ui, |ui| {
262 CollapsingHeader::new("Button").show(ui, |ui| {
263 CollapsingHeader::new("Button Visuals 1").show(ui, |ui| {
264 self.button_visuals(colors, &mut theme.visuals.button_visuals, ui);
265 });
266 });
267
268 CollapsingHeader::new("Label").show(ui, |ui| {
269 CollapsingHeader::new("Label Visuals 1").show(ui, |ui| {
270 self.button_visuals(colors, &mut theme.visuals.label_visuals, ui);
271 });
272 });
273
274 CollapsingHeader::new("Combo Box").show(ui, |ui| {
275 CollapsingHeader::new("Combo Box Visuals 1").show(ui, |ui| {
276 self.combo_box_visuals(colors, &mut theme.visuals.combo_box_visuals, ui);
277 });
278 });
279
280 CollapsingHeader::new("Text Edit").show(ui, |ui| {
281 CollapsingHeader::new("Text Edit Visuals 1").show(ui, |ui| {
282 self.text_edit_visuals(colors, &mut theme.visuals.text_edit_visuals, ui);
283 });
284 });
285 });
286
287 CollapsingHeader::new("Theme Colors").show(ui, |ui| {
288 let old_colors = theme.colors;
289
290 ui.label("BG");
291 self.hsla_edit_button.show("bg", ui, &mut theme.colors.bg);
292
293 ui.label("WidgetBG");
294 self.hsla_edit_button.show("widgetbg", ui, &mut theme.colors.widget_bg);
295
296 ui.label("Hover");
297 self.hsla_edit_button.show("hover", ui, &mut theme.colors.hover);
298
299 ui.label("Text");
300 self.hsla_edit_button.show("text1", ui, &mut theme.colors.text);
301
302 ui.label("Text Muted");
303 self.hsla_edit_button.show("text_muted1", ui, &mut theme.colors.text_muted);
304
305 ui.label("Highlight");
306 self.hsla_edit_button.show("highlight1", ui, &mut theme.colors.highlight);
307
308 ui.label("Border");
309 self.hsla_edit_button.show("border1", ui, &mut theme.colors.border);
310
311 ui.label("Accent");
312 self.hsla_edit_button.show("accent", ui, &mut theme.colors.accent);
313
314 ui.label("Error");
315 self.hsla_edit_button.show("error1", ui, &mut theme.colors.error);
316
317 ui.label("Warning");
318 self.hsla_edit_button.show("warning1", ui, &mut theme.colors.warning);
319
320 ui.label("Success");
321 self.hsla_edit_button.show("success1", ui, &mut theme.colors.success);
322
323 ui.label("Info");
324 self.hsla_edit_button.show("info1", ui, &mut theme.colors.info);
325
326 theme.remap_derived_frames(&old_colors);
327 });
328
329 CollapsingHeader::new("Typography").show(ui, |ui| {
330 ui.label("Very Small");
331 ui.add(Slider::new(&mut theme.typography.very_small, 0.0..=100.0).text("Size"));
332
333 ui.label("Small");
334 ui.add(Slider::new(&mut theme.typography.small, 0.0..=100.0).text("Size"));
335
336 ui.label("Normal");
337 ui.add(Slider::new(&mut theme.typography.normal, 0.0..=100.0).text("Size"));
338
339 ui.label("Large");
340 ui.add(Slider::new(&mut theme.typography.large, 0.0..=100.0).text("Size"));
341
342 ui.label("Very Large");
343 ui.add(Slider::new(&mut theme.typography.very_large, 0.0..=100.0).text("Size"));
344
345 ui.label("Heading");
346 ui.add(Slider::new(&mut theme.typography.heading, 0.0..=100.0).text("Size"));
347 });
348
349 CollapsingHeader::new("Tessellation").show(ui, |ui| {
483 self.tesellation_settings(theme, ui);
484 });
485 });
486 }
487
488 fn tesellation_settings(&mut self, theme: &Theme, ui: &mut Ui) {
489 let text_size = theme.typography.normal;
490
491 let mut options = ui.ctx().tessellation_options(|options| options.clone());
492
493 let text = RichText::new("Feathering").size(text_size);
494
495 ui.checkbox(&mut options.feathering, text);
496
497 ui.add(
498 DragValue::new(&mut options.feathering_size_in_pixels)
499 .speed(0.1)
500 .range(0.0..=100.0),
501 );
502
503 let text = RichText::new("Coarse tessellation culling").size(text_size);
504 ui.checkbox(&mut options.coarse_tessellation_culling, text);
505
506 let text = RichText::new("Precomputed discs").size(text_size);
507 ui.checkbox(&mut options.prerasterized_discs, text);
508
509 let text = RichText::new("Round text to pixels").size(text_size);
510 ui.checkbox(&mut options.round_text_to_pixels, text);
511
512 let text = RichText::new("Round line segments to pixels").size(text_size);
513 ui.checkbox(&mut options.round_line_segments_to_pixels, text);
514
515 let text = RichText::new("Round rects to pixels").size(text_size);
516 ui.checkbox(&mut options.round_rects_to_pixels, text);
517
518 let text = RichText::new("Debug paint text rects").size(text_size);
519 ui.checkbox(&mut options.debug_paint_text_rects, text);
520
521 let text = RichText::new("Debug paint clip rects").size(text_size);
522 ui.checkbox(&mut options.debug_paint_clip_rects, text);
523
524 let text = RichText::new("Debug ignore clip rects").size(text_size);
525 ui.checkbox(&mut options.debug_ignore_clip_rects, text);
526
527 let text = RichText::new("Bezier tolerance").size(text_size);
528 ui.label(text);
529 ui.add(DragValue::new(&mut options.bezier_tolerance).speed(0.1).range(0.0..=1.0));
530
531 let text = RichText::new("Epsilon").size(text_size);
532 ui.label(text);
533 ui.add(DragValue::new(&mut options.epsilon).speed(0.1).range(0.0..=1.0));
534
535 let text = RichText::new("Parallel tessellation").size(text_size);
536 ui.checkbox(&mut options.parallel_tessellation, text);
537
538 let text = RichText::new("Validate meshes").size(text_size);
539 ui.checkbox(&mut options.validate_meshes, text);
540
541 ui.ctx().tessellation_options_mut(|options_mut| {
542 *options_mut = options;
543 });
544 }
545
546 fn button_visuals(&mut self, colors: ThemeColors, visuals: &mut ButtonVisuals, ui: &mut Ui) {
547 let text = RichText::new("Button Visuals");
548 ui.label(text);
549
550 ui.label("Text Color");
551 ui.horizontal(|ui| {
552 let color = self.color_select("1", visuals.text, &colors, ui);
553 if let Some(color) = color {
554 visuals.text = color.color32();
555 }
556
557 self.hsla_edit_button.show("text1", ui, &mut visuals.text);
558 });
559
560 ui.label("Background Color");
561 ui.horizontal(|ui| {
562 let color = self.color_select("2", visuals.bg, &colors, ui);
563 if let Some(color) = color {
564 visuals.bg = color.color32();
565 }
566
567 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
568 });
569
570 ui.label("Background Hover Color");
571 ui.horizontal(|ui| {
572 let color = self.color_select("3", visuals.bg_hover, &colors, ui);
573 if let Some(color) = color {
574 visuals.bg_hover = color.color32();
575 }
576
577 self.hsla_edit_button.show("bg_hover1", ui, &mut visuals.bg_hover);
578 });
579
580 ui.label("Background Click Color");
581 ui.horizontal(|ui| {
582 let color = self.color_select("4", visuals.bg_click, &colors, ui);
583 if let Some(color) = color {
584 visuals.bg_click = color.color32();
585 }
586
587 self.hsla_edit_button.show("bg_click1", ui, &mut visuals.bg_click);
588 });
589
590 ui.label("Background Selected");
591 ui.horizontal(|ui| {
592 let color = self.color_select("5", visuals.bg_selected, &colors, ui);
593
594 if let Some(color) = color {
595 visuals.bg_selected = color.color32();
596 }
597
598 self.hsla_edit_button.show("bg_selected1", ui, &mut visuals.bg_selected);
599 });
600
601 ui.label("Border Color");
602 ui.horizontal(|ui| {
603 let color = self.color_select("6", visuals.border.color, &colors, ui);
604
605 if let Some(color) = color {
606 visuals.border.color = color.color32();
607 }
608
609 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
610 });
611
612 ui.label("Border Hover Color");
613 ui.horizontal(|ui| {
614 let color = self.color_select("7", visuals.border_hover.color, &colors, ui);
615 if let Some(color) = color {
616 visuals.border_hover.color = color.color32();
617 }
618
619 self.hsla_edit_button.show(
620 "border_hover1",
621 ui,
622 &mut visuals.border_hover.color,
623 );
624 });
625
626 ui.label("Border Click Color");
627 ui.horizontal(|ui| {
628 let color = self.color_select("8", visuals.border_click.color, &colors, ui);
629 if let Some(color) = color {
630 visuals.border_click.color = color.color32();
631 }
632
633 self.hsla_edit_button.show(
634 "border_click1",
635 ui,
636 &mut visuals.border_click.color,
637 );
638 });
639
640 ui.label("Corner Radius");
641 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
642 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
643 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
644 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
645
646 ui.label("Shadow");
647 ui.horizontal(|ui| {
648 let color = self.color_select("9", visuals.shadow.color, &colors, ui);
649 if let Some(color) = color {
650 visuals.shadow.color = color.color32();
651 }
652
653 color_edit_button_srgba(
662 ui,
663 &mut visuals.shadow.color,
664 Alpha::BlendOrAdditive,
665 );
666 });
667
668 ui.label("Shadow Offset");
669 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
670 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
671
672 ui.label("Shadow Blur");
673 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
674
675 ui.label("Shadow Spread");
676 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
677 }
678
679 fn combo_box_visuals(
680 &mut self,
681 colors: ThemeColors,
682 visuals: &mut ComboBoxVisuals,
683 ui: &mut Ui,
684 ) {
685 ui.label("Background Color");
686 ui.horizontal(|ui| {
687 let color = self.color_select("2", visuals.bg, &colors, ui);
688 if let Some(color) = color {
689 visuals.bg = color.color32();
690 }
691
692 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
693 });
694
695 ui.label("Background Hover Color");
696 ui.horizontal(|ui| {
697 let color = self.color_select("3", visuals.bg_hover, &colors, ui);
698 if let Some(color) = color {
699 visuals.bg_hover = color.color32();
700 }
701
702 self.hsla_edit_button.show("bg_hover1", ui, &mut visuals.bg_hover);
703 });
704
705 ui.label("Border Color");
706 ui.horizontal(|ui| {
707 let color = self.color_select("4", visuals.border.color, &colors, ui);
708
709 if let Some(color) = color {
710 visuals.border.color = color.color32();
711 }
712
713 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
714 });
715
716 ui.label("Border Hover Color");
717 ui.horizontal(|ui| {
718 let color = self.color_select("5", visuals.border_hover.color, &colors, ui);
719 if let Some(color) = color {
720 visuals.border_hover.color = color.color32();
721 }
722
723 self.hsla_edit_button.show(
724 "border_hover1",
725 ui,
726 &mut visuals.border_hover.color,
727 );
728 });
729
730 ui.label("Border Open Color");
731 ui.horizontal(|ui| {
732 let color = self.color_select("6", visuals.border_open.color, &colors, ui);
733 if let Some(color) = color {
734 visuals.border_open.color = color.color32();
735 }
736
737 self.hsla_edit_button.show("border_open1", ui, &mut visuals.border_open.color);
738 });
739
740 ui.label("Corner Radius");
741 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
742 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
743 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
744 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
745
746 ui.label("Shadow");
747 ui.horizontal(|ui| {
748 let color = self.color_select("9", visuals.shadow.color, &colors, ui);
749 if let Some(color) = color {
750 visuals.shadow.color = color.color32();
751 }
752
753 color_edit_button_srgba(
754 ui,
755 &mut visuals.shadow.color,
756 Alpha::BlendOrAdditive,
757 );
758 });
759
760 ui.label("Shadow Offset");
761 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
762 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
763
764 ui.label("Shadow Blur");
765 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
766
767 ui.label("Shadow Spread");
768 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
769 }
770
771 fn text_edit_visuals(
772 &mut self,
773 colors: ThemeColors,
774 visuals: &mut TextEditVisuals,
775 ui: &mut Ui,
776 ) {
777 ui.label("Text Color");
778 ui.horizontal(|ui| {
779 let color = self.color_select("1", visuals.text, &colors, ui);
780 if let Some(color) = color {
781 visuals.text = color.color32();
782 }
783
784 self.hsla_edit_button.show("text1", ui, &mut visuals.text);
785 });
786
787 ui.label("Background Color");
788 ui.horizontal(|ui| {
789 let color = self.color_select("2", visuals.bg, &colors, ui);
790 if let Some(color) = color {
791 visuals.bg = color.color32();
792 }
793
794 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
795 });
796
797 ui.label("Border Color");
798 ui.horizontal(|ui| {
799 let color = self.color_select("3", visuals.border.color, &colors, ui);
800
801 if let Some(color) = color {
802 visuals.border.color = color.color32();
803 }
804
805 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
806 });
807
808 ui.label("Border Hover Color");
809 ui.horizontal(|ui| {
810 let color = self.color_select("4", visuals.border_hover.color, &colors, ui);
811 if let Some(color) = color {
812 visuals.border_hover.color = color.color32();
813 }
814
815 self.hsla_edit_button.show(
816 "border_hover1",
817 ui,
818 &mut visuals.border_hover.color,
819 );
820 });
821
822 ui.label("Border Open Color");
823 ui.horizontal(|ui| {
824 let color = self.color_select("5", visuals.border_open.color, &colors, ui);
825 if let Some(color) = color {
826 visuals.border_open.color = color.color32();
827 }
828
829 self.hsla_edit_button.show("border_open1", ui, &mut visuals.border_open.color);
830 });
831
832 ui.label("Corner Radius");
833 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
834 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
835 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
836 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
837
838 ui.label("Shadow");
839 ui.horizontal(|ui| {
840 let color = self.color_select("6", visuals.shadow.color, &colors, ui);
841 if let Some(color) = color {
842 visuals.shadow.color = color.color32();
843 }
844
845 color_edit_button_srgba(
846 ui,
847 &mut visuals.shadow.color,
848 Alpha::BlendOrAdditive,
849 );
850 });
851
852 ui.label("Shadow Offset");
853 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
854 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
855
856 ui.label("Shadow Blur");
857 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
858
859 ui.label("Shadow Spread");
860 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
861 }
862
863 fn frame_settings(&mut self, id: &str, frame: &mut Frame, colors: &ThemeColors, ui: &mut Ui) {
937 CollapsingHeader::new("Inner & Outter Margin").show(ui, |ui| {
938 ui.label("Inner Margin");
939 edit_margin(&mut frame.inner_margin, ui);
940
941 ui.label("Outter Margin");
942 edit_margin(&mut frame.outer_margin, ui);
943 });
944
945 ui.label("Rounding");
946 edit_corner_radius(&mut frame.corner_radius, ui);
947
948 ui.label("Shadow");
949 edit_shadow(&mut frame.shadow, ui);
950
951 ui.label("Fill Color");
952 self.hsla_edit_button.show(&format!("{id}_fill"), ui, &mut frame.fill);
953
954 ui.label("Stroke Width & Color");
955 self.edit_stroke(id, colors, &mut frame.stroke, ui);
956 }
957
958 fn _select_widget_state(&mut self, ui: &mut Ui) {
959 ComboBox::from_label("")
960 .selected_text(self.widget_state.to_str())
961 .show_ui(ui, |ui| {
962 for widget in self.widget_state.to_vec() {
963 let value = ui.selectable_value(
964 &mut self.widget_state,
965 widget.clone(),
966 widget.to_str(),
967 );
968
969 if value.clicked() {
970 self.widget_state = widget;
971 }
972 }
973 });
974 }
975
976 fn color_select(
977 &mut self,
978 id: &str,
979 current_color: Color32,
980 colors: &ThemeColors,
981 ui: &mut Ui,
982 ) -> Option<Color> {
983 let all_colors = Color::all_colors_from(colors);
984
985 let mut selected_color = None;
986 let current_color_name = Color::name_from(current_color, colors);
987
988 ComboBox::from_id_salt(id).selected_text(current_color_name).show_ui(ui, |ui| {
989 for color in all_colors {
990 let value = ui.selectable_value(&mut self.color, color.clone(), color.to_str());
991
992 if value.clicked() {
993 selected_color = Some(color);
994 }
995 }
996 });
997 selected_color
998 }
999
1000 fn edit_stroke(&mut self, id: &str, colors: &ThemeColors, stroke: &mut Stroke, ui: &mut Ui) {
1001 ui.add(Slider::new(&mut stroke.width, 0.0..=100.0).text("Stroke Width"));
1002
1003 ui.label("Stroke Color");
1004
1005 ui.horizontal(|ui| {
1006 let color = self.color_select(
1007 &format!("{id}_stroke_pal"),
1008 stroke.color,
1009 colors,
1010 ui,
1011 );
1012 if let Some(color) = color {
1013 stroke.color = color.color32();
1014 }
1015
1016 color_edit_button_srgba(ui, &mut stroke.color, Alpha::BlendOrAdditive);
1017
1018 self.hsla_edit_button.show(&format!("{id}_stroke"), ui, &mut stroke.color);
1019 });
1020 }
1021}
1022
1023fn edit_margin(margin: &mut Margin, ui: &mut Ui) {
1024 ui.add(Slider::new(&mut margin.top, 0..=127).text("Top"));
1025 ui.add(Slider::new(&mut margin.bottom, 0..=127).text("Bottom"));
1026 ui.add(Slider::new(&mut margin.left, 0..=127).text("Left"));
1027 ui.add(Slider::new(&mut margin.right, 0..=127).text("Right"));
1028}
1029
1030fn edit_corner_radius(corner_radius: &mut CornerRadius, ui: &mut Ui) {
1031 ui.add(Slider::new(&mut corner_radius.nw, 0..=255).text("Top Left"));
1032 ui.add(Slider::new(&mut corner_radius.ne, 0..=255).text("Top Right"));
1033 ui.add(Slider::new(&mut corner_radius.sw, 0..=255).text("Bottom Left"));
1034 ui.add(Slider::new(&mut corner_radius.se, 0..=255).text("Bottom Right"));
1035}
1036
1037fn edit_shadow(shadow: &mut Shadow, ui: &mut Ui) {
1038 ui.add(Slider::new(&mut shadow.offset[0], -128..=127).text("Offset X"));
1039 ui.add(Slider::new(&mut shadow.offset[1], -128..=127).text("Offset Y"));
1040 ui.add(Slider::new(&mut shadow.blur, 0..=255).text("Blur"));
1041 ui.add(Slider::new(&mut shadow.spread, 0..=255).text("Spread"));
1042
1043 ui.label("Shadow Color");
1044 color_edit_button_srgba(ui, &mut shadow.color, Alpha::BlendOrAdditive);
1045}
1046
1047#[derive(Clone)]
1049pub struct HslaEditButton {
1050 from_hex_text: String,
1051}
1052
1053impl HslaEditButton {
1054 pub fn new() -> Self {
1056 Self {
1057 from_hex_text: String::new(),
1058 }
1059 }
1060
1061 pub fn show(&mut self, id: &str, ui: &mut Ui, color32: &mut Color32) -> Response {
1063 let stroke = Stroke::new(1.0, Color32::GRAY);
1064 let button_size = Vec2::new(50.0, 20.0);
1065 let (rect, mut response) = ui.allocate_exact_size(button_size, Sense::click());
1066 ui.painter().rect_filled(rect, 4.0, *color32);
1067 ui.painter().rect_stroke(rect, 4.0, stroke, StrokeKind::Inside);
1068
1069 let popup_id = ui.make_persistent_id(id);
1070
1071 let set_command = if response.clicked() {
1072 Some(SetOpenCommand::Toggle)
1073 } else {
1074 None
1075 };
1076
1077 let close_behavior = PopupCloseBehavior::CloseOnClickOutside;
1078 response.layer_id.order = Order::Debug;
1079
1080 let popup = Popup::from_response(&response)
1081 .close_behavior(close_behavior)
1082 .open_memory(set_command);
1083
1084 let working_id = popup_id.with("working_hsla");
1085 let mut working_hsla = ui
1086 .memory(|mem| mem.data.get_temp(working_id))
1087 .unwrap_or_else(|| Hsla::from_color32(*color32));
1088
1089 let popup_res = popup.show(|ui| self.hsla_picker_ui(ui, &mut working_hsla));
1090
1091 if let Some(inner) = popup_res {
1092 if inner.inner {
1094 ui.memory_mut(|mem| mem.data.insert_temp(working_id, working_hsla));
1095 *color32 = working_hsla.to_color32();
1096 response.mark_changed();
1097 }
1098 } else {
1099 ui.memory_mut(|mem| mem.data.remove::<Hsla>(working_id));
1100 }
1101
1102 response
1103 }
1104
1105 fn hsla_picker_ui(&mut self, ui: &mut Ui, hsla: &mut Hsla) -> bool {
1107 let mut changed = false;
1108 let stroke = Stroke::new(1.0, Color32::GRAY);
1109
1110 ui.horizontal(|ui| {
1111 ui.set_width(200.0);
1112
1113 ui.vertical(|ui| {
1115 changed |= sl_2d_picker(ui, hsla);
1116 changed |= hue_slider(ui, hsla);
1117 changed |= alpha_slider(ui, hsla);
1118 });
1119
1120 ui.vertical(|ui| {
1122 let preview_size = Vec2::new(80.0, 80.0);
1124 let (rect, _) = ui.allocate_exact_size(preview_size, Sense::hover());
1125 ui.painter().rect_filled(rect, 4.0, hsla.to_color32());
1126 ui.painter().rect_stroke(rect, 4.0, stroke, StrokeKind::Inside);
1127
1128 ui.label(RichText::new("Preview").strong());
1129
1130 ui.add_space(10.0);
1132 changed |= ui.add(Slider::new(&mut hsla.h, 0.0..=360.0).text("Hue")).changed();
1133 changed |= ui.add(Slider::new(&mut hsla.s, 0.0..=100.0).text("Saturation")).changed();
1134 changed |= ui.add(Slider::new(&mut hsla.l, 0.0..=100.0).text("Lightness")).changed();
1135 changed |= ui.add(Slider::new(&mut hsla.a, 0.0..=1.0).text("Alpha")).changed();
1136 });
1137
1138 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1139 ui.vertical(|ui| {
1140 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1142 let (r, g, b, a) = hsla.to_rgba_components();
1143 let text = RichText::new(format!("RGBA ({r}, {g}, {b}, {a})"));
1144 let button = Button::new(text).min_size(vec2(160.0, 15.0));
1145 if ui.add(button).clicked() {
1146 ui.ctx().copy_text(format!("({r}, {g}, {b}, {a})"));
1147 }
1148 });
1149
1150 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1152 let hex_color = HexColor::Hex6(hsla.to_color32());
1153 let text = RichText::new(format!("HEX {}", hex_color));
1154 let button = Button::new(text).min_size(vec2(160.0, 15.0));
1155 if ui.add(button).clicked() {
1156 ui.ctx().copy_text(format!("{}", hex_color));
1157 }
1158 });
1159
1160 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1162 let text = RichText::new("Convert From HEX");
1163 let button = Button::new(text).small();
1164 ui.add(TextEdit::singleline(&mut self.from_hex_text));
1165 if ui.add(button).clicked() {
1166 let new_color = Hsla::from_hex(&self.from_hex_text);
1167 if let Some(new_color) = new_color {
1168 *hsla = new_color;
1169 changed = true;
1170 }
1171 }
1172 });
1173 });
1174 });
1175 });
1176
1177 changed
1178 }
1179}
1180
1181fn sl_2d_picker(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1183 let size = Vec2::new(150.0, 150.0);
1184 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1185
1186 let mut changed = false;
1187
1188 if response.dragged() {
1189 if let Some(pos) = response.hover_pos() {
1190 let relative = pos - rect.min;
1191 hsla.s = (relative.x / size.x).clamp(0.0, 1.0) * 100.0;
1192 hsla.l = (1.0 - (relative.y / size.y)).clamp(0.0, 1.0) * 100.0; changed = true;
1194 }
1195 }
1196
1197 let painter = ui.painter();
1199 const RES: usize = 64; let cell_size = size / RES as f32;
1201 for i in 0..RES {
1202 for j in 0..RES {
1203 let s = (i as f32 / (RES - 1) as f32) * 100.0;
1204 let l = (1.0 - (j as f32 / (RES - 1) as f32)) * 100.0; let temp_hsla = Hsla {
1206 h: hsla.h,
1207 s,
1208 l,
1209 a: 1.0,
1210 };
1211 let color = temp_hsla.to_color32();
1212
1213 let min = rect.min + Vec2::new(i as f32 * cell_size.x, j as f32 * cell_size.y);
1214 let cell_rect = Rect::from_min_size(min, cell_size);
1215 painter.rect_filled(cell_rect, 0.0, color);
1216 }
1217 }
1218
1219 let x = (hsla.s / 100.0) * size.x;
1221 let y = (1.0 - hsla.l / 100.0) * size.y;
1222 let cursor_pos = rect.min + Vec2::new(x, y);
1223 painter.circle_stroke(cursor_pos, 5.0, Stroke::new(1.0, Color32::WHITE));
1224 painter.circle_stroke(cursor_pos, 5.0, Stroke::new(1.0, Color32::BLACK));
1225
1226 painter.rect_stroke(
1228 rect,
1229 0.0,
1230 Stroke::new(1.0, Color32::GRAY),
1231 StrokeKind::Inside,
1232 );
1233
1234 changed
1235}
1236
1237fn hue_slider(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1239 let size = Vec2::new(150.0, 20.0);
1240 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1241
1242 let mut changed = false;
1243
1244 if response.dragged() {
1245 if let Some(pos) = response.hover_pos() {
1246 let relative_x = (pos.x - rect.min.x) / size.x;
1247 hsla.h = relative_x.clamp(0.0, 1.0) * 360.0;
1248 changed = true;
1249 }
1250 }
1251
1252 let painter = ui.painter();
1254 const RES: usize = 128; let cell_width = size.x / RES as f32;
1256 for i in 0..RES {
1257 let h = (i as f32 / (RES - 1) as f32) * 360.0;
1258 let temp_hsla = Hsla {
1259 h,
1260 s: 100.0,
1261 l: 50.0,
1262 a: 1.0,
1263 }; let color = temp_hsla.to_color32();
1265
1266 let min = rect.min + Vec2::new(i as f32 * cell_width, 0.0);
1267 let cell_rect = Rect::from_min_size(min, Vec2::new(cell_width, size.y));
1268 painter.rect_filled(cell_rect, 0.0, color);
1269 }
1270
1271 let x = (hsla.h / 360.0) * size.x;
1273 let line_start = rect.min + Vec2::new(x, 0.0);
1274 let line_end = rect.min + Vec2::new(x, size.y);
1275 painter.line_segment(
1276 [line_start, line_end],
1277 Stroke::new(2.0, Color32::WHITE),
1278 );
1279
1280 painter.rect_stroke(
1282 rect,
1283 4.0,
1284 Stroke::new(1.0, Color32::GRAY),
1285 StrokeKind::Inside,
1286 );
1287
1288 changed
1289}
1290
1291fn alpha_slider(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1292 let size = Vec2::new(150.0, 20.0);
1293 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1294 let mut changed = false;
1295
1296 if response.dragged() {
1297 if let Some(pos) = response.hover_pos() {
1298 let relative_x = (pos.x - rect.min.x) / size.x;
1299 hsla.a = relative_x.clamp(0.0, 1.0);
1300 changed = true;
1301 }
1302 }
1303
1304 let painter = ui.painter();
1305 let checker_size = 5.0;
1307
1308 for x in (0..=((size.x / checker_size) as usize)).step_by(1) {
1309 for y in (0..=((size.y / checker_size) as usize)).step_by(1) {
1310 let color = if (x + y) % 2 == 0 {
1311 Color32::GRAY
1312 } else {
1313 Color32::LIGHT_GRAY
1314 };
1315 let min = rect.min + Vec2::new(x as f32 * checker_size, y as f32 * checker_size);
1316 let cell_rect = Rect::from_min_size(min, Vec2::splat(checker_size)).intersect(rect);
1317 painter.rect_filled(cell_rect, 0.0, color);
1318 }
1319 }
1320
1321 const RES: usize = 64;
1323 let cell_width = size.x / RES as f32;
1324
1325 for i in 0..RES {
1326 let a = i as f32 / (RES - 1) as f32;
1327 let temp_hsla = Hsla {
1328 h: hsla.h,
1329 s: hsla.s,
1330 l: hsla.l,
1331 a,
1332 };
1333
1334 let color = temp_hsla.to_color32();
1335 let min = rect.min + Vec2::new(i as f32 * cell_width, 0.0);
1336 let cell_rect = Rect::from_min_size(min, Vec2::new(cell_width, size.y));
1337 painter.rect_filled(cell_rect, 0.0, color);
1338 }
1339
1340 let x = hsla.a * size.x;
1342 let line_start = rect.min + Vec2::new(x, 0.0);
1343 let line_end = rect.min + Vec2::new(x, size.y);
1344
1345 painter.line_segment(
1346 [line_start, line_end],
1347 Stroke::new(2.0, Color32::WHITE),
1348 );
1349
1350 painter.rect_stroke(
1352 rect,
1353 4.0,
1354 Stroke::new(1.0, Color32::GRAY),
1355 StrokeKind::Inside,
1356 );
1357 changed
1358}