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