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