Skip to main content

egui_elements/
editor.rs

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