1#![allow(clippy::if_same_then_else)]
4
5use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
6
7use epaint::{Rounding, Shadow, Stroke};
8
9use crate::{
10 ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response,
11 RichText, WidgetText,
12};
13
14#[derive(Clone)]
16pub struct NumberFormatter(
17 Arc<dyn 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String>,
18);
19
20impl NumberFormatter {
21 #[inline]
26 pub fn new(
27 formatter: impl 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String,
28 ) -> Self {
29 Self(Arc::new(formatter))
30 }
31
32 #[inline]
41 pub fn format(&self, value: f64, decimals: RangeInclusive<usize>) -> String {
42 (self.0)(value, decimals)
43 }
44}
45
46impl std::fmt::Debug for NumberFormatter {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.write_str("NumberFormatter")
49 }
50}
51
52impl PartialEq for NumberFormatter {
53 #[inline]
54 fn eq(&self, other: &Self) -> bool {
55 Arc::ptr_eq(&self.0, &other.0)
56 }
57}
58
59#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
66#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
67pub enum TextStyle {
68 Small,
70
71 Body,
73
74 Monospace,
76
77 Button,
81
82 Heading,
84
85 Name(std::sync::Arc<str>),
90}
91
92impl std::fmt::Display for TextStyle {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 match self {
95 Self::Small => "Small".fmt(f),
96 Self::Body => "Body".fmt(f),
97 Self::Monospace => "Monospace".fmt(f),
98 Self::Button => "Button".fmt(f),
99 Self::Heading => "Heading".fmt(f),
100 Self::Name(name) => (*name).fmt(f),
101 }
102 }
103}
104
105impl TextStyle {
106 pub fn resolve(&self, style: &Style) -> FontId {
108 style.text_styles.get(self).cloned().unwrap_or_else(|| {
109 panic!(
110 "Failed to find {:?} in Style::text_styles. Available styles:\n{:#?}",
111 self,
112 style.text_styles()
113 )
114 })
115 }
116}
117
118pub enum FontSelection {
122 Default,
125
126 FontId(FontId),
128
129 Style(TextStyle),
131}
132
133impl Default for FontSelection {
134 #[inline]
135 fn default() -> Self {
136 Self::Default
137 }
138}
139
140impl FontSelection {
141 pub fn resolve(self, style: &Style) -> FontId {
142 match self {
143 Self::Default => {
144 if let Some(override_font_id) = &style.override_font_id {
145 override_font_id.clone()
146 } else if let Some(text_style) = &style.override_text_style {
147 text_style.resolve(style)
148 } else {
149 TextStyle::Body.resolve(style)
150 }
151 }
152 Self::FontId(font_id) => font_id,
153 Self::Style(text_style) => text_style.resolve(style),
154 }
155 }
156}
157
158impl From<FontId> for FontSelection {
159 #[inline(always)]
160 fn from(font_id: FontId) -> Self {
161 Self::FontId(font_id)
162 }
163}
164
165impl From<TextStyle> for FontSelection {
166 #[inline(always)]
167 fn from(text_style: TextStyle) -> Self {
168 Self::Style(text_style)
169 }
170}
171
172#[derive(Clone, Debug, PartialEq)]
181#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
182#[cfg_attr(feature = "serde", serde(default))]
183pub struct Style {
184 pub override_text_style: Option<TextStyle>,
189
190 pub override_font_id: Option<FontId>,
195
196 pub text_styles: BTreeMap<TextStyle, FontId>,
226
227 pub drag_value_text_style: TextStyle,
229
230 #[cfg_attr(feature = "serde", serde(skip))]
234 pub number_formatter: NumberFormatter,
235
236 #[deprecated = "Use wrap_mode instead"]
245 pub wrap: Option<bool>,
246
247 pub wrap_mode: Option<crate::TextWrapMode>,
254
255 pub spacing: Spacing,
257
258 pub interaction: Interaction,
260
261 pub visuals: Visuals,
263
264 pub animation_time: f32,
266
267 #[cfg(debug_assertions)]
271 pub debug: DebugOptions,
272
273 pub explanation_tooltips: bool,
277
278 pub url_in_tooltip: bool,
280
281 pub always_scroll_the_only_direction: bool,
283}
284
285#[test]
286fn style_impl_send_sync() {
287 fn assert_send_sync<T: Send + Sync>() {}
288 assert_send_sync::<Style>();
289}
290
291impl Style {
292 pub fn interact(&self, response: &Response) -> &WidgetVisuals {
297 self.visuals.widgets.style(response)
298 }
299
300 pub fn interact_selectable(&self, response: &Response, selected: bool) -> WidgetVisuals {
301 let mut visuals = *self.visuals.widgets.style(response);
302 if selected {
303 visuals.weak_bg_fill = self.visuals.selection.bg_fill;
304 visuals.bg_fill = self.visuals.selection.bg_fill;
305 visuals.fg_stroke = self.visuals.selection.stroke;
307 }
308 visuals
309 }
310
311 pub fn noninteractive(&self) -> &WidgetVisuals {
313 &self.visuals.widgets.noninteractive
314 }
315
316 pub fn text_styles(&self) -> Vec<TextStyle> {
318 self.text_styles.keys().cloned().collect()
319 }
320}
321
322#[derive(Clone, Debug, PartialEq)]
324#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
325#[cfg_attr(feature = "serde", serde(default))]
326pub struct Spacing {
327 pub item_spacing: Vec2,
334
335 pub window_margin: Margin,
337
338 pub button_padding: Vec2,
340
341 pub menu_margin: Margin,
343
344 pub indent: f32,
346
347 pub interact_size: Vec2, pub slider_width: f32,
354
355 pub slider_rail_height: f32,
357
358 pub combo_width: f32,
360
361 pub text_edit_width: f32,
363
364 pub icon_width: f32,
367
368 pub icon_width_inner: f32,
371
372 pub icon_spacing: f32,
375
376 pub default_area_size: Vec2,
384
385 pub tooltip_width: f32,
387
388 pub menu_width: f32,
392
393 pub menu_spacing: f32,
395
396 pub indent_ends_with_horizontal_line: bool,
398
399 pub combo_height: f32,
401
402 pub scroll: ScrollStyle,
404}
405
406impl Spacing {
407 pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) {
409 let icon_width = self.icon_width;
410 let big_icon_rect = Rect::from_center_size(
411 pos2(rect.left() + icon_width / 2.0, rect.center().y),
412 vec2(icon_width, icon_width),
413 );
414
415 let small_icon_rect =
416 Rect::from_center_size(big_icon_rect.center(), Vec2::splat(self.icon_width_inner));
417
418 (small_icon_rect, big_icon_rect)
419 }
420}
421
422#[derive(Clone, Copy, Debug, PartialEq)]
431#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
432#[cfg_attr(feature = "serde", serde(default))]
433pub struct ScrollStyle {
434 pub floating: bool,
442
443 pub bar_width: f32,
445
446 pub handle_min_length: f32,
448
449 pub bar_inner_margin: f32,
451
452 pub bar_outer_margin: f32,
455
456 pub floating_width: f32,
460
461 pub floating_allocated_width: f32,
468
469 pub foreground_color: bool,
471
472 pub dormant_background_opacity: f32,
478
479 pub active_background_opacity: f32,
485
486 pub interact_background_opacity: f32,
492
493 pub dormant_handle_opacity: f32,
499
500 pub active_handle_opacity: f32,
506
507 pub interact_handle_opacity: f32,
513}
514
515impl Default for ScrollStyle {
516 fn default() -> Self {
517 Self::floating()
518 }
519}
520
521impl ScrollStyle {
522 pub fn solid() -> Self {
524 Self {
525 floating: false,
526 bar_width: 6.0,
527 handle_min_length: 12.0,
528 bar_inner_margin: 4.0,
529 bar_outer_margin: 0.0,
530 floating_width: 2.0,
531 floating_allocated_width: 0.0,
532
533 foreground_color: false,
534
535 dormant_background_opacity: 0.0,
536 active_background_opacity: 0.4,
537 interact_background_opacity: 0.7,
538
539 dormant_handle_opacity: 0.0,
540 active_handle_opacity: 0.6,
541 interact_handle_opacity: 1.0,
542 }
543 }
544
545 pub fn thin() -> Self {
547 Self {
548 floating: true,
549 bar_width: 10.0,
550 floating_allocated_width: 6.0,
551 foreground_color: false,
552
553 dormant_background_opacity: 1.0,
554 dormant_handle_opacity: 1.0,
555
556 active_background_opacity: 1.0,
557 active_handle_opacity: 1.0,
558
559 interact_background_opacity: 0.6,
561 interact_handle_opacity: 0.6,
562
563 ..Self::solid()
564 }
565 }
566
567 pub fn floating() -> Self {
571 Self {
572 floating: true,
573 bar_width: 10.0,
574 foreground_color: true,
575 floating_allocated_width: 0.0,
576 dormant_background_opacity: 0.0,
577 dormant_handle_opacity: 0.0,
578 ..Self::solid()
579 }
580 }
581
582 pub fn allocated_width(&self) -> f32 {
584 if self.floating {
585 self.floating_allocated_width
586 } else {
587 self.bar_inner_margin + self.bar_width + self.bar_outer_margin
588 }
589 }
590
591 pub fn ui(&mut self, ui: &mut Ui) {
592 ui.horizontal(|ui| {
593 ui.label("Presets:");
594 ui.selectable_value(self, Self::solid(), "Solid");
595 ui.selectable_value(self, Self::thin(), "Thin");
596 ui.selectable_value(self, Self::floating(), "Floating");
597 });
598
599 ui.collapsing("Details", |ui| {
600 self.details_ui(ui);
601 });
602 }
603
604 pub fn details_ui(&mut self, ui: &mut Ui) {
605 let Self {
606 floating,
607 bar_width,
608 handle_min_length,
609 bar_inner_margin,
610 bar_outer_margin,
611 floating_width,
612 floating_allocated_width,
613
614 foreground_color,
615
616 dormant_background_opacity,
617 active_background_opacity,
618 interact_background_opacity,
619 dormant_handle_opacity,
620 active_handle_opacity,
621 interact_handle_opacity,
622 } = self;
623
624 ui.horizontal(|ui| {
625 ui.label("Type:");
626 ui.selectable_value(floating, false, "Solid");
627 ui.selectable_value(floating, true, "Floating");
628 });
629
630 ui.horizontal(|ui| {
631 ui.add(DragValue::new(bar_width).range(0.0..=32.0));
632 ui.label("Full bar width");
633 });
634 if *floating {
635 ui.horizontal(|ui| {
636 ui.add(DragValue::new(floating_width).range(0.0..=32.0));
637 ui.label("Thin bar width");
638 });
639 ui.horizontal(|ui| {
640 ui.add(DragValue::new(floating_allocated_width).range(0.0..=32.0));
641 ui.label("Allocated width");
642 });
643 }
644
645 ui.horizontal(|ui| {
646 ui.add(DragValue::new(handle_min_length).range(0.0..=32.0));
647 ui.label("Minimum handle length");
648 });
649 ui.horizontal(|ui| {
650 ui.add(DragValue::new(bar_outer_margin).range(0.0..=32.0));
651 ui.label("Outer margin");
652 });
653
654 ui.horizontal(|ui| {
655 ui.label("Color:");
656 ui.selectable_value(foreground_color, false, "Background");
657 ui.selectable_value(foreground_color, true, "Foreground");
658 });
659
660 if *floating {
661 crate::Grid::new("opacity").show(ui, |ui| {
662 fn opacity_ui(ui: &mut Ui, opacity: &mut f32) {
663 ui.add(DragValue::new(opacity).speed(0.01).range(0.0..=1.0));
664 }
665
666 ui.label("Opacity");
667 ui.label("Dormant");
668 ui.label("Active");
669 ui.label("Interacting");
670 ui.end_row();
671
672 ui.label("Background:");
673 opacity_ui(ui, dormant_background_opacity);
674 opacity_ui(ui, active_background_opacity);
675 opacity_ui(ui, interact_background_opacity);
676 ui.end_row();
677
678 ui.label("Handle:");
679 opacity_ui(ui, dormant_handle_opacity);
680 opacity_ui(ui, active_handle_opacity);
681 opacity_ui(ui, interact_handle_opacity);
682 ui.end_row();
683 });
684 } else {
685 ui.horizontal(|ui| {
686 ui.add(DragValue::new(bar_inner_margin).range(0.0..=32.0));
687 ui.label("Inner margin");
688 });
689 }
690 }
691}
692
693#[derive(Clone, Debug, PartialEq)]
697#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
698#[cfg_attr(feature = "serde", serde(default))]
699pub struct Interaction {
700 pub interact_radius: f32,
705
706 pub resize_grab_radius_side: f32,
708
709 pub resize_grab_radius_corner: f32,
711
712 pub show_tooltips_only_when_still: bool,
714
715 pub tooltip_delay: f32,
717
718 pub tooltip_grace_time: f32,
724
725 pub selectable_labels: bool,
727
728 pub multi_widget_text_select: bool,
733}
734
735#[derive(Clone, Debug, PartialEq)]
737#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
738#[cfg_attr(feature = "serde", serde(default))]
739pub struct TextCursorStyle {
740 pub stroke: Stroke,
742
743 pub preview: bool,
745
746 pub blink: bool,
748
749 pub on_duration: f32,
751
752 pub off_duration: f32,
754}
755
756impl Default for TextCursorStyle {
757 fn default() -> Self {
758 Self {
759 stroke: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), preview: false,
761 blink: true,
762 on_duration: 0.5,
763 off_duration: 0.5,
764 }
765 }
766}
767
768#[derive(Clone, Debug, PartialEq)]
775#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
776#[cfg_attr(feature = "serde", serde(default))]
777pub struct Visuals {
778 pub dark_mode: bool,
784
785 pub override_text_color: Option<Color32>,
799
800 pub widgets: Widgets,
802
803 pub selection: Selection,
804
805 pub hyperlink_color: Color32,
807
808 pub faint_bg_color: Color32,
811
812 pub extreme_bg_color: Color32,
816
817 pub code_bg_color: Color32,
819
820 pub warn_fg_color: Color32,
822
823 pub error_fg_color: Color32,
825
826 pub window_rounding: Rounding,
827 pub window_shadow: Shadow,
828 pub window_fill: Color32,
829 pub window_stroke: Stroke,
830
831 pub window_highlight_topmost: bool,
833
834 pub menu_rounding: Rounding,
835
836 pub panel_fill: Color32,
838
839 pub popup_shadow: Shadow,
840
841 pub resize_corner_size: f32,
842
843 pub text_cursor: TextCursorStyle,
845
846 pub clip_rect_margin: f32,
848
849 pub button_frame: bool,
851
852 pub collapsing_header_frame: bool,
854
855 pub indent_has_left_vline: bool,
857
858 pub striped: bool,
861
862 pub slider_trailing_fill: bool,
866
867 pub handle_shape: HandleShape,
871
872 pub interact_cursor: Option<CursorIcon>,
878
879 pub image_loading_spinners: bool,
881
882 pub numeric_color_space: NumericColorSpace,
884}
885
886impl Visuals {
887 #[inline(always)]
888 pub fn noninteractive(&self) -> &WidgetVisuals {
889 &self.widgets.noninteractive
890 }
891
892 pub fn text_color(&self) -> Color32 {
894 self.override_text_color
895 .unwrap_or_else(|| self.widgets.noninteractive.text_color())
896 }
897
898 pub fn weak_text_color(&self) -> Color32 {
899 self.gray_out(self.text_color())
900 }
901
902 #[inline(always)]
903 pub fn strong_text_color(&self) -> Color32 {
904 self.widgets.active.text_color()
905 }
906
907 #[inline(always)]
909 pub fn window_fill(&self) -> Color32 {
910 self.window_fill
911 }
912
913 #[inline(always)]
914 pub fn window_stroke(&self) -> Stroke {
915 self.window_stroke
916 }
917
918 #[inline(always)]
921 pub fn fade_out_to_color(&self) -> Color32 {
922 self.widgets.noninteractive.weak_bg_fill
923 }
924
925 #[inline(always)]
927 pub fn gray_out(&self, color: Color32) -> Color32 {
928 crate::ecolor::tint_color_towards(color, self.fade_out_to_color())
929 }
930}
931
932#[derive(Clone, Copy, Debug, PartialEq)]
934#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
935#[cfg_attr(feature = "serde", serde(default))]
936pub struct Selection {
937 pub bg_fill: Color32,
938 pub stroke: Stroke,
939}
940
941#[derive(Clone, Copy, Debug, PartialEq)]
943#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
944pub enum HandleShape {
945 Circle,
947
948 Rect {
950 aspect_ratio: f32,
952 },
953}
954
955#[derive(Clone, Debug, PartialEq)]
957#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
958#[cfg_attr(feature = "serde", serde(default))]
959pub struct Widgets {
960 pub noninteractive: WidgetVisuals,
965
966 pub inactive: WidgetVisuals,
968
969 pub hovered: WidgetVisuals,
973
974 pub active: WidgetVisuals,
976
977 pub open: WidgetVisuals,
979}
980
981impl Widgets {
982 pub fn style(&self, response: &Response) -> &WidgetVisuals {
983 if !response.sense.interactive() {
984 &self.noninteractive
985 } else if response.is_pointer_button_down_on() || response.has_focus() || response.clicked()
986 {
987 &self.active
988 } else if response.hovered() || response.highlighted() {
989 &self.hovered
990 } else {
991 &self.inactive
992 }
993 }
994}
995
996#[derive(Clone, Copy, Debug, PartialEq)]
998#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
999pub struct WidgetVisuals {
1000 pub bg_fill: Color32,
1005
1006 pub weak_bg_fill: Color32,
1010
1011 pub bg_stroke: Stroke,
1015
1016 pub rounding: Rounding,
1018
1019 pub fg_stroke: Stroke,
1021
1022 pub expansion: f32,
1024}
1025
1026impl WidgetVisuals {
1027 #[inline(always)]
1028 pub fn text_color(&self) -> Color32 {
1029 self.fg_stroke.color
1030 }
1031}
1032
1033#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1035#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1036#[cfg(debug_assertions)]
1037pub struct DebugOptions {
1038 #[cfg(debug_assertions)]
1046 pub debug_on_hover: bool,
1047
1048 #[cfg(debug_assertions)]
1058 pub debug_on_hover_with_all_modifiers: bool,
1059
1060 #[cfg(debug_assertions)]
1062 pub hover_shows_next: bool,
1063
1064 pub show_expand_width: bool,
1066
1067 pub show_expand_height: bool,
1069
1070 pub show_resize: bool,
1071
1072 pub show_interactive_widgets: bool,
1074
1075 pub show_widget_hits: bool,
1077}
1078
1079#[cfg(debug_assertions)]
1080impl Default for DebugOptions {
1081 fn default() -> Self {
1082 Self {
1083 debug_on_hover: false,
1084 debug_on_hover_with_all_modifiers: cfg!(feature = "callstack")
1085 && !cfg!(target_arch = "wasm32"),
1086 hover_shows_next: false,
1087 show_expand_width: false,
1088 show_expand_height: false,
1089 show_resize: false,
1090 show_interactive_widgets: false,
1091 show_widget_hits: false,
1092 }
1093 }
1094}
1095
1096pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
1100 use FontFamily::{Monospace, Proportional};
1101
1102 [
1103 (TextStyle::Small, FontId::new(9.0, Proportional)),
1104 (TextStyle::Body, FontId::new(12.5, Proportional)),
1105 (TextStyle::Button, FontId::new(12.5, Proportional)),
1106 (TextStyle::Heading, FontId::new(18.0, Proportional)),
1107 (TextStyle::Monospace, FontId::new(12.0, Monospace)),
1108 ]
1109 .into()
1110}
1111
1112impl Default for Style {
1113 fn default() -> Self {
1114 #[allow(deprecated)]
1115 Self {
1116 override_font_id: None,
1117 override_text_style: None,
1118 text_styles: default_text_styles(),
1119 drag_value_text_style: TextStyle::Button,
1120 number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
1121 wrap: None,
1122 wrap_mode: None,
1123 spacing: Spacing::default(),
1124 interaction: Interaction::default(),
1125 visuals: Visuals::default(),
1126 animation_time: 1.0 / 12.0,
1127 #[cfg(debug_assertions)]
1128 debug: Default::default(),
1129 explanation_tooltips: false,
1130 url_in_tooltip: false,
1131 always_scroll_the_only_direction: false,
1132 }
1133 }
1134}
1135
1136impl Default for Spacing {
1137 fn default() -> Self {
1138 Self {
1139 item_spacing: vec2(8.0, 3.0),
1140 window_margin: Margin::same(6.0),
1141 menu_margin: Margin::same(6.0),
1142 button_padding: vec2(4.0, 1.0),
1143 indent: 18.0, interact_size: vec2(40.0, 18.0),
1145 slider_width: 100.0,
1146 slider_rail_height: 8.0,
1147 combo_width: 100.0,
1148 text_edit_width: 280.0,
1149 icon_width: 14.0,
1150 icon_width_inner: 8.0,
1151 icon_spacing: 4.0,
1152 default_area_size: vec2(600.0, 400.0),
1153 tooltip_width: 500.0,
1154 menu_width: 400.0,
1155 menu_spacing: 2.0,
1156 combo_height: 200.0,
1157 scroll: Default::default(),
1158 indent_ends_with_horizontal_line: false,
1159 }
1160 }
1161}
1162
1163impl Default for Interaction {
1164 fn default() -> Self {
1165 Self {
1166 interact_radius: 5.0,
1167 resize_grab_radius_side: 5.0,
1168 resize_grab_radius_corner: 10.0,
1169 show_tooltips_only_when_still: true,
1170 tooltip_delay: 0.5,
1171 tooltip_grace_time: 0.2,
1172 selectable_labels: true,
1173 multi_widget_text_select: true,
1174 }
1175 }
1176}
1177
1178impl Visuals {
1179 pub fn dark() -> Self {
1181 Self {
1182 dark_mode: true,
1183 override_text_color: None,
1184 widgets: Widgets::default(),
1185 selection: Selection::default(),
1186 hyperlink_color: Color32::from_rgb(90, 170, 255),
1187 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(10), code_bg_color: Color32::from_gray(64),
1190 warn_fg_color: Color32::from_rgb(255, 143, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_rounding: Rounding::same(6.0),
1194 window_shadow: Shadow {
1195 offset: vec2(10.0, 20.0),
1196 blur: 15.0,
1197 spread: 0.0,
1198 color: Color32::from_black_alpha(96),
1199 },
1200 window_fill: Color32::from_gray(27),
1201 window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1202 window_highlight_topmost: true,
1203
1204 menu_rounding: Rounding::same(6.0),
1205
1206 panel_fill: Color32::from_gray(27),
1207
1208 popup_shadow: Shadow {
1209 offset: vec2(6.0, 10.0),
1210 blur: 8.0,
1211 spread: 0.0,
1212 color: Color32::from_black_alpha(96),
1213 },
1214
1215 resize_corner_size: 12.0,
1216
1217 text_cursor: Default::default(),
1218
1219 clip_rect_margin: 3.0, button_frame: true,
1221 collapsing_header_frame: false,
1222 indent_has_left_vline: true,
1223
1224 striped: false,
1225
1226 slider_trailing_fill: false,
1227 handle_shape: HandleShape::Circle,
1228
1229 interact_cursor: None,
1230
1231 image_loading_spinners: true,
1232
1233 numeric_color_space: NumericColorSpace::GammaByte,
1234 }
1235 }
1236
1237 pub fn light() -> Self {
1239 Self {
1240 dark_mode: false,
1241 widgets: Widgets::light(),
1242 selection: Selection::light(),
1243 hyperlink_color: Color32::from_rgb(0, 155, 255),
1244 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(255), code_bg_color: Color32::from_gray(230),
1247 warn_fg_color: Color32::from_rgb(255, 100, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_shadow: Shadow {
1251 offset: vec2(10.0, 20.0),
1252 blur: 15.0,
1253 spread: 0.0,
1254 color: Color32::from_black_alpha(25),
1255 },
1256 window_fill: Color32::from_gray(248),
1257 window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
1258
1259 panel_fill: Color32::from_gray(248),
1260
1261 popup_shadow: Shadow {
1262 offset: vec2(6.0, 10.0),
1263 blur: 8.0,
1264 spread: 0.0,
1265 color: Color32::from_black_alpha(25),
1266 },
1267
1268 text_cursor: TextCursorStyle {
1269 stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
1270 ..Default::default()
1271 },
1272
1273 ..Self::dark()
1274 }
1275 }
1276}
1277
1278impl Default for Visuals {
1279 fn default() -> Self {
1280 Self::dark()
1281 }
1282}
1283
1284impl Selection {
1285 fn dark() -> Self {
1286 Self {
1287 bg_fill: Color32::from_rgb(0, 92, 128),
1288 stroke: Stroke::new(1.0, Color32::from_rgb(192, 222, 255)),
1289 }
1290 }
1291
1292 fn light() -> Self {
1293 Self {
1294 bg_fill: Color32::from_rgb(144, 209, 255),
1295 stroke: Stroke::new(1.0, Color32::from_rgb(0, 83, 125)),
1296 }
1297 }
1298}
1299
1300impl Default for Selection {
1301 fn default() -> Self {
1302 Self::dark()
1303 }
1304}
1305
1306impl Widgets {
1307 pub fn dark() -> Self {
1308 Self {
1309 noninteractive: WidgetVisuals {
1310 weak_bg_fill: Color32::from_gray(27),
1311 bg_fill: Color32::from_gray(27),
1312 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),
1315 expansion: 0.0,
1316 },
1317 inactive: WidgetVisuals {
1318 weak_bg_fill: Color32::from_gray(60), bg_fill: Color32::from_gray(60), bg_stroke: Default::default(),
1321 fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), rounding: Rounding::same(2.0),
1323 expansion: 0.0,
1324 },
1325 hovered: WidgetVisuals {
1326 weak_bg_fill: Color32::from_gray(70),
1327 bg_fill: Color32::from_gray(70),
1328 bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
1330 rounding: Rounding::same(3.0),
1331 expansion: 1.0,
1332 },
1333 active: WidgetVisuals {
1334 weak_bg_fill: Color32::from_gray(55),
1335 bg_fill: Color32::from_gray(55),
1336 bg_stroke: Stroke::new(1.0, Color32::WHITE),
1337 fg_stroke: Stroke::new(2.0, Color32::WHITE),
1338 rounding: Rounding::same(2.0),
1339 expansion: 1.0,
1340 },
1341 open: WidgetVisuals {
1342 weak_bg_fill: Color32::from_gray(45),
1343 bg_fill: Color32::from_gray(27),
1344 bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1345 fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
1346 rounding: Rounding::same(2.0),
1347 expansion: 0.0,
1348 },
1349 }
1350 }
1351
1352 pub fn light() -> Self {
1353 Self {
1354 noninteractive: WidgetVisuals {
1355 weak_bg_fill: Color32::from_gray(248),
1356 bg_fill: Color32::from_gray(248),
1357 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),
1360 expansion: 0.0,
1361 },
1362 inactive: WidgetVisuals {
1363 weak_bg_fill: Color32::from_gray(230), bg_fill: Color32::from_gray(230), bg_stroke: Default::default(),
1366 fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), rounding: Rounding::same(2.0),
1368 expansion: 0.0,
1369 },
1370 hovered: WidgetVisuals {
1371 weak_bg_fill: Color32::from_gray(220),
1372 bg_fill: Color32::from_gray(220),
1373 bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), fg_stroke: Stroke::new(1.5, Color32::BLACK),
1375 rounding: Rounding::same(3.0),
1376 expansion: 1.0,
1377 },
1378 active: WidgetVisuals {
1379 weak_bg_fill: Color32::from_gray(165),
1380 bg_fill: Color32::from_gray(165),
1381 bg_stroke: Stroke::new(1.0, Color32::BLACK),
1382 fg_stroke: Stroke::new(2.0, Color32::BLACK),
1383 rounding: Rounding::same(2.0),
1384 expansion: 1.0,
1385 },
1386 open: WidgetVisuals {
1387 weak_bg_fill: Color32::from_gray(220),
1388 bg_fill: Color32::from_gray(220),
1389 bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
1390 fg_stroke: Stroke::new(1.0, Color32::BLACK),
1391 rounding: Rounding::same(2.0),
1392 expansion: 0.0,
1393 },
1394 }
1395 }
1396}
1397
1398impl Default for Widgets {
1399 fn default() -> Self {
1400 Self::dark()
1401 }
1402}
1403
1404use crate::{widgets::*, Ui};
1407
1408impl Style {
1409 pub fn ui(&mut self, ui: &mut crate::Ui) {
1410 #[allow(deprecated)]
1411 let Self {
1412 override_font_id,
1413 override_text_style,
1414 text_styles,
1415 drag_value_text_style,
1416 number_formatter: _, wrap: _,
1418 wrap_mode: _,
1419 spacing,
1420 interaction,
1421 visuals,
1422 animation_time,
1423 #[cfg(debug_assertions)]
1424 debug,
1425 explanation_tooltips,
1426 url_in_tooltip,
1427 always_scroll_the_only_direction,
1428 } = self;
1429
1430 visuals.light_dark_radio_buttons(ui);
1431
1432 crate::Grid::new("_options").show(ui, |ui| {
1433 ui.label("Override font id");
1434 ui.vertical(|ui| {
1435 ui.horizontal(|ui| {
1436 ui.radio_value(override_font_id, None, "None");
1437 if ui.radio(override_font_id.is_some(), "override").clicked() {
1438 *override_font_id = Some(FontId::default());
1439 }
1440 });
1441 if let Some(override_font_id) = override_font_id {
1442 crate::introspection::font_id_ui(ui, override_font_id);
1443 }
1444 });
1445 ui.end_row();
1446
1447 ui.label("Override text style");
1448 crate::ComboBox::from_id_source("Override text style")
1449 .selected_text(match override_text_style {
1450 None => "None".to_owned(),
1451 Some(override_text_style) => override_text_style.to_string(),
1452 })
1453 .show_ui(ui, |ui| {
1454 ui.selectable_value(override_text_style, None, "None");
1455 let all_text_styles = ui.style().text_styles();
1456 for style in all_text_styles {
1457 let text =
1458 crate::RichText::new(style.to_string()).text_style(style.clone());
1459 ui.selectable_value(override_text_style, Some(style), text);
1460 }
1461 });
1462 ui.end_row();
1463
1464 ui.label("Text style of DragValue");
1465 crate::ComboBox::from_id_source("drag_value_text_style")
1466 .selected_text(drag_value_text_style.to_string())
1467 .show_ui(ui, |ui| {
1468 let all_text_styles = ui.style().text_styles();
1469 for style in all_text_styles {
1470 let text =
1471 crate::RichText::new(style.to_string()).text_style(style.clone());
1472 ui.selectable_value(drag_value_text_style, style, text);
1473 }
1474 });
1475 ui.end_row();
1476
1477 ui.label("Animation duration");
1478 ui.add(
1479 DragValue::new(animation_time)
1480 .range(0.0..=1.0)
1481 .speed(0.02)
1482 .suffix(" s"),
1483 );
1484 ui.end_row();
1485 });
1486
1487 ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles));
1488 ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
1489 ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
1490 ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
1491
1492 #[cfg(debug_assertions)]
1493 ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
1494
1495 ui.checkbox(explanation_tooltips, "Explanation tooltips")
1496 .on_hover_text(
1497 "Show explanatory text when hovering DragValue:s and other egui widgets",
1498 );
1499
1500 ui.checkbox(url_in_tooltip, "Show url when hovering links");
1501
1502 ui.checkbox(always_scroll_the_only_direction, "Always scroll the only enabled direction")
1503 .on_hover_text(
1504 "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift",
1505 );
1506
1507 ui.vertical_centered(|ui| reset_button(ui, self, "Reset style"));
1508 }
1509}
1510
1511fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
1512 ui.vertical(|ui| {
1513 crate::Grid::new("text_styles").show(ui, |ui| {
1514 for (text_style, font_id) in &mut *text_styles {
1515 ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
1516 crate::introspection::font_id_ui(ui, font_id);
1517 ui.end_row();
1518 }
1519 });
1520 crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles());
1521 })
1522 .response
1523}
1524
1525impl Spacing {
1526 pub fn ui(&mut self, ui: &mut crate::Ui) {
1527 let Self {
1528 item_spacing,
1529 window_margin,
1530 menu_margin,
1531 button_padding,
1532 indent,
1533 interact_size,
1534 slider_width,
1535 slider_rail_height,
1536 combo_width,
1537 text_edit_width,
1538 icon_width,
1539 icon_width_inner,
1540 icon_spacing,
1541 default_area_size,
1542 tooltip_width,
1543 menu_width,
1544 menu_spacing,
1545 indent_ends_with_horizontal_line,
1546 combo_height,
1547 scroll,
1548 } = self;
1549
1550 Grid::new("spacing")
1551 .num_columns(2)
1552 .spacing([12.0, 8.0])
1553 .striped(true)
1554 .show(ui, |ui| {
1555 ui.label("Item spacing");
1556 ui.add(two_drag_values(item_spacing, 0.0..=20.0));
1557 ui.end_row();
1558
1559 ui.label("Window margin");
1560 ui.add(window_margin);
1561 ui.end_row();
1562
1563 ui.label("Menu margin");
1564 ui.add(menu_margin);
1565 ui.end_row();
1566
1567 ui.label("Button padding");
1568 ui.add(two_drag_values(button_padding, 0.0..=20.0));
1569 ui.end_row();
1570
1571 ui.label("Interact size")
1572 .on_hover_text("Minimum size of an interactive widget");
1573 ui.add(two_drag_values(interact_size, 4.0..=60.0));
1574 ui.end_row();
1575
1576 ui.label("Indent");
1577 ui.add(DragValue::new(indent).range(0.0..=100.0));
1578 ui.end_row();
1579
1580 ui.label("Slider width");
1581 ui.add(DragValue::new(slider_width).range(0.0..=1000.0));
1582 ui.end_row();
1583
1584 ui.label("Slider rail height");
1585 ui.add(DragValue::new(slider_rail_height).range(0.0..=50.0));
1586 ui.end_row();
1587
1588 ui.label("ComboBox width");
1589 ui.add(DragValue::new(combo_width).range(0.0..=1000.0));
1590 ui.end_row();
1591
1592 ui.label("Default area size");
1593 ui.add(two_drag_values(default_area_size, 0.0..=1000.0));
1594 ui.end_row();
1595
1596 ui.label("TextEdit width");
1597 ui.add(DragValue::new(text_edit_width).range(0.0..=1000.0));
1598 ui.end_row();
1599
1600 ui.label("Tooltip wrap width");
1601 ui.add(DragValue::new(tooltip_width).range(0.0..=1000.0));
1602 ui.end_row();
1603
1604 ui.label("Default menu width");
1605 ui.add(DragValue::new(menu_width).range(0.0..=1000.0));
1606 ui.end_row();
1607
1608 ui.label("Menu spacing")
1609 .on_hover_text("Horizontal spacing between menus");
1610 ui.add(DragValue::new(menu_spacing).range(0.0..=10.0));
1611 ui.end_row();
1612
1613 ui.label("Checkboxes etc");
1614 ui.vertical(|ui| {
1615 ui.add(
1616 DragValue::new(icon_width)
1617 .prefix("outer icon width:")
1618 .range(0.0..=60.0),
1619 );
1620 ui.add(
1621 DragValue::new(icon_width_inner)
1622 .prefix("inner icon width:")
1623 .range(0.0..=60.0),
1624 );
1625 ui.add(
1626 DragValue::new(icon_spacing)
1627 .prefix("spacing:")
1628 .range(0.0..=10.0),
1629 );
1630 });
1631 ui.end_row();
1632 });
1633
1634 ui.checkbox(
1635 indent_ends_with_horizontal_line,
1636 "End indented regions with a horizontal separator",
1637 );
1638
1639 ui.horizontal(|ui| {
1640 ui.label("Max height of a combo box");
1641 ui.add(DragValue::new(combo_height).range(0.0..=1000.0));
1642 });
1643
1644 ui.collapsing("Scroll Area", |ui| {
1645 scroll.ui(ui);
1646 });
1647
1648 ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing"));
1649 }
1650}
1651
1652impl Interaction {
1653 pub fn ui(&mut self, ui: &mut crate::Ui) {
1654 let Self {
1655 interact_radius,
1656 resize_grab_radius_side,
1657 resize_grab_radius_corner,
1658 show_tooltips_only_when_still,
1659 tooltip_delay,
1660 tooltip_grace_time,
1661 selectable_labels,
1662 multi_widget_text_select,
1663 } = self;
1664
1665 ui.spacing_mut().item_spacing = vec2(12.0, 8.0);
1666
1667 Grid::new("interaction")
1668 .num_columns(2)
1669 .striped(true)
1670 .show(ui, |ui| {
1671 ui.label("interact_radius")
1672 .on_hover_text("Interact with the closest widget within this radius.");
1673 ui.add(DragValue::new(interact_radius).range(0.0..=20.0));
1674 ui.end_row();
1675
1676 ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize");
1677 ui.add(DragValue::new(resize_grab_radius_side).range(0.0..=20.0));
1678 ui.end_row();
1679
1680 ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize.");
1681 ui.add(DragValue::new(resize_grab_radius_corner).range(0.0..=20.0));
1682 ui.end_row();
1683
1684 ui.label("Tooltip delay").on_hover_text(
1685 "Delay in seconds before showing tooltips after the mouse stops moving",
1686 );
1687 ui.add(
1688 DragValue::new(tooltip_delay)
1689 .range(0.0..=1.0)
1690 .speed(0.05)
1691 .suffix(" s"),
1692 );
1693 ui.end_row();
1694
1695 ui.label("Tooltip grace time").on_hover_text(
1696 "If a tooltip is open and you hover another widget within this grace period, show the next tooltip right away",
1697 );
1698 ui.add(
1699 DragValue::new(tooltip_grace_time)
1700 .range(0.0..=1.0)
1701 .speed(0.05)
1702 .suffix(" s"),
1703 );
1704 ui.end_row();
1705 });
1706
1707 ui.checkbox(
1708 show_tooltips_only_when_still,
1709 "Only show tooltips if mouse is still",
1710 );
1711
1712 ui.horizontal(|ui| {
1713 ui.checkbox(selectable_labels, "Selectable text in labels");
1714 if *selectable_labels {
1715 ui.checkbox(multi_widget_text_select, "Across multiple labels");
1716 }
1717 });
1718
1719 ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings"));
1720 }
1721}
1722
1723impl Widgets {
1724 pub fn ui(&mut self, ui: &mut crate::Ui) {
1725 let Self {
1726 active,
1727 hovered,
1728 inactive,
1729 noninteractive,
1730 open,
1731 } = self;
1732
1733 ui.collapsing("Noninteractive", |ui| {
1734 ui.label(
1735 "The style of a widget that you cannot interact with, e.g. labels and separators.",
1736 );
1737 noninteractive.ui(ui);
1738 });
1739 ui.collapsing("Interactive but inactive", |ui| {
1740 ui.label("The style of an interactive widget, such as a button, at rest.");
1741 inactive.ui(ui);
1742 });
1743 ui.collapsing("Interactive and hovered", |ui| {
1744 ui.label("The style of an interactive widget while you hover it.");
1745 hovered.ui(ui);
1746 });
1747 ui.collapsing("Interactive and active", |ui| {
1748 ui.label("The style of an interactive widget as you are clicking or dragging it.");
1749 active.ui(ui);
1750 });
1751 ui.collapsing("Open menu", |ui| {
1752 ui.label("The style of an open combo-box or menu button");
1753 open.ui(ui);
1754 });
1755
1756 }
1758}
1759
1760impl Selection {
1761 pub fn ui(&mut self, ui: &mut crate::Ui) {
1762 let Self { bg_fill, stroke } = self;
1763 ui.label("Selectable labels");
1764
1765 Grid::new("selectiom").num_columns(2).show(ui, |ui| {
1766 ui.label("Background fill");
1767 ui.color_edit_button_srgba(bg_fill);
1768 ui.end_row();
1769
1770 ui.label("Stroke");
1771 ui.add(stroke);
1772 ui.end_row();
1773 });
1774 }
1775}
1776
1777impl WidgetVisuals {
1778 pub fn ui(&mut self, ui: &mut crate::Ui) {
1779 let Self {
1780 weak_bg_fill,
1781 bg_fill: mandatory_bg_fill,
1782 bg_stroke,
1783 rounding,
1784 fg_stroke,
1785 expansion,
1786 } = self;
1787
1788 Grid::new("widget")
1789 .num_columns(2)
1790 .spacing([12.0, 8.0])
1791 .striped(true)
1792 .show(ui, |ui| {
1793 ui.label("Optional background fill")
1794 .on_hover_text("For buttons, combo-boxes, etc");
1795 ui.color_edit_button_srgba(weak_bg_fill);
1796 ui.end_row();
1797
1798 ui.label("Mandatory background fill")
1799 .on_hover_text("For checkboxes, sliders, etc");
1800 ui.color_edit_button_srgba(mandatory_bg_fill);
1801 ui.end_row();
1802
1803 ui.label("Background stroke");
1804 ui.add(bg_stroke);
1805 ui.end_row();
1806
1807 ui.label("Rounding");
1808 ui.add(rounding);
1809 ui.end_row();
1810
1811 ui.label("Foreground stroke (text)");
1812 ui.add(fg_stroke);
1813 ui.end_row();
1814
1815 ui.label("Expansion")
1816 .on_hover_text("make shapes this much larger");
1817 ui.add(DragValue::new(expansion).speed(0.1));
1818 ui.end_row();
1819 });
1820 }
1821}
1822
1823impl Visuals {
1824 pub fn light_dark_radio_buttons(&mut self, ui: &mut crate::Ui) {
1826 ui.horizontal(|ui| {
1827 ui.selectable_value(self, Self::light(), "☀ Light");
1828 ui.selectable_value(self, Self::dark(), "🌙 Dark");
1829 });
1830 }
1831
1832 #[must_use]
1834 pub fn light_dark_small_toggle_button(&self, ui: &mut crate::Ui) -> Option<Self> {
1835 #![allow(clippy::collapsible_else_if)]
1836 if self.dark_mode {
1837 if ui
1838 .add(Button::new("☀").frame(false))
1839 .on_hover_text("Switch to light mode")
1840 .clicked()
1841 {
1842 return Some(Self::light());
1843 }
1844 } else {
1845 if ui
1846 .add(Button::new("🌙").frame(false))
1847 .on_hover_text("Switch to dark mode")
1848 .clicked()
1849 {
1850 return Some(Self::dark());
1851 }
1852 }
1853 None
1854 }
1855
1856 pub fn ui(&mut self, ui: &mut crate::Ui) {
1857 let Self {
1858 dark_mode: _,
1859 override_text_color: _,
1860 widgets,
1861 selection,
1862 hyperlink_color,
1863 faint_bg_color,
1864 extreme_bg_color,
1865 code_bg_color,
1866 warn_fg_color,
1867 error_fg_color,
1868
1869 window_rounding,
1870 window_shadow,
1871 window_fill,
1872 window_stroke,
1873 window_highlight_topmost,
1874
1875 menu_rounding,
1876
1877 panel_fill,
1878
1879 popup_shadow,
1880
1881 resize_corner_size,
1882
1883 text_cursor,
1884
1885 clip_rect_margin,
1886 button_frame,
1887 collapsing_header_frame,
1888 indent_has_left_vline,
1889
1890 striped,
1891
1892 slider_trailing_fill,
1893 handle_shape,
1894 interact_cursor,
1895
1896 image_loading_spinners,
1897
1898 numeric_color_space,
1899 } = self;
1900
1901 ui.collapsing("Background Colors", |ui| {
1902 ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
1903 ui_color(ui, window_fill, "Windows");
1904 ui_color(ui, panel_fill, "Panels");
1905 ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
1906 "Used for faint accentuation of interactive things, like striped grids.",
1907 );
1908 ui_color(ui, extreme_bg_color, "Extreme")
1909 .on_hover_text("Background of plots and paintings");
1910 });
1911
1912 ui.collapsing("Text color", |ui| {
1913 ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
1914 ui_text_color(
1915 ui,
1916 &mut widgets.inactive.fg_stroke.color,
1917 "Unhovered button",
1918 );
1919 ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
1920 ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
1921
1922 ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
1923 ui_text_color(ui, error_fg_color, RichText::new("Errors"));
1924
1925 ui_text_color(ui, hyperlink_color, "hyperlink_color");
1926
1927 ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(
1928 |ui| {
1929 ui.horizontal(|ui| {
1930 ui.spacing_mut().item_spacing.x = 0.0;
1931 ui.label("For monospaced inlined text ");
1932 ui.code("like this");
1933 ui.label(".");
1934 });
1935 },
1936 );
1937 });
1938
1939 ui.collapsing("Text cursor", |ui| {
1940 text_cursor.ui(ui);
1941 });
1942
1943 ui.collapsing("Window", |ui| {
1944 Grid::new("window")
1945 .num_columns(2)
1946 .spacing([12.0, 8.0])
1947 .striped(true)
1948 .show(ui, |ui| {
1949 ui.label("Fill");
1950 ui.color_edit_button_srgba(window_fill);
1951 ui.end_row();
1952
1953 ui.label("Stroke");
1954 ui.add(window_stroke);
1955 ui.end_row();
1956
1957 ui.label("Rounding");
1958 ui.add(window_rounding);
1959 ui.end_row();
1960
1961 ui.label("Shadow");
1962 ui.add(window_shadow);
1963 ui.end_row();
1964 });
1965
1966 ui.checkbox(window_highlight_topmost, "Highlight topmost Window");
1967 });
1968
1969 ui.collapsing("Menus and popups", |ui| {
1970 Grid::new("menus_and_popups")
1971 .num_columns(2)
1972 .spacing([12.0, 8.0])
1973 .striped(true)
1974 .show(ui, |ui| {
1975 ui.label("Rounding");
1976 ui.add(menu_rounding);
1977 ui.end_row();
1978
1979 ui.label("Shadow");
1980 ui.add(popup_shadow);
1981 ui.end_row();
1982 });
1983 });
1984
1985 ui.collapsing("Widgets", |ui| widgets.ui(ui));
1986 ui.collapsing("Selection", |ui| selection.ui(ui));
1987
1988 ui.collapsing("Misc", |ui| {
1989 ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
1990 ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
1991
1992 ui.checkbox(button_frame, "Button has a frame");
1993 ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
1994 ui.checkbox(
1995 indent_has_left_vline,
1996 "Paint a vertical line to the left of indented regions",
1997 );
1998
1999 ui.checkbox(striped, "Default stripes on grids and tables");
2000
2001 ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
2002
2003 handle_shape.ui(ui);
2004
2005 ComboBox::from_label("Interact cursor")
2006 .selected_text(
2007 interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")),
2008 )
2009 .show_ui(ui, |ui| {
2010 ui.selectable_value(interact_cursor, None, "-");
2011
2012 for cursor in CursorIcon::ALL {
2013 ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}"))
2014 .on_hover_cursor(cursor);
2015 }
2016 })
2017 .response
2018 .on_hover_text("Use this cursor when hovering buttons etc");
2019
2020 ui.checkbox(image_loading_spinners, "Image loading spinners")
2021 .on_hover_text("Show a spinner when an Image is loading");
2022
2023 ui.horizontal(|ui| {
2024 ui.label("Color picker type");
2025 numeric_color_space.toggle_button_ui(ui);
2026 });
2027 });
2028
2029 ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals"));
2030 }
2031}
2032
2033impl TextCursorStyle {
2034 fn ui(&mut self, ui: &mut Ui) {
2035 let Self {
2036 stroke,
2037 preview,
2038 blink,
2039 on_duration,
2040 off_duration,
2041 } = self;
2042
2043 ui.horizontal(|ui| {
2044 ui.label("Stroke");
2045 ui.add(stroke);
2046 });
2047
2048 ui.checkbox(preview, "Preview text cursor on hover");
2049
2050 ui.checkbox(blink, "Blink");
2051
2052 if *blink {
2053 Grid::new("cursor_blink").show(ui, |ui| {
2054 ui.label("On time");
2055 ui.add(
2056 DragValue::new(on_duration)
2057 .speed(0.1)
2058 .range(0.0..=2.0)
2059 .suffix(" s"),
2060 );
2061 ui.end_row();
2062
2063 ui.label("Off time");
2064 ui.add(
2065 DragValue::new(off_duration)
2066 .speed(0.1)
2067 .range(0.0..=2.0)
2068 .suffix(" s"),
2069 );
2070 ui.end_row();
2071 });
2072 }
2073 }
2074}
2075
2076#[cfg(debug_assertions)]
2077impl DebugOptions {
2078 pub fn ui(&mut self, ui: &mut crate::Ui) {
2079 let Self {
2080 debug_on_hover,
2081 debug_on_hover_with_all_modifiers,
2082 hover_shows_next,
2083 show_expand_width,
2084 show_expand_height,
2085 show_resize,
2086 show_interactive_widgets,
2087 show_widget_hits,
2088 } = self;
2089
2090 {
2091 ui.checkbox(debug_on_hover, "Show widget info on hover.");
2092 ui.checkbox(
2093 debug_on_hover_with_all_modifiers,
2094 "Show widget info on hover if holding all modifier keys",
2095 );
2096
2097 ui.checkbox(hover_shows_next, "Show next widget placement on hover");
2098 }
2099
2100 ui.checkbox(
2101 show_expand_width,
2102 "Show which widgets make their parent wider",
2103 );
2104 ui.checkbox(
2105 show_expand_height,
2106 "Show which widgets make their parent higher",
2107 );
2108 ui.checkbox(show_resize, "Debug Resize");
2109
2110 ui.checkbox(
2111 show_interactive_widgets,
2112 "Show an overlay on all interactive widgets",
2113 );
2114
2115 ui.checkbox(show_widget_hits, "Show widgets under mouse pointer");
2116
2117 ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options"));
2118 }
2119}
2120
2121fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive<f32>) -> impl Widget + '_ {
2123 move |ui: &mut crate::Ui| {
2124 ui.horizontal(|ui| {
2125 ui.add(
2126 DragValue::new(&mut value.x)
2127 .range(range.clone())
2128 .prefix("x: "),
2129 );
2130 ui.add(
2131 DragValue::new(&mut value.y)
2132 .range(range.clone())
2133 .prefix("y: "),
2134 );
2135 })
2136 .response
2137 }
2138}
2139
2140fn ui_color(ui: &mut Ui, color: &mut Color32, label: impl Into<WidgetText>) -> Response {
2141 ui.horizontal(|ui| {
2142 ui.color_edit_button_srgba(color);
2143 ui.label(label);
2144 })
2145 .response
2146}
2147
2148fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) -> Response {
2149 ui.horizontal(|ui| {
2150 ui.color_edit_button_srgba(color);
2151 ui.label(label.into().color(*color));
2152 })
2153 .response
2154}
2155
2156impl HandleShape {
2157 pub fn ui(&mut self, ui: &mut Ui) {
2158 ui.horizontal(|ui| {
2159 ui.label("Slider handle");
2160 ui.radio_value(self, Self::Circle, "Circle");
2161 if ui
2162 .radio(matches!(self, Self::Rect { .. }), "Rectangle")
2163 .clicked()
2164 {
2165 *self = Self::Rect { aspect_ratio: 0.5 };
2166 }
2167 if let Self::Rect { aspect_ratio } = self {
2168 ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio"));
2169 }
2170 });
2171 }
2172}
2173
2174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2176#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
2177pub enum NumericColorSpace {
2178 GammaByte,
2182
2183 Linear,
2185 }
2187
2188impl NumericColorSpace {
2189 pub fn toggle_button_ui(&mut self, ui: &mut Ui) -> crate::Response {
2190 let tooltip = match self {
2191 Self::GammaByte => "Showing color values in 0-255 gamma space",
2192 Self::Linear => "Showing color values in 0-1 linear space",
2193 };
2194
2195 let mut response = ui.button(self.to_string()).on_hover_text(tooltip);
2196 if response.clicked() {
2197 *self = match self {
2198 Self::GammaByte => Self::Linear,
2199 Self::Linear => Self::GammaByte,
2200 };
2201 response.mark_changed();
2202 }
2203 response
2204 }
2205}
2206
2207impl std::fmt::Display for NumericColorSpace {
2208 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2209 match self {
2210 Self::GammaByte => write!(f, "U8"),
2211 Self::Linear => write!(f, "F"),
2212 }
2213 }
2214}
2215
2216impl Widget for &mut Margin {
2217 fn ui(self, ui: &mut Ui) -> Response {
2218 let mut same = self.is_same();
2219
2220 let response = if same {
2221 ui.horizontal(|ui| {
2222 ui.checkbox(&mut same, "same");
2223
2224 let mut value = self.left;
2225 ui.add(DragValue::new(&mut value));
2226 *self = Margin::same(value);
2227 })
2228 .response
2229 } else {
2230 ui.vertical(|ui| {
2231 ui.checkbox(&mut same, "same");
2232
2233 crate::Grid::new("margin").num_columns(2).show(ui, |ui| {
2234 ui.label("Left");
2235 ui.add(DragValue::new(&mut self.left));
2236 ui.end_row();
2237
2238 ui.label("Right");
2239 ui.add(DragValue::new(&mut self.right));
2240 ui.end_row();
2241
2242 ui.label("Top");
2243 ui.add(DragValue::new(&mut self.top));
2244 ui.end_row();
2245
2246 ui.label("Bottom");
2247 ui.add(DragValue::new(&mut self.bottom));
2248 ui.end_row();
2249 });
2250 })
2251 .response
2252 };
2253
2254 if same {
2256 *self = Margin::same((self.left + self.right + self.top + self.bottom) / 4.0);
2257 } else if self.is_same() {
2258 self.right *= 1.00001; }
2260
2261 response
2262 }
2263}
2264
2265impl Widget for &mut Rounding {
2266 fn ui(self, ui: &mut Ui) -> Response {
2267 let mut same = self.is_same();
2268
2269 let response = if same {
2270 ui.horizontal(|ui| {
2271 ui.checkbox(&mut same, "same");
2272
2273 let mut cr = self.nw;
2274 ui.add(DragValue::new(&mut cr).range(0.0..=f32::INFINITY));
2275 *self = Rounding::same(cr);
2276 })
2277 .response
2278 } else {
2279 ui.vertical(|ui| {
2280 ui.checkbox(&mut same, "same");
2281
2282 crate::Grid::new("rounding").num_columns(2).show(ui, |ui| {
2283 ui.label("NW");
2284 ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
2285 ui.end_row();
2286
2287 ui.label("NE");
2288 ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
2289 ui.end_row();
2290
2291 ui.label("SW");
2292 ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
2293 ui.end_row();
2294
2295 ui.label("SE");
2296 ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
2297 ui.end_row();
2298 });
2299 })
2300 .response
2301 };
2302
2303 if same {
2305 *self = Rounding::same((self.nw + self.ne + self.sw + self.se) / 4.0);
2306 } else if self.is_same() {
2307 self.se *= 1.00001; }
2309
2310 response
2311 }
2312}
2313
2314impl Widget for &mut Shadow {
2315 fn ui(self, ui: &mut Ui) -> Response {
2316 let epaint::Shadow {
2317 offset,
2318 blur,
2319 spread,
2320 color,
2321 } = self;
2322
2323 ui.vertical(|ui| {
2324 crate::Grid::new("shadow_ui").show(ui, |ui| {
2325 ui.add(
2326 DragValue::new(&mut offset.x)
2327 .speed(1.0)
2328 .range(-100.0..=100.0)
2329 .prefix("x: "),
2330 );
2331 ui.add(
2332 DragValue::new(&mut offset.y)
2333 .speed(1.0)
2334 .range(-100.0..=100.0)
2335 .prefix("y: "),
2336 );
2337 ui.end_row();
2338
2339 ui.add(
2340 DragValue::new(blur)
2341 .speed(1.0)
2342 .range(0.0..=100.0)
2343 .prefix("blur: "),
2344 );
2345
2346 ui.add(
2347 DragValue::new(spread)
2348 .speed(1.0)
2349 .range(0.0..=100.0)
2350 .prefix("spread: "),
2351 );
2352 });
2353 ui.color_edit_button_srgba(color);
2354 })
2355 .response
2356 }
2357}
2358
2359impl Widget for &mut Stroke {
2360 fn ui(self, ui: &mut Ui) -> Response {
2361 let Stroke { width, color } = self;
2362
2363 ui.horizontal(|ui| {
2364 ui.add(DragValue::new(width).speed(0.1).range(0.0..=f32::INFINITY))
2365 .on_hover_text("Width");
2366 ui.color_edit_button_srgba(color);
2367
2368 let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size);
2370 let left = stroke_rect.left_center();
2371 let right = stroke_rect.right_center();
2372 ui.painter().line_segment([left, right], (*width, *color));
2373 })
2374 .response
2375 }
2376}
2377
2378impl Widget for &mut crate::Frame {
2379 fn ui(self, ui: &mut Ui) -> Response {
2380 let crate::Frame {
2381 inner_margin,
2382 outer_margin,
2383 rounding,
2384 shadow,
2385 fill,
2386 stroke,
2387 } = self;
2388
2389 crate::Grid::new("frame")
2390 .num_columns(2)
2391 .spacing([12.0, 8.0])
2392 .striped(true)
2393 .show(ui, |ui| {
2394 ui.label("Inner margin");
2395 ui.add(inner_margin);
2396 ui.end_row();
2397
2398 ui.label("Outer margin");
2399 ui.add(outer_margin);
2400 ui.end_row();
2401
2402 ui.label("Rounding");
2403 ui.add(rounding);
2404 ui.end_row();
2405
2406 ui.label("Shadow");
2407 ui.add(shadow);
2408 ui.end_row();
2409
2410 ui.label("Fill");
2411 ui.color_edit_button_srgba(fill);
2412 ui.end_row();
2413
2414 ui.label("Stroke");
2415 ui.add(stroke);
2416 ui.end_row();
2417 })
2418 .response
2419 }
2420}