Skip to main content

zeus_theme/
editor.rs

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