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