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 = theme.button_padding;
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("Spacing").show(ui, |ui| {
350 ui.label("XS — tight meta / icon+label");
351 ui.add(Slider::new(&mut theme.spacing.xs, 0.0..=48.0).text("px"));
352
353 ui.label("SM — dense lists");
354 ui.add(Slider::new(&mut theme.spacing.sm, 0.0..=48.0).text("px"));
355
356 ui.label("MD — item spacing / related group");
357 ui.add(Slider::new(&mut theme.spacing.md, 0.0..=48.0).text("px"));
358
359 ui.label("LG — section padding");
360 ui.add(Slider::new(&mut theme.spacing.lg, 0.0..=48.0).text("px"));
361
362 ui.label("XL — between groups");
363 ui.add(Slider::new(&mut theme.spacing.xl, 0.0..=48.0).text("px"));
364 });
365
366 CollapsingHeader::new("Tessellation").show(ui, |ui| {
500 self.tesellation_settings(theme, ui);
501 });
502 });
503 }
504
505 fn tesellation_settings(&mut self, theme: &Theme, ui: &mut Ui) {
506 let text_size = theme.typography.normal;
507
508 let mut options = ui.ctx().tessellation_options(|options| options.clone());
509
510 let text = RichText::new("Feathering").size(text_size);
511
512 ui.checkbox(&mut options.feathering, text);
513
514 ui.add(
515 DragValue::new(&mut options.feathering_size_in_pixels)
516 .speed(0.1)
517 .range(0.0..=100.0),
518 );
519
520 let text = RichText::new("Coarse tessellation culling").size(text_size);
521 ui.checkbox(&mut options.coarse_tessellation_culling, text);
522
523 let text = RichText::new("Precomputed discs").size(text_size);
524 ui.checkbox(&mut options.prerasterized_discs, text);
525
526 let text = RichText::new("Round text to pixels").size(text_size);
527 ui.checkbox(&mut options.round_text_to_pixels, text);
528
529 let text = RichText::new("Round line segments to pixels").size(text_size);
530 ui.checkbox(&mut options.round_line_segments_to_pixels, text);
531
532 let text = RichText::new("Round rects to pixels").size(text_size);
533 ui.checkbox(&mut options.round_rects_to_pixels, text);
534
535 let text = RichText::new("Debug paint text rects").size(text_size);
536 ui.checkbox(&mut options.debug_paint_text_rects, text);
537
538 let text = RichText::new("Debug paint clip rects").size(text_size);
539 ui.checkbox(&mut options.debug_paint_clip_rects, text);
540
541 let text = RichText::new("Debug ignore clip rects").size(text_size);
542 ui.checkbox(&mut options.debug_ignore_clip_rects, text);
543
544 let text = RichText::new("Bezier tolerance").size(text_size);
545 ui.label(text);
546 ui.add(DragValue::new(&mut options.bezier_tolerance).speed(0.1).range(0.0..=1.0));
547
548 let text = RichText::new("Epsilon").size(text_size);
549 ui.label(text);
550 ui.add(DragValue::new(&mut options.epsilon).speed(0.1).range(0.0..=1.0));
551
552 let text = RichText::new("Parallel tessellation").size(text_size);
553 ui.checkbox(&mut options.parallel_tessellation, text);
554
555 let text = RichText::new("Validate meshes").size(text_size);
556 ui.checkbox(&mut options.validate_meshes, text);
557
558 ui.ctx().tessellation_options_mut(|options_mut| {
559 *options_mut = options;
560 });
561 }
562
563 fn button_visuals(&mut self, colors: ThemeColors, visuals: &mut ButtonVisuals, ui: &mut Ui) {
564 let text = RichText::new("Button Visuals");
565 ui.label(text);
566
567 ui.label("Text Color");
568 ui.horizontal(|ui| {
569 let color = self.color_select("1", visuals.text, &colors, ui);
570 if let Some(color) = color {
571 visuals.text = color.color32();
572 }
573
574 self.hsla_edit_button.show("text1", ui, &mut visuals.text);
575 });
576
577 ui.label("Background Color");
578 ui.horizontal(|ui| {
579 let color = self.color_select("2", visuals.bg, &colors, ui);
580 if let Some(color) = color {
581 visuals.bg = color.color32();
582 }
583
584 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
585 });
586
587 ui.label("Background Hover Color");
588 ui.horizontal(|ui| {
589 let color = self.color_select("3", visuals.bg_hover, &colors, ui);
590 if let Some(color) = color {
591 visuals.bg_hover = color.color32();
592 }
593
594 self.hsla_edit_button.show("bg_hover1", ui, &mut visuals.bg_hover);
595 });
596
597 ui.label("Background Click Color");
598 ui.horizontal(|ui| {
599 let color = self.color_select("4", visuals.bg_click, &colors, ui);
600 if let Some(color) = color {
601 visuals.bg_click = color.color32();
602 }
603
604 self.hsla_edit_button.show("bg_click1", ui, &mut visuals.bg_click);
605 });
606
607 ui.label("Background Selected");
608 ui.horizontal(|ui| {
609 let color = self.color_select("5", visuals.bg_selected, &colors, ui);
610
611 if let Some(color) = color {
612 visuals.bg_selected = color.color32();
613 }
614
615 self.hsla_edit_button.show("bg_selected1", ui, &mut visuals.bg_selected);
616 });
617
618 ui.label("Border Color");
619 ui.horizontal(|ui| {
620 let color = self.color_select("6", visuals.border.color, &colors, ui);
621
622 if let Some(color) = color {
623 visuals.border.color = color.color32();
624 }
625
626 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
627 });
628
629 ui.label("Border Hover Color");
630 ui.horizontal(|ui| {
631 let color = self.color_select("7", visuals.border_hover.color, &colors, ui);
632 if let Some(color) = color {
633 visuals.border_hover.color = color.color32();
634 }
635
636 self.hsla_edit_button.show(
637 "border_hover1",
638 ui,
639 &mut visuals.border_hover.color,
640 );
641 });
642
643 ui.label("Border Click Color");
644 ui.horizontal(|ui| {
645 let color = self.color_select("8", visuals.border_click.color, &colors, ui);
646 if let Some(color) = color {
647 visuals.border_click.color = color.color32();
648 }
649
650 self.hsla_edit_button.show(
651 "border_click1",
652 ui,
653 &mut visuals.border_click.color,
654 );
655 });
656
657 ui.label("Corner Radius");
658 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
659 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
660 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
661 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
662
663 ui.label("Shadow");
664 ui.horizontal(|ui| {
665 let color = self.color_select("9", visuals.shadow.color, &colors, ui);
666 if let Some(color) = color {
667 visuals.shadow.color = color.color32();
668 }
669
670 color_edit_button_srgba(
679 ui,
680 &mut visuals.shadow.color,
681 Alpha::BlendOrAdditive,
682 );
683 });
684
685 ui.label("Shadow Offset");
686 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
687 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
688
689 ui.label("Shadow Blur");
690 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
691
692 ui.label("Shadow Spread");
693 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
694 }
695
696 fn combo_box_visuals(
697 &mut self,
698 colors: ThemeColors,
699 visuals: &mut ComboBoxVisuals,
700 ui: &mut Ui,
701 ) {
702 ui.label("Background Color");
703 ui.horizontal(|ui| {
704 let color = self.color_select("2", visuals.bg, &colors, ui);
705 if let Some(color) = color {
706 visuals.bg = color.color32();
707 }
708
709 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
710 });
711
712 ui.label("Background Hover Color");
713 ui.horizontal(|ui| {
714 let color = self.color_select("3", visuals.bg_hover, &colors, ui);
715 if let Some(color) = color {
716 visuals.bg_hover = color.color32();
717 }
718
719 self.hsla_edit_button.show("bg_hover1", ui, &mut visuals.bg_hover);
720 });
721
722 ui.label("Border Color");
723 ui.horizontal(|ui| {
724 let color = self.color_select("4", visuals.border.color, &colors, ui);
725
726 if let Some(color) = color {
727 visuals.border.color = color.color32();
728 }
729
730 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
731 });
732
733 ui.label("Border Hover Color");
734 ui.horizontal(|ui| {
735 let color = self.color_select("5", visuals.border_hover.color, &colors, ui);
736 if let Some(color) = color {
737 visuals.border_hover.color = color.color32();
738 }
739
740 self.hsla_edit_button.show(
741 "border_hover1",
742 ui,
743 &mut visuals.border_hover.color,
744 );
745 });
746
747 ui.label("Border Open Color");
748 ui.horizontal(|ui| {
749 let color = self.color_select("6", visuals.border_open.color, &colors, ui);
750 if let Some(color) = color {
751 visuals.border_open.color = color.color32();
752 }
753
754 self.hsla_edit_button.show("border_open1", ui, &mut visuals.border_open.color);
755 });
756
757 ui.label("Corner Radius");
758 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
759 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
760 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
761 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
762
763 ui.label("Shadow");
764 ui.horizontal(|ui| {
765 let color = self.color_select("9", visuals.shadow.color, &colors, ui);
766 if let Some(color) = color {
767 visuals.shadow.color = color.color32();
768 }
769
770 color_edit_button_srgba(
771 ui,
772 &mut visuals.shadow.color,
773 Alpha::BlendOrAdditive,
774 );
775 });
776
777 ui.label("Shadow Offset");
778 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
779 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
780
781 ui.label("Shadow Blur");
782 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
783
784 ui.label("Shadow Spread");
785 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
786 }
787
788 fn text_edit_visuals(
789 &mut self,
790 colors: ThemeColors,
791 visuals: &mut TextEditVisuals,
792 ui: &mut Ui,
793 ) {
794 ui.label("Text Color");
795 ui.horizontal(|ui| {
796 let color = self.color_select("1", visuals.text, &colors, ui);
797 if let Some(color) = color {
798 visuals.text = color.color32();
799 }
800
801 self.hsla_edit_button.show("text1", ui, &mut visuals.text);
802 });
803
804 ui.label("Background Color");
805 ui.horizontal(|ui| {
806 let color = self.color_select("2", visuals.bg, &colors, ui);
807 if let Some(color) = color {
808 visuals.bg = color.color32();
809 }
810
811 self.hsla_edit_button.show("bg1", ui, &mut visuals.bg);
812 });
813
814 ui.label("Border Color");
815 ui.horizontal(|ui| {
816 let color = self.color_select("3", visuals.border.color, &colors, ui);
817
818 if let Some(color) = color {
819 visuals.border.color = color.color32();
820 }
821
822 self.hsla_edit_button.show("border1", ui, &mut visuals.border.color);
823 });
824
825 ui.label("Border Hover Color");
826 ui.horizontal(|ui| {
827 let color = self.color_select("4", visuals.border_hover.color, &colors, ui);
828 if let Some(color) = color {
829 visuals.border_hover.color = color.color32();
830 }
831
832 self.hsla_edit_button.show(
833 "border_hover1",
834 ui,
835 &mut visuals.border_hover.color,
836 );
837 });
838
839 ui.label("Border Open Color");
840 ui.horizontal(|ui| {
841 let color = self.color_select("5", visuals.border_open.color, &colors, ui);
842 if let Some(color) = color {
843 visuals.border_open.color = color.color32();
844 }
845
846 self.hsla_edit_button.show("border_open1", ui, &mut visuals.border_open.color);
847 });
848
849 ui.label("Corner Radius");
850 ui.add(Slider::new(&mut visuals.corner_radius.ne, 0..=100).text("NE"));
851 ui.add(Slider::new(&mut visuals.corner_radius.nw, 0..=100).text("NW"));
852 ui.add(Slider::new(&mut visuals.corner_radius.se, 0..=100).text("SE"));
853 ui.add(Slider::new(&mut visuals.corner_radius.sw, 0..=100).text("SW"));
854
855 ui.label("Shadow");
856 ui.horizontal(|ui| {
857 let color = self.color_select("6", visuals.shadow.color, &colors, ui);
858 if let Some(color) = color {
859 visuals.shadow.color = color.color32();
860 }
861
862 color_edit_button_srgba(
863 ui,
864 &mut visuals.shadow.color,
865 Alpha::BlendOrAdditive,
866 );
867 });
868
869 ui.label("Shadow Offset");
870 ui.add(Slider::new(&mut visuals.shadow.offset[0], -100..=100).text("Offset X"));
871 ui.add(Slider::new(&mut visuals.shadow.offset[1], -100..=100).text("Offset Y"));
872
873 ui.label("Shadow Blur");
874 ui.add(Slider::new(&mut visuals.shadow.blur, 0..=100).text("Blur"));
875
876 ui.label("Shadow Spread");
877 ui.add(Slider::new(&mut visuals.shadow.spread, 0..=100).text("Spread"));
878 }
879
880 fn frame_settings(&mut self, id: &str, frame: &mut Frame, colors: &ThemeColors, ui: &mut Ui) {
954 CollapsingHeader::new("Inner & Outter Margin").show(ui, |ui| {
955 ui.label("Inner Margin");
956 edit_margin(&mut frame.inner_margin, ui);
957
958 ui.label("Outter Margin");
959 edit_margin(&mut frame.outer_margin, ui);
960 });
961
962 ui.label("Rounding");
963 edit_corner_radius(&mut frame.corner_radius, ui);
964
965 ui.label("Shadow");
966 edit_shadow(&mut frame.shadow, ui);
967
968 ui.label("Fill Color");
969 self.hsla_edit_button.show(&format!("{id}_fill"), ui, &mut frame.fill);
970
971 ui.label("Stroke Width & Color");
972 self.edit_stroke(id, colors, &mut frame.stroke, ui);
973 }
974
975 fn _select_widget_state(&mut self, ui: &mut Ui) {
976 ComboBox::from_label("")
977 .selected_text(self.widget_state.to_str())
978 .show_ui(ui, |ui| {
979 for widget in self.widget_state.to_vec() {
980 let value = ui.selectable_value(
981 &mut self.widget_state,
982 widget.clone(),
983 widget.to_str(),
984 );
985
986 if value.clicked() {
987 self.widget_state = widget;
988 }
989 }
990 });
991 }
992
993 fn color_select(
994 &mut self,
995 id: &str,
996 current_color: Color32,
997 colors: &ThemeColors,
998 ui: &mut Ui,
999 ) -> Option<Color> {
1000 let all_colors = Color::all_colors_from(colors);
1001
1002 let mut selected_color = None;
1003 let current_color_name = Color::name_from(current_color, colors);
1004
1005 ComboBox::from_id_salt(id).selected_text(current_color_name).show_ui(ui, |ui| {
1006 for color in all_colors {
1007 let value = ui.selectable_value(&mut self.color, color.clone(), color.to_str());
1008
1009 if value.clicked() {
1010 selected_color = Some(color);
1011 }
1012 }
1013 });
1014 selected_color
1015 }
1016
1017 fn edit_stroke(&mut self, id: &str, colors: &ThemeColors, stroke: &mut Stroke, ui: &mut Ui) {
1018 ui.add(Slider::new(&mut stroke.width, 0.0..=100.0).text("Stroke Width"));
1019
1020 ui.label("Stroke Color");
1021
1022 ui.horizontal(|ui| {
1023 let color = self.color_select(
1024 &format!("{id}_stroke_pal"),
1025 stroke.color,
1026 colors,
1027 ui,
1028 );
1029 if let Some(color) = color {
1030 stroke.color = color.color32();
1031 }
1032
1033 color_edit_button_srgba(ui, &mut stroke.color, Alpha::BlendOrAdditive);
1034
1035 self.hsla_edit_button.show(&format!("{id}_stroke"), ui, &mut stroke.color);
1036 });
1037 }
1038}
1039
1040fn edit_margin(margin: &mut Margin, ui: &mut Ui) {
1041 ui.add(Slider::new(&mut margin.top, 0..=127).text("Top"));
1042 ui.add(Slider::new(&mut margin.bottom, 0..=127).text("Bottom"));
1043 ui.add(Slider::new(&mut margin.left, 0..=127).text("Left"));
1044 ui.add(Slider::new(&mut margin.right, 0..=127).text("Right"));
1045}
1046
1047fn edit_corner_radius(corner_radius: &mut CornerRadius, ui: &mut Ui) {
1048 ui.add(Slider::new(&mut corner_radius.nw, 0..=255).text("Top Left"));
1049 ui.add(Slider::new(&mut corner_radius.ne, 0..=255).text("Top Right"));
1050 ui.add(Slider::new(&mut corner_radius.sw, 0..=255).text("Bottom Left"));
1051 ui.add(Slider::new(&mut corner_radius.se, 0..=255).text("Bottom Right"));
1052}
1053
1054fn edit_shadow(shadow: &mut Shadow, ui: &mut Ui) {
1055 ui.add(Slider::new(&mut shadow.offset[0], -128..=127).text("Offset X"));
1056 ui.add(Slider::new(&mut shadow.offset[1], -128..=127).text("Offset Y"));
1057 ui.add(Slider::new(&mut shadow.blur, 0..=255).text("Blur"));
1058 ui.add(Slider::new(&mut shadow.spread, 0..=255).text("Spread"));
1059
1060 ui.label("Shadow Color");
1061 color_edit_button_srgba(ui, &mut shadow.color, Alpha::BlendOrAdditive);
1062}
1063
1064#[derive(Clone)]
1066pub struct HslaEditButton {
1067 from_hex_text: String,
1068}
1069
1070impl HslaEditButton {
1071 pub fn new() -> Self {
1073 Self {
1074 from_hex_text: String::new(),
1075 }
1076 }
1077
1078 pub fn show(&mut self, id: &str, ui: &mut Ui, color32: &mut Color32) -> Response {
1080 let stroke = Stroke::new(1.0, Color32::GRAY);
1081 let button_size = Vec2::new(50.0, 20.0);
1082 let (rect, mut response) = ui.allocate_exact_size(button_size, Sense::click());
1083 ui.painter().rect_filled(rect, 4.0, *color32);
1084 ui.painter().rect_stroke(rect, 4.0, stroke, StrokeKind::Inside);
1085
1086 let popup_id = ui.make_persistent_id(id);
1087
1088 let set_command = if response.clicked() {
1089 Some(SetOpenCommand::Toggle)
1090 } else {
1091 None
1092 };
1093
1094 let close_behavior = PopupCloseBehavior::CloseOnClickOutside;
1095 response.layer_id.order = Order::Debug;
1096
1097 let popup = Popup::from_response(&response)
1098 .close_behavior(close_behavior)
1099 .open_memory(set_command);
1100
1101 let working_id = popup_id.with("working_hsla");
1102 let mut working_hsla = ui
1103 .memory(|mem| mem.data.get_temp(working_id))
1104 .unwrap_or_else(|| Hsla::from_color32(*color32));
1105
1106 let popup_res = popup.show(|ui| self.hsla_picker_ui(ui, &mut working_hsla));
1107
1108 if let Some(inner) = popup_res {
1109 if inner.inner {
1111 ui.memory_mut(|mem| mem.data.insert_temp(working_id, working_hsla));
1112 *color32 = working_hsla.to_color32();
1113 response.mark_changed();
1114 }
1115 } else {
1116 ui.memory_mut(|mem| mem.data.remove::<Hsla>(working_id));
1117 }
1118
1119 response
1120 }
1121
1122 fn hsla_picker_ui(&mut self, ui: &mut Ui, hsla: &mut Hsla) -> bool {
1124 let mut changed = false;
1125 let stroke = Stroke::new(1.0, Color32::GRAY);
1126
1127 ui.horizontal(|ui| {
1128 ui.set_width(200.0);
1129
1130 ui.vertical(|ui| {
1132 changed |= sl_2d_picker(ui, hsla);
1133 changed |= hue_slider(ui, hsla);
1134 changed |= alpha_slider(ui, hsla);
1135 });
1136
1137 ui.vertical(|ui| {
1139 let preview_size = Vec2::new(80.0, 80.0);
1141 let (rect, _) = ui.allocate_exact_size(preview_size, Sense::hover());
1142 ui.painter().rect_filled(rect, 4.0, hsla.to_color32());
1143 ui.painter().rect_stroke(rect, 4.0, stroke, StrokeKind::Inside);
1144
1145 ui.label(RichText::new("Preview").strong());
1146
1147 ui.add_space(10.0);
1149 changed |= ui.add(Slider::new(&mut hsla.h, 0.0..=360.0).text("Hue")).changed();
1150 changed |= ui.add(Slider::new(&mut hsla.s, 0.0..=100.0).text("Saturation")).changed();
1151 changed |= ui.add(Slider::new(&mut hsla.l, 0.0..=100.0).text("Lightness")).changed();
1152 changed |= ui.add(Slider::new(&mut hsla.a, 0.0..=1.0).text("Alpha")).changed();
1153 });
1154
1155 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1156 ui.vertical(|ui| {
1157 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1159 let (r, g, b, a) = hsla.to_rgba_components();
1160 let text = RichText::new(format!("RGBA ({r}, {g}, {b}, {a})"));
1161 let button = Button::new(text).min_size(vec2(160.0, 15.0));
1162 if ui.add(button).clicked() {
1163 ui.ctx().copy_text(format!("({r}, {g}, {b}, {a})"));
1164 }
1165 });
1166
1167 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1169 let hex_color = HexColor::Hex6(hsla.to_color32());
1170 let text = RichText::new(format!("HEX {}", hex_color));
1171 let button = Button::new(text).min_size(vec2(160.0, 15.0));
1172 if ui.add(button).clicked() {
1173 ui.ctx().copy_text(format!("{}", hex_color));
1174 }
1175 });
1176
1177 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1179 let text = RichText::new("Convert From HEX");
1180 let button = Button::new(text).small();
1181 ui.add(TextEdit::singleline(&mut self.from_hex_text));
1182 if ui.add(button).clicked() {
1183 let new_color = Hsla::from_hex(&self.from_hex_text);
1184 if let Some(new_color) = new_color {
1185 *hsla = new_color;
1186 changed = true;
1187 }
1188 }
1189 });
1190 });
1191 });
1192 });
1193
1194 changed
1195 }
1196}
1197
1198fn sl_2d_picker(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1200 let size = Vec2::new(150.0, 150.0);
1201 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1202
1203 let mut changed = false;
1204
1205 if response.dragged() {
1206 if let Some(pos) = response.hover_pos() {
1207 let relative = pos - rect.min;
1208 hsla.s = (relative.x / size.x).clamp(0.0, 1.0) * 100.0;
1209 hsla.l = (1.0 - (relative.y / size.y)).clamp(0.0, 1.0) * 100.0; changed = true;
1211 }
1212 }
1213
1214 let painter = ui.painter();
1216 const RES: usize = 64; let cell_size = size / RES as f32;
1218 for i in 0..RES {
1219 for j in 0..RES {
1220 let s = (i as f32 / (RES - 1) as f32) * 100.0;
1221 let l = (1.0 - (j as f32 / (RES - 1) as f32)) * 100.0; let temp_hsla = Hsla {
1223 h: hsla.h,
1224 s,
1225 l,
1226 a: 1.0,
1227 };
1228 let color = temp_hsla.to_color32();
1229
1230 let min = rect.min + Vec2::new(i as f32 * cell_size.x, j as f32 * cell_size.y);
1231 let cell_rect = Rect::from_min_size(min, cell_size);
1232 painter.rect_filled(cell_rect, 0.0, color);
1233 }
1234 }
1235
1236 let x = (hsla.s / 100.0) * size.x;
1238 let y = (1.0 - hsla.l / 100.0) * size.y;
1239 let cursor_pos = rect.min + Vec2::new(x, y);
1240 painter.circle_stroke(cursor_pos, 5.0, Stroke::new(1.0, Color32::WHITE));
1241 painter.circle_stroke(cursor_pos, 5.0, Stroke::new(1.0, Color32::BLACK));
1242
1243 painter.rect_stroke(
1245 rect,
1246 0.0,
1247 Stroke::new(1.0, Color32::GRAY),
1248 StrokeKind::Inside,
1249 );
1250
1251 changed
1252}
1253
1254fn hue_slider(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1256 let size = Vec2::new(150.0, 20.0);
1257 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1258
1259 let mut changed = false;
1260
1261 if response.dragged() {
1262 if let Some(pos) = response.hover_pos() {
1263 let relative_x = (pos.x - rect.min.x) / size.x;
1264 hsla.h = relative_x.clamp(0.0, 1.0) * 360.0;
1265 changed = true;
1266 }
1267 }
1268
1269 let painter = ui.painter();
1271 const RES: usize = 128; let cell_width = size.x / RES as f32;
1273 for i in 0..RES {
1274 let h = (i as f32 / (RES - 1) as f32) * 360.0;
1275 let temp_hsla = Hsla {
1276 h,
1277 s: 100.0,
1278 l: 50.0,
1279 a: 1.0,
1280 }; let color = temp_hsla.to_color32();
1282
1283 let min = rect.min + Vec2::new(i as f32 * cell_width, 0.0);
1284 let cell_rect = Rect::from_min_size(min, Vec2::new(cell_width, size.y));
1285 painter.rect_filled(cell_rect, 0.0, color);
1286 }
1287
1288 let x = (hsla.h / 360.0) * size.x;
1290 let line_start = rect.min + Vec2::new(x, 0.0);
1291 let line_end = rect.min + Vec2::new(x, size.y);
1292 painter.line_segment(
1293 [line_start, line_end],
1294 Stroke::new(2.0, Color32::WHITE),
1295 );
1296
1297 painter.rect_stroke(
1299 rect,
1300 4.0,
1301 Stroke::new(1.0, Color32::GRAY),
1302 StrokeKind::Inside,
1303 );
1304
1305 changed
1306}
1307
1308fn alpha_slider(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1309 let size = Vec2::new(150.0, 20.0);
1310 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1311 let mut changed = false;
1312
1313 if response.dragged() {
1314 if let Some(pos) = response.hover_pos() {
1315 let relative_x = (pos.x - rect.min.x) / size.x;
1316 hsla.a = relative_x.clamp(0.0, 1.0);
1317 changed = true;
1318 }
1319 }
1320
1321 let painter = ui.painter();
1322 let checker_size = 5.0;
1324
1325 for x in (0..=((size.x / checker_size) as usize)).step_by(1) {
1326 for y in (0..=((size.y / checker_size) as usize)).step_by(1) {
1327 let color = if (x + y) % 2 == 0 {
1328 Color32::GRAY
1329 } else {
1330 Color32::LIGHT_GRAY
1331 };
1332 let min = rect.min + Vec2::new(x as f32 * checker_size, y as f32 * checker_size);
1333 let cell_rect = Rect::from_min_size(min, Vec2::splat(checker_size)).intersect(rect);
1334 painter.rect_filled(cell_rect, 0.0, color);
1335 }
1336 }
1337
1338 const RES: usize = 64;
1340 let cell_width = size.x / RES as f32;
1341
1342 for i in 0..RES {
1343 let a = i as f32 / (RES - 1) as f32;
1344 let temp_hsla = Hsla {
1345 h: hsla.h,
1346 s: hsla.s,
1347 l: hsla.l,
1348 a,
1349 };
1350
1351 let color = temp_hsla.to_color32();
1352 let min = rect.min + Vec2::new(i as f32 * cell_width, 0.0);
1353 let cell_rect = Rect::from_min_size(min, Vec2::new(cell_width, size.y));
1354 painter.rect_filled(cell_rect, 0.0, color);
1355 }
1356
1357 let x = hsla.a * size.x;
1359 let line_start = rect.min + Vec2::new(x, 0.0);
1360 let line_end = rect.min + Vec2::new(x, size.y);
1361
1362 painter.line_segment(
1363 [line_start, line_end],
1364 Stroke::new(2.0, Color32::WHITE),
1365 );
1366
1367 painter.rect_stroke(
1369 rect,
1370 4.0,
1371 Stroke::new(1.0, Color32::GRAY),
1372 StrokeKind::Inside,
1373 );
1374 changed
1375}