1use egui::{
2 Align, Button, CollapsingHeader, Color32, ComboBox, DragValue, Frame, Layout, Popup,
3 PopupCloseBehavior, Rect, Response, RichText, ScrollArea, Sense, SetOpenCommand, Slider, Stroke,
4 StrokeKind, Ui, Vec2, Window, ecolor::HexColor, vec2,
5};
6
7use super::{Theme, hsla::Hsla, utils};
8use crate::ThemeColors;
9
10#[derive(Clone, PartialEq)]
12pub enum WidgetState {
13 NonInteractive,
14 Inactive,
15 Hovered,
16 Active,
17 Open,
18}
19
20#[derive(Clone, PartialEq)]
21pub enum Color {
22 Bg(Color32),
23 Bg2(Color32),
24 Bg3(Color32),
25 Bg4(Color32),
26 Text(Color32),
27 TextMuted(Color32),
28 Highlight(Color32),
29 Border(Color32),
30 Primary(Color32),
31 Secondary(Color32),
32 Error(Color32),
33 Warning(Color32),
34 Success(Color32),
35 Info(Color32),
36}
37
38impl Color {
39 pub fn all_colors_from(theme: &ThemeColors) -> Vec<Color> {
40 vec![
41 Color::Bg(theme.bg),
42 Color::Bg2(theme.bg2),
43 Color::Bg3(theme.bg3),
44 Color::Bg4(theme.bg4),
45 Color::Text(theme.text),
46 Color::TextMuted(theme.text_muted),
47 Color::Highlight(theme.highlight),
48 Color::Border(theme.border),
49 Color::Primary(theme.primary),
50 Color::Secondary(theme.secondary),
51 Color::Error(theme.error),
52 Color::Warning(theme.warning),
53 Color::Success(theme.success),
54 Color::Info(theme.info),
55 ]
56 }
57
58 pub fn to_str(&self) -> &'static str {
59 match self {
60 Color::Bg(_) => "Bg",
61 Color::Bg2(_) => "Bg2",
62 Color::Bg3(_) => "Bg3",
63 Color::Bg4(_) => "Bg4",
64 Color::Text(_) => "Text",
65 Color::TextMuted(_) => "Text Muted",
66 Color::Highlight(_) => "Highlight",
67 Color::Border(_) => "Border",
68 Color::Primary(_) => "Primary",
69 Color::Secondary(_) => "Secondary",
70 Color::Error(_) => "Error",
71 Color::Warning(_) => "Warning",
72 Color::Success(_) => "Success",
73 Color::Info(_) => "Info",
74 }
75 }
76
77 pub fn color32(&self) -> Color32 {
78 match self {
79 Color::Bg(color) => *color,
80 Color::Bg2(color) => *color,
81 Color::Bg3(color) => *color,
82 Color::Bg4(color) => *color,
83 Color::Text(color) => *color,
84 Color::TextMuted(color) => *color,
85 Color::Highlight(color) => *color,
86 Color::Border(color) => *color,
87 Color::Primary(color) => *color,
88 Color::Secondary(color) => *color,
89 Color::Error(color) => *color,
90 Color::Warning(color) => *color,
91 Color::Success(color) => *color,
92 Color::Info(color) => *color,
93 }
94 }
95
96 pub fn name_from(color: Color32, theme_colors: &ThemeColors) -> &'static str {
97 if color == theme_colors.bg {
98 "Bg"
99 } else if color == theme_colors.bg2 {
100 "Bg2"
101 } else if color == theme_colors.bg3 {
102 "Bg3"
103 } else if color == theme_colors.bg4 {
104 "Bg4"
105 } else if color == theme_colors.text {
106 "Text"
107 } else if color == theme_colors.text_muted {
108 "Text Muted"
109 } else if color == theme_colors.highlight {
110 "Highlight"
111 } else if color == theme_colors.border {
112 "Border"
113 } else if color == theme_colors.primary {
114 "Primary"
115 } else if color == theme_colors.secondary {
116 "Secondary"
117 } else if color == theme_colors.error {
118 "Error"
119 } else if color == theme_colors.warning {
120 "Warning"
121 } else if color == theme_colors.success {
122 "Success"
123 } else if color == theme_colors.info {
124 "Info"
125 } else {
126 "Unknown"
127 }
128 }
129}
130
131impl WidgetState {
132 pub fn to_str(&self) -> &'static str {
134 match self {
135 WidgetState::NonInteractive => "Non-interactive",
136 WidgetState::Inactive => "Inactive",
137 WidgetState::Hovered => "Hovered",
138 WidgetState::Active => "Active",
139 WidgetState::Open => "Open",
140 }
141 }
142
143 pub fn to_vec(&self) -> Vec<WidgetState> {
145 let non_interactive = Self::NonInteractive;
146 let inactive = Self::Inactive;
147 let hovered = Self::Hovered;
148 let active = Self::Active;
149 let open = Self::Open;
150
151 vec![non_interactive, inactive, hovered, active, open]
152 }
153}
154
155#[derive(Clone)]
156pub struct ThemeEditor {
157 pub open: bool,
158 pub widget_state: WidgetState,
160 pub color: Color,
161 pub bg_color: Color32,
162 pub size: (f32, f32),
163}
164
165impl ThemeEditor {
166 pub fn new() -> Self {
167 Self {
168 open: false,
169 widget_state: WidgetState::NonInteractive,
170 color: Color::Bg(Color32::TRANSPARENT),
171 bg_color: Color32::from_rgba_premultiplied(32, 45, 70, 255),
172 size: (300.0, 300.0),
173 }
174 }
175
176 pub fn show(&mut self, theme: &mut Theme, ui: &mut Ui) -> Option<Theme> {
180 if !self.open {
181 return None;
182 }
183
184 let mut open = self.open;
185 let mut new_theme = None;
186 let frame = Frame::window(ui.style()).fill(self.bg_color);
187
188 Window::new("Theme Editor")
189 .open(&mut open)
190 .resizable([true, true])
191 .frame(frame)
192 .show(ui.ctx(), |ui| {
193 ui.set_min_width(self.size.0);
194 ui.set_min_height(self.size.1);
195 ui.spacing_mut().button_padding = vec2(10.0, 8.0);
196 ui.style_mut().visuals = super::themes::dark::theme().style.visuals.clone();
197
198 new_theme = utils::change_theme(theme, ui);
199
200 ui.add_space(20.0);
201
202 ScrollArea::vertical().show(ui, |ui| {
203 ui.set_width(self.size.0);
204 ui.set_height(self.size.1);
205 self.ui(theme, ui);
206 });
207 });
208 self.open = open;
209 new_theme
210 }
211
212 pub fn ui(&mut self, theme: &mut Theme, ui: &mut Ui) {
214 ui.vertical_centered(|ui| {
215 ui.spacing_mut().item_spacing.y = 10.0;
216
217 CollapsingHeader::new("Theme Frames").show(ui, |ui| {
218 CollapsingHeader::new("Native Window Frame").show(ui, |ui| {
219 self.frame_settings(&mut theme.window_frame, ui);
220 });
221
222 CollapsingHeader::new("Frame 1").show(ui, |ui| {
223 self.frame_settings(&mut theme.frame1, ui);
224 });
225
226 CollapsingHeader::new("Frame 2").show(ui, |ui| {
227 self.frame_settings(&mut theme.frame2, ui);
228 });
229 });
230
231 CollapsingHeader::new("Theme Colors").show(ui, |ui| {
232 ui.label("BG");
233 hsla_edit_button("bg1", ui, &mut theme.colors.bg);
234
235 ui.label("BG2");
236 hsla_edit_button("bg2", ui, &mut theme.colors.bg2);
237
238 ui.label("BG3");
239 hsla_edit_button("bg3", ui, &mut theme.colors.bg3);
240
241 ui.label("BG4");
242 hsla_edit_button("bg4", ui, &mut theme.colors.bg4);
243
244 ui.label("Text");
245 hsla_edit_button("text1", ui, &mut theme.colors.text);
246
247 ui.label("Text Muted");
248 hsla_edit_button("text_muted1", ui, &mut theme.colors.text_muted);
249
250 ui.label("Highlight");
251 hsla_edit_button("highlight1", ui, &mut theme.colors.highlight);
252
253 ui.label("Border");
254 hsla_edit_button("border1", ui, &mut theme.colors.border);
255
256 ui.label("Primary");
257 hsla_edit_button("primary1", ui, &mut theme.colors.primary);
258
259 ui.label("Secondary");
260 hsla_edit_button("secondary1", ui, &mut theme.colors.secondary);
261
262 ui.label("Error");
263 hsla_edit_button("error1", ui, &mut theme.colors.error);
264
265 ui.label("Warning");
266 hsla_edit_button("warning1", ui, &mut theme.colors.warning);
267
268 ui.label("Success");
269 hsla_edit_button("success1", ui, &mut theme.colors.success);
270
271 ui.label("Info");
272 hsla_edit_button("info1", ui, &mut theme.colors.info);
273 });
274
275 CollapsingHeader::new("Text Sizes").show(ui, |ui| {
276 ui.label("Very Small");
277 ui.add(Slider::new(&mut theme.text_sizes.very_small, 0.0..=100.0).text("Size"));
278
279 ui.label("Small");
280 ui.add(Slider::new(&mut theme.text_sizes.small, 0.0..=100.0).text("Size"));
281
282 ui.label("Normal");
283 ui.add(Slider::new(&mut theme.text_sizes.normal, 0.0..=100.0).text("Size"));
284
285 ui.label("Large");
286 ui.add(Slider::new(&mut theme.text_sizes.large, 0.0..=100.0).text("Size"));
287
288 ui.label("Very Large");
289 ui.add(Slider::new(&mut theme.text_sizes.very_large, 0.0..=100.0).text("Size"));
290
291 ui.label("Heading");
292 ui.add(Slider::new(&mut theme.text_sizes.heading, 0.0..=100.0).text("Size"));
293 });
294
295 CollapsingHeader::new("Other Colors").show(ui, |ui| {
296 ui.label("Selection Stroke");
297 ui.add(
298 Slider::new(
299 &mut theme.style.visuals.selection.stroke.width,
300 0.0..=10.0,
301 )
302 .text("Stroke Width"),
303 );
304 ui.label("Selection Stroke Color");
305 hsla_edit_button(
306 "selection_stroke_color1",
307 ui,
308 &mut theme.style.visuals.selection.stroke.color,
309 );
310
311 ui.label("Selection Bg Fill");
312 hsla_edit_button(
313 "selection_bg_fill1",
314 ui,
315 &mut theme.style.visuals.selection.bg_fill,
316 );
317
318 ui.label("Hyperlink Color");
319 hsla_edit_button(
320 "hyperlink_color1",
321 ui,
322 &mut theme.style.visuals.hyperlink_color,
323 );
324
325 ui.label("Faint Background Color");
326 hsla_edit_button(
327 "faint_bg_color1",
328 ui,
329 &mut theme.style.visuals.faint_bg_color,
330 );
331
332 ui.label("Extreme Background Color");
333 hsla_edit_button(
334 "extreme_bg_color1",
335 ui,
336 &mut theme.style.visuals.extreme_bg_color,
337 );
338
339 ui.label("Code Background Color");
340 hsla_edit_button(
341 "code_bg_color1",
342 ui,
343 &mut theme.style.visuals.code_bg_color,
344 );
345
346 ui.label("Warning Text Color");
347 hsla_edit_button(
348 "warn_fg_color1",
349 ui,
350 &mut theme.style.visuals.warn_fg_color,
351 );
352
353 ui.label("Error Text Color");
354 hsla_edit_button(
355 "error_fg_color1",
356 ui,
357 &mut theme.style.visuals.error_fg_color,
358 );
359
360 ui.label("Panel Fill Color");
361 hsla_edit_button(
362 "panel_fill1",
363 ui,
364 &mut theme.style.visuals.panel_fill,
365 );
366 });
367
368 CollapsingHeader::new("Window Visuals").show(ui, |ui| {
369 ui.label("Window Rounding");
370 ui.add(
371 Slider::new(
372 &mut theme.style.visuals.window_corner_radius.nw,
373 0..=35,
374 )
375 .text("Top Left"),
376 );
377
378 ui.add(
379 Slider::new(
380 &mut theme.style.visuals.window_corner_radius.ne,
381 0..=35,
382 )
383 .text("Top Right"),
384 );
385
386 ui.add(
387 Slider::new(
388 &mut theme.style.visuals.window_corner_radius.sw,
389 0..=35,
390 )
391 .text("Bottom Left"),
392 );
393
394 ui.add(
395 Slider::new(
396 &mut theme.style.visuals.window_corner_radius.se,
397 0..=35,
398 )
399 .text("Bottom Right"),
400 );
401
402 ui.label("Window Shadow");
403 ui.add(
404 Slider::new(
405 &mut theme.style.visuals.window_shadow.offset[0],
406 -100..=100,
407 )
408 .text("Offset X"),
409 );
410
411 ui.add(
412 Slider::new(
413 &mut theme.style.visuals.window_shadow.offset[1],
414 -100..=100,
415 )
416 .text("Offset Y"),
417 );
418
419 ui.add(
420 Slider::new(
421 &mut theme.style.visuals.window_shadow.blur,
422 0..=100,
423 )
424 .text("Blur"),
425 );
426
427 ui.add(
428 Slider::new(
429 &mut theme.style.visuals.window_shadow.spread,
430 0..=100,
431 )
432 .text("Spread"),
433 );
434
435 ui.label("Shadow Color");
436 hsla_edit_button(
437 "window_shadow_color1",
438 ui,
439 &mut theme.style.visuals.window_shadow.color,
440 );
441
442 ui.label("Window Fill Color");
443 hsla_edit_button(
444 "window_fill1",
445 ui,
446 &mut theme.style.visuals.window_fill,
447 );
448
449 ui.label("Window Stroke Color");
450 ui.add(
451 Slider::new(
452 &mut theme.style.visuals.window_stroke.width,
453 0.0..=10.0,
454 )
455 .text("Stroke Width"),
456 );
457
458 hsla_edit_button(
459 "window_stroke_color1",
460 ui,
461 &mut theme.style.visuals.window_stroke.color,
462 );
463
464 ui.label("Window Highlight Topmost");
465 ui.checkbox(
466 &mut theme.style.visuals.window_highlight_topmost,
467 "Highlight Topmost",
468 );
469 });
470
471 CollapsingHeader::new("Popup Shadow").show(ui, |ui| {
472 ui.add(
473 Slider::new(
474 &mut theme.style.visuals.popup_shadow.offset[0],
475 -100..=100,
476 )
477 .text("Offset X"),
478 );
479
480 ui.add(
481 Slider::new(
482 &mut theme.style.visuals.popup_shadow.offset[1],
483 -100..=100,
484 )
485 .text("Offset Y"),
486 );
487
488 ui.add(
489 Slider::new(
490 &mut theme.style.visuals.popup_shadow.blur,
491 0..=100,
492 )
493 .text("Blur"),
494 );
495
496 ui.add(
497 Slider::new(
498 &mut theme.style.visuals.popup_shadow.spread,
499 0..=100,
500 )
501 .text("Spread"),
502 );
503
504 hsla_edit_button(
505 "popup_shadow_color1",
506 ui,
507 &mut theme.style.visuals.popup_shadow.color,
508 );
509 });
510
511 CollapsingHeader::new("Menu Rounding").show(ui, |ui| {
512 ui.add(
513 Slider::new(
514 &mut theme.style.visuals.menu_corner_radius.nw,
515 0..=35,
516 )
517 .text("Top Left"),
518 );
519
520 ui.add(
521 Slider::new(
522 &mut theme.style.visuals.menu_corner_radius.ne,
523 0..=35,
524 )
525 .text("Top Right"),
526 );
527
528 ui.add(
529 Slider::new(
530 &mut theme.style.visuals.menu_corner_radius.sw,
531 0..=35,
532 )
533 .text("Bottom Left"),
534 );
535
536 ui.add(
537 Slider::new(
538 &mut theme.style.visuals.menu_corner_radius.se,
539 0..=35,
540 )
541 .text("Bottom Right"),
542 );
543 });
544
545 CollapsingHeader::new("Widget Visuals").show(ui, |ui| {
546 self.widget_settings(theme, ui);
547 });
548
549 CollapsingHeader::new("Other Settings").show(ui, |ui| {
550 ui.label("Resize Corner Size");
551 ui.add(
552 Slider::new(
553 &mut theme.style.visuals.resize_corner_size,
554 0.0..=100.0,
555 )
556 .text("Corner Size"),
557 );
558
559 ui.label("Button Frame");
560 ui.checkbox(
561 &mut theme.style.visuals.button_frame,
562 "Button Frame",
563 );
564 });
565
566 CollapsingHeader::new("Tessellation").show(ui, |ui| {
567 self.tesellation_settings(theme, ui);
568 });
569 });
570 }
571
572 fn tesellation_settings(&mut self, theme: &Theme, ui: &mut Ui) {
573 let text_size = theme.text_sizes.normal;
574
575 let mut options = ui.ctx().tessellation_options(|options| options.clone());
576
577 let text = RichText::new("Feathering").size(text_size);
578
579 ui.checkbox(&mut options.feathering, text);
580
581 ui.add(
582 DragValue::new(&mut options.feathering_size_in_pixels)
583 .speed(0.1)
584 .range(0.0..=100.0),
585 );
586
587 let text = RichText::new("Coarse tessellation culling").size(text_size);
588 ui.checkbox(&mut options.coarse_tessellation_culling, text);
589
590 let text = RichText::new("Precomputed discs").size(text_size);
591 ui.checkbox(&mut options.prerasterized_discs, text);
592
593 let text = RichText::new("Round text to pixels").size(text_size);
594 ui.checkbox(&mut options.round_text_to_pixels, text);
595
596 let text = RichText::new("Round line segments to pixels").size(text_size);
597 ui.checkbox(&mut options.round_line_segments_to_pixels, text);
598
599 let text = RichText::new("Round rects to pixels").size(text_size);
600 ui.checkbox(&mut options.round_rects_to_pixels, text);
601
602 let text = RichText::new("Debug paint text rects").size(text_size);
603 ui.checkbox(&mut options.debug_paint_text_rects, text);
604
605 let text = RichText::new("Debug paint clip rects").size(text_size);
606 ui.checkbox(&mut options.debug_paint_clip_rects, text);
607
608 let text = RichText::new("Debug ignore clip rects").size(text_size);
609 ui.checkbox(&mut options.debug_ignore_clip_rects, text);
610
611 let text = RichText::new("Bezier tolerance").size(text_size);
612 ui.label(text);
613 ui.add(DragValue::new(&mut options.bezier_tolerance).speed(0.1).range(0.0..=1.0));
614
615 let text = RichText::new("Epsilon").size(text_size);
616 ui.label(text);
617 ui.add(DragValue::new(&mut options.epsilon).speed(0.1).range(0.0..=1.0));
618
619 let text = RichText::new("Parallel tessellation").size(text_size);
620 ui.checkbox(&mut options.parallel_tessellation, text);
621
622 let text = RichText::new("Validate meshes").size(text_size);
623 ui.checkbox(&mut options.validate_meshes, text);
624
625 ui.ctx().tessellation_options_mut(|options_mut| {
626 *options_mut = options;
627 });
628 }
629
630 fn widget_settings(&mut self, theme: &mut Theme, ui: &mut Ui) {
631 self.select_widget_state(ui);
632
633 let widget_visuals = match self.widget_state {
634 WidgetState::NonInteractive => &mut theme.style.visuals.widgets.noninteractive,
635 WidgetState::Inactive => &mut theme.style.visuals.widgets.inactive,
636 WidgetState::Hovered => &mut theme.style.visuals.widgets.hovered,
637 WidgetState::Active => &mut theme.style.visuals.widgets.active,
638 WidgetState::Open => &mut theme.style.visuals.widgets.open,
639 };
640
641 ui.label("Background Fill Color");
642
643 ui.horizontal(|ui| {
644 let color = self.color_select("1", widget_visuals.bg_fill, &theme.colors, ui);
645 if let Some(color) = color {
646 widget_visuals.bg_fill = color.color32();
647 }
648
649 hsla_edit_button("bg_fill1", ui, &mut widget_visuals.bg_fill);
650 });
651
652 ui.label("Weak Background Fill Color");
653
654 ui.horizontal(|ui| {
655 let color = self.color_select(
656 "2",
657 widget_visuals.weak_bg_fill,
658 &theme.colors,
659 ui,
660 );
661 if let Some(color) = color {
662 widget_visuals.weak_bg_fill = color.color32();
663 }
664
665 hsla_edit_button(
666 "weak_bg_fill1",
667 ui,
668 &mut widget_visuals.weak_bg_fill,
669 );
670 });
671
672 ui.label("Background Stroke Width");
673 ui.add(Slider::new(
674 &mut widget_visuals.bg_stroke.width,
675 0.0..=10.0,
676 ));
677
678 ui.label("Background Stroke Color");
679 ui.horizontal(|ui| {
680 let color = self.color_select(
681 "3",
682 widget_visuals.bg_stroke.color,
683 &theme.colors,
684 ui,
685 );
686 if let Some(color) = color {
687 widget_visuals.bg_stroke.color = color.color32();
688 }
689
690 hsla_edit_button(
691 "bg_stroke_color1",
692 ui,
693 &mut widget_visuals.bg_stroke.color,
694 );
695 });
696
697 ui.label("Rounding");
698 ui.add(Slider::new(&mut widget_visuals.corner_radius.nw, 0..=255).text("Top Left"));
699 ui.add(Slider::new(&mut widget_visuals.corner_radius.ne, 0..=255).text("Top Right"));
700 ui.add(Slider::new(&mut widget_visuals.corner_radius.sw, 0..=255).text("Bottom Left"));
701 ui.add(Slider::new(&mut widget_visuals.corner_radius.se, 0..=255).text("Bottom Right"));
702
703 ui.label("Foreground Stroke Width");
704 ui.add(Slider::new(
705 &mut widget_visuals.fg_stroke.width,
706 0.0..=10.0,
707 ));
708
709 ui.label("Foreground Stroke Color");
710 ui.horizontal(|ui| {
711 let color = self.color_select(
712 "4",
713 widget_visuals.fg_stroke.color,
714 &theme.colors,
715 ui,
716 );
717
718 if let Some(color) = color {
719 widget_visuals.fg_stroke.color = color.color32();
720 }
721
722 hsla_edit_button(
723 "fg_stroke_color1",
724 ui,
725 &mut widget_visuals.fg_stroke.color,
726 );
727 });
728
729 ui.label("Expansion");
730 ui.add(Slider::new(&mut widget_visuals.expansion, 0.0..=100.0).text("Expansion"));
731 }
732
733 fn frame_settings(&mut self, frame: &mut Frame, ui: &mut Ui) {
734 CollapsingHeader::new("Inner & Outter Margin").show(ui, |ui| {
735 ui.label("Inner Margin");
736 ui.add(Slider::new(&mut frame.inner_margin.top, 0..=127).text("Top"));
737 ui.add(Slider::new(&mut frame.inner_margin.bottom, 0..=127).text("Bottom"));
738 ui.add(Slider::new(&mut frame.inner_margin.left, 0..=127).text("Left"));
739 ui.add(Slider::new(&mut frame.inner_margin.right, 0..=127).text("Right"));
740
741 ui.label("Outter Margin");
742 ui.add(Slider::new(&mut frame.outer_margin.top, 0..=127).text("Top"));
743 ui.add(Slider::new(&mut frame.outer_margin.bottom, 0..=127).text("Bottom"));
744 ui.add(Slider::new(&mut frame.outer_margin.left, 0..=127).text("Left"));
745 ui.add(Slider::new(&mut frame.outer_margin.right, 0..=127).text("Right"));
746 });
747
748 ui.label("Rounding");
749 ui.add(Slider::new(&mut frame.corner_radius.nw, 0..=255).text("Top Left"));
750 ui.add(Slider::new(&mut frame.corner_radius.ne, 0..=255).text("Top Right"));
751 ui.add(Slider::new(&mut frame.corner_radius.sw, 0..=255).text("Bottom Left"));
752 ui.add(Slider::new(&mut frame.corner_radius.se, 0..=255).text("Bottom Right"));
753
754 ui.label("Shadow");
755 ui.add(Slider::new(&mut frame.shadow.offset[0], -128..=127).text("Offset X"));
756 ui.add(Slider::new(&mut frame.shadow.offset[1], -128..=127).text("Offset Y"));
757 ui.add(Slider::new(&mut frame.shadow.blur, 0..=255).text("Blur"));
758 ui.add(Slider::new(&mut frame.shadow.spread, 0..=255).text("Spread"));
759
760 ui.label("Shadow Color");
761 hsla_edit_button("shadow_color1", ui, &mut frame.shadow.color);
762
763 ui.label("Fill Color");
764 hsla_edit_button("fill_color1", ui, &mut frame.fill);
765
766 ui.label("Stroke Width & Color");
767 ui.add(Slider::new(&mut frame.stroke.width, 0.0..=100.0).text("Stroke Width"));
768 hsla_edit_button("stroke_color1", ui, &mut frame.stroke.color);
769 }
770
771 fn select_widget_state(&mut self, ui: &mut Ui) {
772 ComboBox::from_label("")
773 .selected_text(self.widget_state.to_str())
774 .show_ui(ui, |ui| {
775 for widget in self.widget_state.to_vec() {
776 let value = ui.selectable_value(
777 &mut self.widget_state,
778 widget.clone(),
779 widget.to_str(),
780 );
781
782 if value.clicked() {
783 self.widget_state = widget;
784 }
785 }
786 });
787 }
788
789 fn color_select(
790 &mut self,
791 id: &str,
792 current_color: Color32,
793 colors: &ThemeColors,
794 ui: &mut Ui,
795 ) -> Option<Color> {
796 let all_colors = Color::all_colors_from(colors);
797
798 let mut selected_color = None;
799 let current_color_name = Color::name_from(current_color, colors);
800
801 ComboBox::from_id_salt(id).selected_text(current_color_name).show_ui(ui, |ui| {
802 for color in all_colors {
803 let value = ui.selectable_value(&mut self.color, color.clone(), color.to_str());
804
805 if value.clicked() {
806 selected_color = Some(color);
807 }
808 }
809 });
810 selected_color
811 }
812}
813
814pub fn hsla_edit_button(id: &str, ui: &mut Ui, color32: &mut Color32) -> Response {
815 let stroke = Stroke::new(1.0, Color32::GRAY);
816 let button_size = Vec2::new(50.0, 20.0);
817 let (rect, mut response) = ui.allocate_exact_size(button_size, Sense::click());
818 ui.painter().rect_filled(rect, 4.0, *color32);
819 ui.painter().rect_stroke(rect, 4.0, stroke, StrokeKind::Inside);
820
821 let popup_id = ui.make_persistent_id(id);
822
823 let set_command = if response.clicked() {
824 Some(SetOpenCommand::Toggle)
825 } else {
826 None
827 };
828
829 let close_behavior = PopupCloseBehavior::CloseOnClickOutside;
830 let popup = Popup::from_response(&response)
831 .close_behavior(close_behavior)
832 .open_memory(set_command);
833
834 let working_id = popup_id.with("working_hsla");
835 let mut working_hsla = ui
836 .memory(|mem| mem.data.get_temp(working_id))
837 .unwrap_or_else(|| Hsla::from_color32(*color32));
838
839 let popup_res = popup.show(|ui| hsla_picker_ui(ui, &mut working_hsla));
840
841 if let Some(inner) = popup_res {
842 if inner.inner {
844 ui.memory_mut(|mem| mem.data.insert_temp(working_id, working_hsla));
845 *color32 = working_hsla.to_color32();
846 response.mark_changed();
847 }
848 } else {
849 ui.memory_mut(|mem| mem.data.remove::<Hsla>(working_id));
850 }
851
852 response
853}
854
855fn hsla_picker_ui(ui: &mut Ui, hsla: &mut Hsla) -> bool {
857 let mut changed = false;
858 let stroke = Stroke::new(1.0, Color32::GRAY);
859
860 ui.horizontal(|ui| {
861 ui.set_width(200.0);
862
863 ui.vertical(|ui| {
865 changed |= sl_2d_picker(ui, hsla);
866 changed |= hue_slider(ui, hsla);
867 changed |= alpha_slider(ui, hsla);
868 });
869
870 ui.vertical(|ui| {
872 let preview_size = Vec2::new(80.0, 80.0);
874 let (rect, _) = ui.allocate_exact_size(preview_size, Sense::hover());
875 ui.painter().rect_filled(rect, 4.0, hsla.to_color32());
876 ui.painter().rect_stroke(rect, 4.0, stroke, StrokeKind::Inside);
877
878 ui.label(RichText::new("Preview").strong());
879
880 ui.add_space(10.0);
882 changed |= ui.add(Slider::new(&mut hsla.h, 0.0..=360.0).text("Hue")).changed();
883 changed |= ui.add(Slider::new(&mut hsla.s, 0.0..=100.0).text("Saturation")).changed();
884 changed |= ui.add(Slider::new(&mut hsla.l, 0.0..=100.0).text("Lightness")).changed();
885 changed |= ui.add(Slider::new(&mut hsla.a, 0.0..=1.0).text("Alpha")).changed();
886 });
887
888 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
889 ui.vertical(|ui| {
890 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
892 let (r, g, b, a) = hsla.to_rgba_components();
893 let text = RichText::new(format!("RGBA ({r}, {g}, {b}, {a})"));
894 let button = Button::new(text).min_size(vec2(160.0, 15.0));
895 if ui.add(button).clicked() {
896 ui.ctx().copy_text(format!("({r}, {g}, {b}, {a})"));
897 }
898 });
899
900 ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
902 let hex_color = HexColor::Hex6(hsla.to_color32());
903 let text = RichText::new(format!("HEX {}", hex_color));
904 let button = Button::new(text).min_size(vec2(160.0, 15.0));
905 if ui.add(button).clicked() {
906 ui.ctx().copy_text(format!("{}", hex_color));
907 }
908 });
909 });
910 });
911 });
912
913 changed
914}
915
916fn sl_2d_picker(ui: &mut Ui, hsla: &mut Hsla) -> bool {
918 let size = Vec2::new(150.0, 150.0);
919 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
920
921 let mut changed = false;
922
923 if response.dragged() {
924 if let Some(pos) = response.hover_pos() {
925 let relative = pos - rect.min;
926 hsla.s = (relative.x / size.x).clamp(0.0, 1.0) * 100.0;
927 hsla.l = (1.0 - (relative.y / size.y)).clamp(0.0, 1.0) * 100.0; changed = true;
929 }
930 }
931
932 let painter = ui.painter();
934 const RES: usize = 64; let cell_size = size / RES as f32;
936 for i in 0..RES {
937 for j in 0..RES {
938 let s = (i as f32 / (RES - 1) as f32) * 100.0;
939 let l = (1.0 - (j as f32 / (RES - 1) as f32)) * 100.0; let temp_hsla = Hsla {
941 h: hsla.h,
942 s,
943 l,
944 a: 1.0,
945 };
946 let color = temp_hsla.to_color32();
947
948 let min = rect.min + Vec2::new(i as f32 * cell_size.x, j as f32 * cell_size.y);
949 let cell_rect = Rect::from_min_size(min, cell_size);
950 painter.rect_filled(cell_rect, 0.0, color);
951 }
952 }
953
954 let x = (hsla.s / 100.0) * size.x;
956 let y = (1.0 - hsla.l / 100.0) * size.y;
957 let cursor_pos = rect.min + Vec2::new(x, y);
958 painter.circle_stroke(cursor_pos, 5.0, Stroke::new(1.0, Color32::WHITE));
959 painter.circle_stroke(cursor_pos, 5.0, Stroke::new(1.0, Color32::BLACK));
960
961 painter.rect_stroke(
963 rect,
964 0.0,
965 Stroke::new(1.0, Color32::GRAY),
966 StrokeKind::Inside,
967 );
968
969 changed
970}
971
972fn hue_slider(ui: &mut Ui, hsla: &mut Hsla) -> bool {
974 let size = Vec2::new(150.0, 20.0);
975 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
976
977 let mut changed = false;
978
979 if response.dragged() {
980 if let Some(pos) = response.hover_pos() {
981 let relative_x = (pos.x - rect.min.x) / size.x;
982 hsla.h = relative_x.clamp(0.0, 1.0) * 360.0;
983 changed = true;
984 }
985 }
986
987 let painter = ui.painter();
989 const RES: usize = 128; let cell_width = size.x / RES as f32;
991 for i in 0..RES {
992 let h = (i as f32 / (RES - 1) as f32) * 360.0;
993 let temp_hsla = Hsla {
994 h,
995 s: 100.0,
996 l: 50.0,
997 a: 1.0,
998 }; let color = temp_hsla.to_color32();
1000
1001 let min = rect.min + Vec2::new(i as f32 * cell_width, 0.0);
1002 let cell_rect = Rect::from_min_size(min, Vec2::new(cell_width, size.y));
1003 painter.rect_filled(cell_rect, 0.0, color);
1004 }
1005
1006 let x = (hsla.h / 360.0) * size.x;
1008 let line_start = rect.min + Vec2::new(x, 0.0);
1009 let line_end = rect.min + Vec2::new(x, size.y);
1010 painter.line_segment(
1011 [line_start, line_end],
1012 Stroke::new(2.0, Color32::WHITE),
1013 );
1014
1015 painter.rect_stroke(
1017 rect,
1018 4.0,
1019 Stroke::new(1.0, Color32::GRAY),
1020 StrokeKind::Inside,
1021 );
1022
1023 changed
1024}
1025
1026fn alpha_slider(ui: &mut Ui, hsla: &mut Hsla) -> bool {
1027 let size = Vec2::new(150.0, 20.0);
1028 let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
1029 let mut changed = false;
1030
1031 if response.dragged() {
1032 if let Some(pos) = response.hover_pos() {
1033 let relative_x = (pos.x - rect.min.x) / size.x;
1034 hsla.a = relative_x.clamp(0.0, 1.0);
1035 changed = true;
1036 }
1037 }
1038
1039 let painter = ui.painter();
1040 let checker_size = 5.0;
1042
1043 for x in (0..=((size.x / checker_size) as usize)).step_by(1) {
1044 for y in (0..=((size.y / checker_size) as usize)).step_by(1) {
1045 let color = if (x + y) % 2 == 0 {
1046 Color32::GRAY
1047 } else {
1048 Color32::LIGHT_GRAY
1049 };
1050 let min = rect.min + Vec2::new(x as f32 * checker_size, y as f32 * checker_size);
1051 let cell_rect = Rect::from_min_size(min, Vec2::splat(checker_size)).intersect(rect);
1052 painter.rect_filled(cell_rect, 0.0, color);
1053 }
1054 }
1055
1056 const RES: usize = 64;
1058 let cell_width = size.x / RES as f32;
1059
1060 for i in 0..RES {
1061 let a = i as f32 / (RES - 1) as f32;
1062 let temp_hsla = Hsla {
1063 h: hsla.h,
1064 s: hsla.s,
1065 l: hsla.l,
1066 a,
1067 };
1068
1069 let color = temp_hsla.to_color32();
1070 let min = rect.min + Vec2::new(i as f32 * cell_width, 0.0);
1071 let cell_rect = Rect::from_min_size(min, Vec2::new(cell_width, size.y));
1072 painter.rect_filled(cell_rect, 0.0, color);
1073 }
1074
1075 let x = hsla.a * size.x;
1077 let line_start = rect.min + Vec2::new(x, 0.0);
1078 let line_end = rect.min + Vec2::new(x, size.y);
1079
1080 painter.line_segment(
1081 [line_start, line_end],
1082 Stroke::new(2.0, Color32::WHITE),
1083 );
1084
1085 painter.rect_stroke(
1087 rect,
1088 4.0,
1089 Stroke::new(1.0, Color32::GRAY),
1090 StrokeKind::Inside,
1091 );
1092 changed
1093}