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