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