1#![allow(clippy::if_same_then_else)]
4
5use emath::Align;
6use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak};
7use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
8
9use crate::{
10 ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response, RichText, TextWrapMode,
11 WidgetText,
12 ecolor::Color32,
13 emath::{Rangef, Rect, Vec2, pos2, vec2},
14 reset_button_with,
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
121#[derive(Debug, Clone)]
125pub enum FontSelection {
126 Default,
129
130 FontId(FontId),
132
133 Style(TextStyle),
135}
136
137impl Default for FontSelection {
138 #[inline]
139 fn default() -> Self {
140 Self::Default
141 }
142}
143
144impl FontSelection {
145 pub fn resolve(self, style: &Style) -> FontId {
150 self.resolve_with_fallback(style, TextStyle::Body.into())
151 }
152
153 pub fn resolve_with_fallback(self, style: &Style, fallback: Self) -> FontId {
157 match self {
158 Self::Default => {
159 if let Some(override_font_id) = &style.override_font_id {
160 override_font_id.clone()
161 } else if let Some(text_style) = &style.override_text_style {
162 text_style.resolve(style)
163 } else {
164 fallback.resolve(style)
165 }
166 }
167 Self::FontId(font_id) => font_id,
168 Self::Style(text_style) => text_style.resolve(style),
169 }
170 }
171}
172
173impl From<FontId> for FontSelection {
174 #[inline(always)]
175 fn from(font_id: FontId) -> Self {
176 Self::FontId(font_id)
177 }
178}
179
180impl From<TextStyle> for FontSelection {
181 #[inline(always)]
182 fn from(text_style: TextStyle) -> Self {
183 Self::Style(text_style)
184 }
185}
186
187#[derive(Clone, Default)]
192pub struct StyleModifier(Option<Arc<dyn Fn(&mut Style) + Send + Sync>>);
193
194impl std::fmt::Debug for StyleModifier {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 f.write_str("StyleModifier")
197 }
198}
199
200impl<T> From<T> for StyleModifier
201where
202 T: Fn(&mut Style) + Send + Sync + 'static,
203{
204 fn from(f: T) -> Self {
205 Self(Some(Arc::new(f)))
206 }
207}
208
209impl From<Style> for StyleModifier {
210 fn from(style: Style) -> Self {
211 Self(Some(Arc::new(move |s| *s = style.clone())))
212 }
213}
214
215impl StyleModifier {
216 pub fn new(f: impl Fn(&mut Style) + Send + Sync + 'static) -> Self {
218 Self::from(f)
219 }
220
221 pub fn apply(&self, style: &mut Style) {
224 if let Some(f) = &self.0 {
225 f(style);
226 }
227 }
228}
229
230#[derive(Clone, Debug, PartialEq)]
240#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
241#[cfg_attr(feature = "serde", serde(default))]
242pub struct Style {
243 pub override_text_style: Option<TextStyle>,
248
249 pub override_font_id: Option<FontId>,
254
255 pub override_text_valign: Option<Align>,
259
260 pub text_styles: BTreeMap<TextStyle, FontId>,
288
289 pub drag_value_text_style: TextStyle,
291
292 #[cfg_attr(feature = "serde", serde(skip))]
296 pub number_formatter: NumberFormatter,
297
298 #[deprecated = "Use wrap_mode instead"]
307 pub wrap: Option<bool>,
308
309 pub wrap_mode: Option<crate::TextWrapMode>,
316
317 pub spacing: Spacing,
319
320 pub interaction: Interaction,
322
323 pub visuals: Visuals,
325
326 pub animation_time: f32,
328
329 #[cfg(debug_assertions)]
333 pub debug: DebugOptions,
334
335 pub explanation_tooltips: bool,
339
340 pub url_in_tooltip: bool,
342
343 pub always_scroll_the_only_direction: bool,
345
346 pub scroll_animation: ScrollAnimation,
348
349 pub compact_menu_style: bool,
351}
352
353#[test]
354fn style_impl_send_sync() {
355 fn assert_send_sync<T: Send + Sync>() {}
356 assert_send_sync::<Style>();
357}
358
359impl Style {
360 pub fn interact(&self, response: &Response) -> &WidgetVisuals {
365 self.visuals.widgets.style(response)
366 }
367
368 pub fn interact_selectable(&self, response: &Response, selected: bool) -> WidgetVisuals {
369 let mut visuals = *self.visuals.widgets.style(response);
370 if selected {
371 visuals.weak_bg_fill = self.visuals.selection.bg_fill;
372 visuals.bg_fill = self.visuals.selection.bg_fill;
373 visuals.fg_stroke = self.visuals.selection.stroke;
375 }
376 visuals
377 }
378
379 pub fn noninteractive(&self) -> &WidgetVisuals {
381 &self.visuals.widgets.noninteractive
382 }
383
384 pub fn text_styles(&self) -> Vec<TextStyle> {
386 self.text_styles.keys().cloned().collect()
387 }
388}
389
390#[derive(Clone, Debug, PartialEq)]
392#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
393#[cfg_attr(feature = "serde", serde(default))]
394pub struct Spacing {
395 pub item_spacing: Vec2,
402
403 pub window_margin: Margin,
405
406 pub button_padding: Vec2,
408
409 pub menu_margin: Margin,
411
412 pub indent: f32,
414
415 pub interact_size: Vec2, pub slider_width: f32,
422
423 pub slider_rail_height: f32,
425
426 pub combo_width: f32,
428
429 pub text_edit_width: f32,
431
432 pub icon_width: f32,
435
436 pub icon_width_inner: f32,
439
440 pub icon_spacing: f32,
443
444 pub default_area_size: Vec2,
452
453 pub tooltip_width: f32,
455
456 pub menu_width: f32,
460
461 pub menu_spacing: f32,
463
464 pub indent_ends_with_horizontal_line: bool,
466
467 pub combo_height: f32,
469
470 pub scroll: ScrollStyle,
472}
473
474impl Spacing {
475 pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) {
477 let icon_width = self.icon_width;
478 let big_icon_rect = Rect::from_center_size(
479 pos2(rect.left() + icon_width / 2.0, rect.center().y),
480 vec2(icon_width, icon_width),
481 );
482
483 let small_icon_rect =
484 Rect::from_center_size(big_icon_rect.center(), Vec2::splat(self.icon_width_inner));
485
486 (small_icon_rect, big_icon_rect)
487 }
488}
489
490#[derive(Clone, Copy, Debug, PartialEq)]
499#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
500#[cfg_attr(feature = "serde", serde(default))]
501pub struct ScrollStyle {
502 pub floating: bool,
510
511 pub bar_width: f32,
513
514 pub handle_min_length: f32,
516
517 pub bar_inner_margin: f32,
519
520 pub bar_outer_margin: f32,
523
524 pub floating_width: f32,
528
529 pub floating_allocated_width: f32,
536
537 pub foreground_color: bool,
539
540 pub dormant_background_opacity: f32,
546
547 pub active_background_opacity: f32,
553
554 pub interact_background_opacity: f32,
560
561 pub dormant_handle_opacity: f32,
567
568 pub active_handle_opacity: f32,
574
575 pub interact_handle_opacity: f32,
581}
582
583impl Default for ScrollStyle {
584 fn default() -> Self {
585 Self::floating()
586 }
587}
588
589impl ScrollStyle {
590 pub fn solid() -> Self {
592 Self {
593 floating: false,
594 bar_width: 6.0,
595 handle_min_length: 12.0,
596 bar_inner_margin: 4.0,
597 bar_outer_margin: 0.0,
598 floating_width: 2.0,
599 floating_allocated_width: 0.0,
600
601 foreground_color: false,
602
603 dormant_background_opacity: 0.0,
604 active_background_opacity: 0.4,
605 interact_background_opacity: 0.7,
606
607 dormant_handle_opacity: 0.0,
608 active_handle_opacity: 0.6,
609 interact_handle_opacity: 1.0,
610 }
611 }
612
613 pub fn thin() -> Self {
615 Self {
616 floating: true,
617 bar_width: 10.0,
618 floating_allocated_width: 6.0,
619 foreground_color: false,
620
621 dormant_background_opacity: 1.0,
622 dormant_handle_opacity: 1.0,
623
624 active_background_opacity: 1.0,
625 active_handle_opacity: 1.0,
626
627 interact_background_opacity: 0.6,
629 interact_handle_opacity: 0.6,
630
631 ..Self::solid()
632 }
633 }
634
635 pub fn floating() -> Self {
639 Self {
640 floating: true,
641 bar_width: 10.0,
642 foreground_color: true,
643 floating_allocated_width: 0.0,
644 dormant_background_opacity: 0.0,
645 dormant_handle_opacity: 0.0,
646 ..Self::solid()
647 }
648 }
649
650 pub fn allocated_width(&self) -> f32 {
652 if self.floating {
653 self.floating_allocated_width
654 } else {
655 self.bar_inner_margin + self.bar_width + self.bar_outer_margin
656 }
657 }
658
659 pub fn ui(&mut self, ui: &mut Ui) {
660 ui.horizontal(|ui| {
661 ui.label("Presets:");
662 ui.selectable_value(self, Self::solid(), "Solid");
663 ui.selectable_value(self, Self::thin(), "Thin");
664 ui.selectable_value(self, Self::floating(), "Floating");
665 });
666
667 ui.collapsing("Details", |ui| {
668 self.details_ui(ui);
669 });
670 }
671
672 pub fn details_ui(&mut self, ui: &mut Ui) {
673 let Self {
674 floating,
675 bar_width,
676 handle_min_length,
677 bar_inner_margin,
678 bar_outer_margin,
679 floating_width,
680 floating_allocated_width,
681
682 foreground_color,
683
684 dormant_background_opacity,
685 active_background_opacity,
686 interact_background_opacity,
687 dormant_handle_opacity,
688 active_handle_opacity,
689 interact_handle_opacity,
690 } = self;
691
692 ui.horizontal(|ui| {
693 ui.label("Type:");
694 ui.selectable_value(floating, false, "Solid");
695 ui.selectable_value(floating, true, "Floating");
696 });
697
698 ui.horizontal(|ui| {
699 ui.add(DragValue::new(bar_width).range(0.0..=32.0));
700 ui.label("Full bar width");
701 });
702 if *floating {
703 ui.horizontal(|ui| {
704 ui.add(DragValue::new(floating_width).range(0.0..=32.0));
705 ui.label("Thin bar width");
706 });
707 ui.horizontal(|ui| {
708 ui.add(DragValue::new(floating_allocated_width).range(0.0..=32.0));
709 ui.label("Allocated width");
710 });
711 }
712
713 ui.horizontal(|ui| {
714 ui.add(DragValue::new(handle_min_length).range(0.0..=32.0));
715 ui.label("Minimum handle length");
716 });
717 ui.horizontal(|ui| {
718 ui.add(DragValue::new(bar_outer_margin).range(0.0..=32.0));
719 ui.label("Outer margin");
720 });
721
722 ui.horizontal(|ui| {
723 ui.label("Color:");
724 ui.selectable_value(foreground_color, false, "Background");
725 ui.selectable_value(foreground_color, true, "Foreground");
726 });
727
728 if *floating {
729 crate::Grid::new("opacity").show(ui, |ui| {
730 fn opacity_ui(ui: &mut Ui, opacity: &mut f32) {
731 ui.add(DragValue::new(opacity).speed(0.01).range(0.0..=1.0));
732 }
733
734 ui.label("Opacity");
735 ui.label("Dormant");
736 ui.label("Active");
737 ui.label("Interacting");
738 ui.end_row();
739
740 ui.label("Background:");
741 opacity_ui(ui, dormant_background_opacity);
742 opacity_ui(ui, active_background_opacity);
743 opacity_ui(ui, interact_background_opacity);
744 ui.end_row();
745
746 ui.label("Handle:");
747 opacity_ui(ui, dormant_handle_opacity);
748 opacity_ui(ui, active_handle_opacity);
749 opacity_ui(ui, interact_handle_opacity);
750 ui.end_row();
751 });
752 } else {
753 ui.horizontal(|ui| {
754 ui.add(DragValue::new(bar_inner_margin).range(0.0..=32.0));
755 ui.label("Inner margin");
756 });
757 }
758 }
759}
760
761#[derive(Copy, Clone, Debug, PartialEq)]
768#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
769#[cfg_attr(feature = "serde", serde(default))]
770pub struct ScrollAnimation {
771 pub points_per_second: f32,
773
774 pub duration: Rangef,
776}
777
778impl Default for ScrollAnimation {
779 fn default() -> Self {
780 Self {
781 points_per_second: 1000.0,
782 duration: Rangef::new(0.1, 0.3),
783 }
784 }
785}
786
787impl ScrollAnimation {
788 pub fn new(points_per_second: f32, duration: Rangef) -> Self {
790 Self {
791 points_per_second,
792 duration,
793 }
794 }
795
796 pub fn none() -> Self {
798 Self {
799 points_per_second: f32::INFINITY,
800 duration: Rangef::new(0.0, 0.0),
801 }
802 }
803
804 pub fn duration(t: f32) -> Self {
806 Self {
807 points_per_second: f32::INFINITY,
808 duration: Rangef::new(t, t),
809 }
810 }
811
812 pub fn ui(&mut self, ui: &mut crate::Ui) {
813 crate::Grid::new("scroll_animation").show(ui, |ui| {
814 ui.label("Scroll animation:");
815 ui.add(
816 DragValue::new(&mut self.points_per_second)
817 .speed(100.0)
818 .range(0.0..=5000.0),
819 );
820 ui.label("points/second");
821 ui.end_row();
822
823 ui.label("Min duration:");
824 ui.add(
825 DragValue::new(&mut self.duration.min)
826 .speed(0.01)
827 .range(0.0..=self.duration.max),
828 );
829 ui.label("seconds");
830 ui.end_row();
831
832 ui.label("Max duration:");
833 ui.add(
834 DragValue::new(&mut self.duration.max)
835 .speed(0.01)
836 .range(0.0..=1.0),
837 );
838 ui.label("seconds");
839 ui.end_row();
840 });
841 }
842}
843
844#[derive(Clone, Debug, PartialEq)]
848#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
849#[cfg_attr(feature = "serde", serde(default))]
850pub struct Interaction {
851 pub interact_radius: f32,
856
857 pub resize_grab_radius_side: f32,
859
860 pub resize_grab_radius_corner: f32,
862
863 pub show_tooltips_only_when_still: bool,
865
866 pub tooltip_delay: f32,
868
869 pub tooltip_grace_time: f32,
875
876 pub selectable_labels: bool,
878
879 pub multi_widget_text_select: bool,
884}
885
886#[derive(Clone, Debug, PartialEq)]
888#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
889#[cfg_attr(feature = "serde", serde(default))]
890pub struct TextCursorStyle {
891 pub stroke: Stroke,
893
894 pub preview: bool,
896
897 pub blink: bool,
899
900 pub on_duration: f32,
902
903 pub off_duration: f32,
905}
906
907impl Default for TextCursorStyle {
908 fn default() -> Self {
909 Self {
910 stroke: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), preview: false,
912 blink: true,
913 on_duration: 0.5,
914 off_duration: 0.5,
915 }
916 }
917}
918
919#[derive(Clone, Debug, PartialEq)]
926#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
927#[cfg_attr(feature = "serde", serde(default))]
928pub struct Visuals {
929 pub dark_mode: bool,
935
936 pub text_alpha_from_coverage: AlphaFromCoverage,
938
939 pub override_text_color: Option<Color32>,
953
954 pub weak_text_alpha: f32,
958
959 pub weak_text_color: Option<Color32>,
964
965 pub widgets: Widgets,
967
968 pub selection: Selection,
969
970 pub hyperlink_color: Color32,
972
973 pub faint_bg_color: Color32,
976
977 pub extreme_bg_color: Color32,
981
982 pub text_edit_bg_color: Option<Color32>,
986
987 pub code_bg_color: Color32,
989
990 pub warn_fg_color: Color32,
992
993 pub error_fg_color: Color32,
995
996 pub window_corner_radius: CornerRadius,
997 pub window_shadow: Shadow,
998 pub window_fill: Color32,
999 pub window_stroke: Stroke,
1000
1001 pub window_highlight_topmost: bool,
1003
1004 pub menu_corner_radius: CornerRadius,
1005
1006 pub panel_fill: Color32,
1008
1009 pub popup_shadow: Shadow,
1010
1011 pub resize_corner_size: f32,
1012
1013 pub text_cursor: TextCursorStyle,
1015
1016 pub clip_rect_margin: f32,
1018
1019 pub button_frame: bool,
1021
1022 pub collapsing_header_frame: bool,
1024
1025 pub indent_has_left_vline: bool,
1027
1028 pub striped: bool,
1031
1032 pub slider_trailing_fill: bool,
1036
1037 pub handle_shape: HandleShape,
1041
1042 pub interact_cursor: Option<CursorIcon>,
1048
1049 pub image_loading_spinners: bool,
1051
1052 pub numeric_color_space: NumericColorSpace,
1054
1055 pub disabled_alpha: f32,
1057}
1058
1059impl Visuals {
1060 #[inline(always)]
1061 pub fn noninteractive(&self) -> &WidgetVisuals {
1062 &self.widgets.noninteractive
1063 }
1064
1065 pub fn text_color(&self) -> Color32 {
1067 self.override_text_color
1068 .unwrap_or_else(|| self.widgets.noninteractive.text_color())
1069 }
1070
1071 pub fn weak_text_color(&self) -> Color32 {
1072 self.weak_text_color
1073 .unwrap_or_else(|| self.text_color().gamma_multiply(self.weak_text_alpha))
1074 }
1075
1076 #[inline(always)]
1077 pub fn strong_text_color(&self) -> Color32 {
1078 self.widgets.active.text_color()
1079 }
1080
1081 pub fn text_edit_bg_color(&self) -> Color32 {
1083 self.text_edit_bg_color.unwrap_or(self.extreme_bg_color)
1084 }
1085
1086 #[inline(always)]
1088 pub fn window_fill(&self) -> Color32 {
1089 self.window_fill
1090 }
1091
1092 #[inline(always)]
1093 pub fn window_stroke(&self) -> Stroke {
1094 self.window_stroke
1095 }
1096
1097 #[inline(always)]
1099 #[deprecated = "Use disabled_alpha(). Fading is now handled by modifying the alpha channel."]
1100 pub fn fade_out_to_color(&self) -> Color32 {
1101 self.widgets.noninteractive.weak_bg_fill
1102 }
1103
1104 #[inline(always)]
1106 pub fn disabled_alpha(&self) -> f32 {
1107 self.disabled_alpha
1108 }
1109
1110 #[inline(always)]
1115 pub fn disable(&self, color: Color32) -> Color32 {
1116 color.gamma_multiply(self.disabled_alpha())
1117 }
1118
1119 #[doc(alias = "grey_out")]
1121 #[inline(always)]
1122 pub fn gray_out(&self, color: Color32) -> Color32 {
1123 crate::ecolor::tint_color_towards(color, self.widgets.noninteractive.weak_bg_fill)
1124 }
1125}
1126
1127#[derive(Clone, Copy, Debug, PartialEq)]
1129#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1130#[cfg_attr(feature = "serde", serde(default))]
1131pub struct Selection {
1132 pub bg_fill: Color32,
1133 pub stroke: Stroke,
1134}
1135
1136#[derive(Clone, Copy, Debug, PartialEq)]
1138#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1139pub enum HandleShape {
1140 Circle,
1142
1143 Rect {
1145 aspect_ratio: f32,
1147 },
1148}
1149
1150#[derive(Clone, Debug, PartialEq)]
1152#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1153#[cfg_attr(feature = "serde", serde(default))]
1154pub struct Widgets {
1155 pub noninteractive: WidgetVisuals,
1160
1161 pub inactive: WidgetVisuals,
1163
1164 pub hovered: WidgetVisuals,
1168
1169 pub active: WidgetVisuals,
1171
1172 pub open: WidgetVisuals,
1174}
1175
1176impl Widgets {
1177 pub fn style(&self, response: &Response) -> &WidgetVisuals {
1178 if !response.sense.interactive() {
1179 &self.noninteractive
1180 } else if response.is_pointer_button_down_on() || response.has_focus() || response.clicked()
1181 {
1182 &self.active
1183 } else if response.hovered() || response.highlighted() {
1184 &self.hovered
1185 } else {
1186 &self.inactive
1187 }
1188 }
1189}
1190
1191#[derive(Clone, Copy, Debug, PartialEq)]
1193#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1194pub struct WidgetVisuals {
1195 pub bg_fill: Color32,
1200
1201 pub weak_bg_fill: Color32,
1205
1206 pub bg_stroke: Stroke,
1210
1211 pub corner_radius: CornerRadius,
1213
1214 pub fg_stroke: Stroke,
1216
1217 pub expansion: f32,
1219}
1220
1221impl WidgetVisuals {
1222 #[inline(always)]
1223 pub fn text_color(&self) -> Color32 {
1224 self.fg_stroke.color
1225 }
1226
1227 #[deprecated = "Renamed to corner_radius"]
1228 pub fn rounding(&self) -> CornerRadius {
1229 self.corner_radius
1230 }
1231}
1232
1233#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1235#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1236#[cfg(debug_assertions)]
1237pub struct DebugOptions {
1238 #[cfg(debug_assertions)]
1246 pub debug_on_hover: bool,
1247
1248 #[cfg(debug_assertions)]
1258 pub debug_on_hover_with_all_modifiers: bool,
1259
1260 #[cfg(debug_assertions)]
1262 pub hover_shows_next: bool,
1263
1264 pub show_expand_width: bool,
1266
1267 pub show_expand_height: bool,
1269
1270 pub show_resize: bool,
1271
1272 pub show_interactive_widgets: bool,
1274
1275 pub show_widget_hits: bool,
1277
1278 pub show_unaligned: bool,
1282}
1283
1284#[cfg(debug_assertions)]
1285impl Default for DebugOptions {
1286 fn default() -> Self {
1287 Self {
1288 debug_on_hover: false,
1289 debug_on_hover_with_all_modifiers: cfg!(feature = "callstack")
1290 && !cfg!(target_arch = "wasm32"),
1291 hover_shows_next: false,
1292 show_expand_width: false,
1293 show_expand_height: false,
1294 show_resize: false,
1295 show_interactive_widgets: false,
1296 show_widget_hits: false,
1297 show_unaligned: cfg!(debug_assertions),
1298 }
1299 }
1300}
1301
1302pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
1306 use FontFamily::{Monospace, Proportional};
1307
1308 [
1309 (TextStyle::Small, FontId::new(9.0, Proportional)),
1310 (TextStyle::Body, FontId::new(12.5, Proportional)),
1311 (TextStyle::Button, FontId::new(12.5, Proportional)),
1312 (TextStyle::Heading, FontId::new(18.0, Proportional)),
1313 (TextStyle::Monospace, FontId::new(12.0, Monospace)),
1314 ]
1315 .into()
1316}
1317
1318impl Default for Style {
1319 fn default() -> Self {
1320 #[expect(deprecated)]
1321 Self {
1322 override_font_id: None,
1323 override_text_style: None,
1324 override_text_valign: Some(Align::Center),
1325 text_styles: default_text_styles(),
1326 drag_value_text_style: TextStyle::Button,
1327 number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
1328 wrap: None,
1329 wrap_mode: None,
1330 spacing: Spacing::default(),
1331 interaction: Interaction::default(),
1332 visuals: Visuals::default(),
1333 animation_time: 1.0 / 12.0,
1334 #[cfg(debug_assertions)]
1335 debug: Default::default(),
1336 explanation_tooltips: false,
1337 url_in_tooltip: false,
1338 always_scroll_the_only_direction: false,
1339 scroll_animation: ScrollAnimation::default(),
1340 compact_menu_style: true,
1341 }
1342 }
1343}
1344
1345impl Default for Spacing {
1346 fn default() -> Self {
1347 Self {
1348 item_spacing: vec2(8.0, 3.0),
1349 window_margin: Margin::same(6),
1350 menu_margin: Margin::same(6),
1351 button_padding: vec2(4.0, 1.0),
1352 indent: 18.0, interact_size: vec2(40.0, 18.0),
1354 slider_width: 100.0,
1355 slider_rail_height: 8.0,
1356 combo_width: 100.0,
1357 text_edit_width: 280.0,
1358 icon_width: 14.0,
1359 icon_width_inner: 8.0,
1360 icon_spacing: 4.0,
1361 default_area_size: vec2(600.0, 400.0),
1362 tooltip_width: 500.0,
1363 menu_width: 400.0,
1364 menu_spacing: 2.0,
1365 combo_height: 200.0,
1366 scroll: Default::default(),
1367 indent_ends_with_horizontal_line: false,
1368 }
1369 }
1370}
1371
1372impl Default for Interaction {
1373 fn default() -> Self {
1374 Self {
1375 interact_radius: 5.0,
1376 resize_grab_radius_side: 5.0,
1377 resize_grab_radius_corner: 10.0,
1378 show_tooltips_only_when_still: true,
1379 tooltip_delay: 0.5,
1380 tooltip_grace_time: 0.2,
1381 selectable_labels: true,
1382 multi_widget_text_select: true,
1383 }
1384 }
1385}
1386
1387impl Visuals {
1388 pub fn dark() -> Self {
1390 Self {
1391 dark_mode: true,
1392 text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
1393 override_text_color: None,
1394 weak_text_alpha: 0.6,
1395 weak_text_color: None,
1396 widgets: Widgets::default(),
1397 selection: Selection::default(),
1398 hyperlink_color: Color32::from_rgb(90, 170, 255),
1399 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(10), text_edit_bg_color: None, code_bg_color: Color32::from_gray(64),
1403 warn_fg_color: Color32::from_rgb(255, 143, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_corner_radius: CornerRadius::same(6),
1407 window_shadow: Shadow {
1408 offset: [10, 20],
1409 blur: 15,
1410 spread: 0,
1411 color: Color32::from_black_alpha(96),
1412 },
1413 window_fill: Color32::from_gray(27),
1414 window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1415 window_highlight_topmost: true,
1416
1417 menu_corner_radius: CornerRadius::same(6),
1418
1419 panel_fill: Color32::from_gray(27),
1420
1421 popup_shadow: Shadow {
1422 offset: [6, 10],
1423 blur: 8,
1424 spread: 0,
1425 color: Color32::from_black_alpha(96),
1426 },
1427
1428 resize_corner_size: 12.0,
1429
1430 text_cursor: Default::default(),
1431
1432 clip_rect_margin: 3.0, button_frame: true,
1434 collapsing_header_frame: false,
1435 indent_has_left_vline: true,
1436
1437 striped: false,
1438
1439 slider_trailing_fill: false,
1440 handle_shape: HandleShape::Circle,
1441
1442 interact_cursor: None,
1443
1444 image_loading_spinners: true,
1445
1446 numeric_color_space: NumericColorSpace::GammaByte,
1447 disabled_alpha: 0.5,
1448 }
1449 }
1450
1451 pub fn light() -> Self {
1453 Self {
1454 dark_mode: false,
1455 text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
1456 widgets: Widgets::light(),
1457 selection: Selection::light(),
1458 hyperlink_color: Color32::from_rgb(0, 155, 255),
1459 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(255), code_bg_color: Color32::from_gray(230),
1462 warn_fg_color: Color32::from_rgb(255, 100, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_shadow: Shadow {
1466 offset: [10, 20],
1467 blur: 15,
1468 spread: 0,
1469 color: Color32::from_black_alpha(25),
1470 },
1471 window_fill: Color32::from_gray(248),
1472 window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
1473
1474 panel_fill: Color32::from_gray(248),
1475
1476 popup_shadow: Shadow {
1477 offset: [6, 10],
1478 blur: 8,
1479 spread: 0,
1480 color: Color32::from_black_alpha(25),
1481 },
1482
1483 text_cursor: TextCursorStyle {
1484 stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
1485 ..Default::default()
1486 },
1487
1488 ..Self::dark()
1489 }
1490 }
1491}
1492
1493impl Default for Visuals {
1494 fn default() -> Self {
1495 Self::dark()
1496 }
1497}
1498
1499impl Selection {
1500 fn dark() -> Self {
1501 Self {
1502 bg_fill: Color32::from_rgb(0, 92, 128),
1503 stroke: Stroke::new(1.0, Color32::from_rgb(192, 222, 255)),
1504 }
1505 }
1506
1507 fn light() -> Self {
1508 Self {
1509 bg_fill: Color32::from_rgb(144, 209, 255),
1510 stroke: Stroke::new(1.0, Color32::from_rgb(0, 83, 125)),
1511 }
1512 }
1513}
1514
1515impl Default for Selection {
1516 fn default() -> Self {
1517 Self::dark()
1518 }
1519}
1520
1521impl Widgets {
1522 pub fn dark() -> Self {
1523 Self {
1524 noninteractive: WidgetVisuals {
1525 weak_bg_fill: Color32::from_gray(27),
1526 bg_fill: Color32::from_gray(27),
1527 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),
1530 expansion: 0.0,
1531 },
1532 inactive: WidgetVisuals {
1533 weak_bg_fill: Color32::from_gray(60), bg_fill: Color32::from_gray(60), bg_stroke: Default::default(),
1536 fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), corner_radius: CornerRadius::same(2),
1538 expansion: 0.0,
1539 },
1540 hovered: WidgetVisuals {
1541 weak_bg_fill: Color32::from_gray(70),
1542 bg_fill: Color32::from_gray(70),
1543 bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
1545 corner_radius: CornerRadius::same(3),
1546 expansion: 1.0,
1547 },
1548 active: WidgetVisuals {
1549 weak_bg_fill: Color32::from_gray(55),
1550 bg_fill: Color32::from_gray(55),
1551 bg_stroke: Stroke::new(1.0, Color32::WHITE),
1552 fg_stroke: Stroke::new(2.0, Color32::WHITE),
1553 corner_radius: CornerRadius::same(2),
1554 expansion: 1.0,
1555 },
1556 open: WidgetVisuals {
1557 weak_bg_fill: Color32::from_gray(45),
1558 bg_fill: Color32::from_gray(27),
1559 bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1560 fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
1561 corner_radius: CornerRadius::same(2),
1562 expansion: 0.0,
1563 },
1564 }
1565 }
1566
1567 pub fn light() -> Self {
1568 Self {
1569 noninteractive: WidgetVisuals {
1570 weak_bg_fill: Color32::from_gray(248),
1571 bg_fill: Color32::from_gray(248),
1572 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),
1575 expansion: 0.0,
1576 },
1577 inactive: WidgetVisuals {
1578 weak_bg_fill: Color32::from_gray(230), bg_fill: Color32::from_gray(230), bg_stroke: Default::default(),
1581 fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), corner_radius: CornerRadius::same(2),
1583 expansion: 0.0,
1584 },
1585 hovered: WidgetVisuals {
1586 weak_bg_fill: Color32::from_gray(220),
1587 bg_fill: Color32::from_gray(220),
1588 bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), fg_stroke: Stroke::new(1.5, Color32::BLACK),
1590 corner_radius: CornerRadius::same(3),
1591 expansion: 1.0,
1592 },
1593 active: WidgetVisuals {
1594 weak_bg_fill: Color32::from_gray(165),
1595 bg_fill: Color32::from_gray(165),
1596 bg_stroke: Stroke::new(1.0, Color32::BLACK),
1597 fg_stroke: Stroke::new(2.0, Color32::BLACK),
1598 corner_radius: CornerRadius::same(2),
1599 expansion: 1.0,
1600 },
1601 open: WidgetVisuals {
1602 weak_bg_fill: Color32::from_gray(220),
1603 bg_fill: Color32::from_gray(220),
1604 bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
1605 fg_stroke: Stroke::new(1.0, Color32::BLACK),
1606 corner_radius: CornerRadius::same(2),
1607 expansion: 0.0,
1608 },
1609 }
1610 }
1611}
1612
1613impl Default for Widgets {
1614 fn default() -> Self {
1615 Self::dark()
1616 }
1617}
1618
1619use crate::{
1622 Ui,
1623 widgets::{DragValue, Slider, Widget, reset_button},
1624};
1625
1626impl Style {
1627 pub fn ui(&mut self, ui: &mut crate::Ui) {
1628 #[expect(deprecated)]
1629 let Self {
1630 override_font_id,
1631 override_text_style,
1632 override_text_valign,
1633 text_styles,
1634 drag_value_text_style,
1635 number_formatter: _, wrap: _,
1637 wrap_mode,
1638 spacing,
1639 interaction,
1640 visuals,
1641 animation_time,
1642 #[cfg(debug_assertions)]
1643 debug,
1644 explanation_tooltips,
1645 url_in_tooltip,
1646 always_scroll_the_only_direction,
1647 scroll_animation,
1648 compact_menu_style,
1649 } = self;
1650
1651 crate::Grid::new("_options").show(ui, |ui| {
1652 ui.label("Override font id");
1653 ui.vertical(|ui| {
1654 ui.horizontal(|ui| {
1655 ui.radio_value(override_font_id, None, "None");
1656 if ui.radio(override_font_id.is_some(), "override").clicked() {
1657 *override_font_id = Some(FontId::default());
1658 }
1659 });
1660 if let Some(override_font_id) = override_font_id {
1661 crate::introspection::font_id_ui(ui, override_font_id);
1662 }
1663 });
1664 ui.end_row();
1665
1666 ui.label("Override text style");
1667 crate::ComboBox::from_id_salt("override_text_style")
1668 .selected_text(match override_text_style {
1669 None => "None".to_owned(),
1670 Some(override_text_style) => override_text_style.to_string(),
1671 })
1672 .show_ui(ui, |ui| {
1673 ui.selectable_value(override_text_style, None, "None");
1674 let all_text_styles = ui.style().text_styles();
1675 for style in all_text_styles {
1676 let text =
1677 crate::RichText::new(style.to_string()).text_style(style.clone());
1678 ui.selectable_value(override_text_style, Some(style), text);
1679 }
1680 });
1681 ui.end_row();
1682
1683 fn valign_name(valign: Align) -> &'static str {
1684 match valign {
1685 Align::TOP => "Top",
1686 Align::Center => "Center",
1687 Align::BOTTOM => "Bottom",
1688 }
1689 }
1690
1691 ui.label("Override text valign");
1692 crate::ComboBox::from_id_salt("override_text_valign")
1693 .selected_text(match override_text_valign {
1694 None => "None",
1695 Some(override_text_valign) => valign_name(*override_text_valign),
1696 })
1697 .show_ui(ui, |ui| {
1698 ui.selectable_value(override_text_valign, None, "None");
1699 for align in [Align::TOP, Align::Center, Align::BOTTOM] {
1700 ui.selectable_value(override_text_valign, Some(align), valign_name(align));
1701 }
1702 });
1703 ui.end_row();
1704
1705 ui.label("Text style of DragValue");
1706 crate::ComboBox::from_id_salt("drag_value_text_style")
1707 .selected_text(drag_value_text_style.to_string())
1708 .show_ui(ui, |ui| {
1709 let all_text_styles = ui.style().text_styles();
1710 for style in all_text_styles {
1711 let text =
1712 crate::RichText::new(style.to_string()).text_style(style.clone());
1713 ui.selectable_value(drag_value_text_style, style, text);
1714 }
1715 });
1716 ui.end_row();
1717
1718 ui.label("Text Wrap Mode");
1719 crate::ComboBox::from_id_salt("text_wrap_mode")
1720 .selected_text(format!("{wrap_mode:?}"))
1721 .show_ui(ui, |ui| {
1722 let all_wrap_mode: Vec<Option<TextWrapMode>> = vec![
1723 None,
1724 Some(TextWrapMode::Extend),
1725 Some(TextWrapMode::Wrap),
1726 Some(TextWrapMode::Truncate),
1727 ];
1728 for style in all_wrap_mode {
1729 let text = crate::RichText::new(format!("{style:?}"));
1730 ui.selectable_value(wrap_mode, style, text);
1731 }
1732 });
1733 ui.end_row();
1734
1735 ui.label("Animation duration");
1736 ui.add(
1737 DragValue::new(animation_time)
1738 .range(0.0..=1.0)
1739 .speed(0.02)
1740 .suffix(" s"),
1741 );
1742 ui.end_row();
1743 });
1744
1745 ui.collapsing("🔠 Text styles", |ui| text_styles_ui(ui, text_styles));
1746 ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
1747 ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
1748 ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
1749 ui.collapsing("🔄 Scroll animation", |ui| scroll_animation.ui(ui));
1750
1751 #[cfg(debug_assertions)]
1752 ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
1753
1754 ui.checkbox(compact_menu_style, "Compact menu style");
1755
1756 ui.checkbox(explanation_tooltips, "Explanation tooltips")
1757 .on_hover_text(
1758 "Show explanatory text when hovering DragValue:s and other egui widgets",
1759 );
1760
1761 ui.checkbox(url_in_tooltip, "Show url when hovering links");
1762
1763 ui.checkbox(always_scroll_the_only_direction, "Always scroll the only enabled direction")
1764 .on_hover_text(
1765 "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift",
1766 );
1767
1768 ui.vertical_centered(|ui| reset_button(ui, self, "Reset style"));
1769 }
1770}
1771
1772fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
1773 ui.vertical(|ui| {
1774 crate::Grid::new("text_styles").show(ui, |ui| {
1775 for (text_style, font_id) in &mut *text_styles {
1776 ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
1777 crate::introspection::font_id_ui(ui, font_id);
1778 ui.end_row();
1779 }
1780 });
1781 crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles());
1782 })
1783 .response
1784}
1785
1786impl Spacing {
1787 pub fn ui(&mut self, ui: &mut crate::Ui) {
1788 let Self {
1789 item_spacing,
1790 window_margin,
1791 menu_margin,
1792 button_padding,
1793 indent,
1794 interact_size,
1795 slider_width,
1796 slider_rail_height,
1797 combo_width,
1798 text_edit_width,
1799 icon_width,
1800 icon_width_inner,
1801 icon_spacing,
1802 default_area_size,
1803 tooltip_width,
1804 menu_width,
1805 menu_spacing,
1806 indent_ends_with_horizontal_line,
1807 combo_height,
1808 scroll,
1809 } = self;
1810
1811 Grid::new("spacing")
1812 .num_columns(2)
1813 .spacing([12.0, 8.0])
1814 .striped(true)
1815 .show(ui, |ui| {
1816 ui.label("Item spacing");
1817 ui.add(two_drag_values(item_spacing, 0.0..=20.0));
1818 ui.end_row();
1819
1820 ui.label("Window margin");
1821 ui.add(window_margin);
1822 ui.end_row();
1823
1824 ui.label("Menu margin");
1825 ui.add(menu_margin);
1826 ui.end_row();
1827
1828 ui.label("Button padding");
1829 ui.add(two_drag_values(button_padding, 0.0..=20.0));
1830 ui.end_row();
1831
1832 ui.label("Interact size")
1833 .on_hover_text("Minimum size of an interactive widget");
1834 ui.add(two_drag_values(interact_size, 4.0..=60.0));
1835 ui.end_row();
1836
1837 ui.label("Indent");
1838 ui.add(DragValue::new(indent).range(0.0..=100.0));
1839 ui.end_row();
1840
1841 ui.label("Slider width");
1842 ui.add(DragValue::new(slider_width).range(0.0..=1000.0));
1843 ui.end_row();
1844
1845 ui.label("Slider rail height");
1846 ui.add(DragValue::new(slider_rail_height).range(0.0..=50.0));
1847 ui.end_row();
1848
1849 ui.label("ComboBox width");
1850 ui.add(DragValue::new(combo_width).range(0.0..=1000.0));
1851 ui.end_row();
1852
1853 ui.label("Default area size");
1854 ui.add(two_drag_values(default_area_size, 0.0..=1000.0));
1855 ui.end_row();
1856
1857 ui.label("TextEdit width");
1858 ui.add(DragValue::new(text_edit_width).range(0.0..=1000.0));
1859 ui.end_row();
1860
1861 ui.label("Tooltip wrap width");
1862 ui.add(DragValue::new(tooltip_width).range(0.0..=1000.0));
1863 ui.end_row();
1864
1865 ui.label("Default menu width");
1866 ui.add(DragValue::new(menu_width).range(0.0..=1000.0));
1867 ui.end_row();
1868
1869 ui.label("Menu spacing")
1870 .on_hover_text("Horizontal spacing between menus");
1871 ui.add(DragValue::new(menu_spacing).range(0.0..=10.0));
1872 ui.end_row();
1873
1874 ui.label("Checkboxes etc");
1875 ui.vertical(|ui| {
1876 ui.add(
1877 DragValue::new(icon_width)
1878 .prefix("outer icon width:")
1879 .range(0.0..=60.0),
1880 );
1881 ui.add(
1882 DragValue::new(icon_width_inner)
1883 .prefix("inner icon width:")
1884 .range(0.0..=60.0),
1885 );
1886 ui.add(
1887 DragValue::new(icon_spacing)
1888 .prefix("spacing:")
1889 .range(0.0..=10.0),
1890 );
1891 });
1892 ui.end_row();
1893 });
1894
1895 ui.checkbox(
1896 indent_ends_with_horizontal_line,
1897 "End indented regions with a horizontal separator",
1898 );
1899
1900 ui.horizontal(|ui| {
1901 ui.label("Max height of a combo box");
1902 ui.add(DragValue::new(combo_height).range(0.0..=1000.0));
1903 });
1904
1905 ui.collapsing("Scroll Area", |ui| {
1906 scroll.ui(ui);
1907 });
1908
1909 ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing"));
1910 }
1911}
1912
1913impl Interaction {
1914 pub fn ui(&mut self, ui: &mut crate::Ui) {
1915 let Self {
1916 interact_radius,
1917 resize_grab_radius_side,
1918 resize_grab_radius_corner,
1919 show_tooltips_only_when_still,
1920 tooltip_delay,
1921 tooltip_grace_time,
1922 selectable_labels,
1923 multi_widget_text_select,
1924 } = self;
1925
1926 ui.spacing_mut().item_spacing = vec2(12.0, 8.0);
1927
1928 Grid::new("interaction")
1929 .num_columns(2)
1930 .striped(true)
1931 .show(ui, |ui| {
1932 ui.label("interact_radius")
1933 .on_hover_text("Interact with the closest widget within this radius.");
1934 ui.add(DragValue::new(interact_radius).range(0.0..=20.0));
1935 ui.end_row();
1936
1937 ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize");
1938 ui.add(DragValue::new(resize_grab_radius_side).range(0.0..=20.0));
1939 ui.end_row();
1940
1941 ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize.");
1942 ui.add(DragValue::new(resize_grab_radius_corner).range(0.0..=20.0));
1943 ui.end_row();
1944
1945 ui.label("Tooltip delay").on_hover_text(
1946 "Delay in seconds before showing tooltips after the mouse stops moving",
1947 );
1948 ui.add(
1949 DragValue::new(tooltip_delay)
1950 .range(0.0..=1.0)
1951 .speed(0.05)
1952 .suffix(" s"),
1953 );
1954 ui.end_row();
1955
1956 ui.label("Tooltip grace time").on_hover_text(
1957 "If a tooltip is open and you hover another widget within this grace period, show the next tooltip right away",
1958 );
1959 ui.add(
1960 DragValue::new(tooltip_grace_time)
1961 .range(0.0..=1.0)
1962 .speed(0.05)
1963 .suffix(" s"),
1964 );
1965 ui.end_row();
1966 });
1967
1968 ui.checkbox(
1969 show_tooltips_only_when_still,
1970 "Only show tooltips if mouse is still",
1971 );
1972
1973 ui.horizontal(|ui| {
1974 ui.checkbox(selectable_labels, "Selectable text in labels");
1975 if *selectable_labels {
1976 ui.checkbox(multi_widget_text_select, "Across multiple labels");
1977 }
1978 });
1979
1980 ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings"));
1981 }
1982}
1983
1984impl Widgets {
1985 pub fn ui(&mut self, ui: &mut crate::Ui) {
1986 let Self {
1987 active,
1988 hovered,
1989 inactive,
1990 noninteractive,
1991 open,
1992 } = self;
1993
1994 ui.collapsing("Noninteractive", |ui| {
1995 ui.label(
1996 "The style of a widget that you cannot interact with, e.g. labels and separators.",
1997 );
1998 noninteractive.ui(ui);
1999 });
2000 ui.collapsing("Interactive but inactive", |ui| {
2001 ui.label("The style of an interactive widget, such as a button, at rest.");
2002 inactive.ui(ui);
2003 });
2004 ui.collapsing("Interactive and hovered", |ui| {
2005 ui.label("The style of an interactive widget while you hover it.");
2006 hovered.ui(ui);
2007 });
2008 ui.collapsing("Interactive and active", |ui| {
2009 ui.label("The style of an interactive widget as you are clicking or dragging it.");
2010 active.ui(ui);
2011 });
2012 ui.collapsing("Open menu", |ui| {
2013 ui.label("The style of an open combo-box or menu button");
2014 open.ui(ui);
2015 });
2016
2017 }
2019}
2020
2021impl Selection {
2022 pub fn ui(&mut self, ui: &mut crate::Ui) {
2023 let Self { bg_fill, stroke } = self;
2024 ui.label("Selectable labels");
2025
2026 Grid::new("selectiom").num_columns(2).show(ui, |ui| {
2027 ui.label("Background fill");
2028 ui.color_edit_button_srgba(bg_fill);
2029 ui.end_row();
2030
2031 ui.label("Stroke");
2032 ui.add(stroke);
2033 ui.end_row();
2034 });
2035 }
2036}
2037
2038impl WidgetVisuals {
2039 pub fn ui(&mut self, ui: &mut crate::Ui) {
2040 let Self {
2041 weak_bg_fill,
2042 bg_fill: mandatory_bg_fill,
2043 bg_stroke,
2044 corner_radius,
2045 fg_stroke,
2046 expansion,
2047 } = self;
2048
2049 Grid::new("widget")
2050 .num_columns(2)
2051 .spacing([12.0, 8.0])
2052 .striped(true)
2053 .show(ui, |ui| {
2054 ui.label("Optional background fill")
2055 .on_hover_text("For buttons, combo-boxes, etc");
2056 ui.color_edit_button_srgba(weak_bg_fill);
2057 ui.end_row();
2058
2059 ui.label("Mandatory background fill")
2060 .on_hover_text("For checkboxes, sliders, etc");
2061 ui.color_edit_button_srgba(mandatory_bg_fill);
2062 ui.end_row();
2063
2064 ui.label("Background stroke");
2065 ui.add(bg_stroke);
2066 ui.end_row();
2067
2068 ui.label("Corner radius");
2069 ui.add(corner_radius);
2070 ui.end_row();
2071
2072 ui.label("Foreground stroke (text)");
2073 ui.add(fg_stroke);
2074 ui.end_row();
2075
2076 ui.label("Expansion")
2077 .on_hover_text("make shapes this much larger");
2078 ui.add(DragValue::new(expansion).speed(0.1));
2079 ui.end_row();
2080 });
2081 }
2082}
2083
2084impl Visuals {
2085 pub fn ui(&mut self, ui: &mut crate::Ui) {
2086 let Self {
2087 dark_mode,
2088 text_alpha_from_coverage,
2089 override_text_color: _,
2090 weak_text_alpha,
2091 weak_text_color,
2092 widgets,
2093 selection,
2094 hyperlink_color,
2095 faint_bg_color,
2096 extreme_bg_color,
2097 text_edit_bg_color,
2098 code_bg_color,
2099 warn_fg_color,
2100 error_fg_color,
2101
2102 window_corner_radius,
2103 window_shadow,
2104 window_fill,
2105 window_stroke,
2106 window_highlight_topmost,
2107
2108 menu_corner_radius,
2109
2110 panel_fill,
2111
2112 popup_shadow,
2113
2114 resize_corner_size,
2115
2116 text_cursor,
2117
2118 clip_rect_margin,
2119 button_frame,
2120 collapsing_header_frame,
2121 indent_has_left_vline,
2122
2123 striped,
2124
2125 slider_trailing_fill,
2126 handle_shape,
2127 interact_cursor,
2128
2129 image_loading_spinners,
2130
2131 numeric_color_space,
2132 disabled_alpha,
2133 } = self;
2134
2135 fn ui_optional_color(
2136 ui: &mut Ui,
2137 color: &mut Option<Color32>,
2138 default_value: Color32,
2139 label: impl Into<WidgetText>,
2140 ) -> Response {
2141 let label_response = ui.label(label);
2142
2143 ui.horizontal(|ui| {
2144 let mut set = color.is_some();
2145 ui.checkbox(&mut set, "");
2146 if set {
2147 let color = color.get_or_insert(default_value);
2148 ui.color_edit_button_srgba(color);
2149 } else {
2150 *color = None;
2151 };
2152 });
2153
2154 ui.end_row();
2155
2156 label_response
2157 }
2158
2159 ui.collapsing("Background colors", |ui| {
2160 Grid::new("background_colors")
2161 .num_columns(2)
2162 .show(ui, |ui| {
2163 fn ui_color(
2164 ui: &mut Ui,
2165 color: &mut Color32,
2166 label: impl Into<WidgetText>,
2167 ) -> Response {
2168 let label_response = ui.label(label);
2169 ui.color_edit_button_srgba(color);
2170 ui.end_row();
2171 label_response
2172 }
2173
2174 ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
2175 ui_color(ui, window_fill, "Windows");
2176 ui_color(ui, panel_fill, "Panels");
2177 ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
2178 "Used for faint accentuation of interactive things, like striped grids.",
2179 );
2180 ui_color(ui, extreme_bg_color, "Extreme")
2181 .on_hover_text("Background of plots and paintings");
2182
2183 ui_optional_color(ui, text_edit_bg_color, *extreme_bg_color, "TextEdit")
2184 .on_hover_text("Background of TextEdit");
2185 });
2186 });
2187
2188 ui.collapsing("Text color", |ui| {
2189 fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
2190 ui.label(label.into().color(*color));
2191 ui.color_edit_button_srgba(color);
2192 ui.end_row();
2193 }
2194
2195 Grid::new("text_color").num_columns(2).show(ui, |ui| {
2196 ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
2197
2198 ui_text_color(
2199 ui,
2200 &mut widgets.inactive.fg_stroke.color,
2201 "Unhovered button",
2202 );
2203 ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
2204 ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
2205
2206 ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
2207 ui_text_color(ui, error_fg_color, RichText::new("Errors"));
2208
2209 ui_text_color(ui, hyperlink_color, "hyperlink_color");
2210
2211 ui.label(RichText::new("Code background").code())
2212 .on_hover_ui(|ui| {
2213 ui.horizontal(|ui| {
2214 ui.spacing_mut().item_spacing.x = 0.0;
2215 ui.label("For monospaced inlined text ");
2216 ui.code("like this");
2217 ui.label(".");
2218 });
2219 });
2220 ui.color_edit_button_srgba(code_bg_color);
2221 ui.end_row();
2222
2223 ui.label("Weak text alpha");
2224 ui.add_enabled(
2225 weak_text_color.is_none(),
2226 DragValue::new(weak_text_alpha).speed(0.01).range(0.0..=1.0),
2227 );
2228 ui.end_row();
2229
2230 ui_optional_color(
2231 ui,
2232 weak_text_color,
2233 widgets.noninteractive.text_color(),
2234 "Weak text color",
2235 );
2236 });
2237
2238 ui.add_space(4.0);
2239
2240 text_alpha_from_coverage_ui(ui, text_alpha_from_coverage);
2241 });
2242
2243 ui.collapsing("Text cursor", |ui| {
2244 text_cursor.ui(ui);
2245 });
2246
2247 ui.collapsing("Window", |ui| {
2248 Grid::new("window")
2249 .num_columns(2)
2250 .spacing([12.0, 8.0])
2251 .striped(true)
2252 .show(ui, |ui| {
2253 ui.label("Fill");
2254 ui.color_edit_button_srgba(window_fill);
2255 ui.end_row();
2256
2257 ui.label("Stroke");
2258 ui.add(window_stroke);
2259 ui.end_row();
2260
2261 ui.label("Corner radius");
2262 ui.add(window_corner_radius);
2263 ui.end_row();
2264
2265 ui.label("Shadow");
2266 ui.add(window_shadow);
2267 ui.end_row();
2268 });
2269
2270 ui.checkbox(window_highlight_topmost, "Highlight topmost Window");
2271 });
2272
2273 ui.collapsing("Menus and popups", |ui| {
2274 Grid::new("menus_and_popups")
2275 .num_columns(2)
2276 .spacing([12.0, 8.0])
2277 .striped(true)
2278 .show(ui, |ui| {
2279 ui.label("Corner radius");
2280 ui.add(menu_corner_radius);
2281 ui.end_row();
2282
2283 ui.label("Shadow");
2284 ui.add(popup_shadow);
2285 ui.end_row();
2286 });
2287 });
2288
2289 ui.collapsing("Widgets", |ui| widgets.ui(ui));
2290 ui.collapsing("Selection", |ui| selection.ui(ui));
2291
2292 ui.collapsing("Misc", |ui| {
2293 ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
2294 ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
2295
2296 ui.checkbox(button_frame, "Button has a frame");
2297 ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
2298 ui.checkbox(
2299 indent_has_left_vline,
2300 "Paint a vertical line to the left of indented regions",
2301 );
2302
2303 ui.checkbox(striped, "Default stripes on grids and tables");
2304
2305 ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
2306
2307 handle_shape.ui(ui);
2308
2309 ComboBox::from_label("Interact cursor")
2310 .selected_text(
2311 interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")),
2312 )
2313 .show_ui(ui, |ui| {
2314 ui.selectable_value(interact_cursor, None, "-");
2315
2316 for cursor in CursorIcon::ALL {
2317 ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}"))
2318 .on_hover_cursor(cursor);
2319 }
2320 })
2321 .response
2322 .on_hover_text("Use this cursor when hovering buttons etc");
2323
2324 ui.checkbox(image_loading_spinners, "Image loading spinners")
2325 .on_hover_text("Show a spinner when an Image is loading");
2326
2327 ui.horizontal(|ui| {
2328 ui.label("Color picker type");
2329 numeric_color_space.toggle_button_ui(ui);
2330 });
2331
2332 ui.add(Slider::new(disabled_alpha, 0.0..=1.0).text("Disabled element alpha"));
2333 });
2334
2335 let dark_mode = *dark_mode;
2336 ui.vertical_centered(|ui| {
2337 reset_button_with(
2338 ui,
2339 self,
2340 "Reset visuals",
2341 if dark_mode {
2342 Self::dark()
2343 } else {
2344 Self::light()
2345 },
2346 );
2347 });
2348 }
2349}
2350
2351fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) {
2352 let mut dark_mode_special =
2353 *text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
2354
2355 ui.horizontal(|ui| {
2356 ui.label("Text rendering:");
2357
2358 ui.checkbox(&mut dark_mode_special, "Dark-mode special");
2359
2360 if dark_mode_special {
2361 *text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq;
2362 } else {
2363 let mut gamma = match text_alpha_from_coverage {
2364 AlphaFromCoverage::Linear => 1.0,
2365 AlphaFromCoverage::Gamma(gamma) => *gamma,
2366 AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, };
2368
2369 ui.add(
2370 DragValue::new(&mut gamma)
2371 .speed(0.01)
2372 .range(0.1..=4.0)
2373 .prefix("Gamma: "),
2374 );
2375
2376 if gamma == 1.0 {
2377 *text_alpha_from_coverage = AlphaFromCoverage::Linear;
2378 } else {
2379 *text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
2380 }
2381 }
2382 });
2383}
2384
2385impl TextCursorStyle {
2386 fn ui(&mut self, ui: &mut Ui) {
2387 let Self {
2388 stroke,
2389 preview,
2390 blink,
2391 on_duration,
2392 off_duration,
2393 } = self;
2394
2395 ui.horizontal(|ui| {
2396 ui.label("Stroke");
2397 ui.add(stroke);
2398 });
2399
2400 ui.checkbox(preview, "Preview text cursor on hover");
2401
2402 ui.checkbox(blink, "Blink");
2403
2404 if *blink {
2405 Grid::new("cursor_blink").show(ui, |ui| {
2406 ui.label("On time");
2407 ui.add(
2408 DragValue::new(on_duration)
2409 .speed(0.1)
2410 .range(0.0..=2.0)
2411 .suffix(" s"),
2412 );
2413 ui.end_row();
2414
2415 ui.label("Off time");
2416 ui.add(
2417 DragValue::new(off_duration)
2418 .speed(0.1)
2419 .range(0.0..=2.0)
2420 .suffix(" s"),
2421 );
2422 ui.end_row();
2423 });
2424 }
2425 }
2426}
2427
2428#[cfg(debug_assertions)]
2429impl DebugOptions {
2430 pub fn ui(&mut self, ui: &mut crate::Ui) {
2431 let Self {
2432 debug_on_hover,
2433 debug_on_hover_with_all_modifiers,
2434 hover_shows_next,
2435 show_expand_width,
2436 show_expand_height,
2437 show_resize,
2438 show_interactive_widgets,
2439 show_widget_hits,
2440 show_unaligned,
2441 } = self;
2442
2443 {
2444 ui.checkbox(debug_on_hover, "Show widget info on hover.");
2445 ui.checkbox(
2446 debug_on_hover_with_all_modifiers,
2447 "Show widget info on hover if holding all modifier keys",
2448 );
2449
2450 ui.checkbox(hover_shows_next, "Show next widget placement on hover");
2451 }
2452
2453 ui.checkbox(
2454 show_expand_width,
2455 "Show which widgets make their parent wider",
2456 );
2457 ui.checkbox(
2458 show_expand_height,
2459 "Show which widgets make their parent higher",
2460 );
2461 ui.checkbox(show_resize, "Debug Resize");
2462
2463 ui.checkbox(
2464 show_interactive_widgets,
2465 "Show an overlay on all interactive widgets",
2466 );
2467
2468 ui.checkbox(show_widget_hits, "Show widgets under mouse pointer");
2469
2470 ui.checkbox(
2471 show_unaligned,
2472 "Show rectangles not aligned to integer point coordinates",
2473 );
2474
2475 ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options"));
2476 }
2477}
2478
2479fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive<f32>) -> impl Widget + '_ {
2481 move |ui: &mut crate::Ui| {
2482 ui.horizontal(|ui| {
2483 ui.add(
2484 DragValue::new(&mut value.x)
2485 .range(range.clone())
2486 .prefix("x: "),
2487 );
2488 ui.add(
2489 DragValue::new(&mut value.y)
2490 .range(range.clone())
2491 .prefix("y: "),
2492 );
2493 })
2494 .response
2495 }
2496}
2497
2498impl HandleShape {
2499 pub fn ui(&mut self, ui: &mut Ui) {
2500 ui.horizontal(|ui| {
2501 ui.label("Slider handle");
2502 ui.radio_value(self, Self::Circle, "Circle");
2503 if ui
2504 .radio(matches!(self, Self::Rect { .. }), "Rectangle")
2505 .clicked()
2506 {
2507 *self = Self::Rect { aspect_ratio: 0.5 };
2508 }
2509 if let Self::Rect { aspect_ratio } = self {
2510 ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio"));
2511 }
2512 });
2513 }
2514}
2515
2516#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2518#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
2519pub enum NumericColorSpace {
2520 GammaByte,
2524
2525 Linear,
2527 }
2529
2530impl NumericColorSpace {
2531 pub fn toggle_button_ui(&mut self, ui: &mut Ui) -> crate::Response {
2532 let tooltip = match self {
2533 Self::GammaByte => "Showing color values in 0-255 gamma space",
2534 Self::Linear => "Showing color values in 0-1 linear space",
2535 };
2536
2537 let mut response = ui.button(self.to_string()).on_hover_text(tooltip);
2538 if response.clicked() {
2539 *self = match self {
2540 Self::GammaByte => Self::Linear,
2541 Self::Linear => Self::GammaByte,
2542 };
2543 response.mark_changed();
2544 }
2545 response
2546 }
2547}
2548
2549impl std::fmt::Display for NumericColorSpace {
2550 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2551 match self {
2552 Self::GammaByte => write!(f, "U8"),
2553 Self::Linear => write!(f, "F"),
2554 }
2555 }
2556}
2557
2558impl Widget for &mut Margin {
2559 fn ui(self, ui: &mut Ui) -> Response {
2560 let mut same = self.is_same();
2561
2562 let response = if same {
2563 ui.horizontal(|ui| {
2564 ui.checkbox(&mut same, "same");
2565
2566 let mut value = self.left;
2567 ui.add(DragValue::new(&mut value).range(0.0..=100.0));
2568 *self = Margin::same(value);
2569 })
2570 .response
2571 } else {
2572 ui.vertical(|ui| {
2573 ui.checkbox(&mut same, "same");
2574
2575 crate::Grid::new("margin").num_columns(2).show(ui, |ui| {
2576 ui.label("Left");
2577 ui.add(DragValue::new(&mut self.left).range(0.0..=100.0));
2578 ui.end_row();
2579
2580 ui.label("Right");
2581 ui.add(DragValue::new(&mut self.right).range(0.0..=100.0));
2582 ui.end_row();
2583
2584 ui.label("Top");
2585 ui.add(DragValue::new(&mut self.top).range(0.0..=100.0));
2586 ui.end_row();
2587
2588 ui.label("Bottom");
2589 ui.add(DragValue::new(&mut self.bottom).range(0.0..=100.0));
2590 ui.end_row();
2591 });
2592 })
2593 .response
2594 };
2595
2596 if same {
2598 *self =
2599 Margin::from((self.leftf() + self.rightf() + self.topf() + self.bottomf()) / 4.0);
2600 } else {
2601 if self.is_same() {
2603 if self.right == i8::MAX {
2604 self.right = i8::MAX - 1;
2605 } else {
2606 self.right += 1;
2607 }
2608 }
2609 }
2610
2611 response
2612 }
2613}
2614
2615impl Widget for &mut CornerRadius {
2616 fn ui(self, ui: &mut Ui) -> Response {
2617 let mut same = self.is_same();
2618
2619 let response = if same {
2620 ui.horizontal(|ui| {
2621 ui.checkbox(&mut same, "same");
2622
2623 let mut cr = self.nw;
2624 ui.add(DragValue::new(&mut cr).range(0.0..=f32::INFINITY));
2625 *self = CornerRadius::same(cr);
2626 })
2627 .response
2628 } else {
2629 ui.vertical(|ui| {
2630 ui.checkbox(&mut same, "same");
2631
2632 crate::Grid::new("Corner radius")
2633 .num_columns(2)
2634 .show(ui, |ui| {
2635 ui.label("NW");
2636 ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
2637 ui.end_row();
2638
2639 ui.label("NE");
2640 ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
2641 ui.end_row();
2642
2643 ui.label("SW");
2644 ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
2645 ui.end_row();
2646
2647 ui.label("SE");
2648 ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
2649 ui.end_row();
2650 });
2651 })
2652 .response
2653 };
2654
2655 if same {
2657 *self = CornerRadius::from(self.average());
2658 } else {
2659 if self.is_same() {
2661 if self.average() == 0.0 {
2662 self.se = 1;
2663 } else {
2664 self.se -= 1;
2665 }
2666 }
2667 }
2668
2669 response
2670 }
2671}
2672
2673impl Widget for &mut Shadow {
2674 fn ui(self, ui: &mut Ui) -> Response {
2675 let epaint::Shadow {
2676 offset,
2677 blur,
2678 spread,
2679 color,
2680 } = self;
2681
2682 ui.vertical(|ui| {
2683 crate::Grid::new("shadow_ui").show(ui, |ui| {
2684 ui.add(
2685 DragValue::new(&mut offset[0])
2686 .speed(1.0)
2687 .range(-100.0..=100.0)
2688 .prefix("x: "),
2689 );
2690 ui.add(
2691 DragValue::new(&mut offset[1])
2692 .speed(1.0)
2693 .range(-100.0..=100.0)
2694 .prefix("y: "),
2695 );
2696 ui.end_row();
2697
2698 ui.add(
2699 DragValue::new(blur)
2700 .speed(1.0)
2701 .range(0.0..=100.0)
2702 .prefix("blur: "),
2703 );
2704
2705 ui.add(
2706 DragValue::new(spread)
2707 .speed(1.0)
2708 .range(0.0..=100.0)
2709 .prefix("spread: "),
2710 );
2711 });
2712 ui.color_edit_button_srgba(color);
2713 })
2714 .response
2715 }
2716}
2717
2718impl Widget for &mut Stroke {
2719 fn ui(self, ui: &mut Ui) -> Response {
2720 let Stroke { width, color } = self;
2721
2722 ui.horizontal(|ui| {
2723 ui.add(DragValue::new(width).speed(0.1).range(0.0..=f32::INFINITY))
2724 .on_hover_text("Width");
2725 ui.color_edit_button_srgba(color);
2726
2727 let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size);
2729 let left = stroke_rect.left_center();
2730 let right = stroke_rect.right_center();
2731 ui.painter().line_segment([left, right], (*width, *color));
2732 })
2733 .response
2734 }
2735}
2736
2737impl Widget for &mut crate::Frame {
2738 fn ui(self, ui: &mut Ui) -> Response {
2739 let crate::Frame {
2740 inner_margin,
2741 outer_margin,
2742 corner_radius,
2743 shadow,
2744 fill,
2745 stroke,
2746 } = self;
2747
2748 crate::Grid::new("frame")
2749 .num_columns(2)
2750 .spacing([12.0, 8.0])
2751 .striped(true)
2752 .show(ui, |ui| {
2753 ui.label("Inner margin");
2754 ui.add(inner_margin);
2755 ui.end_row();
2756
2757 ui.label("Outer margin");
2758 ui.push_id("outer", |ui| ui.add(outer_margin));
2760 ui.end_row();
2761
2762 ui.label("Corner radius");
2763 ui.add(corner_radius);
2764 ui.end_row();
2765
2766 ui.label("Shadow");
2767 ui.add(shadow);
2768 ui.end_row();
2769
2770 ui.label("Fill");
2771 ui.color_edit_button_srgba(fill);
2772 ui.end_row();
2773
2774 ui.label("Stroke");
2775 ui.add(stroke);
2776 ui.end_row();
2777 })
2778 .response
2779 }
2780}
2781
2782impl Widget for &mut FontTweak {
2783 fn ui(self, ui: &mut Ui) -> Response {
2784 let original: FontTweak = *self;
2785
2786 let mut response = Grid::new("font_tweak")
2787 .num_columns(2)
2788 .show(ui, |ui| {
2789 let FontTweak {
2790 scale,
2791 y_offset_factor,
2792 y_offset,
2793 baseline_offset_factor,
2794 } = self;
2795
2796 ui.label("Scale");
2797 let speed = *scale * 0.01;
2798 ui.add(DragValue::new(scale).range(0.01..=10.0).speed(speed));
2799 ui.end_row();
2800
2801 ui.label("y_offset_factor");
2802 ui.add(DragValue::new(y_offset_factor).speed(-0.0025));
2803 ui.end_row();
2804
2805 ui.label("y_offset");
2806 ui.add(DragValue::new(y_offset).speed(-0.02));
2807 ui.end_row();
2808
2809 ui.label("baseline_offset_factor");
2810 ui.add(DragValue::new(baseline_offset_factor).speed(-0.0025));
2811 ui.end_row();
2812
2813 if ui.button("Reset").clicked() {
2814 *self = Default::default();
2815 }
2816 })
2817 .response;
2818
2819 if *self != original {
2820 response.mark_changed();
2821 }
2822
2823 response
2824 }
2825}