1#![allow(clippy::if_same_then_else)]
4
5use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
6
7use emath::Align;
8use epaint::{text::FontTweak, CornerRadius, 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_corner_radius: CornerRadius,
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_corner_radius: CornerRadius,
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 corner_radius: CornerRadius,
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 #[deprecated = "Renamed to corner_radius"]
1126 pub fn rounding(&self) -> CornerRadius {
1127 self.corner_radius
1128 }
1129}
1130
1131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1133#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1134#[cfg(debug_assertions)]
1135pub struct DebugOptions {
1136 #[cfg(debug_assertions)]
1144 pub debug_on_hover: bool,
1145
1146 #[cfg(debug_assertions)]
1156 pub debug_on_hover_with_all_modifiers: bool,
1157
1158 #[cfg(debug_assertions)]
1160 pub hover_shows_next: bool,
1161
1162 pub show_expand_width: bool,
1164
1165 pub show_expand_height: bool,
1167
1168 pub show_resize: bool,
1169
1170 pub show_interactive_widgets: bool,
1172
1173 pub show_widget_hits: bool,
1175
1176 pub show_unaligned: bool,
1180}
1181
1182#[cfg(debug_assertions)]
1183impl Default for DebugOptions {
1184 fn default() -> Self {
1185 Self {
1186 debug_on_hover: false,
1187 debug_on_hover_with_all_modifiers: cfg!(feature = "callstack")
1188 && !cfg!(target_arch = "wasm32"),
1189 hover_shows_next: false,
1190 show_expand_width: false,
1191 show_expand_height: false,
1192 show_resize: false,
1193 show_interactive_widgets: false,
1194 show_widget_hits: false,
1195 show_unaligned: cfg!(debug_assertions),
1196 }
1197 }
1198}
1199
1200pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
1204 use FontFamily::{Monospace, Proportional};
1205
1206 [
1207 (TextStyle::Small, FontId::new(9.0, Proportional)),
1208 (TextStyle::Body, FontId::new(12.5, Proportional)),
1209 (TextStyle::Button, FontId::new(12.5, Proportional)),
1210 (TextStyle::Heading, FontId::new(18.0, Proportional)),
1211 (TextStyle::Monospace, FontId::new(12.0, Monospace)),
1212 ]
1213 .into()
1214}
1215
1216impl Default for Style {
1217 fn default() -> Self {
1218 #[allow(deprecated)]
1219 Self {
1220 override_font_id: None,
1221 override_text_style: None,
1222 override_text_valign: Some(Align::Center),
1223 text_styles: default_text_styles(),
1224 drag_value_text_style: TextStyle::Button,
1225 number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
1226 wrap: None,
1227 wrap_mode: None,
1228 spacing: Spacing::default(),
1229 interaction: Interaction::default(),
1230 visuals: Visuals::default(),
1231 animation_time: 1.0 / 12.0,
1232 #[cfg(debug_assertions)]
1233 debug: Default::default(),
1234 explanation_tooltips: false,
1235 url_in_tooltip: false,
1236 always_scroll_the_only_direction: false,
1237 scroll_animation: ScrollAnimation::default(),
1238 }
1239 }
1240}
1241
1242impl Default for Spacing {
1243 fn default() -> Self {
1244 Self {
1245 item_spacing: vec2(8.0, 3.0),
1246 window_margin: Margin::same(6),
1247 menu_margin: Margin::same(6),
1248 button_padding: vec2(4.0, 1.0),
1249 indent: 18.0, interact_size: vec2(40.0, 18.0),
1251 slider_width: 100.0,
1252 slider_rail_height: 8.0,
1253 combo_width: 100.0,
1254 text_edit_width: 280.0,
1255 icon_width: 14.0,
1256 icon_width_inner: 8.0,
1257 icon_spacing: 4.0,
1258 default_area_size: vec2(600.0, 400.0),
1259 tooltip_width: 500.0,
1260 menu_width: 400.0,
1261 menu_spacing: 2.0,
1262 combo_height: 200.0,
1263 scroll: Default::default(),
1264 indent_ends_with_horizontal_line: false,
1265 }
1266 }
1267}
1268
1269impl Default for Interaction {
1270 fn default() -> Self {
1271 Self {
1272 interact_radius: 5.0,
1273 resize_grab_radius_side: 5.0,
1274 resize_grab_radius_corner: 10.0,
1275 show_tooltips_only_when_still: true,
1276 tooltip_delay: 0.5,
1277 tooltip_grace_time: 0.2,
1278 selectable_labels: true,
1279 multi_widget_text_select: true,
1280 }
1281 }
1282}
1283
1284impl Visuals {
1285 pub fn dark() -> Self {
1287 Self {
1288 dark_mode: true,
1289 override_text_color: None,
1290 widgets: Widgets::default(),
1291 selection: Selection::default(),
1292 hyperlink_color: Color32::from_rgb(90, 170, 255),
1293 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(10), code_bg_color: Color32::from_gray(64),
1296 warn_fg_color: Color32::from_rgb(255, 143, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_corner_radius: CornerRadius::same(6),
1300 window_shadow: Shadow {
1301 offset: [10, 20],
1302 blur: 15,
1303 spread: 0,
1304 color: Color32::from_black_alpha(96),
1305 },
1306 window_fill: Color32::from_gray(27),
1307 window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1308 window_highlight_topmost: true,
1309
1310 menu_corner_radius: CornerRadius::same(6),
1311
1312 panel_fill: Color32::from_gray(27),
1313
1314 popup_shadow: Shadow {
1315 offset: [6, 10],
1316 blur: 8,
1317 spread: 0,
1318 color: Color32::from_black_alpha(96),
1319 },
1320
1321 resize_corner_size: 12.0,
1322
1323 text_cursor: Default::default(),
1324
1325 clip_rect_margin: 3.0, button_frame: true,
1327 collapsing_header_frame: false,
1328 indent_has_left_vline: true,
1329
1330 striped: false,
1331
1332 slider_trailing_fill: false,
1333 handle_shape: HandleShape::Circle,
1334
1335 interact_cursor: None,
1336
1337 image_loading_spinners: true,
1338
1339 numeric_color_space: NumericColorSpace::GammaByte,
1340 }
1341 }
1342
1343 pub fn light() -> Self {
1345 Self {
1346 dark_mode: false,
1347 widgets: Widgets::light(),
1348 selection: Selection::light(),
1349 hyperlink_color: Color32::from_rgb(0, 155, 255),
1350 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(255), code_bg_color: Color32::from_gray(230),
1353 warn_fg_color: Color32::from_rgb(255, 100, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_shadow: Shadow {
1357 offset: [10, 20],
1358 blur: 15,
1359 spread: 0,
1360 color: Color32::from_black_alpha(25),
1361 },
1362 window_fill: Color32::from_gray(248),
1363 window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
1364
1365 panel_fill: Color32::from_gray(248),
1366
1367 popup_shadow: Shadow {
1368 offset: [6, 10],
1369 blur: 8,
1370 spread: 0,
1371 color: Color32::from_black_alpha(25),
1372 },
1373
1374 text_cursor: TextCursorStyle {
1375 stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
1376 ..Default::default()
1377 },
1378
1379 ..Self::dark()
1380 }
1381 }
1382}
1383
1384impl Default for Visuals {
1385 fn default() -> Self {
1386 Self::dark()
1387 }
1388}
1389
1390impl Selection {
1391 fn dark() -> Self {
1392 Self {
1393 bg_fill: Color32::from_rgb(0, 92, 128),
1394 stroke: Stroke::new(1.0, Color32::from_rgb(192, 222, 255)),
1395 }
1396 }
1397
1398 fn light() -> Self {
1399 Self {
1400 bg_fill: Color32::from_rgb(144, 209, 255),
1401 stroke: Stroke::new(1.0, Color32::from_rgb(0, 83, 125)),
1402 }
1403 }
1404}
1405
1406impl Default for Selection {
1407 fn default() -> Self {
1408 Self::dark()
1409 }
1410}
1411
1412impl Widgets {
1413 pub fn dark() -> Self {
1414 Self {
1415 noninteractive: WidgetVisuals {
1416 weak_bg_fill: Color32::from_gray(27),
1417 bg_fill: Color32::from_gray(27),
1418 bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), corner_radius: CornerRadius::same(2),
1421 expansion: 0.0,
1422 },
1423 inactive: WidgetVisuals {
1424 weak_bg_fill: Color32::from_gray(60), bg_fill: Color32::from_gray(60), bg_stroke: Default::default(),
1427 fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), corner_radius: CornerRadius::same(2),
1429 expansion: 0.0,
1430 },
1431 hovered: WidgetVisuals {
1432 weak_bg_fill: Color32::from_gray(70),
1433 bg_fill: Color32::from_gray(70),
1434 bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
1436 corner_radius: CornerRadius::same(3),
1437 expansion: 1.0,
1438 },
1439 active: WidgetVisuals {
1440 weak_bg_fill: Color32::from_gray(55),
1441 bg_fill: Color32::from_gray(55),
1442 bg_stroke: Stroke::new(1.0, Color32::WHITE),
1443 fg_stroke: Stroke::new(2.0, Color32::WHITE),
1444 corner_radius: CornerRadius::same(2),
1445 expansion: 1.0,
1446 },
1447 open: WidgetVisuals {
1448 weak_bg_fill: Color32::from_gray(45),
1449 bg_fill: Color32::from_gray(27),
1450 bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1451 fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
1452 corner_radius: CornerRadius::same(2),
1453 expansion: 0.0,
1454 },
1455 }
1456 }
1457
1458 pub fn light() -> Self {
1459 Self {
1460 noninteractive: WidgetVisuals {
1461 weak_bg_fill: Color32::from_gray(248),
1462 bg_fill: Color32::from_gray(248),
1463 bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), corner_radius: CornerRadius::same(2),
1466 expansion: 0.0,
1467 },
1468 inactive: WidgetVisuals {
1469 weak_bg_fill: Color32::from_gray(230), bg_fill: Color32::from_gray(230), bg_stroke: Default::default(),
1472 fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), corner_radius: CornerRadius::same(2),
1474 expansion: 0.0,
1475 },
1476 hovered: WidgetVisuals {
1477 weak_bg_fill: Color32::from_gray(220),
1478 bg_fill: Color32::from_gray(220),
1479 bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), fg_stroke: Stroke::new(1.5, Color32::BLACK),
1481 corner_radius: CornerRadius::same(3),
1482 expansion: 1.0,
1483 },
1484 active: WidgetVisuals {
1485 weak_bg_fill: Color32::from_gray(165),
1486 bg_fill: Color32::from_gray(165),
1487 bg_stroke: Stroke::new(1.0, Color32::BLACK),
1488 fg_stroke: Stroke::new(2.0, Color32::BLACK),
1489 corner_radius: CornerRadius::same(2),
1490 expansion: 1.0,
1491 },
1492 open: WidgetVisuals {
1493 weak_bg_fill: Color32::from_gray(220),
1494 bg_fill: Color32::from_gray(220),
1495 bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
1496 fg_stroke: Stroke::new(1.0, Color32::BLACK),
1497 corner_radius: CornerRadius::same(2),
1498 expansion: 0.0,
1499 },
1500 }
1501 }
1502}
1503
1504impl Default for Widgets {
1505 fn default() -> Self {
1506 Self::dark()
1507 }
1508}
1509
1510use crate::{
1513 widgets::{reset_button, DragValue, Slider, Widget},
1514 Ui,
1515};
1516
1517impl Style {
1518 pub fn ui(&mut self, ui: &mut crate::Ui) {
1519 #[allow(deprecated)]
1520 let Self {
1521 override_font_id,
1522 override_text_style,
1523 override_text_valign,
1524 text_styles,
1525 drag_value_text_style,
1526 number_formatter: _, wrap: _,
1528 wrap_mode,
1529 spacing,
1530 interaction,
1531 visuals,
1532 animation_time,
1533 #[cfg(debug_assertions)]
1534 debug,
1535 explanation_tooltips,
1536 url_in_tooltip,
1537 always_scroll_the_only_direction,
1538 scroll_animation,
1539 } = self;
1540
1541 crate::Grid::new("_options").show(ui, |ui| {
1542 ui.label("Override font id");
1543 ui.vertical(|ui| {
1544 ui.horizontal(|ui| {
1545 ui.radio_value(override_font_id, None, "None");
1546 if ui.radio(override_font_id.is_some(), "override").clicked() {
1547 *override_font_id = Some(FontId::default());
1548 }
1549 });
1550 if let Some(override_font_id) = override_font_id {
1551 crate::introspection::font_id_ui(ui, override_font_id);
1552 }
1553 });
1554 ui.end_row();
1555
1556 ui.label("Override text style");
1557 crate::ComboBox::from_id_salt("override_text_style")
1558 .selected_text(match override_text_style {
1559 None => "None".to_owned(),
1560 Some(override_text_style) => override_text_style.to_string(),
1561 })
1562 .show_ui(ui, |ui| {
1563 ui.selectable_value(override_text_style, None, "None");
1564 let all_text_styles = ui.style().text_styles();
1565 for style in all_text_styles {
1566 let text =
1567 crate::RichText::new(style.to_string()).text_style(style.clone());
1568 ui.selectable_value(override_text_style, Some(style), text);
1569 }
1570 });
1571 ui.end_row();
1572
1573 fn valign_name(valign: Align) -> &'static str {
1574 match valign {
1575 Align::TOP => "Top",
1576 Align::Center => "Center",
1577 Align::BOTTOM => "Bottom",
1578 }
1579 }
1580
1581 ui.label("Override text valign");
1582 crate::ComboBox::from_id_salt("override_text_valign")
1583 .selected_text(match override_text_valign {
1584 None => "None",
1585 Some(override_text_valign) => valign_name(*override_text_valign),
1586 })
1587 .show_ui(ui, |ui| {
1588 ui.selectable_value(override_text_valign, None, "None");
1589 for align in [Align::TOP, Align::Center, Align::BOTTOM] {
1590 ui.selectable_value(override_text_valign, Some(align), valign_name(align));
1591 }
1592 });
1593 ui.end_row();
1594
1595 ui.label("Text style of DragValue");
1596 crate::ComboBox::from_id_salt("drag_value_text_style")
1597 .selected_text(drag_value_text_style.to_string())
1598 .show_ui(ui, |ui| {
1599 let all_text_styles = ui.style().text_styles();
1600 for style in all_text_styles {
1601 let text =
1602 crate::RichText::new(style.to_string()).text_style(style.clone());
1603 ui.selectable_value(drag_value_text_style, style, text);
1604 }
1605 });
1606 ui.end_row();
1607
1608 ui.label("Text Wrap Mode");
1609 crate::ComboBox::from_id_salt("text_wrap_mode")
1610 .selected_text(format!("{wrap_mode:?}"))
1611 .show_ui(ui, |ui| {
1612 let all_wrap_mode: Vec<Option<TextWrapMode>> = vec![
1613 None,
1614 Some(TextWrapMode::Extend),
1615 Some(TextWrapMode::Wrap),
1616 Some(TextWrapMode::Truncate),
1617 ];
1618 for style in all_wrap_mode {
1619 let text = crate::RichText::new(format!("{style:?}"));
1620 ui.selectable_value(wrap_mode, style, text);
1621 }
1622 });
1623 ui.end_row();
1624
1625 ui.label("Animation duration");
1626 ui.add(
1627 DragValue::new(animation_time)
1628 .range(0.0..=1.0)
1629 .speed(0.02)
1630 .suffix(" s"),
1631 );
1632 ui.end_row();
1633 });
1634
1635 ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles));
1636 ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
1637 ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
1638 ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
1639 ui.collapsing("🔄 Scroll Animation", |ui| scroll_animation.ui(ui));
1640
1641 #[cfg(debug_assertions)]
1642 ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
1643
1644 ui.checkbox(explanation_tooltips, "Explanation tooltips")
1645 .on_hover_text(
1646 "Show explanatory text when hovering DragValue:s and other egui widgets",
1647 );
1648
1649 ui.checkbox(url_in_tooltip, "Show url when hovering links");
1650
1651 ui.checkbox(always_scroll_the_only_direction, "Always scroll the only enabled direction")
1652 .on_hover_text(
1653 "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift",
1654 );
1655
1656 ui.vertical_centered(|ui| reset_button(ui, self, "Reset style"));
1657 }
1658}
1659
1660fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
1661 ui.vertical(|ui| {
1662 crate::Grid::new("text_styles").show(ui, |ui| {
1663 for (text_style, font_id) in &mut *text_styles {
1664 ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
1665 crate::introspection::font_id_ui(ui, font_id);
1666 ui.end_row();
1667 }
1668 });
1669 crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles());
1670 })
1671 .response
1672}
1673
1674impl Spacing {
1675 pub fn ui(&mut self, ui: &mut crate::Ui) {
1676 let Self {
1677 item_spacing,
1678 window_margin,
1679 menu_margin,
1680 button_padding,
1681 indent,
1682 interact_size,
1683 slider_width,
1684 slider_rail_height,
1685 combo_width,
1686 text_edit_width,
1687 icon_width,
1688 icon_width_inner,
1689 icon_spacing,
1690 default_area_size,
1691 tooltip_width,
1692 menu_width,
1693 menu_spacing,
1694 indent_ends_with_horizontal_line,
1695 combo_height,
1696 scroll,
1697 } = self;
1698
1699 Grid::new("spacing")
1700 .num_columns(2)
1701 .spacing([12.0, 8.0])
1702 .striped(true)
1703 .show(ui, |ui| {
1704 ui.label("Item spacing");
1705 ui.add(two_drag_values(item_spacing, 0.0..=20.0));
1706 ui.end_row();
1707
1708 ui.label("Window margin");
1709 ui.add(window_margin);
1710 ui.end_row();
1711
1712 ui.label("Menu margin");
1713 ui.add(menu_margin);
1714 ui.end_row();
1715
1716 ui.label("Button padding");
1717 ui.add(two_drag_values(button_padding, 0.0..=20.0));
1718 ui.end_row();
1719
1720 ui.label("Interact size")
1721 .on_hover_text("Minimum size of an interactive widget");
1722 ui.add(two_drag_values(interact_size, 4.0..=60.0));
1723 ui.end_row();
1724
1725 ui.label("Indent");
1726 ui.add(DragValue::new(indent).range(0.0..=100.0));
1727 ui.end_row();
1728
1729 ui.label("Slider width");
1730 ui.add(DragValue::new(slider_width).range(0.0..=1000.0));
1731 ui.end_row();
1732
1733 ui.label("Slider rail height");
1734 ui.add(DragValue::new(slider_rail_height).range(0.0..=50.0));
1735 ui.end_row();
1736
1737 ui.label("ComboBox width");
1738 ui.add(DragValue::new(combo_width).range(0.0..=1000.0));
1739 ui.end_row();
1740
1741 ui.label("Default area size");
1742 ui.add(two_drag_values(default_area_size, 0.0..=1000.0));
1743 ui.end_row();
1744
1745 ui.label("TextEdit width");
1746 ui.add(DragValue::new(text_edit_width).range(0.0..=1000.0));
1747 ui.end_row();
1748
1749 ui.label("Tooltip wrap width");
1750 ui.add(DragValue::new(tooltip_width).range(0.0..=1000.0));
1751 ui.end_row();
1752
1753 ui.label("Default menu width");
1754 ui.add(DragValue::new(menu_width).range(0.0..=1000.0));
1755 ui.end_row();
1756
1757 ui.label("Menu spacing")
1758 .on_hover_text("Horizontal spacing between menus");
1759 ui.add(DragValue::new(menu_spacing).range(0.0..=10.0));
1760 ui.end_row();
1761
1762 ui.label("Checkboxes etc");
1763 ui.vertical(|ui| {
1764 ui.add(
1765 DragValue::new(icon_width)
1766 .prefix("outer icon width:")
1767 .range(0.0..=60.0),
1768 );
1769 ui.add(
1770 DragValue::new(icon_width_inner)
1771 .prefix("inner icon width:")
1772 .range(0.0..=60.0),
1773 );
1774 ui.add(
1775 DragValue::new(icon_spacing)
1776 .prefix("spacing:")
1777 .range(0.0..=10.0),
1778 );
1779 });
1780 ui.end_row();
1781 });
1782
1783 ui.checkbox(
1784 indent_ends_with_horizontal_line,
1785 "End indented regions with a horizontal separator",
1786 );
1787
1788 ui.horizontal(|ui| {
1789 ui.label("Max height of a combo box");
1790 ui.add(DragValue::new(combo_height).range(0.0..=1000.0));
1791 });
1792
1793 ui.collapsing("Scroll Area", |ui| {
1794 scroll.ui(ui);
1795 });
1796
1797 ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing"));
1798 }
1799}
1800
1801impl Interaction {
1802 pub fn ui(&mut self, ui: &mut crate::Ui) {
1803 let Self {
1804 interact_radius,
1805 resize_grab_radius_side,
1806 resize_grab_radius_corner,
1807 show_tooltips_only_when_still,
1808 tooltip_delay,
1809 tooltip_grace_time,
1810 selectable_labels,
1811 multi_widget_text_select,
1812 } = self;
1813
1814 ui.spacing_mut().item_spacing = vec2(12.0, 8.0);
1815
1816 Grid::new("interaction")
1817 .num_columns(2)
1818 .striped(true)
1819 .show(ui, |ui| {
1820 ui.label("interact_radius")
1821 .on_hover_text("Interact with the closest widget within this radius.");
1822 ui.add(DragValue::new(interact_radius).range(0.0..=20.0));
1823 ui.end_row();
1824
1825 ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize");
1826 ui.add(DragValue::new(resize_grab_radius_side).range(0.0..=20.0));
1827 ui.end_row();
1828
1829 ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize.");
1830 ui.add(DragValue::new(resize_grab_radius_corner).range(0.0..=20.0));
1831 ui.end_row();
1832
1833 ui.label("Tooltip delay").on_hover_text(
1834 "Delay in seconds before showing tooltips after the mouse stops moving",
1835 );
1836 ui.add(
1837 DragValue::new(tooltip_delay)
1838 .range(0.0..=1.0)
1839 .speed(0.05)
1840 .suffix(" s"),
1841 );
1842 ui.end_row();
1843
1844 ui.label("Tooltip grace time").on_hover_text(
1845 "If a tooltip is open and you hover another widget within this grace period, show the next tooltip right away",
1846 );
1847 ui.add(
1848 DragValue::new(tooltip_grace_time)
1849 .range(0.0..=1.0)
1850 .speed(0.05)
1851 .suffix(" s"),
1852 );
1853 ui.end_row();
1854 });
1855
1856 ui.checkbox(
1857 show_tooltips_only_when_still,
1858 "Only show tooltips if mouse is still",
1859 );
1860
1861 ui.horizontal(|ui| {
1862 ui.checkbox(selectable_labels, "Selectable text in labels");
1863 if *selectable_labels {
1864 ui.checkbox(multi_widget_text_select, "Across multiple labels");
1865 }
1866 });
1867
1868 ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings"));
1869 }
1870}
1871
1872impl Widgets {
1873 pub fn ui(&mut self, ui: &mut crate::Ui) {
1874 let Self {
1875 active,
1876 hovered,
1877 inactive,
1878 noninteractive,
1879 open,
1880 } = self;
1881
1882 ui.collapsing("Noninteractive", |ui| {
1883 ui.label(
1884 "The style of a widget that you cannot interact with, e.g. labels and separators.",
1885 );
1886 noninteractive.ui(ui);
1887 });
1888 ui.collapsing("Interactive but inactive", |ui| {
1889 ui.label("The style of an interactive widget, such as a button, at rest.");
1890 inactive.ui(ui);
1891 });
1892 ui.collapsing("Interactive and hovered", |ui| {
1893 ui.label("The style of an interactive widget while you hover it.");
1894 hovered.ui(ui);
1895 });
1896 ui.collapsing("Interactive and active", |ui| {
1897 ui.label("The style of an interactive widget as you are clicking or dragging it.");
1898 active.ui(ui);
1899 });
1900 ui.collapsing("Open menu", |ui| {
1901 ui.label("The style of an open combo-box or menu button");
1902 open.ui(ui);
1903 });
1904
1905 }
1907}
1908
1909impl Selection {
1910 pub fn ui(&mut self, ui: &mut crate::Ui) {
1911 let Self { bg_fill, stroke } = self;
1912 ui.label("Selectable labels");
1913
1914 Grid::new("selectiom").num_columns(2).show(ui, |ui| {
1915 ui.label("Background fill");
1916 ui.color_edit_button_srgba(bg_fill);
1917 ui.end_row();
1918
1919 ui.label("Stroke");
1920 ui.add(stroke);
1921 ui.end_row();
1922 });
1923 }
1924}
1925
1926impl WidgetVisuals {
1927 pub fn ui(&mut self, ui: &mut crate::Ui) {
1928 let Self {
1929 weak_bg_fill,
1930 bg_fill: mandatory_bg_fill,
1931 bg_stroke,
1932 corner_radius,
1933 fg_stroke,
1934 expansion,
1935 } = self;
1936
1937 Grid::new("widget")
1938 .num_columns(2)
1939 .spacing([12.0, 8.0])
1940 .striped(true)
1941 .show(ui, |ui| {
1942 ui.label("Optional background fill")
1943 .on_hover_text("For buttons, combo-boxes, etc");
1944 ui.color_edit_button_srgba(weak_bg_fill);
1945 ui.end_row();
1946
1947 ui.label("Mandatory background fill")
1948 .on_hover_text("For checkboxes, sliders, etc");
1949 ui.color_edit_button_srgba(mandatory_bg_fill);
1950 ui.end_row();
1951
1952 ui.label("Background stroke");
1953 ui.add(bg_stroke);
1954 ui.end_row();
1955
1956 ui.label("Corner radius");
1957 ui.add(corner_radius);
1958 ui.end_row();
1959
1960 ui.label("Foreground stroke (text)");
1961 ui.add(fg_stroke);
1962 ui.end_row();
1963
1964 ui.label("Expansion")
1965 .on_hover_text("make shapes this much larger");
1966 ui.add(DragValue::new(expansion).speed(0.1));
1967 ui.end_row();
1968 });
1969 }
1970}
1971
1972impl Visuals {
1973 pub fn ui(&mut self, ui: &mut crate::Ui) {
1974 let Self {
1975 dark_mode: _,
1976 override_text_color: _,
1977 widgets,
1978 selection,
1979 hyperlink_color,
1980 faint_bg_color,
1981 extreme_bg_color,
1982 code_bg_color,
1983 warn_fg_color,
1984 error_fg_color,
1985
1986 window_corner_radius,
1987 window_shadow,
1988 window_fill,
1989 window_stroke,
1990 window_highlight_topmost,
1991
1992 menu_corner_radius,
1993
1994 panel_fill,
1995
1996 popup_shadow,
1997
1998 resize_corner_size,
1999
2000 text_cursor,
2001
2002 clip_rect_margin,
2003 button_frame,
2004 collapsing_header_frame,
2005 indent_has_left_vline,
2006
2007 striped,
2008
2009 slider_trailing_fill,
2010 handle_shape,
2011 interact_cursor,
2012
2013 image_loading_spinners,
2014
2015 numeric_color_space,
2016 } = self;
2017
2018 ui.collapsing("Background Colors", |ui| {
2019 ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
2020 ui_color(ui, window_fill, "Windows");
2021 ui_color(ui, panel_fill, "Panels");
2022 ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
2023 "Used for faint accentuation of interactive things, like striped grids.",
2024 );
2025 ui_color(ui, extreme_bg_color, "Extreme")
2026 .on_hover_text("Background of plots and paintings");
2027 });
2028
2029 ui.collapsing("Text color", |ui| {
2030 ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
2031 ui_text_color(
2032 ui,
2033 &mut widgets.inactive.fg_stroke.color,
2034 "Unhovered button",
2035 );
2036 ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
2037 ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
2038
2039 ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
2040 ui_text_color(ui, error_fg_color, RichText::new("Errors"));
2041
2042 ui_text_color(ui, hyperlink_color, "hyperlink_color");
2043
2044 ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(
2045 |ui| {
2046 ui.horizontal(|ui| {
2047 ui.spacing_mut().item_spacing.x = 0.0;
2048 ui.label("For monospaced inlined text ");
2049 ui.code("like this");
2050 ui.label(".");
2051 });
2052 },
2053 );
2054 });
2055
2056 ui.collapsing("Text cursor", |ui| {
2057 text_cursor.ui(ui);
2058 });
2059
2060 ui.collapsing("Window", |ui| {
2061 Grid::new("window")
2062 .num_columns(2)
2063 .spacing([12.0, 8.0])
2064 .striped(true)
2065 .show(ui, |ui| {
2066 ui.label("Fill");
2067 ui.color_edit_button_srgba(window_fill);
2068 ui.end_row();
2069
2070 ui.label("Stroke");
2071 ui.add(window_stroke);
2072 ui.end_row();
2073
2074 ui.label("Corner radius");
2075 ui.add(window_corner_radius);
2076 ui.end_row();
2077
2078 ui.label("Shadow");
2079 ui.add(window_shadow);
2080 ui.end_row();
2081 });
2082
2083 ui.checkbox(window_highlight_topmost, "Highlight topmost Window");
2084 });
2085
2086 ui.collapsing("Menus and popups", |ui| {
2087 Grid::new("menus_and_popups")
2088 .num_columns(2)
2089 .spacing([12.0, 8.0])
2090 .striped(true)
2091 .show(ui, |ui| {
2092 ui.label("Corner radius");
2093 ui.add(menu_corner_radius);
2094 ui.end_row();
2095
2096 ui.label("Shadow");
2097 ui.add(popup_shadow);
2098 ui.end_row();
2099 });
2100 });
2101
2102 ui.collapsing("Widgets", |ui| widgets.ui(ui));
2103 ui.collapsing("Selection", |ui| selection.ui(ui));
2104
2105 ui.collapsing("Misc", |ui| {
2106 ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
2107 ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
2108
2109 ui.checkbox(button_frame, "Button has a frame");
2110 ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
2111 ui.checkbox(
2112 indent_has_left_vline,
2113 "Paint a vertical line to the left of indented regions",
2114 );
2115
2116 ui.checkbox(striped, "Default stripes on grids and tables");
2117
2118 ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
2119
2120 handle_shape.ui(ui);
2121
2122 ComboBox::from_label("Interact cursor")
2123 .selected_text(
2124 interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")),
2125 )
2126 .show_ui(ui, |ui| {
2127 ui.selectable_value(interact_cursor, None, "-");
2128
2129 for cursor in CursorIcon::ALL {
2130 ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}"))
2131 .on_hover_cursor(cursor);
2132 }
2133 })
2134 .response
2135 .on_hover_text("Use this cursor when hovering buttons etc");
2136
2137 ui.checkbox(image_loading_spinners, "Image loading spinners")
2138 .on_hover_text("Show a spinner when an Image is loading");
2139
2140 ui.horizontal(|ui| {
2141 ui.label("Color picker type");
2142 numeric_color_space.toggle_button_ui(ui);
2143 });
2144 });
2145
2146 ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals"));
2147 }
2148}
2149
2150impl TextCursorStyle {
2151 fn ui(&mut self, ui: &mut Ui) {
2152 let Self {
2153 stroke,
2154 preview,
2155 blink,
2156 on_duration,
2157 off_duration,
2158 } = self;
2159
2160 ui.horizontal(|ui| {
2161 ui.label("Stroke");
2162 ui.add(stroke);
2163 });
2164
2165 ui.checkbox(preview, "Preview text cursor on hover");
2166
2167 ui.checkbox(blink, "Blink");
2168
2169 if *blink {
2170 Grid::new("cursor_blink").show(ui, |ui| {
2171 ui.label("On time");
2172 ui.add(
2173 DragValue::new(on_duration)
2174 .speed(0.1)
2175 .range(0.0..=2.0)
2176 .suffix(" s"),
2177 );
2178 ui.end_row();
2179
2180 ui.label("Off time");
2181 ui.add(
2182 DragValue::new(off_duration)
2183 .speed(0.1)
2184 .range(0.0..=2.0)
2185 .suffix(" s"),
2186 );
2187 ui.end_row();
2188 });
2189 }
2190 }
2191}
2192
2193#[cfg(debug_assertions)]
2194impl DebugOptions {
2195 pub fn ui(&mut self, ui: &mut crate::Ui) {
2196 let Self {
2197 debug_on_hover,
2198 debug_on_hover_with_all_modifiers,
2199 hover_shows_next,
2200 show_expand_width,
2201 show_expand_height,
2202 show_resize,
2203 show_interactive_widgets,
2204 show_widget_hits,
2205 show_unaligned,
2206 } = self;
2207
2208 {
2209 ui.checkbox(debug_on_hover, "Show widget info on hover.");
2210 ui.checkbox(
2211 debug_on_hover_with_all_modifiers,
2212 "Show widget info on hover if holding all modifier keys",
2213 );
2214
2215 ui.checkbox(hover_shows_next, "Show next widget placement on hover");
2216 }
2217
2218 ui.checkbox(
2219 show_expand_width,
2220 "Show which widgets make their parent wider",
2221 );
2222 ui.checkbox(
2223 show_expand_height,
2224 "Show which widgets make their parent higher",
2225 );
2226 ui.checkbox(show_resize, "Debug Resize");
2227
2228 ui.checkbox(
2229 show_interactive_widgets,
2230 "Show an overlay on all interactive widgets",
2231 );
2232
2233 ui.checkbox(show_widget_hits, "Show widgets under mouse pointer");
2234
2235 ui.checkbox(
2236 show_unaligned,
2237 "Show rectangles not aligned to integer point coordinates",
2238 );
2239
2240 ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options"));
2241 }
2242}
2243
2244fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive<f32>) -> impl Widget + '_ {
2246 move |ui: &mut crate::Ui| {
2247 ui.horizontal(|ui| {
2248 ui.add(
2249 DragValue::new(&mut value.x)
2250 .range(range.clone())
2251 .prefix("x: "),
2252 );
2253 ui.add(
2254 DragValue::new(&mut value.y)
2255 .range(range.clone())
2256 .prefix("y: "),
2257 );
2258 })
2259 .response
2260 }
2261}
2262
2263fn ui_color(ui: &mut Ui, color: &mut Color32, label: impl Into<WidgetText>) -> Response {
2264 ui.horizontal(|ui| {
2265 ui.color_edit_button_srgba(color);
2266 ui.label(label);
2267 })
2268 .response
2269}
2270
2271fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) -> Response {
2272 ui.horizontal(|ui| {
2273 ui.color_edit_button_srgba(color);
2274 ui.label(label.into().color(*color));
2275 })
2276 .response
2277}
2278
2279impl HandleShape {
2280 pub fn ui(&mut self, ui: &mut Ui) {
2281 ui.horizontal(|ui| {
2282 ui.label("Slider handle");
2283 ui.radio_value(self, Self::Circle, "Circle");
2284 if ui
2285 .radio(matches!(self, Self::Rect { .. }), "Rectangle")
2286 .clicked()
2287 {
2288 *self = Self::Rect { aspect_ratio: 0.5 };
2289 }
2290 if let Self::Rect { aspect_ratio } = self {
2291 ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio"));
2292 }
2293 });
2294 }
2295}
2296
2297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2299#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
2300pub enum NumericColorSpace {
2301 GammaByte,
2305
2306 Linear,
2308 }
2310
2311impl NumericColorSpace {
2312 pub fn toggle_button_ui(&mut self, ui: &mut Ui) -> crate::Response {
2313 let tooltip = match self {
2314 Self::GammaByte => "Showing color values in 0-255 gamma space",
2315 Self::Linear => "Showing color values in 0-1 linear space",
2316 };
2317
2318 let mut response = ui.button(self.to_string()).on_hover_text(tooltip);
2319 if response.clicked() {
2320 *self = match self {
2321 Self::GammaByte => Self::Linear,
2322 Self::Linear => Self::GammaByte,
2323 };
2324 response.mark_changed();
2325 }
2326 response
2327 }
2328}
2329
2330impl std::fmt::Display for NumericColorSpace {
2331 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2332 match self {
2333 Self::GammaByte => write!(f, "U8"),
2334 Self::Linear => write!(f, "F"),
2335 }
2336 }
2337}
2338
2339impl Widget for &mut Margin {
2340 fn ui(self, ui: &mut Ui) -> Response {
2341 let mut same = self.is_same();
2342
2343 let response = if same {
2344 ui.horizontal(|ui| {
2345 ui.checkbox(&mut same, "same");
2346
2347 let mut value = self.left;
2348 ui.add(DragValue::new(&mut value).range(0.0..=100.0));
2349 *self = Margin::same(value);
2350 })
2351 .response
2352 } else {
2353 ui.vertical(|ui| {
2354 ui.checkbox(&mut same, "same");
2355
2356 crate::Grid::new("margin").num_columns(2).show(ui, |ui| {
2357 ui.label("Left");
2358 ui.add(DragValue::new(&mut self.left).range(0.0..=100.0));
2359 ui.end_row();
2360
2361 ui.label("Right");
2362 ui.add(DragValue::new(&mut self.right).range(0.0..=100.0));
2363 ui.end_row();
2364
2365 ui.label("Top");
2366 ui.add(DragValue::new(&mut self.top).range(0.0..=100.0));
2367 ui.end_row();
2368
2369 ui.label("Bottom");
2370 ui.add(DragValue::new(&mut self.bottom).range(0.0..=100.0));
2371 ui.end_row();
2372 });
2373 })
2374 .response
2375 };
2376
2377 if same {
2379 *self =
2380 Margin::from((self.leftf() + self.rightf() + self.topf() + self.bottomf()) / 4.0);
2381 } else {
2382 if self.is_same() {
2384 if self.right == i8::MAX {
2385 self.right = i8::MAX - 1;
2386 } else {
2387 self.right += 1;
2388 }
2389 }
2390 }
2391
2392 response
2393 }
2394}
2395
2396impl Widget for &mut CornerRadius {
2397 fn ui(self, ui: &mut Ui) -> Response {
2398 let mut same = self.is_same();
2399
2400 let response = if same {
2401 ui.horizontal(|ui| {
2402 ui.checkbox(&mut same, "same");
2403
2404 let mut cr = self.nw;
2405 ui.add(DragValue::new(&mut cr).range(0.0..=f32::INFINITY));
2406 *self = CornerRadius::same(cr);
2407 })
2408 .response
2409 } else {
2410 ui.vertical(|ui| {
2411 ui.checkbox(&mut same, "same");
2412
2413 crate::Grid::new("Corner radius")
2414 .num_columns(2)
2415 .show(ui, |ui| {
2416 ui.label("NW");
2417 ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
2418 ui.end_row();
2419
2420 ui.label("NE");
2421 ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
2422 ui.end_row();
2423
2424 ui.label("SW");
2425 ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
2426 ui.end_row();
2427
2428 ui.label("SE");
2429 ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
2430 ui.end_row();
2431 });
2432 })
2433 .response
2434 };
2435
2436 if same {
2438 *self = CornerRadius::from(self.average());
2439 } else {
2440 if self.is_same() {
2442 if self.average() == 0.0 {
2443 self.se = 1;
2444 } else {
2445 self.se -= 1;
2446 }
2447 }
2448 }
2449
2450 response
2451 }
2452}
2453
2454impl Widget for &mut Shadow {
2455 fn ui(self, ui: &mut Ui) -> Response {
2456 let epaint::Shadow {
2457 offset,
2458 blur,
2459 spread,
2460 color,
2461 } = self;
2462
2463 ui.vertical(|ui| {
2464 crate::Grid::new("shadow_ui").show(ui, |ui| {
2465 ui.add(
2466 DragValue::new(&mut offset[0])
2467 .speed(1.0)
2468 .range(-100.0..=100.0)
2469 .prefix("x: "),
2470 );
2471 ui.add(
2472 DragValue::new(&mut offset[1])
2473 .speed(1.0)
2474 .range(-100.0..=100.0)
2475 .prefix("y: "),
2476 );
2477 ui.end_row();
2478
2479 ui.add(
2480 DragValue::new(blur)
2481 .speed(1.0)
2482 .range(0.0..=100.0)
2483 .prefix("blur: "),
2484 );
2485
2486 ui.add(
2487 DragValue::new(spread)
2488 .speed(1.0)
2489 .range(0.0..=100.0)
2490 .prefix("spread: "),
2491 );
2492 });
2493 ui.color_edit_button_srgba(color);
2494 })
2495 .response
2496 }
2497}
2498
2499impl Widget for &mut Stroke {
2500 fn ui(self, ui: &mut Ui) -> Response {
2501 let Stroke { width, color } = self;
2502
2503 ui.horizontal(|ui| {
2504 ui.add(DragValue::new(width).speed(0.1).range(0.0..=f32::INFINITY))
2505 .on_hover_text("Width");
2506 ui.color_edit_button_srgba(color);
2507
2508 let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size);
2510 let left = stroke_rect.left_center();
2511 let right = stroke_rect.right_center();
2512 ui.painter().line_segment([left, right], (*width, *color));
2513 })
2514 .response
2515 }
2516}
2517
2518impl Widget for &mut crate::Frame {
2519 fn ui(self, ui: &mut Ui) -> Response {
2520 let crate::Frame {
2521 inner_margin,
2522 outer_margin,
2523 corner_radius,
2524 shadow,
2525 fill,
2526 stroke,
2527 } = self;
2528
2529 crate::Grid::new("frame")
2530 .num_columns(2)
2531 .spacing([12.0, 8.0])
2532 .striped(true)
2533 .show(ui, |ui| {
2534 ui.label("Inner margin");
2535 ui.add(inner_margin);
2536 ui.end_row();
2537
2538 ui.label("Outer margin");
2539 ui.push_id("outer", |ui| ui.add(outer_margin));
2541 ui.end_row();
2542
2543 ui.label("Corner radius");
2544 ui.add(corner_radius);
2545 ui.end_row();
2546
2547 ui.label("Shadow");
2548 ui.add(shadow);
2549 ui.end_row();
2550
2551 ui.label("Fill");
2552 ui.color_edit_button_srgba(fill);
2553 ui.end_row();
2554
2555 ui.label("Stroke");
2556 ui.add(stroke);
2557 ui.end_row();
2558 })
2559 .response
2560 }
2561}
2562
2563impl Widget for &mut FontTweak {
2564 fn ui(self, ui: &mut Ui) -> Response {
2565 let original: FontTweak = *self;
2566
2567 let mut response = Grid::new("font_tweak")
2568 .num_columns(2)
2569 .show(ui, |ui| {
2570 let FontTweak {
2571 scale,
2572 y_offset_factor,
2573 y_offset,
2574 baseline_offset_factor,
2575 } = self;
2576
2577 ui.label("Scale");
2578 let speed = *scale * 0.01;
2579 ui.add(DragValue::new(scale).range(0.01..=10.0).speed(speed));
2580 ui.end_row();
2581
2582 ui.label("y_offset_factor");
2583 ui.add(DragValue::new(y_offset_factor).speed(-0.0025));
2584 ui.end_row();
2585
2586 ui.label("y_offset");
2587 ui.add(DragValue::new(y_offset).speed(-0.02));
2588 ui.end_row();
2589
2590 ui.label("baseline_offset_factor");
2591 ui.add(DragValue::new(baseline_offset_factor).speed(-0.0025));
2592 ui.end_row();
2593
2594 if ui.button("Reset").clicked() {
2595 *self = Default::default();
2596 }
2597 })
2598 .response;
2599
2600 if *self != original {
2601 response.mark_changed();
2602 }
2603
2604 response
2605 }
2606}