zeus_theme/
editor.rs

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