1#![allow(clippy::if_same_then_else)]
4
5use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
6
7use emath::Align;
8use epaint::{text::FontTweak, Rounding, Shadow, Stroke};
9
10use crate::{
11 ecolor::Color32,
12 emath::{pos2, vec2, Rangef, Rect, Vec2},
13 ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response, RichText, TextWrapMode,
14 WidgetText,
15};
16
17#[derive(Clone)]
19pub struct NumberFormatter(
20 Arc<dyn 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String>,
21);
22
23impl NumberFormatter {
24 #[inline]
29 pub fn new(
30 formatter: impl 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String,
31 ) -> Self {
32 Self(Arc::new(formatter))
33 }
34
35 #[inline]
44 pub fn format(&self, value: f64, decimals: RangeInclusive<usize>) -> String {
45 (self.0)(value, decimals)
46 }
47}
48
49impl std::fmt::Debug for NumberFormatter {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.write_str("NumberFormatter")
52 }
53}
54
55impl PartialEq for NumberFormatter {
56 #[inline]
57 fn eq(&self, other: &Self) -> bool {
58 Arc::ptr_eq(&self.0, &other.0)
59 }
60}
61
62#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
69#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
70pub enum TextStyle {
71 Small,
73
74 Body,
76
77 Monospace,
79
80 Button,
84
85 Heading,
87
88 Name(std::sync::Arc<str>),
93}
94
95impl std::fmt::Display for TextStyle {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 match self {
98 Self::Small => "Small".fmt(f),
99 Self::Body => "Body".fmt(f),
100 Self::Monospace => "Monospace".fmt(f),
101 Self::Button => "Button".fmt(f),
102 Self::Heading => "Heading".fmt(f),
103 Self::Name(name) => (*name).fmt(f),
104 }
105 }
106}
107
108impl TextStyle {
109 pub fn resolve(&self, style: &Style) -> FontId {
111 style.text_styles.get(self).cloned().unwrap_or_else(|| {
112 panic!(
113 "Failed to find {:?} in Style::text_styles. Available styles:\n{:#?}",
114 self,
115 style.text_styles()
116 )
117 })
118 }
119}
120
121pub enum FontSelection {
125 Default,
128
129 FontId(FontId),
131
132 Style(TextStyle),
134}
135
136impl Default for FontSelection {
137 #[inline]
138 fn default() -> Self {
139 Self::Default
140 }
141}
142
143impl FontSelection {
144 pub fn resolve(self, style: &Style) -> FontId {
145 match self {
146 Self::Default => {
147 if let Some(override_font_id) = &style.override_font_id {
148 override_font_id.clone()
149 } else if let Some(text_style) = &style.override_text_style {
150 text_style.resolve(style)
151 } else {
152 TextStyle::Body.resolve(style)
153 }
154 }
155 Self::FontId(font_id) => font_id,
156 Self::Style(text_style) => text_style.resolve(style),
157 }
158 }
159}
160
161impl From<FontId> for FontSelection {
162 #[inline(always)]
163 fn from(font_id: FontId) -> Self {
164 Self::FontId(font_id)
165 }
166}
167
168impl From<TextStyle> for FontSelection {
169 #[inline(always)]
170 fn from(text_style: TextStyle) -> Self {
171 Self::Style(text_style)
172 }
173}
174
175#[derive(Clone, Debug, PartialEq)]
185#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
186#[cfg_attr(feature = "serde", serde(default))]
187pub struct Style {
188 pub override_text_style: Option<TextStyle>,
193
194 pub override_font_id: Option<FontId>,
199
200 pub override_text_valign: Option<Align>,
204
205 pub text_styles: BTreeMap<TextStyle, FontId>,
233
234 pub drag_value_text_style: TextStyle,
236
237 #[cfg_attr(feature = "serde", serde(skip))]
241 pub number_formatter: NumberFormatter,
242
243 #[deprecated = "Use wrap_mode instead"]
252 pub wrap: Option<bool>,
253
254 pub wrap_mode: Option<crate::TextWrapMode>,
261
262 pub spacing: Spacing,
264
265 pub interaction: Interaction,
267
268 pub visuals: Visuals,
270
271 pub animation_time: f32,
273
274 #[cfg(debug_assertions)]
278 pub debug: DebugOptions,
279
280 pub explanation_tooltips: bool,
284
285 pub url_in_tooltip: bool,
287
288 pub always_scroll_the_only_direction: bool,
290
291 pub scroll_animation: ScrollAnimation,
293}
294
295#[test]
296fn style_impl_send_sync() {
297 fn assert_send_sync<T: Send + Sync>() {}
298 assert_send_sync::<Style>();
299}
300
301impl Style {
302 pub fn interact(&self, response: &Response) -> &WidgetVisuals {
307 self.visuals.widgets.style(response)
308 }
309
310 pub fn interact_selectable(&self, response: &Response, selected: bool) -> WidgetVisuals {
311 let mut visuals = *self.visuals.widgets.style(response);
312 if selected {
313 visuals.weak_bg_fill = self.visuals.selection.bg_fill;
314 visuals.bg_fill = self.visuals.selection.bg_fill;
315 visuals.fg_stroke = self.visuals.selection.stroke;
317 }
318 visuals
319 }
320
321 pub fn noninteractive(&self) -> &WidgetVisuals {
323 &self.visuals.widgets.noninteractive
324 }
325
326 pub fn text_styles(&self) -> Vec<TextStyle> {
328 self.text_styles.keys().cloned().collect()
329 }
330}
331
332#[derive(Clone, Debug, PartialEq)]
334#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
335#[cfg_attr(feature = "serde", serde(default))]
336pub struct Spacing {
337 pub item_spacing: Vec2,
344
345 pub window_margin: Margin,
347
348 pub button_padding: Vec2,
350
351 pub menu_margin: Margin,
353
354 pub indent: f32,
356
357 pub interact_size: Vec2, pub slider_width: f32,
364
365 pub slider_rail_height: f32,
367
368 pub combo_width: f32,
370
371 pub text_edit_width: f32,
373
374 pub icon_width: f32,
377
378 pub icon_width_inner: f32,
381
382 pub icon_spacing: f32,
385
386 pub default_area_size: Vec2,
394
395 pub tooltip_width: f32,
397
398 pub menu_width: f32,
402
403 pub menu_spacing: f32,
405
406 pub indent_ends_with_horizontal_line: bool,
408
409 pub combo_height: f32,
411
412 pub scroll: ScrollStyle,
414}
415
416impl Spacing {
417 pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) {
419 let icon_width = self.icon_width;
420 let big_icon_rect = Rect::from_center_size(
421 pos2(rect.left() + icon_width / 2.0, rect.center().y),
422 vec2(icon_width, icon_width),
423 );
424
425 let small_icon_rect =
426 Rect::from_center_size(big_icon_rect.center(), Vec2::splat(self.icon_width_inner));
427
428 (small_icon_rect, big_icon_rect)
429 }
430}
431
432#[derive(Clone, Copy, Debug, PartialEq)]
441#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
442#[cfg_attr(feature = "serde", serde(default))]
443pub struct ScrollStyle {
444 pub floating: bool,
452
453 pub bar_width: f32,
455
456 pub handle_min_length: f32,
458
459 pub bar_inner_margin: f32,
461
462 pub bar_outer_margin: f32,
465
466 pub floating_width: f32,
470
471 pub floating_allocated_width: f32,
478
479 pub foreground_color: bool,
481
482 pub dormant_background_opacity: f32,
488
489 pub active_background_opacity: f32,
495
496 pub interact_background_opacity: f32,
502
503 pub dormant_handle_opacity: f32,
509
510 pub active_handle_opacity: f32,
516
517 pub interact_handle_opacity: f32,
523}
524
525impl Default for ScrollStyle {
526 fn default() -> Self {
527 Self::floating()
528 }
529}
530
531impl ScrollStyle {
532 pub fn solid() -> Self {
534 Self {
535 floating: false,
536 bar_width: 6.0,
537 handle_min_length: 12.0,
538 bar_inner_margin: 4.0,
539 bar_outer_margin: 0.0,
540 floating_width: 2.0,
541 floating_allocated_width: 0.0,
542
543 foreground_color: false,
544
545 dormant_background_opacity: 0.0,
546 active_background_opacity: 0.4,
547 interact_background_opacity: 0.7,
548
549 dormant_handle_opacity: 0.0,
550 active_handle_opacity: 0.6,
551 interact_handle_opacity: 1.0,
552 }
553 }
554
555 pub fn thin() -> Self {
557 Self {
558 floating: true,
559 bar_width: 10.0,
560 floating_allocated_width: 6.0,
561 foreground_color: false,
562
563 dormant_background_opacity: 1.0,
564 dormant_handle_opacity: 1.0,
565
566 active_background_opacity: 1.0,
567 active_handle_opacity: 1.0,
568
569 interact_background_opacity: 0.6,
571 interact_handle_opacity: 0.6,
572
573 ..Self::solid()
574 }
575 }
576
577 pub fn floating() -> Self {
581 Self {
582 floating: true,
583 bar_width: 10.0,
584 foreground_color: true,
585 floating_allocated_width: 0.0,
586 dormant_background_opacity: 0.0,
587 dormant_handle_opacity: 0.0,
588 ..Self::solid()
589 }
590 }
591
592 pub fn allocated_width(&self) -> f32 {
594 if self.floating {
595 self.floating_allocated_width
596 } else {
597 self.bar_inner_margin + self.bar_width + self.bar_outer_margin
598 }
599 }
600
601 pub fn ui(&mut self, ui: &mut Ui) {
602 ui.horizontal(|ui| {
603 ui.label("Presets:");
604 ui.selectable_value(self, Self::solid(), "Solid");
605 ui.selectable_value(self, Self::thin(), "Thin");
606 ui.selectable_value(self, Self::floating(), "Floating");
607 });
608
609 ui.collapsing("Details", |ui| {
610 self.details_ui(ui);
611 });
612 }
613
614 pub fn details_ui(&mut self, ui: &mut Ui) {
615 let Self {
616 floating,
617 bar_width,
618 handle_min_length,
619 bar_inner_margin,
620 bar_outer_margin,
621 floating_width,
622 floating_allocated_width,
623
624 foreground_color,
625
626 dormant_background_opacity,
627 active_background_opacity,
628 interact_background_opacity,
629 dormant_handle_opacity,
630 active_handle_opacity,
631 interact_handle_opacity,
632 } = self;
633
634 ui.horizontal(|ui| {
635 ui.label("Type:");
636 ui.selectable_value(floating, false, "Solid");
637 ui.selectable_value(floating, true, "Floating");
638 });
639
640 ui.horizontal(|ui| {
641 ui.add(DragValue::new(bar_width).range(0.0..=32.0));
642 ui.label("Full bar width");
643 });
644 if *floating {
645 ui.horizontal(|ui| {
646 ui.add(DragValue::new(floating_width).range(0.0..=32.0));
647 ui.label("Thin bar width");
648 });
649 ui.horizontal(|ui| {
650 ui.add(DragValue::new(floating_allocated_width).range(0.0..=32.0));
651 ui.label("Allocated width");
652 });
653 }
654
655 ui.horizontal(|ui| {
656 ui.add(DragValue::new(handle_min_length).range(0.0..=32.0));
657 ui.label("Minimum handle length");
658 });
659 ui.horizontal(|ui| {
660 ui.add(DragValue::new(bar_outer_margin).range(0.0..=32.0));
661 ui.label("Outer margin");
662 });
663
664 ui.horizontal(|ui| {
665 ui.label("Color:");
666 ui.selectable_value(foreground_color, false, "Background");
667 ui.selectable_value(foreground_color, true, "Foreground");
668 });
669
670 if *floating {
671 crate::Grid::new("opacity").show(ui, |ui| {
672 fn opacity_ui(ui: &mut Ui, opacity: &mut f32) {
673 ui.add(DragValue::new(opacity).speed(0.01).range(0.0..=1.0));
674 }
675
676 ui.label("Opacity");
677 ui.label("Dormant");
678 ui.label("Active");
679 ui.label("Interacting");
680 ui.end_row();
681
682 ui.label("Background:");
683 opacity_ui(ui, dormant_background_opacity);
684 opacity_ui(ui, active_background_opacity);
685 opacity_ui(ui, interact_background_opacity);
686 ui.end_row();
687
688 ui.label("Handle:");
689 opacity_ui(ui, dormant_handle_opacity);
690 opacity_ui(ui, active_handle_opacity);
691 opacity_ui(ui, interact_handle_opacity);
692 ui.end_row();
693 });
694 } else {
695 ui.horizontal(|ui| {
696 ui.add(DragValue::new(bar_inner_margin).range(0.0..=32.0));
697 ui.label("Inner margin");
698 });
699 }
700 }
701}
702
703#[derive(Copy, Clone, Debug, PartialEq)]
709#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
710#[cfg_attr(feature = "serde", serde(default))]
711pub struct ScrollAnimation {
712 pub points_per_second: f32,
714
715 pub duration: Rangef,
717}
718
719impl Default for ScrollAnimation {
720 fn default() -> Self {
721 Self {
722 points_per_second: 1000.0,
723 duration: Rangef::new(0.1, 0.3),
724 }
725 }
726}
727
728impl ScrollAnimation {
729 pub fn new(points_per_second: f32, duration: Rangef) -> Self {
731 Self {
732 points_per_second,
733 duration,
734 }
735 }
736
737 pub fn none() -> Self {
739 Self {
740 points_per_second: f32::INFINITY,
741 duration: Rangef::new(0.0, 0.0),
742 }
743 }
744
745 pub fn duration(t: f32) -> Self {
747 Self {
748 points_per_second: f32::INFINITY,
749 duration: Rangef::new(t, t),
750 }
751 }
752
753 pub fn ui(&mut self, ui: &mut crate::Ui) {
754 crate::Grid::new("scroll_animation").show(ui, |ui| {
755 ui.label("Scroll animation:");
756 ui.add(
757 DragValue::new(&mut self.points_per_second)
758 .speed(100.0)
759 .range(0.0..=5000.0),
760 );
761 ui.label("points/second");
762 ui.end_row();
763
764 ui.label("Min duration:");
765 ui.add(
766 DragValue::new(&mut self.duration.min)
767 .speed(0.01)
768 .range(0.0..=self.duration.max),
769 );
770 ui.label("seconds");
771 ui.end_row();
772
773 ui.label("Max duration:");
774 ui.add(
775 DragValue::new(&mut self.duration.max)
776 .speed(0.01)
777 .range(0.0..=1.0),
778 );
779 ui.label("seconds");
780 ui.end_row();
781 });
782 }
783}
784
785#[derive(Clone, Debug, PartialEq)]
789#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
790#[cfg_attr(feature = "serde", serde(default))]
791pub struct Interaction {
792 pub interact_radius: f32,
797
798 pub resize_grab_radius_side: f32,
800
801 pub resize_grab_radius_corner: f32,
803
804 pub show_tooltips_only_when_still: bool,
806
807 pub tooltip_delay: f32,
809
810 pub tooltip_grace_time: f32,
816
817 pub selectable_labels: bool,
819
820 pub multi_widget_text_select: bool,
825}
826
827#[derive(Clone, Debug, PartialEq)]
829#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
830#[cfg_attr(feature = "serde", serde(default))]
831pub struct TextCursorStyle {
832 pub stroke: Stroke,
834
835 pub preview: bool,
837
838 pub blink: bool,
840
841 pub on_duration: f32,
843
844 pub off_duration: f32,
846}
847
848impl Default for TextCursorStyle {
849 fn default() -> Self {
850 Self {
851 stroke: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), preview: false,
853 blink: true,
854 on_duration: 0.5,
855 off_duration: 0.5,
856 }
857 }
858}
859
860#[derive(Clone, Debug, PartialEq)]
867#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
868#[cfg_attr(feature = "serde", serde(default))]
869pub struct Visuals {
870 pub dark_mode: bool,
876
877 pub override_text_color: Option<Color32>,
891
892 pub widgets: Widgets,
894
895 pub selection: Selection,
896
897 pub hyperlink_color: Color32,
899
900 pub faint_bg_color: Color32,
903
904 pub extreme_bg_color: Color32,
908
909 pub code_bg_color: Color32,
911
912 pub warn_fg_color: Color32,
914
915 pub error_fg_color: Color32,
917
918 pub window_rounding: Rounding,
919 pub window_shadow: Shadow,
920 pub window_fill: Color32,
921 pub window_stroke: Stroke,
922
923 pub window_highlight_topmost: bool,
925
926 pub menu_rounding: Rounding,
927
928 pub panel_fill: Color32,
930
931 pub popup_shadow: Shadow,
932
933 pub resize_corner_size: f32,
934
935 pub text_cursor: TextCursorStyle,
937
938 pub clip_rect_margin: f32,
940
941 pub button_frame: bool,
943
944 pub collapsing_header_frame: bool,
946
947 pub indent_has_left_vline: bool,
949
950 pub striped: bool,
953
954 pub slider_trailing_fill: bool,
958
959 pub handle_shape: HandleShape,
963
964 pub interact_cursor: Option<CursorIcon>,
970
971 pub image_loading_spinners: bool,
973
974 pub numeric_color_space: NumericColorSpace,
976}
977
978impl Visuals {
979 #[inline(always)]
980 pub fn noninteractive(&self) -> &WidgetVisuals {
981 &self.widgets.noninteractive
982 }
983
984 pub fn text_color(&self) -> Color32 {
986 self.override_text_color
987 .unwrap_or_else(|| self.widgets.noninteractive.text_color())
988 }
989
990 pub fn weak_text_color(&self) -> Color32 {
991 self.gray_out(self.text_color())
992 }
993
994 #[inline(always)]
995 pub fn strong_text_color(&self) -> Color32 {
996 self.widgets.active.text_color()
997 }
998
999 #[inline(always)]
1001 pub fn window_fill(&self) -> Color32 {
1002 self.window_fill
1003 }
1004
1005 #[inline(always)]
1006 pub fn window_stroke(&self) -> Stroke {
1007 self.window_stroke
1008 }
1009
1010 #[inline(always)]
1013 pub fn fade_out_to_color(&self) -> Color32 {
1014 self.widgets.noninteractive.weak_bg_fill
1015 }
1016
1017 #[doc(alias = "grey_out")]
1019 #[inline(always)]
1020 pub fn gray_out(&self, color: Color32) -> Color32 {
1021 crate::ecolor::tint_color_towards(color, self.fade_out_to_color())
1022 }
1023}
1024
1025#[derive(Clone, Copy, Debug, PartialEq)]
1027#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1028#[cfg_attr(feature = "serde", serde(default))]
1029pub struct Selection {
1030 pub bg_fill: Color32,
1031 pub stroke: Stroke,
1032}
1033
1034#[derive(Clone, Copy, Debug, PartialEq)]
1036#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1037pub enum HandleShape {
1038 Circle,
1040
1041 Rect {
1043 aspect_ratio: f32,
1045 },
1046}
1047
1048#[derive(Clone, Debug, PartialEq)]
1050#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1051#[cfg_attr(feature = "serde", serde(default))]
1052pub struct Widgets {
1053 pub noninteractive: WidgetVisuals,
1058
1059 pub inactive: WidgetVisuals,
1061
1062 pub hovered: WidgetVisuals,
1066
1067 pub active: WidgetVisuals,
1069
1070 pub open: WidgetVisuals,
1072}
1073
1074impl Widgets {
1075 pub fn style(&self, response: &Response) -> &WidgetVisuals {
1076 if !response.sense.interactive() {
1077 &self.noninteractive
1078 } else if response.is_pointer_button_down_on() || response.has_focus() || response.clicked()
1079 {
1080 &self.active
1081 } else if response.hovered() || response.highlighted() {
1082 &self.hovered
1083 } else {
1084 &self.inactive
1085 }
1086 }
1087}
1088
1089#[derive(Clone, Copy, Debug, PartialEq)]
1091#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1092pub struct WidgetVisuals {
1093 pub bg_fill: Color32,
1098
1099 pub weak_bg_fill: Color32,
1103
1104 pub bg_stroke: Stroke,
1108
1109 pub rounding: Rounding,
1111
1112 pub fg_stroke: Stroke,
1114
1115 pub expansion: f32,
1117}
1118
1119impl WidgetVisuals {
1120 #[inline(always)]
1121 pub fn text_color(&self) -> Color32 {
1122 self.fg_stroke.color
1123 }
1124}
1125
1126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1128#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1129#[cfg(debug_assertions)]
1130pub struct DebugOptions {
1131 #[cfg(debug_assertions)]
1139 pub debug_on_hover: bool,
1140
1141 #[cfg(debug_assertions)]
1151 pub debug_on_hover_with_all_modifiers: bool,
1152
1153 #[cfg(debug_assertions)]
1155 pub hover_shows_next: bool,
1156
1157 pub show_expand_width: bool,
1159
1160 pub show_expand_height: bool,
1162
1163 pub show_resize: bool,
1164
1165 pub show_interactive_widgets: bool,
1167
1168 pub show_widget_hits: bool,
1170
1171 pub show_unaligned: bool,
1177}
1178
1179#[cfg(debug_assertions)]
1180impl Default for DebugOptions {
1181 fn default() -> Self {
1182 Self {
1183 debug_on_hover: false,
1184 debug_on_hover_with_all_modifiers: cfg!(feature = "callstack")
1185 && !cfg!(target_arch = "wasm32"),
1186 hover_shows_next: false,
1187 show_expand_width: false,
1188 show_expand_height: false,
1189 show_resize: false,
1190 show_interactive_widgets: false,
1191 show_widget_hits: false,
1192 show_unaligned: false,
1193 }
1194 }
1195}
1196
1197pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
1201 use FontFamily::{Monospace, Proportional};
1202
1203 [
1204 (TextStyle::Small, FontId::new(9.0, Proportional)),
1205 (TextStyle::Body, FontId::new(12.5, Proportional)),
1206 (TextStyle::Button, FontId::new(12.5, Proportional)),
1207 (TextStyle::Heading, FontId::new(18.0, Proportional)),
1208 (TextStyle::Monospace, FontId::new(12.0, Monospace)),
1209 ]
1210 .into()
1211}
1212
1213impl Default for Style {
1214 fn default() -> Self {
1215 #[allow(deprecated)]
1216 Self {
1217 override_font_id: None,
1218 override_text_style: None,
1219 override_text_valign: Some(Align::Center),
1220 text_styles: default_text_styles(),
1221 drag_value_text_style: TextStyle::Button,
1222 number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
1223 wrap: None,
1224 wrap_mode: None,
1225 spacing: Spacing::default(),
1226 interaction: Interaction::default(),
1227 visuals: Visuals::default(),
1228 animation_time: 1.0 / 12.0,
1229 #[cfg(debug_assertions)]
1230 debug: Default::default(),
1231 explanation_tooltips: false,
1232 url_in_tooltip: false,
1233 always_scroll_the_only_direction: false,
1234 scroll_animation: ScrollAnimation::default(),
1235 }
1236 }
1237}
1238
1239impl Default for Spacing {
1240 fn default() -> Self {
1241 Self {
1242 item_spacing: vec2(8.0, 3.0),
1243 window_margin: Margin::same(6.0),
1244 menu_margin: Margin::same(6.0),
1245 button_padding: vec2(4.0, 1.0),
1246 indent: 18.0, interact_size: vec2(40.0, 18.0),
1248 slider_width: 100.0,
1249 slider_rail_height: 8.0,
1250 combo_width: 100.0,
1251 text_edit_width: 280.0,
1252 icon_width: 14.0,
1253 icon_width_inner: 8.0,
1254 icon_spacing: 4.0,
1255 default_area_size: vec2(600.0, 400.0),
1256 tooltip_width: 500.0,
1257 menu_width: 400.0,
1258 menu_spacing: 2.0,
1259 combo_height: 200.0,
1260 scroll: Default::default(),
1261 indent_ends_with_horizontal_line: false,
1262 }
1263 }
1264}
1265
1266impl Default for Interaction {
1267 fn default() -> Self {
1268 Self {
1269 interact_radius: 5.0,
1270 resize_grab_radius_side: 5.0,
1271 resize_grab_radius_corner: 10.0,
1272 show_tooltips_only_when_still: true,
1273 tooltip_delay: 0.5,
1274 tooltip_grace_time: 0.2,
1275 selectable_labels: true,
1276 multi_widget_text_select: true,
1277 }
1278 }
1279}
1280
1281impl Visuals {
1282 pub fn dark() -> Self {
1284 Self {
1285 dark_mode: true,
1286 override_text_color: None,
1287 widgets: Widgets::default(),
1288 selection: Selection::default(),
1289 hyperlink_color: Color32::from_rgb(90, 170, 255),
1290 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(10), code_bg_color: Color32::from_gray(64),
1293 warn_fg_color: Color32::from_rgb(255, 143, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_rounding: Rounding::same(6.0),
1297 window_shadow: Shadow {
1298 offset: vec2(10.0, 20.0),
1299 blur: 15.0,
1300 spread: 0.0,
1301 color: Color32::from_black_alpha(96),
1302 },
1303 window_fill: Color32::from_gray(27),
1304 window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1305 window_highlight_topmost: true,
1306
1307 menu_rounding: Rounding::same(6.0),
1308
1309 panel_fill: Color32::from_gray(27),
1310
1311 popup_shadow: Shadow {
1312 offset: vec2(6.0, 10.0),
1313 blur: 8.0,
1314 spread: 0.0,
1315 color: Color32::from_black_alpha(96),
1316 },
1317
1318 resize_corner_size: 12.0,
1319
1320 text_cursor: Default::default(),
1321
1322 clip_rect_margin: 3.0, button_frame: true,
1324 collapsing_header_frame: false,
1325 indent_has_left_vline: true,
1326
1327 striped: false,
1328
1329 slider_trailing_fill: false,
1330 handle_shape: HandleShape::Circle,
1331
1332 interact_cursor: None,
1333
1334 image_loading_spinners: true,
1335
1336 numeric_color_space: NumericColorSpace::GammaByte,
1337 }
1338 }
1339
1340 pub fn light() -> Self {
1342 Self {
1343 dark_mode: false,
1344 widgets: Widgets::light(),
1345 selection: Selection::light(),
1346 hyperlink_color: Color32::from_rgb(0, 155, 255),
1347 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(255), code_bg_color: Color32::from_gray(230),
1350 warn_fg_color: Color32::from_rgb(255, 100, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_shadow: Shadow {
1354 offset: vec2(10.0, 20.0),
1355 blur: 15.0,
1356 spread: 0.0,
1357 color: Color32::from_black_alpha(25),
1358 },
1359 window_fill: Color32::from_gray(248),
1360 window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
1361
1362 panel_fill: Color32::from_gray(248),
1363
1364 popup_shadow: Shadow {
1365 offset: vec2(6.0, 10.0),
1366 blur: 8.0,
1367 spread: 0.0,
1368 color: Color32::from_black_alpha(25),
1369 },
1370
1371 text_cursor: TextCursorStyle {
1372 stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
1373 ..Default::default()
1374 },
1375
1376 ..Self::dark()
1377 }
1378 }
1379}
1380
1381impl Default for Visuals {
1382 fn default() -> Self {
1383 Self::dark()
1384 }
1385}
1386
1387impl Selection {
1388 fn dark() -> Self {
1389 Self {
1390 bg_fill: Color32::from_rgb(0, 92, 128),
1391 stroke: Stroke::new(1.0, Color32::from_rgb(192, 222, 255)),
1392 }
1393 }
1394
1395 fn light() -> Self {
1396 Self {
1397 bg_fill: Color32::from_rgb(144, 209, 255),
1398 stroke: Stroke::new(1.0, Color32::from_rgb(0, 83, 125)),
1399 }
1400 }
1401}
1402
1403impl Default for Selection {
1404 fn default() -> Self {
1405 Self::dark()
1406 }
1407}
1408
1409impl Widgets {
1410 pub fn dark() -> Self {
1411 Self {
1412 noninteractive: WidgetVisuals {
1413 weak_bg_fill: Color32::from_gray(27),
1414 bg_fill: Color32::from_gray(27),
1415 bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), rounding: Rounding::same(2.0),
1418 expansion: 0.0,
1419 },
1420 inactive: WidgetVisuals {
1421 weak_bg_fill: Color32::from_gray(60), bg_fill: Color32::from_gray(60), bg_stroke: Default::default(),
1424 fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), rounding: Rounding::same(2.0),
1426 expansion: 0.0,
1427 },
1428 hovered: WidgetVisuals {
1429 weak_bg_fill: Color32::from_gray(70),
1430 bg_fill: Color32::from_gray(70),
1431 bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
1433 rounding: Rounding::same(3.0),
1434 expansion: 1.0,
1435 },
1436 active: WidgetVisuals {
1437 weak_bg_fill: Color32::from_gray(55),
1438 bg_fill: Color32::from_gray(55),
1439 bg_stroke: Stroke::new(1.0, Color32::WHITE),
1440 fg_stroke: Stroke::new(2.0, Color32::WHITE),
1441 rounding: Rounding::same(2.0),
1442 expansion: 1.0,
1443 },
1444 open: WidgetVisuals {
1445 weak_bg_fill: Color32::from_gray(45),
1446 bg_fill: Color32::from_gray(27),
1447 bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1448 fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
1449 rounding: Rounding::same(2.0),
1450 expansion: 0.0,
1451 },
1452 }
1453 }
1454
1455 pub fn light() -> Self {
1456 Self {
1457 noninteractive: WidgetVisuals {
1458 weak_bg_fill: Color32::from_gray(248),
1459 bg_fill: Color32::from_gray(248),
1460 bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), rounding: Rounding::same(2.0),
1463 expansion: 0.0,
1464 },
1465 inactive: WidgetVisuals {
1466 weak_bg_fill: Color32::from_gray(230), bg_fill: Color32::from_gray(230), bg_stroke: Default::default(),
1469 fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), rounding: Rounding::same(2.0),
1471 expansion: 0.0,
1472 },
1473 hovered: WidgetVisuals {
1474 weak_bg_fill: Color32::from_gray(220),
1475 bg_fill: Color32::from_gray(220),
1476 bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), fg_stroke: Stroke::new(1.5, Color32::BLACK),
1478 rounding: Rounding::same(3.0),
1479 expansion: 1.0,
1480 },
1481 active: WidgetVisuals {
1482 weak_bg_fill: Color32::from_gray(165),
1483 bg_fill: Color32::from_gray(165),
1484 bg_stroke: Stroke::new(1.0, Color32::BLACK),
1485 fg_stroke: Stroke::new(2.0, Color32::BLACK),
1486 rounding: Rounding::same(2.0),
1487 expansion: 1.0,
1488 },
1489 open: WidgetVisuals {
1490 weak_bg_fill: Color32::from_gray(220),
1491 bg_fill: Color32::from_gray(220),
1492 bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
1493 fg_stroke: Stroke::new(1.0, Color32::BLACK),
1494 rounding: Rounding::same(2.0),
1495 expansion: 0.0,
1496 },
1497 }
1498 }
1499}
1500
1501impl Default for Widgets {
1502 fn default() -> Self {
1503 Self::dark()
1504 }
1505}
1506
1507use crate::{
1510 widgets::{reset_button, DragValue, Slider, Widget},
1511 Ui,
1512};
1513
1514impl Style {
1515 pub fn ui(&mut self, ui: &mut crate::Ui) {
1516 #[allow(deprecated)]
1517 let Self {
1518 override_font_id,
1519 override_text_style,
1520 override_text_valign,
1521 text_styles,
1522 drag_value_text_style,
1523 number_formatter: _, wrap: _,
1525 wrap_mode,
1526 spacing,
1527 interaction,
1528 visuals,
1529 animation_time,
1530 #[cfg(debug_assertions)]
1531 debug,
1532 explanation_tooltips,
1533 url_in_tooltip,
1534 always_scroll_the_only_direction,
1535 scroll_animation,
1536 } = self;
1537
1538 crate::Grid::new("_options").show(ui, |ui| {
1539 ui.label("Override font id");
1540 ui.vertical(|ui| {
1541 ui.horizontal(|ui| {
1542 ui.radio_value(override_font_id, None, "None");
1543 if ui.radio(override_font_id.is_some(), "override").clicked() {
1544 *override_font_id = Some(FontId::default());
1545 }
1546 });
1547 if let Some(override_font_id) = override_font_id {
1548 crate::introspection::font_id_ui(ui, override_font_id);
1549 }
1550 });
1551 ui.end_row();
1552
1553 ui.label("Override text style");
1554 crate::ComboBox::from_id_salt("override_text_style")
1555 .selected_text(match override_text_style {
1556 None => "None".to_owned(),
1557 Some(override_text_style) => override_text_style.to_string(),
1558 })
1559 .show_ui(ui, |ui| {
1560 ui.selectable_value(override_text_style, None, "None");
1561 let all_text_styles = ui.style().text_styles();
1562 for style in all_text_styles {
1563 let text =
1564 crate::RichText::new(style.to_string()).text_style(style.clone());
1565 ui.selectable_value(override_text_style, Some(style), text);
1566 }
1567 });
1568 ui.end_row();
1569
1570 fn valign_name(valign: Align) -> &'static str {
1571 match valign {
1572 Align::TOP => "Top",
1573 Align::Center => "Center",
1574 Align::BOTTOM => "Bottom",
1575 }
1576 }
1577
1578 ui.label("Override text valign");
1579 crate::ComboBox::from_id_salt("override_text_valign")
1580 .selected_text(match override_text_valign {
1581 None => "None",
1582 Some(override_text_valign) => valign_name(*override_text_valign),
1583 })
1584 .show_ui(ui, |ui| {
1585 ui.selectable_value(override_text_valign, None, "None");
1586 for align in [Align::TOP, Align::Center, Align::BOTTOM] {
1587 ui.selectable_value(override_text_valign, Some(align), valign_name(align));
1588 }
1589 });
1590 ui.end_row();
1591
1592 ui.label("Text style of DragValue");
1593 crate::ComboBox::from_id_salt("drag_value_text_style")
1594 .selected_text(drag_value_text_style.to_string())
1595 .show_ui(ui, |ui| {
1596 let all_text_styles = ui.style().text_styles();
1597 for style in all_text_styles {
1598 let text =
1599 crate::RichText::new(style.to_string()).text_style(style.clone());
1600 ui.selectable_value(drag_value_text_style, style, text);
1601 }
1602 });
1603 ui.end_row();
1604
1605 ui.label("Text Wrap Mode");
1606 crate::ComboBox::from_id_salt("text_wrap_mode")
1607 .selected_text(format!("{wrap_mode:?}"))
1608 .show_ui(ui, |ui| {
1609 let all_wrap_mode: Vec<Option<TextWrapMode>> = vec![
1610 None,
1611 Some(TextWrapMode::Extend),
1612 Some(TextWrapMode::Wrap),
1613 Some(TextWrapMode::Truncate),
1614 ];
1615 for style in all_wrap_mode {
1616 let text = crate::RichText::new(format!("{style:?}"));
1617 ui.selectable_value(wrap_mode, style, text);
1618 }
1619 });
1620 ui.end_row();
1621
1622 ui.label("Animation duration");
1623 ui.add(
1624 DragValue::new(animation_time)
1625 .range(0.0..=1.0)
1626 .speed(0.02)
1627 .suffix(" s"),
1628 );
1629 ui.end_row();
1630 });
1631
1632 ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles));
1633 ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
1634 ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
1635 ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
1636 ui.collapsing("🔄 Scroll Animation", |ui| scroll_animation.ui(ui));
1637
1638 #[cfg(debug_assertions)]
1639 ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
1640
1641 ui.checkbox(explanation_tooltips, "Explanation tooltips")
1642 .on_hover_text(
1643 "Show explanatory text when hovering DragValue:s and other egui widgets",
1644 );
1645
1646 ui.checkbox(url_in_tooltip, "Show url when hovering links");
1647
1648 ui.checkbox(always_scroll_the_only_direction, "Always scroll the only enabled direction")
1649 .on_hover_text(
1650 "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift",
1651 );
1652
1653 ui.vertical_centered(|ui| reset_button(ui, self, "Reset style"));
1654 }
1655}
1656
1657fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
1658 ui.vertical(|ui| {
1659 crate::Grid::new("text_styles").show(ui, |ui| {
1660 for (text_style, font_id) in &mut *text_styles {
1661 ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
1662 crate::introspection::font_id_ui(ui, font_id);
1663 ui.end_row();
1664 }
1665 });
1666 crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles());
1667 })
1668 .response
1669}
1670
1671impl Spacing {
1672 pub fn ui(&mut self, ui: &mut crate::Ui) {
1673 let Self {
1674 item_spacing,
1675 window_margin,
1676 menu_margin,
1677 button_padding,
1678 indent,
1679 interact_size,
1680 slider_width,
1681 slider_rail_height,
1682 combo_width,
1683 text_edit_width,
1684 icon_width,
1685 icon_width_inner,
1686 icon_spacing,
1687 default_area_size,
1688 tooltip_width,
1689 menu_width,
1690 menu_spacing,
1691 indent_ends_with_horizontal_line,
1692 combo_height,
1693 scroll,
1694 } = self;
1695
1696 Grid::new("spacing")
1697 .num_columns(2)
1698 .spacing([12.0, 8.0])
1699 .striped(true)
1700 .show(ui, |ui| {
1701 ui.label("Item spacing");
1702 ui.add(two_drag_values(item_spacing, 0.0..=20.0));
1703 ui.end_row();
1704
1705 ui.label("Window margin");
1706 ui.add(window_margin);
1707 ui.end_row();
1708
1709 ui.label("Menu margin");
1710 ui.add(menu_margin);
1711 ui.end_row();
1712
1713 ui.label("Button padding");
1714 ui.add(two_drag_values(button_padding, 0.0..=20.0));
1715 ui.end_row();
1716
1717 ui.label("Interact size")
1718 .on_hover_text("Minimum size of an interactive widget");
1719 ui.add(two_drag_values(interact_size, 4.0..=60.0));
1720 ui.end_row();
1721
1722 ui.label("Indent");
1723 ui.add(DragValue::new(indent).range(0.0..=100.0));
1724 ui.end_row();
1725
1726 ui.label("Slider width");
1727 ui.add(DragValue::new(slider_width).range(0.0..=1000.0));
1728 ui.end_row();
1729
1730 ui.label("Slider rail height");
1731 ui.add(DragValue::new(slider_rail_height).range(0.0..=50.0));
1732 ui.end_row();
1733
1734 ui.label("ComboBox width");
1735 ui.add(DragValue::new(combo_width).range(0.0..=1000.0));
1736 ui.end_row();
1737
1738 ui.label("Default area size");
1739 ui.add(two_drag_values(default_area_size, 0.0..=1000.0));
1740 ui.end_row();
1741
1742 ui.label("TextEdit width");
1743 ui.add(DragValue::new(text_edit_width).range(0.0..=1000.0));
1744 ui.end_row();
1745
1746 ui.label("Tooltip wrap width");
1747 ui.add(DragValue::new(tooltip_width).range(0.0..=1000.0));
1748 ui.end_row();
1749
1750 ui.label("Default menu width");
1751 ui.add(DragValue::new(menu_width).range(0.0..=1000.0));
1752 ui.end_row();
1753
1754 ui.label("Menu spacing")
1755 .on_hover_text("Horizontal spacing between menus");
1756 ui.add(DragValue::new(menu_spacing).range(0.0..=10.0));
1757 ui.end_row();
1758
1759 ui.label("Checkboxes etc");
1760 ui.vertical(|ui| {
1761 ui.add(
1762 DragValue::new(icon_width)
1763 .prefix("outer icon width:")
1764 .range(0.0..=60.0),
1765 );
1766 ui.add(
1767 DragValue::new(icon_width_inner)
1768 .prefix("inner icon width:")
1769 .range(0.0..=60.0),
1770 );
1771 ui.add(
1772 DragValue::new(icon_spacing)
1773 .prefix("spacing:")
1774 .range(0.0..=10.0),
1775 );
1776 });
1777 ui.end_row();
1778 });
1779
1780 ui.checkbox(
1781 indent_ends_with_horizontal_line,
1782 "End indented regions with a horizontal separator",
1783 );
1784
1785 ui.horizontal(|ui| {
1786 ui.label("Max height of a combo box");
1787 ui.add(DragValue::new(combo_height).range(0.0..=1000.0));
1788 });
1789
1790 ui.collapsing("Scroll Area", |ui| {
1791 scroll.ui(ui);
1792 });
1793
1794 ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing"));
1795 }
1796}
1797
1798impl Interaction {
1799 pub fn ui(&mut self, ui: &mut crate::Ui) {
1800 let Self {
1801 interact_radius,
1802 resize_grab_radius_side,
1803 resize_grab_radius_corner,
1804 show_tooltips_only_when_still,
1805 tooltip_delay,
1806 tooltip_grace_time,
1807 selectable_labels,
1808 multi_widget_text_select,
1809 } = self;
1810
1811 ui.spacing_mut().item_spacing = vec2(12.0, 8.0);
1812
1813 Grid::new("interaction")
1814 .num_columns(2)
1815 .striped(true)
1816 .show(ui, |ui| {
1817 ui.label("interact_radius")
1818 .on_hover_text("Interact with the closest widget within this radius.");
1819 ui.add(DragValue::new(interact_radius).range(0.0..=20.0));
1820 ui.end_row();
1821
1822 ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize");
1823 ui.add(DragValue::new(resize_grab_radius_side).range(0.0..=20.0));
1824 ui.end_row();
1825
1826 ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize.");
1827 ui.add(DragValue::new(resize_grab_radius_corner).range(0.0..=20.0));
1828 ui.end_row();
1829
1830 ui.label("Tooltip delay").on_hover_text(
1831 "Delay in seconds before showing tooltips after the mouse stops moving",
1832 );
1833 ui.add(
1834 DragValue::new(tooltip_delay)
1835 .range(0.0..=1.0)
1836 .speed(0.05)
1837 .suffix(" s"),
1838 );
1839 ui.end_row();
1840
1841 ui.label("Tooltip grace time").on_hover_text(
1842 "If a tooltip is open and you hover another widget within this grace period, show the next tooltip right away",
1843 );
1844 ui.add(
1845 DragValue::new(tooltip_grace_time)
1846 .range(0.0..=1.0)
1847 .speed(0.05)
1848 .suffix(" s"),
1849 );
1850 ui.end_row();
1851 });
1852
1853 ui.checkbox(
1854 show_tooltips_only_when_still,
1855 "Only show tooltips if mouse is still",
1856 );
1857
1858 ui.horizontal(|ui| {
1859 ui.checkbox(selectable_labels, "Selectable text in labels");
1860 if *selectable_labels {
1861 ui.checkbox(multi_widget_text_select, "Across multiple labels");
1862 }
1863 });
1864
1865 ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings"));
1866 }
1867}
1868
1869impl Widgets {
1870 pub fn ui(&mut self, ui: &mut crate::Ui) {
1871 let Self {
1872 active,
1873 hovered,
1874 inactive,
1875 noninteractive,
1876 open,
1877 } = self;
1878
1879 ui.collapsing("Noninteractive", |ui| {
1880 ui.label(
1881 "The style of a widget that you cannot interact with, e.g. labels and separators.",
1882 );
1883 noninteractive.ui(ui);
1884 });
1885 ui.collapsing("Interactive but inactive", |ui| {
1886 ui.label("The style of an interactive widget, such as a button, at rest.");
1887 inactive.ui(ui);
1888 });
1889 ui.collapsing("Interactive and hovered", |ui| {
1890 ui.label("The style of an interactive widget while you hover it.");
1891 hovered.ui(ui);
1892 });
1893 ui.collapsing("Interactive and active", |ui| {
1894 ui.label("The style of an interactive widget as you are clicking or dragging it.");
1895 active.ui(ui);
1896 });
1897 ui.collapsing("Open menu", |ui| {
1898 ui.label("The style of an open combo-box or menu button");
1899 open.ui(ui);
1900 });
1901
1902 }
1904}
1905
1906impl Selection {
1907 pub fn ui(&mut self, ui: &mut crate::Ui) {
1908 let Self { bg_fill, stroke } = self;
1909 ui.label("Selectable labels");
1910
1911 Grid::new("selectiom").num_columns(2).show(ui, |ui| {
1912 ui.label("Background fill");
1913 ui.color_edit_button_srgba(bg_fill);
1914 ui.end_row();
1915
1916 ui.label("Stroke");
1917 ui.add(stroke);
1918 ui.end_row();
1919 });
1920 }
1921}
1922
1923impl WidgetVisuals {
1924 pub fn ui(&mut self, ui: &mut crate::Ui) {
1925 let Self {
1926 weak_bg_fill,
1927 bg_fill: mandatory_bg_fill,
1928 bg_stroke,
1929 rounding,
1930 fg_stroke,
1931 expansion,
1932 } = self;
1933
1934 Grid::new("widget")
1935 .num_columns(2)
1936 .spacing([12.0, 8.0])
1937 .striped(true)
1938 .show(ui, |ui| {
1939 ui.label("Optional background fill")
1940 .on_hover_text("For buttons, combo-boxes, etc");
1941 ui.color_edit_button_srgba(weak_bg_fill);
1942 ui.end_row();
1943
1944 ui.label("Mandatory background fill")
1945 .on_hover_text("For checkboxes, sliders, etc");
1946 ui.color_edit_button_srgba(mandatory_bg_fill);
1947 ui.end_row();
1948
1949 ui.label("Background stroke");
1950 ui.add(bg_stroke);
1951 ui.end_row();
1952
1953 ui.label("Rounding");
1954 ui.add(rounding);
1955 ui.end_row();
1956
1957 ui.label("Foreground stroke (text)");
1958 ui.add(fg_stroke);
1959 ui.end_row();
1960
1961 ui.label("Expansion")
1962 .on_hover_text("make shapes this much larger");
1963 ui.add(DragValue::new(expansion).speed(0.1));
1964 ui.end_row();
1965 });
1966 }
1967}
1968
1969impl Visuals {
1970 pub fn ui(&mut self, ui: &mut crate::Ui) {
1971 let Self {
1972 dark_mode: _,
1973 override_text_color: _,
1974 widgets,
1975 selection,
1976 hyperlink_color,
1977 faint_bg_color,
1978 extreme_bg_color,
1979 code_bg_color,
1980 warn_fg_color,
1981 error_fg_color,
1982
1983 window_rounding,
1984 window_shadow,
1985 window_fill,
1986 window_stroke,
1987 window_highlight_topmost,
1988
1989 menu_rounding,
1990
1991 panel_fill,
1992
1993 popup_shadow,
1994
1995 resize_corner_size,
1996
1997 text_cursor,
1998
1999 clip_rect_margin,
2000 button_frame,
2001 collapsing_header_frame,
2002 indent_has_left_vline,
2003
2004 striped,
2005
2006 slider_trailing_fill,
2007 handle_shape,
2008 interact_cursor,
2009
2010 image_loading_spinners,
2011
2012 numeric_color_space,
2013 } = self;
2014
2015 ui.collapsing("Background Colors", |ui| {
2016 ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
2017 ui_color(ui, window_fill, "Windows");
2018 ui_color(ui, panel_fill, "Panels");
2019 ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
2020 "Used for faint accentuation of interactive things, like striped grids.",
2021 );
2022 ui_color(ui, extreme_bg_color, "Extreme")
2023 .on_hover_text("Background of plots and paintings");
2024 });
2025
2026 ui.collapsing("Text color", |ui| {
2027 ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
2028 ui_text_color(
2029 ui,
2030 &mut widgets.inactive.fg_stroke.color,
2031 "Unhovered button",
2032 );
2033 ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
2034 ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
2035
2036 ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
2037 ui_text_color(ui, error_fg_color, RichText::new("Errors"));
2038
2039 ui_text_color(ui, hyperlink_color, "hyperlink_color");
2040
2041 ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(
2042 |ui| {
2043 ui.horizontal(|ui| {
2044 ui.spacing_mut().item_spacing.x = 0.0;
2045 ui.label("For monospaced inlined text ");
2046 ui.code("like this");
2047 ui.label(".");
2048 });
2049 },
2050 );
2051 });
2052
2053 ui.collapsing("Text cursor", |ui| {
2054 text_cursor.ui(ui);
2055 });
2056
2057 ui.collapsing("Window", |ui| {
2058 Grid::new("window")
2059 .num_columns(2)
2060 .spacing([12.0, 8.0])
2061 .striped(true)
2062 .show(ui, |ui| {
2063 ui.label("Fill");
2064 ui.color_edit_button_srgba(window_fill);
2065 ui.end_row();
2066
2067 ui.label("Stroke");
2068 ui.add(window_stroke);
2069 ui.end_row();
2070
2071 ui.label("Rounding");
2072 ui.add(window_rounding);
2073 ui.end_row();
2074
2075 ui.label("Shadow");
2076 ui.add(window_shadow);
2077 ui.end_row();
2078 });
2079
2080 ui.checkbox(window_highlight_topmost, "Highlight topmost Window");
2081 });
2082
2083 ui.collapsing("Menus and popups", |ui| {
2084 Grid::new("menus_and_popups")
2085 .num_columns(2)
2086 .spacing([12.0, 8.0])
2087 .striped(true)
2088 .show(ui, |ui| {
2089 ui.label("Rounding");
2090 ui.add(menu_rounding);
2091 ui.end_row();
2092
2093 ui.label("Shadow");
2094 ui.add(popup_shadow);
2095 ui.end_row();
2096 });
2097 });
2098
2099 ui.collapsing("Widgets", |ui| widgets.ui(ui));
2100 ui.collapsing("Selection", |ui| selection.ui(ui));
2101
2102 ui.collapsing("Misc", |ui| {
2103 ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
2104 ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
2105
2106 ui.checkbox(button_frame, "Button has a frame");
2107 ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
2108 ui.checkbox(
2109 indent_has_left_vline,
2110 "Paint a vertical line to the left of indented regions",
2111 );
2112
2113 ui.checkbox(striped, "Default stripes on grids and tables");
2114
2115 ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
2116
2117 handle_shape.ui(ui);
2118
2119 ComboBox::from_label("Interact cursor")
2120 .selected_text(
2121 interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")),
2122 )
2123 .show_ui(ui, |ui| {
2124 ui.selectable_value(interact_cursor, None, "-");
2125
2126 for cursor in CursorIcon::ALL {
2127 ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}"))
2128 .on_hover_cursor(cursor);
2129 }
2130 })
2131 .response
2132 .on_hover_text("Use this cursor when hovering buttons etc");
2133
2134 ui.checkbox(image_loading_spinners, "Image loading spinners")
2135 .on_hover_text("Show a spinner when an Image is loading");
2136
2137 ui.horizontal(|ui| {
2138 ui.label("Color picker type");
2139 numeric_color_space.toggle_button_ui(ui);
2140 });
2141 });
2142
2143 ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals"));
2144 }
2145}
2146
2147impl TextCursorStyle {
2148 fn ui(&mut self, ui: &mut Ui) {
2149 let Self {
2150 stroke,
2151 preview,
2152 blink,
2153 on_duration,
2154 off_duration,
2155 } = self;
2156
2157 ui.horizontal(|ui| {
2158 ui.label("Stroke");
2159 ui.add(stroke);
2160 });
2161
2162 ui.checkbox(preview, "Preview text cursor on hover");
2163
2164 ui.checkbox(blink, "Blink");
2165
2166 if *blink {
2167 Grid::new("cursor_blink").show(ui, |ui| {
2168 ui.label("On time");
2169 ui.add(
2170 DragValue::new(on_duration)
2171 .speed(0.1)
2172 .range(0.0..=2.0)
2173 .suffix(" s"),
2174 );
2175 ui.end_row();
2176
2177 ui.label("Off time");
2178 ui.add(
2179 DragValue::new(off_duration)
2180 .speed(0.1)
2181 .range(0.0..=2.0)
2182 .suffix(" s"),
2183 );
2184 ui.end_row();
2185 });
2186 }
2187 }
2188}
2189
2190#[cfg(debug_assertions)]
2191impl DebugOptions {
2192 pub fn ui(&mut self, ui: &mut crate::Ui) {
2193 let Self {
2194 debug_on_hover,
2195 debug_on_hover_with_all_modifiers,
2196 hover_shows_next,
2197 show_expand_width,
2198 show_expand_height,
2199 show_resize,
2200 show_interactive_widgets,
2201 show_widget_hits,
2202 show_unaligned,
2203 } = self;
2204
2205 {
2206 ui.checkbox(debug_on_hover, "Show widget info on hover.");
2207 ui.checkbox(
2208 debug_on_hover_with_all_modifiers,
2209 "Show widget info on hover if holding all modifier keys",
2210 );
2211
2212 ui.checkbox(hover_shows_next, "Show next widget placement on hover");
2213 }
2214
2215 ui.checkbox(
2216 show_expand_width,
2217 "Show which widgets make their parent wider",
2218 );
2219 ui.checkbox(
2220 show_expand_height,
2221 "Show which widgets make their parent higher",
2222 );
2223 ui.checkbox(show_resize, "Debug Resize");
2224
2225 ui.checkbox(
2226 show_interactive_widgets,
2227 "Show an overlay on all interactive widgets",
2228 );
2229
2230 ui.checkbox(show_widget_hits, "Show widgets under mouse pointer");
2231
2232 ui.checkbox(
2233 show_unaligned,
2234 "Show rectangles not aligned to integer point coordinates",
2235 );
2236
2237 ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options"));
2238 }
2239}
2240
2241fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive<f32>) -> impl Widget + '_ {
2243 move |ui: &mut crate::Ui| {
2244 ui.horizontal(|ui| {
2245 ui.add(
2246 DragValue::new(&mut value.x)
2247 .range(range.clone())
2248 .prefix("x: "),
2249 );
2250 ui.add(
2251 DragValue::new(&mut value.y)
2252 .range(range.clone())
2253 .prefix("y: "),
2254 );
2255 })
2256 .response
2257 }
2258}
2259
2260fn ui_color(ui: &mut Ui, color: &mut Color32, label: impl Into<WidgetText>) -> Response {
2261 ui.horizontal(|ui| {
2262 ui.color_edit_button_srgba(color);
2263 ui.label(label);
2264 })
2265 .response
2266}
2267
2268fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) -> Response {
2269 ui.horizontal(|ui| {
2270 ui.color_edit_button_srgba(color);
2271 ui.label(label.into().color(*color));
2272 })
2273 .response
2274}
2275
2276impl HandleShape {
2277 pub fn ui(&mut self, ui: &mut Ui) {
2278 ui.horizontal(|ui| {
2279 ui.label("Slider handle");
2280 ui.radio_value(self, Self::Circle, "Circle");
2281 if ui
2282 .radio(matches!(self, Self::Rect { .. }), "Rectangle")
2283 .clicked()
2284 {
2285 *self = Self::Rect { aspect_ratio: 0.5 };
2286 }
2287 if let Self::Rect { aspect_ratio } = self {
2288 ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio"));
2289 }
2290 });
2291 }
2292}
2293
2294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2296#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
2297pub enum NumericColorSpace {
2298 GammaByte,
2302
2303 Linear,
2305 }
2307
2308impl NumericColorSpace {
2309 pub fn toggle_button_ui(&mut self, ui: &mut Ui) -> crate::Response {
2310 let tooltip = match self {
2311 Self::GammaByte => "Showing color values in 0-255 gamma space",
2312 Self::Linear => "Showing color values in 0-1 linear space",
2313 };
2314
2315 let mut response = ui.button(self.to_string()).on_hover_text(tooltip);
2316 if response.clicked() {
2317 *self = match self {
2318 Self::GammaByte => Self::Linear,
2319 Self::Linear => Self::GammaByte,
2320 };
2321 response.mark_changed();
2322 }
2323 response
2324 }
2325}
2326
2327impl std::fmt::Display for NumericColorSpace {
2328 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2329 match self {
2330 Self::GammaByte => write!(f, "U8"),
2331 Self::Linear => write!(f, "F"),
2332 }
2333 }
2334}
2335
2336impl Widget for &mut Margin {
2337 fn ui(self, ui: &mut Ui) -> Response {
2338 let mut same = self.is_same();
2339
2340 let response = if same {
2341 ui.horizontal(|ui| {
2342 ui.checkbox(&mut same, "same");
2343
2344 let mut value = self.left;
2345 ui.add(DragValue::new(&mut value).range(0.0..=100.0));
2346 *self = Margin::same(value);
2347 })
2348 .response
2349 } else {
2350 ui.vertical(|ui| {
2351 ui.checkbox(&mut same, "same");
2352
2353 crate::Grid::new("margin").num_columns(2).show(ui, |ui| {
2354 ui.label("Left");
2355 ui.add(DragValue::new(&mut self.left).range(0.0..=100.0));
2356 ui.end_row();
2357
2358 ui.label("Right");
2359 ui.add(DragValue::new(&mut self.right).range(0.0..=100.0));
2360 ui.end_row();
2361
2362 ui.label("Top");
2363 ui.add(DragValue::new(&mut self.top).range(0.0..=100.0));
2364 ui.end_row();
2365
2366 ui.label("Bottom");
2367 ui.add(DragValue::new(&mut self.bottom).range(0.0..=100.0));
2368 ui.end_row();
2369 });
2370 })
2371 .response
2372 };
2373
2374 if same {
2376 *self = Margin::same((self.left + self.right + self.top + self.bottom) / 4.0);
2377 } else if self.is_same() {
2378 self.right *= 1.00001; }
2380
2381 response
2382 }
2383}
2384
2385impl Widget for &mut Rounding {
2386 fn ui(self, ui: &mut Ui) -> Response {
2387 let mut same = self.is_same();
2388
2389 let response = if same {
2390 ui.horizontal(|ui| {
2391 ui.checkbox(&mut same, "same");
2392
2393 let mut cr = self.nw;
2394 ui.add(DragValue::new(&mut cr).range(0.0..=f32::INFINITY));
2395 *self = Rounding::same(cr);
2396 })
2397 .response
2398 } else {
2399 ui.vertical(|ui| {
2400 ui.checkbox(&mut same, "same");
2401
2402 crate::Grid::new("rounding").num_columns(2).show(ui, |ui| {
2403 ui.label("NW");
2404 ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
2405 ui.end_row();
2406
2407 ui.label("NE");
2408 ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
2409 ui.end_row();
2410
2411 ui.label("SW");
2412 ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
2413 ui.end_row();
2414
2415 ui.label("SE");
2416 ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
2417 ui.end_row();
2418 });
2419 })
2420 .response
2421 };
2422
2423 if same {
2425 *self = Rounding::same((self.nw + self.ne + self.sw + self.se) / 4.0);
2426 } else if self.is_same() {
2427 self.se *= 1.00001; }
2429
2430 response
2431 }
2432}
2433
2434impl Widget for &mut Shadow {
2435 fn ui(self, ui: &mut Ui) -> Response {
2436 let epaint::Shadow {
2437 offset,
2438 blur,
2439 spread,
2440 color,
2441 } = self;
2442
2443 ui.vertical(|ui| {
2444 crate::Grid::new("shadow_ui").show(ui, |ui| {
2445 ui.add(
2446 DragValue::new(&mut offset.x)
2447 .speed(1.0)
2448 .range(-100.0..=100.0)
2449 .prefix("x: "),
2450 );
2451 ui.add(
2452 DragValue::new(&mut offset.y)
2453 .speed(1.0)
2454 .range(-100.0..=100.0)
2455 .prefix("y: "),
2456 );
2457 ui.end_row();
2458
2459 ui.add(
2460 DragValue::new(blur)
2461 .speed(1.0)
2462 .range(0.0..=100.0)
2463 .prefix("blur: "),
2464 );
2465
2466 ui.add(
2467 DragValue::new(spread)
2468 .speed(1.0)
2469 .range(0.0..=100.0)
2470 .prefix("spread: "),
2471 );
2472 });
2473 ui.color_edit_button_srgba(color);
2474 })
2475 .response
2476 }
2477}
2478
2479impl Widget for &mut Stroke {
2480 fn ui(self, ui: &mut Ui) -> Response {
2481 let Stroke { width, color } = self;
2482
2483 ui.horizontal(|ui| {
2484 ui.add(DragValue::new(width).speed(0.1).range(0.0..=f32::INFINITY))
2485 .on_hover_text("Width");
2486 ui.color_edit_button_srgba(color);
2487
2488 let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size);
2490 let left = ui
2491 .painter()
2492 .round_pos_to_pixel_center(stroke_rect.left_center());
2493 let right = ui
2494 .painter()
2495 .round_pos_to_pixel_center(stroke_rect.right_center());
2496 ui.painter().line_segment([left, right], (*width, *color));
2497 })
2498 .response
2499 }
2500}
2501
2502impl Widget for &mut crate::Frame {
2503 fn ui(self, ui: &mut Ui) -> Response {
2504 let crate::Frame {
2505 inner_margin,
2506 outer_margin,
2507 rounding,
2508 shadow,
2509 fill,
2510 stroke,
2511 } = self;
2512
2513 crate::Grid::new("frame")
2514 .num_columns(2)
2515 .spacing([12.0, 8.0])
2516 .striped(true)
2517 .show(ui, |ui| {
2518 ui.label("Inner margin");
2519 ui.add(inner_margin);
2520 ui.end_row();
2521
2522 ui.label("Outer margin");
2523 ui.push_id("outer", |ui| ui.add(outer_margin));
2525 ui.end_row();
2526
2527 ui.label("Rounding");
2528 ui.add(rounding);
2529 ui.end_row();
2530
2531 ui.label("Shadow");
2532 ui.add(shadow);
2533 ui.end_row();
2534
2535 ui.label("Fill");
2536 ui.color_edit_button_srgba(fill);
2537 ui.end_row();
2538
2539 ui.label("Stroke");
2540 ui.add(stroke);
2541 ui.end_row();
2542 })
2543 .response
2544 }
2545}
2546
2547impl Widget for &mut FontTweak {
2548 fn ui(self, ui: &mut Ui) -> Response {
2549 let original: FontTweak = *self;
2550
2551 let mut response = Grid::new("font_tweak")
2552 .num_columns(2)
2553 .show(ui, |ui| {
2554 let FontTweak {
2555 scale,
2556 y_offset_factor,
2557 y_offset,
2558 baseline_offset_factor,
2559 } = self;
2560
2561 ui.label("Scale");
2562 let speed = *scale * 0.01;
2563 ui.add(DragValue::new(scale).range(0.01..=10.0).speed(speed));
2564 ui.end_row();
2565
2566 ui.label("y_offset_factor");
2567 ui.add(DragValue::new(y_offset_factor).speed(-0.0025));
2568 ui.end_row();
2569
2570 ui.label("y_offset");
2571 ui.add(DragValue::new(y_offset).speed(-0.02));
2572 ui.end_row();
2573
2574 ui.label("baseline_offset_factor");
2575 ui.add(DragValue::new(baseline_offset_factor).speed(-0.0025));
2576 ui.end_row();
2577
2578 if ui.button("Reset").clicked() {
2579 *self = Default::default();
2580 }
2581 })
2582 .response;
2583
2584 if *self != original {
2585 response.mark_changed();
2586 }
2587
2588 response
2589 }
2590}