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