1use emath::Align;
4use epaint::{
5 AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions,
6 mutex::Mutex,
7 text::{FontTweak, Tag},
8};
9use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
10
11use crate::{
12 ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response, RichText, TextWrapMode,
13 WidgetText,
14 ecolor::Color32,
15 emath::{Rangef, Rect, Vec2, pos2, vec2},
16 reset_button_with,
17};
18
19#[derive(Clone)]
21pub struct NumberFormatter(
22 Arc<dyn 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String>,
23);
24
25impl NumberFormatter {
26 #[inline]
31 pub fn new(
32 formatter: impl 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String,
33 ) -> Self {
34 Self(Arc::new(formatter))
35 }
36
37 #[inline]
46 pub fn format(&self, value: f64, decimals: RangeInclusive<usize>) -> String {
47 (self.0)(value, decimals)
48 }
49}
50
51impl std::fmt::Debug for NumberFormatter {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.write_str("NumberFormatter")
54 }
55}
56
57impl PartialEq for NumberFormatter {
58 #[inline]
59 fn eq(&self, other: &Self) -> bool {
60 Arc::ptr_eq(&self.0, &other.0)
61 }
62}
63
64#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
71#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
72pub enum TextStyle {
73 Small,
75
76 Body,
78
79 Monospace,
81
82 Button,
86
87 Heading,
89
90 Name(std::sync::Arc<str>),
95}
96
97impl std::fmt::Display for TextStyle {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 match self {
100 Self::Small => "Small".fmt(f),
101 Self::Body => "Body".fmt(f),
102 Self::Monospace => "Monospace".fmt(f),
103 Self::Button => "Button".fmt(f),
104 Self::Heading => "Heading".fmt(f),
105 Self::Name(name) => (*name).fmt(f),
106 }
107 }
108}
109
110impl TextStyle {
111 pub fn resolve(&self, style: &Style) -> FontId {
113 style.text_styles.get(self).cloned().unwrap_or_else(|| {
114 panic!(
115 "Failed to find {:?} in Style::text_styles. Available styles:\n{:#?}",
116 self,
117 style.text_styles()
118 )
119 })
120 }
121}
122
123#[derive(Debug, Clone)]
127pub enum FontSelection {
128 Default,
131
132 FontId(FontId),
134
135 Style(TextStyle),
137}
138
139impl Default for FontSelection {
140 #[inline]
141 fn default() -> Self {
142 Self::Default
143 }
144}
145
146impl FontSelection {
147 pub fn resolve(self, style: &Style) -> FontId {
152 self.resolve_with_fallback(style, TextStyle::Body.into())
153 }
154
155 pub fn resolve_with_fallback(self, style: &Style, fallback: Self) -> FontId {
159 match self {
160 Self::Default => {
161 if let Some(override_font_id) = &style.override_font_id {
162 override_font_id.clone()
163 } else if let Some(text_style) = &style.override_text_style {
164 text_style.resolve(style)
165 } else {
166 fallback.resolve(style)
167 }
168 }
169 Self::FontId(font_id) => font_id,
170 Self::Style(text_style) => text_style.resolve(style),
171 }
172 }
173}
174
175impl From<FontId> for FontSelection {
176 #[inline(always)]
177 fn from(font_id: FontId) -> Self {
178 Self::FontId(font_id)
179 }
180}
181
182impl From<TextStyle> for FontSelection {
183 #[inline(always)]
184 fn from(text_style: TextStyle) -> Self {
185 Self::Style(text_style)
186 }
187}
188
189#[derive(Clone, Default)]
194pub struct StyleModifier(Option<Arc<dyn Fn(&mut Style) + Send + Sync>>);
195
196impl std::fmt::Debug for StyleModifier {
197 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198 f.write_str("StyleModifier")
199 }
200}
201
202impl<T> From<T> for StyleModifier
203where
204 T: Fn(&mut Style) + Send + Sync + 'static,
205{
206 fn from(f: T) -> Self {
207 Self(Some(Arc::new(f)))
208 }
209}
210
211impl From<Style> for StyleModifier {
212 fn from(style: Style) -> Self {
213 Self(Some(Arc::new(move |s| *s = style.clone())))
214 }
215}
216
217impl StyleModifier {
218 pub fn new(f: impl Fn(&mut Style) + Send + Sync + 'static) -> Self {
220 Self::from(f)
221 }
222
223 pub fn apply(&self, style: &mut Style) {
226 if let Some(f) = &self.0 {
227 f(style);
228 }
229 }
230}
231
232#[derive(Clone, Debug, PartialEq)]
242#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
243#[cfg_attr(feature = "serde", serde(default))]
244pub struct Style {
245 pub override_text_style: Option<TextStyle>,
250
251 pub override_font_id: Option<FontId>,
256
257 pub override_text_valign: Option<Align>,
261
262 pub text_styles: BTreeMap<TextStyle, FontId>,
290
291 pub drag_value_text_style: TextStyle,
293
294 #[cfg_attr(feature = "serde", serde(skip))]
298 pub number_formatter: NumberFormatter,
299
300 #[deprecated = "Use wrap_mode instead"]
309 pub wrap: Option<bool>,
310
311 pub wrap_mode: Option<crate::TextWrapMode>,
318
319 pub spacing: Spacing,
321
322 pub interaction: Interaction,
324
325 pub visuals: Visuals,
327
328 pub animation_time: f32,
330
331 #[cfg(debug_assertions)]
335 pub debug: DebugOptions,
336
337 pub explanation_tooltips: bool,
341
342 pub url_in_tooltip: bool,
344
345 pub always_scroll_the_only_direction: bool,
347
348 pub scroll_animation: ScrollAnimation,
350
351 pub compact_menu_style: bool,
353}
354
355#[test]
356fn style_impl_send_sync() {
357 fn assert_send_sync<T: Send + Sync>() {}
358 assert_send_sync::<Style>();
359}
360
361impl Style {
362 pub fn interact(&self, response: &Response) -> &WidgetVisuals {
367 self.visuals.widgets.style(response)
368 }
369
370 pub fn interact_selectable(&self, response: &Response, selected: bool) -> WidgetVisuals {
371 let mut visuals = *self.visuals.widgets.style(response);
372 if selected {
373 visuals.weak_bg_fill = self.visuals.selection.bg_fill;
374 visuals.bg_fill = self.visuals.selection.bg_fill;
375 visuals.fg_stroke = self.visuals.selection.stroke;
377 }
378 visuals
379 }
380
381 pub fn noninteractive(&self) -> &WidgetVisuals {
383 &self.visuals.widgets.noninteractive
384 }
385
386 pub fn text_styles(&self) -> Vec<TextStyle> {
388 self.text_styles.keys().cloned().collect()
389 }
390}
391
392#[derive(Clone, Debug, PartialEq)]
394#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
395#[cfg_attr(feature = "serde", serde(default))]
396pub struct Spacing {
397 pub item_spacing: Vec2,
404
405 pub window_margin: Margin,
407
408 pub button_padding: Vec2,
410
411 pub menu_margin: Margin,
413
414 pub indent: f32,
416
417 pub interact_size: Vec2, pub slider_width: f32,
424
425 pub slider_rail_height: f32,
427
428 pub combo_width: f32,
430
431 pub text_edit_width: f32,
433
434 pub icon_width: f32,
437
438 pub icon_width_inner: f32,
441
442 pub icon_spacing: f32,
445
446 pub default_area_size: Vec2,
454
455 pub tooltip_width: f32,
457
458 pub menu_width: f32,
462
463 pub menu_spacing: f32,
465
466 pub indent_ends_with_horizontal_line: bool,
468
469 pub combo_height: f32,
471
472 pub scroll: ScrollStyle,
474}
475
476impl Spacing {
477 pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) {
479 let icon_width = self.icon_width;
480 let big_icon_rect = Rect::from_center_size(
481 pos2(rect.left() + icon_width / 2.0, rect.center().y),
482 vec2(icon_width, icon_width),
483 );
484
485 let small_icon_rect =
486 Rect::from_center_size(big_icon_rect.center(), Vec2::splat(self.icon_width_inner));
487
488 (small_icon_rect, big_icon_rect)
489 }
490}
491
492#[derive(Clone, Copy, Debug, PartialEq)]
501#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
502#[cfg_attr(feature = "serde", serde(default))]
503pub struct ScrollStyle {
504 pub floating: bool,
512
513 pub content_margin: Margin,
518
519 pub bar_width: f32,
521
522 pub handle_min_length: f32,
524
525 pub bar_inner_margin: f32,
527
528 pub bar_outer_margin: f32,
531
532 pub floating_width: f32,
536
537 pub floating_allocated_width: f32,
544
545 pub foreground_color: bool,
547
548 pub dormant_background_opacity: f32,
554
555 pub active_background_opacity: f32,
561
562 pub interact_background_opacity: f32,
568
569 pub dormant_handle_opacity: f32,
575
576 pub active_handle_opacity: f32,
582
583 pub interact_handle_opacity: f32,
589
590 pub fade: ScrollFadeStyle,
591}
592
593impl Default for ScrollStyle {
594 fn default() -> Self {
595 Self::floating()
596 }
597}
598
599impl ScrollStyle {
600 pub fn solid() -> Self {
602 Self {
603 floating: false,
604 content_margin: Margin::ZERO,
605 bar_width: 6.0,
606 handle_min_length: 12.0,
607 bar_inner_margin: 4.0,
608 bar_outer_margin: 0.0,
609 floating_width: 2.0,
610 floating_allocated_width: 0.0,
611
612 foreground_color: false,
613
614 dormant_background_opacity: 0.0,
615 active_background_opacity: 0.4,
616 interact_background_opacity: 0.7,
617
618 dormant_handle_opacity: 0.0,
619 active_handle_opacity: 0.6,
620 interact_handle_opacity: 1.0,
621
622 fade: Default::default(),
623 }
624 }
625
626 pub fn thin() -> Self {
628 Self {
629 floating: true,
630 bar_width: 10.0,
631 floating_allocated_width: 6.0,
632 foreground_color: false,
633
634 dormant_background_opacity: 1.0,
635 dormant_handle_opacity: 1.0,
636
637 active_background_opacity: 1.0,
638 active_handle_opacity: 1.0,
639
640 interact_background_opacity: 0.6,
642 interact_handle_opacity: 0.6,
643
644 ..Self::solid()
645 }
646 }
647
648 pub fn floating() -> Self {
652 Self {
653 floating: true,
654 bar_width: 10.0,
655 foreground_color: true,
656 floating_allocated_width: 0.0,
657 dormant_background_opacity: 0.0,
658 dormant_handle_opacity: 0.0,
659 ..Self::solid()
660 }
661 }
662
663 pub fn allocated_width(&self) -> f32 {
665 if self.floating {
666 self.floating_allocated_width
667 } else {
668 self.bar_inner_margin + self.bar_width + self.bar_outer_margin
669 }
670 }
671
672 pub fn ui(&mut self, ui: &mut Ui) {
673 ui.horizontal(|ui| {
674 ui.label("Presets:");
675 ui.selectable_value(self, Self::solid(), "Solid");
676 ui.selectable_value(self, Self::thin(), "Thin");
677 ui.selectable_value(self, Self::floating(), "Floating");
678 });
679
680 ui.collapsing("Details", |ui| {
681 self.details_ui(ui);
682 });
683 }
684
685 pub fn details_ui(&mut self, ui: &mut Ui) {
686 let Self {
687 floating,
688
689 content_margin,
690
691 bar_width,
692 handle_min_length,
693 bar_inner_margin,
694 bar_outer_margin,
695 floating_width,
696 floating_allocated_width,
697
698 foreground_color,
699
700 dormant_background_opacity,
701 active_background_opacity,
702 interact_background_opacity,
703 dormant_handle_opacity,
704 active_handle_opacity,
705 interact_handle_opacity,
706
707 fade,
708 } = self;
709
710 ui.horizontal(|ui| {
711 ui.label("Type:");
712 ui.selectable_value(floating, false, "Solid");
713 ui.selectable_value(floating, true, "Floating");
714 });
715
716 ui.horizontal(|ui| {
717 ui.label("Content margin:");
718 content_margin.ui(ui);
719 });
720
721 ui.horizontal(|ui| {
722 ui.add(DragValue::new(bar_width).range(0.0..=32.0));
723 ui.label("Full bar width");
724 });
725 if *floating {
726 ui.horizontal(|ui| {
727 ui.add(DragValue::new(floating_width).range(0.0..=32.0));
728 ui.label("Thin bar width");
729 });
730 ui.horizontal(|ui| {
731 ui.add(DragValue::new(floating_allocated_width).range(0.0..=32.0));
732 ui.label("Allocated width");
733 });
734 }
735
736 ui.horizontal(|ui| {
737 ui.add(DragValue::new(handle_min_length).range(0.0..=32.0));
738 ui.label("Minimum handle length");
739 });
740 ui.horizontal(|ui| {
741 ui.add(DragValue::new(bar_outer_margin).range(0.0..=32.0));
742 ui.label("Outer margin");
743 });
744
745 ui.horizontal(|ui| {
746 ui.label("Color:");
747 ui.selectable_value(foreground_color, false, "Background");
748 ui.selectable_value(foreground_color, true, "Foreground");
749 });
750
751 if *floating {
752 crate::Grid::new("opacity").show(ui, |ui| {
753 fn opacity_ui(ui: &mut Ui, opacity: &mut f32) {
754 ui.add(DragValue::new(opacity).speed(0.01).range(0.0..=1.0));
755 }
756
757 ui.label("Opacity");
758 ui.label("Dormant");
759 ui.label("Active");
760 ui.label("Interacting");
761 ui.end_row();
762
763 ui.label("Background:");
764 opacity_ui(ui, dormant_background_opacity);
765 opacity_ui(ui, active_background_opacity);
766 opacity_ui(ui, interact_background_opacity);
767 ui.end_row();
768
769 ui.label("Handle:");
770 opacity_ui(ui, dormant_handle_opacity);
771 opacity_ui(ui, active_handle_opacity);
772 opacity_ui(ui, interact_handle_opacity);
773 ui.end_row();
774 });
775 } else {
776 ui.horizontal(|ui| {
777 ui.add(DragValue::new(bar_inner_margin).range(0.0..=32.0));
778 ui.label("Inner margin");
779 });
780 }
781
782 ui.separator();
783 fade.ui(ui);
784 }
785}
786
787#[derive(Clone, Copy, Debug, PartialEq)]
790#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
791#[cfg_attr(feature = "serde", serde(default))]
792pub struct ScrollFadeStyle {
793 pub strength: f32,
797
798 pub size: f32,
801}
802
803impl Default for ScrollFadeStyle {
804 fn default() -> Self {
805 Self {
806 strength: 0.5,
807 size: 20.0,
808 }
809 }
810}
811
812impl ScrollFadeStyle {
813 pub fn ui(&mut self, ui: &mut Ui) {
814 let Self { strength, size } = self;
815
816 ui.horizontal(|ui| {
817 ui.add(DragValue::new(strength).speed(0.01).range(0.0..=1.0));
818 ui.label("Fade strength");
819 });
820
821 if 0.0 < *strength {
822 ui.horizontal(|ui| {
823 ui.add(DragValue::new(size).range(0.0..=64.0));
824 ui.label("Fade size");
825 });
826 }
827 }
828}
829
830#[derive(Copy, Clone, Debug, PartialEq)]
837#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
838#[cfg_attr(feature = "serde", serde(default))]
839pub struct ScrollAnimation {
840 pub points_per_second: f32,
842
843 pub duration: Rangef,
845}
846
847impl Default for ScrollAnimation {
848 fn default() -> Self {
849 Self {
850 points_per_second: 1000.0,
851 duration: Rangef::new(0.1, 0.3),
852 }
853 }
854}
855
856impl ScrollAnimation {
857 pub fn new(points_per_second: f32, duration: Rangef) -> Self {
859 Self {
860 points_per_second,
861 duration,
862 }
863 }
864
865 pub fn none() -> Self {
867 Self {
868 points_per_second: f32::INFINITY,
869 duration: Rangef::new(0.0, 0.0),
870 }
871 }
872
873 pub fn duration(t: f32) -> Self {
875 Self {
876 points_per_second: f32::INFINITY,
877 duration: Rangef::new(t, t),
878 }
879 }
880
881 pub fn ui(&mut self, ui: &mut crate::Ui) {
882 crate::Grid::new("scroll_animation").show(ui, |ui| {
883 ui.label("Scroll animation:");
884 ui.add(
885 DragValue::new(&mut self.points_per_second)
886 .speed(100.0)
887 .range(0.0..=5000.0),
888 );
889 ui.label("points/second");
890 ui.end_row();
891
892 ui.label("Min duration:");
893 ui.add(
894 DragValue::new(&mut self.duration.min)
895 .speed(0.01)
896 .range(0.0..=self.duration.max),
897 );
898 ui.label("seconds");
899 ui.end_row();
900
901 ui.label("Max duration:");
902 ui.add(
903 DragValue::new(&mut self.duration.max)
904 .speed(0.01)
905 .range(0.0..=1.0),
906 );
907 ui.label("seconds");
908 ui.end_row();
909 });
910 }
911}
912
913#[derive(Clone, Debug, PartialEq)]
917#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
918#[cfg_attr(feature = "serde", serde(default))]
919pub struct Interaction {
920 pub interact_radius: f32,
925
926 pub resize_grab_radius_side: f32,
928
929 pub resize_grab_radius_corner: f32,
931
932 pub show_tooltips_only_when_still: bool,
934
935 pub tooltip_delay: f32,
937
938 pub tooltip_grace_time: f32,
944
945 pub selectable_labels: bool,
947
948 pub multi_widget_text_select: bool,
953}
954
955#[derive(Clone, Debug, PartialEq)]
957#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
958#[cfg_attr(feature = "serde", serde(default))]
959pub struct TextCursorStyle {
960 pub stroke: Stroke,
962
963 pub preview: bool,
965
966 pub blink: bool,
968
969 pub on_duration: f32,
971
972 pub off_duration: f32,
974}
975
976impl Default for TextCursorStyle {
977 fn default() -> Self {
978 Self {
979 stroke: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), preview: false,
981 blink: true,
982 on_duration: 0.5,
983 off_duration: 0.5,
984 }
985 }
986}
987
988#[derive(Clone, Debug, PartialEq)]
995#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
996#[cfg_attr(feature = "serde", serde(default))]
997pub struct Visuals {
998 pub dark_mode: bool,
1004
1005 pub text_options: TextOptions,
1010
1011 pub override_text_color: Option<Color32>,
1025
1026 pub weak_text_alpha: f32,
1030
1031 pub weak_text_color: Option<Color32>,
1036
1037 pub widgets: Widgets,
1039
1040 pub selection: Selection,
1041
1042 pub hyperlink_color: Color32,
1044
1045 pub faint_bg_color: Color32,
1048
1049 pub extreme_bg_color: Color32,
1053
1054 pub text_edit_bg_color: Option<Color32>,
1058
1059 pub code_bg_color: Color32,
1061
1062 pub warn_fg_color: Color32,
1064
1065 pub error_fg_color: Color32,
1067
1068 pub window_corner_radius: CornerRadius,
1069 pub window_shadow: Shadow,
1070 pub window_fill: Color32,
1071 pub window_stroke: Stroke,
1072
1073 pub window_highlight_topmost: bool,
1075
1076 pub menu_corner_radius: CornerRadius,
1077
1078 pub panel_fill: Color32,
1080
1081 pub popup_shadow: Shadow,
1082
1083 pub resize_corner_size: f32,
1084
1085 pub text_cursor: TextCursorStyle,
1087
1088 pub clip_rect_margin: f32,
1090
1091 pub button_frame: bool,
1093
1094 pub collapsing_header_frame: bool,
1096
1097 pub indent_has_left_vline: bool,
1099
1100 pub striped: bool,
1103
1104 pub slider_trailing_fill: bool,
1108
1109 pub handle_shape: HandleShape,
1113
1114 pub interact_cursor: Option<CursorIcon>,
1120
1121 pub image_loading_spinners: bool,
1123
1124 pub numeric_color_space: NumericColorSpace,
1126
1127 pub disabled_alpha: f32,
1129}
1130
1131impl Visuals {
1132 #[inline(always)]
1133 pub fn noninteractive(&self) -> &WidgetVisuals {
1134 &self.widgets.noninteractive
1135 }
1136
1137 pub fn text_color(&self) -> Color32 {
1139 self.override_text_color
1140 .unwrap_or_else(|| self.widgets.noninteractive.text_color())
1141 }
1142
1143 pub fn weak_text_color(&self) -> Color32 {
1144 self.weak_text_color
1145 .unwrap_or_else(|| self.text_color().gamma_multiply(self.weak_text_alpha))
1146 }
1147
1148 #[inline(always)]
1149 pub fn strong_text_color(&self) -> Color32 {
1150 self.widgets.active.text_color()
1151 }
1152
1153 pub fn text_edit_bg_color(&self) -> Color32 {
1155 self.text_edit_bg_color.unwrap_or(self.extreme_bg_color)
1156 }
1157
1158 #[inline(always)]
1160 pub fn window_fill(&self) -> Color32 {
1161 self.window_fill
1162 }
1163
1164 #[inline(always)]
1165 pub fn window_stroke(&self) -> Stroke {
1166 self.window_stroke
1167 }
1168
1169 #[inline(always)]
1171 #[deprecated = "Use disabled_alpha(). Fading is now handled by modifying the alpha channel."]
1172 pub fn fade_out_to_color(&self) -> Color32 {
1173 self.widgets.noninteractive.weak_bg_fill
1174 }
1175
1176 #[inline(always)]
1178 pub fn disabled_alpha(&self) -> f32 {
1179 self.disabled_alpha
1180 }
1181
1182 #[inline(always)]
1187 pub fn disable(&self, color: Color32) -> Color32 {
1188 color.gamma_multiply(self.disabled_alpha())
1189 }
1190
1191 #[doc(alias = "grey_out")]
1193 #[inline(always)]
1194 pub fn gray_out(&self, color: Color32) -> Color32 {
1195 crate::ecolor::tint_color_towards(color, self.widgets.noninteractive.weak_bg_fill)
1196 }
1197}
1198
1199#[derive(Clone, Copy, Debug, PartialEq)]
1201#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1202#[cfg_attr(feature = "serde", serde(default))]
1203pub struct Selection {
1204 pub bg_fill: Color32,
1206
1207 pub stroke: Stroke,
1209}
1210
1211#[derive(Clone, Copy, Debug, PartialEq)]
1213#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1214pub enum HandleShape {
1215 Circle,
1217
1218 Rect {
1220 aspect_ratio: f32,
1222 },
1223}
1224
1225#[derive(Clone, Debug, PartialEq)]
1227#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1228#[cfg_attr(feature = "serde", serde(default))]
1229pub struct Widgets {
1230 pub noninteractive: WidgetVisuals,
1235
1236 pub inactive: WidgetVisuals,
1238
1239 pub hovered: WidgetVisuals,
1243
1244 pub active: WidgetVisuals,
1246
1247 pub open: WidgetVisuals,
1249}
1250
1251impl Widgets {
1252 pub fn style(&self, response: &Response) -> &WidgetVisuals {
1253 if !response.sense.interactive() {
1254 &self.noninteractive
1255 } else if response.is_pointer_button_down_on() || response.has_focus() || response.clicked()
1256 {
1257 &self.active
1258 } else if response.hovered() || response.highlighted() {
1259 &self.hovered
1260 } else {
1261 &self.inactive
1262 }
1263 }
1264}
1265
1266#[derive(Clone, Copy, Debug, PartialEq)]
1268#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1269pub struct WidgetVisuals {
1270 pub bg_fill: Color32,
1275
1276 pub weak_bg_fill: Color32,
1280
1281 pub bg_stroke: Stroke,
1285
1286 pub corner_radius: CornerRadius,
1288
1289 pub fg_stroke: Stroke,
1291
1292 pub expansion: f32,
1299}
1300
1301impl WidgetVisuals {
1302 #[inline(always)]
1303 pub fn text_color(&self) -> Color32 {
1304 self.fg_stroke.color
1305 }
1306
1307 #[deprecated = "Renamed to corner_radius"]
1308 pub fn rounding(&self) -> CornerRadius {
1309 self.corner_radius
1310 }
1311}
1312
1313#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1315#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1316#[cfg(debug_assertions)]
1317pub struct DebugOptions {
1318 #[cfg(debug_assertions)]
1326 pub debug_on_hover: bool,
1327
1328 #[cfg(debug_assertions)]
1338 pub debug_on_hover_with_all_modifiers: bool,
1339
1340 #[cfg(debug_assertions)]
1342 pub hover_shows_next: bool,
1343
1344 pub show_expand_width: bool,
1346
1347 pub show_expand_height: bool,
1349
1350 pub show_resize: bool,
1351
1352 pub show_interactive_widgets: bool,
1354
1355 pub show_widget_hits: bool,
1357
1358 pub warn_if_rect_changes_id: bool,
1361
1362 pub show_unaligned: bool,
1366
1367 pub show_focused_widget: bool,
1373}
1374
1375#[cfg(debug_assertions)]
1376impl Default for DebugOptions {
1377 fn default() -> Self {
1378 Self {
1379 debug_on_hover: false,
1380 debug_on_hover_with_all_modifiers: cfg!(feature = "callstack")
1381 && !cfg!(target_arch = "wasm32"),
1382 hover_shows_next: false,
1383 show_expand_width: false,
1384 show_expand_height: false,
1385 show_resize: false,
1386 show_interactive_widgets: false,
1387 show_widget_hits: false,
1388 warn_if_rect_changes_id: cfg!(debug_assertions),
1389 show_unaligned: cfg!(debug_assertions),
1390 show_focused_widget: false,
1391 }
1392 }
1393}
1394
1395pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
1399 use FontFamily::{Monospace, Proportional};
1400
1401 [
1402 (TextStyle::Small, FontId::new(9.0, Proportional)),
1403 (TextStyle::Body, FontId::new(13.0, Proportional)),
1404 (TextStyle::Button, FontId::new(13.0, Proportional)),
1405 (TextStyle::Heading, FontId::new(18.0, Proportional)),
1406 (TextStyle::Monospace, FontId::new(13.0, Monospace)),
1407 ]
1408 .into()
1409}
1410
1411impl Default for Style {
1412 fn default() -> Self {
1413 #[expect(deprecated)]
1414 Self {
1415 override_font_id: None,
1416 override_text_style: None,
1417 override_text_valign: Some(Align::Center),
1418 text_styles: default_text_styles(),
1419 drag_value_text_style: TextStyle::Button,
1420 number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
1421 wrap: None,
1422 wrap_mode: None,
1423 spacing: Spacing::default(),
1424 interaction: Interaction::default(),
1425 visuals: Visuals::default(),
1426 animation_time: 6.0 / 60.0, #[cfg(debug_assertions)]
1428 debug: Default::default(),
1429 explanation_tooltips: false,
1430 url_in_tooltip: false,
1431 always_scroll_the_only_direction: false,
1432 scroll_animation: ScrollAnimation::default(),
1433 compact_menu_style: true,
1434 }
1435 }
1436}
1437
1438impl Default for Spacing {
1439 fn default() -> Self {
1440 Self {
1441 item_spacing: vec2(8.0, 3.0),
1442 window_margin: Margin::same(6),
1443 menu_margin: Margin::same(6),
1444 button_padding: vec2(4.0, 1.0),
1445 indent: 18.0, interact_size: vec2(40.0, 18.0),
1447 slider_width: 100.0,
1448 slider_rail_height: 8.0,
1449 combo_width: 100.0,
1450 text_edit_width: 280.0,
1451 icon_width: 14.0,
1452 icon_width_inner: 8.0,
1453 icon_spacing: 4.0,
1454 default_area_size: vec2(600.0, 400.0),
1455 tooltip_width: 500.0,
1456 menu_width: 400.0,
1457 menu_spacing: 2.0,
1458 combo_height: 200.0,
1459 scroll: Default::default(),
1460 indent_ends_with_horizontal_line: false,
1461 }
1462 }
1463}
1464
1465impl Default for Interaction {
1466 fn default() -> Self {
1467 Self {
1468 interact_radius: 5.0,
1469 resize_grab_radius_side: 3.0,
1470 resize_grab_radius_corner: 10.0,
1471 show_tooltips_only_when_still: true,
1472 tooltip_delay: 0.5,
1473 tooltip_grace_time: 0.2,
1474 selectable_labels: true,
1475 multi_widget_text_select: true,
1476 }
1477 }
1478}
1479
1480impl Visuals {
1481 pub fn dark() -> Self {
1483 Self {
1484 dark_mode: true,
1485 text_options: TextOptions {
1486 alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
1487 ..Default::default()
1488 },
1489 override_text_color: None,
1490 weak_text_alpha: 0.6,
1491 weak_text_color: None,
1492 widgets: Widgets::default(),
1493 selection: Selection::default(),
1494 hyperlink_color: Color32::from_rgb(90, 170, 255),
1495 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),
1499 warn_fg_color: Color32::from_rgb(255, 143, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_corner_radius: CornerRadius::same(6),
1503 window_shadow: Shadow {
1504 offset: [10, 20],
1505 blur: 15,
1506 spread: 0,
1507 color: Color32::from_black_alpha(96),
1508 },
1509 window_fill: Color32::from_gray(27),
1510 window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1511 window_highlight_topmost: true,
1512
1513 menu_corner_radius: CornerRadius::same(6),
1514
1515 panel_fill: Color32::from_gray(27),
1516
1517 popup_shadow: Shadow {
1518 offset: [6, 10],
1519 blur: 8,
1520 spread: 0,
1521 color: Color32::from_black_alpha(96),
1522 },
1523
1524 resize_corner_size: 12.0,
1525
1526 text_cursor: Default::default(),
1527
1528 clip_rect_margin: 3.0, button_frame: true,
1530 collapsing_header_frame: false,
1531 indent_has_left_vline: true,
1532
1533 striped: false,
1534
1535 slider_trailing_fill: false,
1536 handle_shape: HandleShape::Rect { aspect_ratio: 0.75 },
1537
1538 interact_cursor: None,
1539
1540 image_loading_spinners: true,
1541
1542 numeric_color_space: NumericColorSpace::GammaByte,
1543 disabled_alpha: 0.5,
1544 }
1545 }
1546
1547 pub fn light() -> Self {
1549 Self {
1550 dark_mode: false,
1551 text_options: TextOptions {
1552 alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
1553 ..Default::default()
1554 },
1555 widgets: Widgets::light(),
1556 selection: Selection::light(),
1557 hyperlink_color: Color32::from_rgb(0, 155, 255),
1558 faint_bg_color: Color32::from_additive_luminance(5), extreme_bg_color: Color32::from_gray(255), code_bg_color: Color32::from_gray(230),
1561 warn_fg_color: Color32::from_rgb(255, 100, 0), error_fg_color: Color32::from_rgb(255, 0, 0), window_shadow: Shadow {
1565 offset: [10, 20],
1566 blur: 15,
1567 spread: 0,
1568 color: Color32::from_black_alpha(25),
1569 },
1570 window_fill: Color32::from_gray(248),
1571 window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
1572
1573 panel_fill: Color32::from_gray(248),
1574
1575 popup_shadow: Shadow {
1576 offset: [6, 10],
1577 blur: 8,
1578 spread: 0,
1579 color: Color32::from_black_alpha(25),
1580 },
1581
1582 text_cursor: TextCursorStyle {
1583 stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
1584 ..Default::default()
1585 },
1586
1587 ..Self::dark()
1588 }
1589 }
1590}
1591
1592impl Default for Visuals {
1593 fn default() -> Self {
1594 Self::dark()
1595 }
1596}
1597
1598impl Selection {
1599 fn dark() -> Self {
1600 Self {
1601 bg_fill: Color32::from_rgb(0, 92, 128),
1602 stroke: Stroke::new(1.0, Color32::from_rgb(192, 222, 255)),
1603 }
1604 }
1605
1606 fn light() -> Self {
1607 Self {
1608 bg_fill: Color32::from_rgb(144, 209, 255),
1609 stroke: Stroke::new(1.0, Color32::from_rgb(0, 83, 125)),
1610 }
1611 }
1612}
1613
1614impl Default for Selection {
1615 fn default() -> Self {
1616 Self::dark()
1617 }
1618}
1619
1620impl Widgets {
1621 pub fn dark() -> Self {
1622 Self {
1623 noninteractive: WidgetVisuals {
1624 weak_bg_fill: Color32::from_gray(27),
1625 bg_fill: Color32::from_gray(27),
1626 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),
1629 expansion: 0.0,
1630 },
1631 inactive: WidgetVisuals {
1632 weak_bg_fill: Color32::from_gray(60), bg_fill: Color32::from_gray(60), bg_stroke: Default::default(),
1635 fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), corner_radius: CornerRadius::same(2),
1637 expansion: 0.0,
1638 },
1639 hovered: WidgetVisuals {
1640 weak_bg_fill: Color32::from_gray(70),
1641 bg_fill: Color32::from_gray(70),
1642 bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
1644 corner_radius: CornerRadius::same(3),
1645 expansion: 0.0,
1646 },
1647 active: WidgetVisuals {
1648 weak_bg_fill: Color32::from_gray(55),
1649 bg_fill: Color32::from_gray(55),
1650 bg_stroke: Stroke::new(1.0, Color32::WHITE),
1651 fg_stroke: Stroke::new(2.0, Color32::WHITE),
1652 corner_radius: CornerRadius::same(2),
1653 expansion: 0.0,
1654 },
1655 open: WidgetVisuals {
1656 weak_bg_fill: Color32::from_gray(45),
1657 bg_fill: Color32::from_gray(27),
1658 bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1659 fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
1660 corner_radius: CornerRadius::same(2),
1661 expansion: 0.0,
1662 },
1663 }
1664 }
1665
1666 pub fn light() -> Self {
1667 Self {
1668 noninteractive: WidgetVisuals {
1669 weak_bg_fill: Color32::from_gray(248),
1670 bg_fill: Color32::from_gray(248),
1671 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),
1674 expansion: 0.0,
1675 },
1676 inactive: WidgetVisuals {
1677 weak_bg_fill: Color32::from_gray(230), bg_fill: Color32::from_gray(230), bg_stroke: Default::default(),
1680 fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), corner_radius: CornerRadius::same(2),
1682 expansion: 0.0,
1683 },
1684 hovered: WidgetVisuals {
1685 weak_bg_fill: Color32::from_gray(220),
1686 bg_fill: Color32::from_gray(220),
1687 bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), fg_stroke: Stroke::new(1.5, Color32::BLACK),
1689 corner_radius: CornerRadius::same(3),
1690 expansion: 0.0,
1691 },
1692 active: WidgetVisuals {
1693 weak_bg_fill: Color32::from_gray(165),
1694 bg_fill: Color32::from_gray(165),
1695 bg_stroke: Stroke::new(1.0, Color32::BLACK),
1696 fg_stroke: Stroke::new(2.0, Color32::BLACK),
1697 corner_radius: CornerRadius::same(2),
1698 expansion: 0.0,
1699 },
1700 open: WidgetVisuals {
1701 weak_bg_fill: Color32::from_gray(220),
1702 bg_fill: Color32::from_gray(220),
1703 bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
1704 fg_stroke: Stroke::new(1.0, Color32::BLACK),
1705 corner_radius: CornerRadius::same(2),
1706 expansion: 0.0,
1707 },
1708 }
1709 }
1710}
1711
1712impl Default for Widgets {
1713 fn default() -> Self {
1714 Self::dark()
1715 }
1716}
1717
1718use crate::{
1721 Ui,
1722 widgets::{DragValue, Slider, Widget, reset_button},
1723};
1724
1725impl Style {
1726 pub fn ui(&mut self, ui: &mut crate::Ui) {
1727 #[expect(deprecated)]
1728 let Self {
1729 override_font_id,
1730 override_text_style,
1731 override_text_valign,
1732 text_styles,
1733 drag_value_text_style,
1734 number_formatter: _, wrap: _,
1736 wrap_mode,
1737 spacing,
1738 interaction,
1739 visuals,
1740 animation_time,
1741 #[cfg(debug_assertions)]
1742 debug,
1743 explanation_tooltips,
1744 url_in_tooltip,
1745 always_scroll_the_only_direction,
1746 scroll_animation,
1747 compact_menu_style,
1748 } = self;
1749
1750 crate::Grid::new("_options").show(ui, |ui| {
1751 ui.label("Override font id");
1752 ui.vertical(|ui| {
1753 ui.horizontal(|ui| {
1754 ui.radio_value(override_font_id, None, "None");
1755 if ui.radio(override_font_id.is_some(), "override").clicked() {
1756 *override_font_id = Some(FontId::default());
1757 }
1758 });
1759 if let Some(override_font_id) = override_font_id {
1760 crate::introspection::font_id_ui(ui, override_font_id);
1761 }
1762 });
1763 ui.end_row();
1764
1765 ui.label("Override text style");
1766 crate::ComboBox::from_id_salt("override_text_style")
1767 .selected_text(match override_text_style {
1768 None => "None".to_owned(),
1769 Some(override_text_style) => override_text_style.to_string(),
1770 })
1771 .show_ui(ui, |ui| {
1772 ui.selectable_value(override_text_style, None, "None");
1773 let all_text_styles = ui.style().text_styles();
1774 for style in all_text_styles {
1775 let text =
1776 crate::RichText::new(style.to_string()).text_style(style.clone());
1777 ui.selectable_value(override_text_style, Some(style), text);
1778 }
1779 });
1780 ui.end_row();
1781
1782 fn valign_name(valign: Align) -> &'static str {
1783 match valign {
1784 Align::TOP => "Top",
1785 Align::Center => "Center",
1786 Align::BOTTOM => "Bottom",
1787 }
1788 }
1789
1790 ui.label("Override text valign");
1791 crate::ComboBox::from_id_salt("override_text_valign")
1792 .selected_text(match override_text_valign {
1793 None => "None",
1794 Some(override_text_valign) => valign_name(*override_text_valign),
1795 })
1796 .show_ui(ui, |ui| {
1797 ui.selectable_value(override_text_valign, None, "None");
1798 for align in [Align::TOP, Align::Center, Align::BOTTOM] {
1799 ui.selectable_value(override_text_valign, Some(align), valign_name(align));
1800 }
1801 });
1802 ui.end_row();
1803
1804 ui.label("Text style of DragValue");
1805 crate::ComboBox::from_id_salt("drag_value_text_style")
1806 .selected_text(drag_value_text_style.to_string())
1807 .show_ui(ui, |ui| {
1808 let all_text_styles = ui.style().text_styles();
1809 for style in all_text_styles {
1810 let text =
1811 crate::RichText::new(style.to_string()).text_style(style.clone());
1812 ui.selectable_value(drag_value_text_style, style, text);
1813 }
1814 });
1815 ui.end_row();
1816
1817 ui.label("Text Wrap Mode");
1818 crate::ComboBox::from_id_salt("text_wrap_mode")
1819 .selected_text(format!("{wrap_mode:?}"))
1820 .show_ui(ui, |ui| {
1821 let all_wrap_mode: Vec<Option<TextWrapMode>> = vec![
1822 None,
1823 Some(TextWrapMode::Extend),
1824 Some(TextWrapMode::Wrap),
1825 Some(TextWrapMode::Truncate),
1826 ];
1827 for style in all_wrap_mode {
1828 let text = crate::RichText::new(format!("{style:?}"));
1829 ui.selectable_value(wrap_mode, style, text);
1830 }
1831 });
1832 ui.end_row();
1833
1834 ui.label("Animation duration");
1835 ui.add(
1836 DragValue::new(animation_time)
1837 .range(0.0..=1.0)
1838 .speed(0.02)
1839 .suffix(" s"),
1840 );
1841 ui.end_row();
1842 });
1843
1844 ui.collapsing("🔠 Text styles", |ui| text_styles_ui(ui, text_styles));
1845 ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
1846 ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
1847 ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
1848 ui.collapsing("🔄 Scroll animation", |ui| scroll_animation.ui(ui));
1849
1850 #[cfg(debug_assertions)]
1851 ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
1852
1853 ui.checkbox(compact_menu_style, "Compact menu style");
1854
1855 ui.checkbox(explanation_tooltips, "Explanation tooltips")
1856 .on_hover_text(
1857 "Show explanatory text when hovering DragValue:s and other egui widgets",
1858 );
1859
1860 ui.checkbox(url_in_tooltip, "Show url when hovering links");
1861
1862 ui.checkbox(always_scroll_the_only_direction, "Always scroll the only enabled direction")
1863 .on_hover_text(
1864 "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift",
1865 );
1866
1867 ui.vertical_centered(|ui| reset_button(ui, self, "Reset style"));
1868 }
1869}
1870
1871fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
1872 ui.vertical(|ui| {
1873 crate::Grid::new("text_styles").show(ui, |ui| {
1874 for (text_style, font_id) in &mut *text_styles {
1875 ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
1876 crate::introspection::font_id_ui(ui, font_id);
1877 ui.end_row();
1878 }
1879 });
1880 crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles());
1881 })
1882 .response
1883}
1884
1885impl Spacing {
1886 pub fn ui(&mut self, ui: &mut crate::Ui) {
1887 let Self {
1888 item_spacing,
1889 window_margin,
1890 menu_margin,
1891 button_padding,
1892 indent,
1893 interact_size,
1894 slider_width,
1895 slider_rail_height,
1896 combo_width,
1897 text_edit_width,
1898 icon_width,
1899 icon_width_inner,
1900 icon_spacing,
1901 default_area_size,
1902 tooltip_width,
1903 menu_width,
1904 menu_spacing,
1905 indent_ends_with_horizontal_line,
1906 combo_height,
1907 scroll,
1908 } = self;
1909
1910 Grid::new("spacing")
1911 .num_columns(2)
1912 .spacing([12.0, 8.0])
1913 .striped(true)
1914 .show(ui, |ui| {
1915 ui.label("Item spacing");
1916 ui.add(two_drag_values(item_spacing, 0.0..=20.0));
1917 ui.end_row();
1918
1919 ui.label("Window margin");
1920 ui.add(window_margin);
1921 ui.end_row();
1922
1923 ui.label("ScrollArea margin");
1924 scroll.content_margin.ui(ui);
1925 ui.end_row();
1926
1927 ui.label("Menu margin");
1928 ui.add(menu_margin);
1929 ui.end_row();
1930
1931 ui.label("Button padding");
1932 ui.add(two_drag_values(button_padding, 0.0..=20.0));
1933 ui.end_row();
1934
1935 ui.label("Interact size")
1936 .on_hover_text("Minimum size of an interactive widget");
1937 ui.add(two_drag_values(interact_size, 4.0..=60.0));
1938 ui.end_row();
1939
1940 ui.label("Indent");
1941 ui.add(DragValue::new(indent).range(0.0..=100.0));
1942 ui.end_row();
1943
1944 ui.label("Slider width");
1945 ui.add(DragValue::new(slider_width).range(0.0..=1000.0));
1946 ui.end_row();
1947
1948 ui.label("Slider rail height");
1949 ui.add(DragValue::new(slider_rail_height).range(0.0..=50.0));
1950 ui.end_row();
1951
1952 ui.label("ComboBox width");
1953 ui.add(DragValue::new(combo_width).range(0.0..=1000.0));
1954 ui.end_row();
1955
1956 ui.label("Default area size");
1957 ui.add(two_drag_values(default_area_size, 0.0..=1000.0));
1958 ui.end_row();
1959
1960 ui.label("TextEdit width");
1961 ui.add(DragValue::new(text_edit_width).range(0.0..=1000.0));
1962 ui.end_row();
1963
1964 ui.label("Tooltip wrap width");
1965 ui.add(DragValue::new(tooltip_width).range(0.0..=1000.0));
1966 ui.end_row();
1967
1968 ui.label("Default menu width");
1969 ui.add(DragValue::new(menu_width).range(0.0..=1000.0));
1970 ui.end_row();
1971
1972 ui.label("Menu spacing")
1973 .on_hover_text("Horizontal spacing between menus");
1974 ui.add(DragValue::new(menu_spacing).range(0.0..=10.0));
1975 ui.end_row();
1976
1977 ui.label("Checkboxes etc");
1978 ui.vertical(|ui| {
1979 ui.add(
1980 DragValue::new(icon_width)
1981 .prefix("outer icon width:")
1982 .range(0.0..=60.0),
1983 );
1984 ui.add(
1985 DragValue::new(icon_width_inner)
1986 .prefix("inner icon width:")
1987 .range(0.0..=60.0),
1988 );
1989 ui.add(
1990 DragValue::new(icon_spacing)
1991 .prefix("spacing:")
1992 .range(0.0..=10.0),
1993 );
1994 });
1995 ui.end_row();
1996 });
1997
1998 ui.checkbox(
1999 indent_ends_with_horizontal_line,
2000 "End indented regions with a horizontal separator",
2001 );
2002
2003 ui.horizontal(|ui| {
2004 ui.label("Max height of a combo box");
2005 ui.add(DragValue::new(combo_height).range(0.0..=1000.0));
2006 });
2007
2008 ui.collapsing("Scroll Area", |ui| {
2009 scroll.ui(ui);
2010 });
2011
2012 ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing"));
2013 }
2014}
2015
2016impl Interaction {
2017 pub fn ui(&mut self, ui: &mut crate::Ui) {
2018 let Self {
2019 interact_radius,
2020 resize_grab_radius_side,
2021 resize_grab_radius_corner,
2022 show_tooltips_only_when_still,
2023 tooltip_delay,
2024 tooltip_grace_time,
2025 selectable_labels,
2026 multi_widget_text_select,
2027 } = self;
2028
2029 ui.spacing_mut().item_spacing = vec2(12.0, 8.0);
2030
2031 Grid::new("interaction")
2032 .num_columns(2)
2033 .striped(true)
2034 .show(ui, |ui| {
2035 ui.label("interact_radius")
2036 .on_hover_text("Interact with the closest widget within this radius.");
2037 ui.add(DragValue::new(interact_radius).range(0.0..=20.0));
2038 ui.end_row();
2039
2040 ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize");
2041 ui.add(DragValue::new(resize_grab_radius_side).range(0.0..=20.0));
2042 ui.end_row();
2043
2044 ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize.");
2045 ui.add(DragValue::new(resize_grab_radius_corner).range(0.0..=20.0));
2046 ui.end_row();
2047
2048 ui.label("Tooltip delay").on_hover_text(
2049 "Delay in seconds before showing tooltips after the mouse stops moving",
2050 );
2051 ui.add(
2052 DragValue::new(tooltip_delay)
2053 .range(0.0..=1.0)
2054 .speed(0.05)
2055 .suffix(" s"),
2056 );
2057 ui.end_row();
2058
2059 ui.label("Tooltip grace time").on_hover_text(
2060 "If a tooltip is open and you hover another widget within this grace period, show the next tooltip right away",
2061 );
2062 ui.add(
2063 DragValue::new(tooltip_grace_time)
2064 .range(0.0..=1.0)
2065 .speed(0.05)
2066 .suffix(" s"),
2067 );
2068 ui.end_row();
2069 });
2070
2071 ui.checkbox(
2072 show_tooltips_only_when_still,
2073 "Only show tooltips if mouse is still",
2074 );
2075
2076 ui.horizontal(|ui| {
2077 ui.checkbox(selectable_labels, "Selectable text in labels");
2078 if *selectable_labels {
2079 ui.checkbox(multi_widget_text_select, "Across multiple labels");
2080 }
2081 });
2082
2083 ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings"));
2084 }
2085}
2086
2087impl Widgets {
2088 pub fn ui(&mut self, ui: &mut crate::Ui) {
2089 let Self {
2090 active,
2091 hovered,
2092 inactive,
2093 noninteractive,
2094 open,
2095 } = self;
2096
2097 ui.collapsing("Noninteractive", |ui| {
2098 ui.label(
2099 "The style of a widget that you cannot interact with, e.g. labels and separators.",
2100 );
2101 noninteractive.ui(ui);
2102 });
2103 ui.collapsing("Interactive but inactive", |ui| {
2104 ui.label("The style of an interactive widget, such as a button, at rest.");
2105 inactive.ui(ui);
2106 });
2107 ui.collapsing("Interactive and hovered", |ui| {
2108 ui.label("The style of an interactive widget while you hover it.");
2109 hovered.ui(ui);
2110 });
2111 ui.collapsing("Interactive and active", |ui| {
2112 ui.label("The style of an interactive widget as you are clicking or dragging it.");
2113 active.ui(ui);
2114 });
2115 ui.collapsing("Open menu", |ui| {
2116 ui.label("The style of an open combo-box or menu button");
2117 open.ui(ui);
2118 });
2119
2120 }
2122}
2123
2124impl Selection {
2125 pub fn ui(&mut self, ui: &mut crate::Ui) {
2126 let Self { bg_fill, stroke } = self;
2127 ui.label("Selectable labels");
2128
2129 Grid::new("selectiom").num_columns(2).show(ui, |ui| {
2130 ui.label("Background fill");
2131 ui.color_edit_button_srgba(bg_fill);
2132 ui.end_row();
2133
2134 ui.label("Stroke");
2135 ui.add(stroke);
2136 ui.end_row();
2137 });
2138 }
2139}
2140
2141impl WidgetVisuals {
2142 pub fn ui(&mut self, ui: &mut crate::Ui) {
2143 let Self {
2144 weak_bg_fill,
2145 bg_fill: mandatory_bg_fill,
2146 bg_stroke,
2147 corner_radius,
2148 fg_stroke,
2149 expansion,
2150 } = self;
2151
2152 Grid::new("widget")
2153 .num_columns(2)
2154 .spacing([12.0, 8.0])
2155 .striped(true)
2156 .show(ui, |ui| {
2157 ui.label("Optional background fill")
2158 .on_hover_text("For buttons, combo-boxes, etc");
2159 ui.color_edit_button_srgba(weak_bg_fill);
2160 ui.end_row();
2161
2162 ui.label("Mandatory background fill")
2163 .on_hover_text("For checkboxes, sliders, etc");
2164 ui.color_edit_button_srgba(mandatory_bg_fill);
2165 ui.end_row();
2166
2167 ui.label("Background stroke");
2168 ui.add(bg_stroke);
2169 ui.end_row();
2170
2171 ui.label("Corner radius");
2172 ui.add(corner_radius);
2173 ui.end_row();
2174
2175 ui.label("Foreground stroke (text)");
2176 ui.add(fg_stroke);
2177 ui.end_row();
2178
2179 ui.label("Expansion")
2180 .on_hover_text("make shapes this much larger");
2181 ui.add(DragValue::new(expansion).speed(0.1));
2182 ui.end_row();
2183 });
2184 }
2185}
2186
2187impl Visuals {
2188 pub fn ui(&mut self, ui: &mut crate::Ui) {
2189 let Self {
2190 dark_mode,
2191 text_options,
2192 override_text_color: _,
2193 weak_text_alpha,
2194 weak_text_color,
2195 widgets,
2196 selection,
2197 hyperlink_color,
2198 faint_bg_color,
2199 extreme_bg_color,
2200 text_edit_bg_color,
2201 code_bg_color,
2202 warn_fg_color,
2203 error_fg_color,
2204
2205 window_corner_radius,
2206 window_shadow,
2207 window_fill,
2208 window_stroke,
2209 window_highlight_topmost,
2210
2211 menu_corner_radius,
2212
2213 panel_fill,
2214
2215 popup_shadow,
2216
2217 resize_corner_size,
2218
2219 text_cursor,
2220
2221 clip_rect_margin,
2222 button_frame,
2223 collapsing_header_frame,
2224 indent_has_left_vline,
2225
2226 striped,
2227
2228 slider_trailing_fill,
2229 handle_shape,
2230 interact_cursor,
2231
2232 image_loading_spinners,
2233
2234 numeric_color_space,
2235 disabled_alpha,
2236 } = self;
2237
2238 fn ui_optional_color(
2239 ui: &mut Ui,
2240 color: &mut Option<Color32>,
2241 default_value: Color32,
2242 label: impl Into<WidgetText>,
2243 ) -> Response {
2244 let label_response = ui.label(label);
2245
2246 ui.horizontal(|ui| {
2247 let mut set = color.is_some();
2248 ui.checkbox(&mut set, "");
2249 if set {
2250 let color = color.get_or_insert(default_value);
2251 ui.color_edit_button_srgba(color);
2252 } else {
2253 *color = None;
2254 }
2255 });
2256
2257 ui.end_row();
2258
2259 label_response
2260 }
2261
2262 ui.collapsing("Background colors", |ui| {
2263 Grid::new("background_colors")
2264 .num_columns(2)
2265 .show(ui, |ui| {
2266 fn ui_color(
2267 ui: &mut Ui,
2268 color: &mut Color32,
2269 label: impl Into<WidgetText>,
2270 ) -> Response {
2271 let label_response = ui.label(label);
2272 ui.color_edit_button_srgba(color);
2273 ui.end_row();
2274 label_response
2275 }
2276
2277 ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
2278 ui_color(ui, window_fill, "Windows");
2279 ui_color(ui, panel_fill, "Panels");
2280 ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
2281 "Used for faint accentuation of interactive things, like striped grids.",
2282 );
2283 ui_color(ui, extreme_bg_color, "Extreme")
2284 .on_hover_text("Background of plots and paintings");
2285
2286 ui_optional_color(ui, text_edit_bg_color, *extreme_bg_color, "TextEdit")
2287 .on_hover_text("Background of TextEdit");
2288 });
2289 });
2290
2291 ui.collapsing("Text rendering", |ui| {
2292 fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
2293 ui.label(label.into().color(*color));
2294 ui.color_edit_button_srgba(color);
2295 ui.end_row();
2296 }
2297
2298 Grid::new("text_color").num_columns(2).show(ui, |ui| {
2299 ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
2300
2301 ui_text_color(
2302 ui,
2303 &mut widgets.inactive.fg_stroke.color,
2304 "Unhovered button",
2305 );
2306 ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
2307 ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
2308
2309 ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
2310 ui_text_color(ui, error_fg_color, RichText::new("Errors"));
2311
2312 ui_text_color(ui, hyperlink_color, "hyperlink_color");
2313
2314 ui.label(RichText::new("Code background").code())
2315 .on_hover_ui(|ui| {
2316 ui.horizontal(|ui| {
2317 ui.spacing_mut().item_spacing.x = 0.0;
2318 ui.label("For monospaced inlined text ");
2319 ui.code("like this");
2320 ui.label(".");
2321 });
2322 });
2323 ui.color_edit_button_srgba(code_bg_color);
2324 ui.end_row();
2325
2326 ui.label("Weak text alpha");
2327 ui.add_enabled(
2328 weak_text_color.is_none(),
2329 DragValue::new(weak_text_alpha).speed(0.01).range(0.0..=1.0),
2330 );
2331 ui.end_row();
2332
2333 ui_optional_color(
2334 ui,
2335 weak_text_color,
2336 widgets.noninteractive.text_color(),
2337 "Weak text color",
2338 );
2339 });
2340
2341 ui.add_space(4.0);
2342
2343 let TextOptions {
2344 max_texture_side: _,
2345 alpha_from_coverage,
2346 font_hinting,
2347 } = text_options;
2348
2349 text_alpha_from_coverage_ui(ui, alpha_from_coverage);
2350
2351 ui.checkbox(font_hinting, "Enable font hinting");
2352 });
2353
2354 ui.collapsing("Text cursor", |ui| {
2355 text_cursor.ui(ui);
2356 });
2357
2358 ui.collapsing("Window", |ui| {
2359 Grid::new("window")
2360 .num_columns(2)
2361 .spacing([12.0, 8.0])
2362 .striped(true)
2363 .show(ui, |ui| {
2364 ui.label("Fill");
2365 ui.color_edit_button_srgba(window_fill);
2366 ui.end_row();
2367
2368 ui.label("Stroke");
2369 ui.add(window_stroke);
2370 ui.end_row();
2371
2372 ui.label("Corner radius");
2373 ui.add(window_corner_radius);
2374 ui.end_row();
2375
2376 ui.label("Shadow");
2377 ui.add(window_shadow);
2378 ui.end_row();
2379 });
2380
2381 ui.checkbox(window_highlight_topmost, "Highlight topmost Window");
2382 });
2383
2384 ui.collapsing("Menus and popups", |ui| {
2385 Grid::new("menus_and_popups")
2386 .num_columns(2)
2387 .spacing([12.0, 8.0])
2388 .striped(true)
2389 .show(ui, |ui| {
2390 ui.label("Corner radius");
2391 ui.add(menu_corner_radius);
2392 ui.end_row();
2393
2394 ui.label("Shadow");
2395 ui.add(popup_shadow);
2396 ui.end_row();
2397 });
2398 });
2399
2400 ui.collapsing("Widgets", |ui| widgets.ui(ui));
2401 ui.collapsing("Selection", |ui| selection.ui(ui));
2402
2403 ui.collapsing("Misc", |ui| {
2404 ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
2405 ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
2406
2407 ui.checkbox(button_frame, "Button has a frame");
2408 ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
2409 ui.checkbox(
2410 indent_has_left_vline,
2411 "Paint a vertical line to the left of indented regions",
2412 );
2413
2414 ui.checkbox(striped, "Default stripes on grids and tables");
2415
2416 ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
2417
2418 handle_shape.ui(ui);
2419
2420 ComboBox::from_label("Interact cursor")
2421 .selected_text(
2422 interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")),
2423 )
2424 .show_ui(ui, |ui| {
2425 ui.selectable_value(interact_cursor, None, "-");
2426
2427 for cursor in CursorIcon::ALL {
2428 ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}"))
2429 .on_hover_cursor(cursor);
2430 }
2431 })
2432 .response
2433 .on_hover_text("Use this cursor when hovering buttons etc");
2434
2435 ui.checkbox(image_loading_spinners, "Image loading spinners")
2436 .on_hover_text("Show a spinner when an Image is loading");
2437
2438 ui.horizontal(|ui| {
2439 ui.label("Color picker type");
2440 numeric_color_space.toggle_button_ui(ui);
2441 });
2442
2443 ui.add(Slider::new(disabled_alpha, 0.0..=1.0).text("Disabled element alpha"));
2444 });
2445
2446 let dark_mode = *dark_mode;
2447 ui.vertical_centered(|ui| {
2448 reset_button_with(
2449 ui,
2450 self,
2451 "Reset visuals",
2452 if dark_mode {
2453 Self::dark()
2454 } else {
2455 Self::light()
2456 },
2457 );
2458 });
2459 }
2460}
2461
2462fn text_alpha_from_coverage_ui(ui: &mut Ui, alpha_from_coverage: &mut AlphaFromCoverage) {
2463 let mut dark_mode_special =
2464 *alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
2465
2466 ui.horizontal(|ui| {
2467 ui.label("Text rendering:");
2468
2469 ui.checkbox(&mut dark_mode_special, "Dark-mode special");
2470
2471 if dark_mode_special {
2472 *alpha_from_coverage = AlphaFromCoverage::DARK_MODE_DEFAULT;
2473 } else {
2474 let mut gamma = match alpha_from_coverage {
2475 AlphaFromCoverage::Linear => 1.0,
2476 AlphaFromCoverage::Gamma(gamma) => *gamma,
2477 AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, };
2479
2480 ui.add(
2481 DragValue::new(&mut gamma)
2482 .speed(0.01)
2483 .range(0.1..=4.0)
2484 .prefix("Gamma: "),
2485 );
2486
2487 if gamma == 1.0 {
2488 *alpha_from_coverage = AlphaFromCoverage::Linear;
2489 } else {
2490 *alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
2491 }
2492 }
2493 });
2494}
2495
2496impl TextCursorStyle {
2497 fn ui(&mut self, ui: &mut Ui) {
2498 let Self {
2499 stroke,
2500 preview,
2501 blink,
2502 on_duration,
2503 off_duration,
2504 } = self;
2505
2506 ui.horizontal(|ui| {
2507 ui.label("Stroke");
2508 ui.add(stroke);
2509 });
2510
2511 ui.checkbox(preview, "Preview text cursor on hover");
2512
2513 ui.checkbox(blink, "Blink");
2514
2515 if *blink {
2516 Grid::new("cursor_blink").show(ui, |ui| {
2517 ui.label("On time");
2518 ui.add(
2519 DragValue::new(on_duration)
2520 .speed(0.1)
2521 .range(0.0..=2.0)
2522 .suffix(" s"),
2523 );
2524 ui.end_row();
2525
2526 ui.label("Off time");
2527 ui.add(
2528 DragValue::new(off_duration)
2529 .speed(0.1)
2530 .range(0.0..=2.0)
2531 .suffix(" s"),
2532 );
2533 ui.end_row();
2534 });
2535 }
2536 }
2537}
2538
2539#[cfg(debug_assertions)]
2540impl DebugOptions {
2541 pub fn ui(&mut self, ui: &mut crate::Ui) {
2542 let Self {
2543 debug_on_hover,
2544 debug_on_hover_with_all_modifiers,
2545 hover_shows_next,
2546 show_expand_width,
2547 show_expand_height,
2548 show_resize,
2549 show_interactive_widgets,
2550 show_widget_hits,
2551 warn_if_rect_changes_id,
2552 show_unaligned,
2553 show_focused_widget,
2554 } = self;
2555
2556 {
2557 ui.checkbox(debug_on_hover, "Show widget info on hover.");
2558 ui.checkbox(
2559 debug_on_hover_with_all_modifiers,
2560 "Show widget info on hover if holding all modifier keys",
2561 );
2562
2563 ui.checkbox(hover_shows_next, "Show next widget placement on hover");
2564 }
2565
2566 ui.checkbox(
2567 show_expand_width,
2568 "Show which widgets make their parent wider",
2569 );
2570 ui.checkbox(
2571 show_expand_height,
2572 "Show which widgets make their parent higher",
2573 );
2574 ui.checkbox(show_resize, "Debug Resize");
2575
2576 ui.checkbox(
2577 show_interactive_widgets,
2578 "Show an overlay on all interactive widgets",
2579 );
2580
2581 ui.checkbox(show_widget_hits, "Show widgets under mouse pointer");
2582
2583 ui.checkbox(
2584 warn_if_rect_changes_id,
2585 "Warn if a Rect changes Id between frames",
2586 );
2587
2588 ui.checkbox(
2589 show_unaligned,
2590 "Show rectangles not aligned to integer point coordinates",
2591 );
2592
2593 ui.checkbox(
2594 show_focused_widget,
2595 "Highlight which widget has keyboard focus",
2596 );
2597
2598 ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options"));
2599 }
2600}
2601
2602fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive<f32>) -> impl Widget + '_ {
2604 move |ui: &mut crate::Ui| {
2605 ui.horizontal(|ui| {
2606 ui.add(
2607 DragValue::new(&mut value.x)
2608 .range(range.clone())
2609 .prefix("x: "),
2610 );
2611 ui.add(
2612 DragValue::new(&mut value.y)
2613 .range(range.clone())
2614 .prefix("y: "),
2615 );
2616 })
2617 .response
2618 }
2619}
2620
2621impl HandleShape {
2622 pub fn ui(&mut self, ui: &mut Ui) {
2623 ui.horizontal(|ui| {
2624 ui.label("Slider handle");
2625 ui.radio_value(self, Self::Circle, "Circle");
2626 if ui
2627 .radio(matches!(self, Self::Rect { .. }), "Rectangle")
2628 .clicked()
2629 {
2630 *self = Self::Rect { aspect_ratio: 0.5 };
2631 }
2632 if let Self::Rect { aspect_ratio } = self {
2633 ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio"));
2634 }
2635 });
2636 }
2637}
2638
2639#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2641#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
2642pub enum NumericColorSpace {
2643 GammaByte,
2647
2648 Linear,
2650 }
2652
2653impl NumericColorSpace {
2654 pub fn toggle_button_ui(&mut self, ui: &mut Ui) -> crate::Response {
2655 let tooltip = match self {
2656 Self::GammaByte => "Showing color values in 0-255 gamma space",
2657 Self::Linear => "Showing color values in 0-1 linear space",
2658 };
2659
2660 let mut response = ui.button(self.to_string()).on_hover_text(tooltip);
2661 if response.clicked() {
2662 *self = match self {
2663 Self::GammaByte => Self::Linear,
2664 Self::Linear => Self::GammaByte,
2665 };
2666 response.mark_changed();
2667 }
2668 response
2669 }
2670}
2671
2672impl std::fmt::Display for NumericColorSpace {
2673 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2674 match self {
2675 Self::GammaByte => write!(f, "U8"),
2676 Self::Linear => write!(f, "F"),
2677 }
2678 }
2679}
2680
2681impl Widget for &mut Margin {
2682 fn ui(self, ui: &mut Ui) -> Response {
2683 let mut same = self.is_same();
2684
2685 let response = if same {
2686 ui.horizontal(|ui| {
2687 ui.checkbox(&mut same, "same");
2688
2689 let mut value = self.left;
2690 ui.add(DragValue::new(&mut value).range(0.0..=100.0));
2691 *self = Margin::same(value);
2692 })
2693 .response
2694 } else {
2695 ui.vertical(|ui| {
2696 ui.checkbox(&mut same, "same");
2697
2698 crate::Grid::new("margin").num_columns(2).show(ui, |ui| {
2699 ui.label("Left");
2700 ui.add(DragValue::new(&mut self.left).range(0.0..=100.0));
2701 ui.end_row();
2702
2703 ui.label("Right");
2704 ui.add(DragValue::new(&mut self.right).range(0.0..=100.0));
2705 ui.end_row();
2706
2707 ui.label("Top");
2708 ui.add(DragValue::new(&mut self.top).range(0.0..=100.0));
2709 ui.end_row();
2710
2711 ui.label("Bottom");
2712 ui.add(DragValue::new(&mut self.bottom).range(0.0..=100.0));
2713 ui.end_row();
2714 });
2715 })
2716 .response
2717 };
2718
2719 if same {
2721 *self =
2722 Margin::from((self.leftf() + self.rightf() + self.topf() + self.bottomf()) / 4.0);
2723 } else {
2724 if self.is_same() {
2726 if self.right == i8::MAX {
2727 self.right = i8::MAX - 1;
2728 } else {
2729 self.right += 1;
2730 }
2731 }
2732 }
2733
2734 response
2735 }
2736}
2737
2738impl Widget for &mut CornerRadius {
2739 fn ui(self, ui: &mut Ui) -> Response {
2740 let mut same = self.is_same();
2741
2742 let response = if same {
2743 ui.horizontal(|ui| {
2744 ui.checkbox(&mut same, "same");
2745
2746 let mut cr = self.nw;
2747 ui.add(DragValue::new(&mut cr).range(0.0..=f32::INFINITY));
2748 *self = CornerRadius::same(cr);
2749 })
2750 .response
2751 } else {
2752 ui.vertical(|ui| {
2753 ui.checkbox(&mut same, "same");
2754
2755 crate::Grid::new("Corner radius")
2756 .num_columns(2)
2757 .show(ui, |ui| {
2758 ui.label("NW");
2759 ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
2760 ui.end_row();
2761
2762 ui.label("NE");
2763 ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
2764 ui.end_row();
2765
2766 ui.label("SW");
2767 ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
2768 ui.end_row();
2769
2770 ui.label("SE");
2771 ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
2772 ui.end_row();
2773 });
2774 })
2775 .response
2776 };
2777
2778 if same {
2780 *self = CornerRadius::from(self.average());
2781 } else {
2782 if self.is_same() {
2784 if self.average() == 0.0 {
2785 self.se = 1;
2786 } else {
2787 self.se -= 1;
2788 }
2789 }
2790 }
2791
2792 response
2793 }
2794}
2795
2796impl Widget for &mut Shadow {
2797 fn ui(self, ui: &mut Ui) -> Response {
2798 let epaint::Shadow {
2799 offset,
2800 blur,
2801 spread,
2802 color,
2803 } = self;
2804
2805 ui.vertical(|ui| {
2806 crate::Grid::new("shadow_ui").show(ui, |ui| {
2807 ui.add(
2808 DragValue::new(&mut offset[0])
2809 .speed(1.0)
2810 .range(-100.0..=100.0)
2811 .prefix("x: "),
2812 );
2813 ui.add(
2814 DragValue::new(&mut offset[1])
2815 .speed(1.0)
2816 .range(-100.0..=100.0)
2817 .prefix("y: "),
2818 );
2819 ui.end_row();
2820
2821 ui.add(
2822 DragValue::new(blur)
2823 .speed(1.0)
2824 .range(0.0..=100.0)
2825 .prefix("blur: "),
2826 );
2827
2828 ui.add(
2829 DragValue::new(spread)
2830 .speed(1.0)
2831 .range(0.0..=100.0)
2832 .prefix("spread: "),
2833 );
2834 });
2835 ui.color_edit_button_srgba(color);
2836 })
2837 .response
2838 }
2839}
2840
2841impl Widget for &mut Stroke {
2842 fn ui(self, ui: &mut Ui) -> Response {
2843 let Stroke { width, color } = self;
2844
2845 ui.horizontal(|ui| {
2846 ui.add(DragValue::new(width).speed(0.1).range(0.0..=1e9))
2847 .on_hover_text("Width");
2848 ui.color_edit_button_srgba(color);
2849
2850 let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size);
2852 let left = stroke_rect.left_center();
2853 let right = stroke_rect.right_center();
2854 ui.painter().line_segment([left, right], (*width, *color));
2855 })
2856 .response
2857 }
2858}
2859
2860impl Widget for &mut crate::Frame {
2861 fn ui(self, ui: &mut Ui) -> Response {
2862 let crate::Frame {
2863 inner_margin,
2864 outer_margin,
2865 corner_radius,
2866 shadow,
2867 fill,
2868 stroke,
2869 } = self;
2870
2871 crate::Grid::new("frame")
2872 .num_columns(2)
2873 .spacing([12.0, 8.0])
2874 .striped(true)
2875 .show(ui, |ui| {
2876 ui.label("Inner margin");
2877 ui.add(inner_margin);
2878 ui.end_row();
2879
2880 ui.label("Outer margin");
2881 ui.push_id("outer", |ui| ui.add(outer_margin));
2883 ui.end_row();
2884
2885 ui.label("Corner radius");
2886 ui.add(corner_radius);
2887 ui.end_row();
2888
2889 ui.label("Shadow");
2890 ui.add(shadow);
2891 ui.end_row();
2892
2893 ui.label("Fill");
2894 ui.color_edit_button_srgba(fill);
2895 ui.end_row();
2896
2897 ui.label("Stroke");
2898 ui.add(stroke);
2899 ui.end_row();
2900 })
2901 .response
2902 }
2903}
2904
2905impl Widget for &mut FontTweak {
2906 fn ui(self, ui: &mut Ui) -> Response {
2907 let original: FontTweak = self.clone();
2908
2909 let mut response = Grid::new("font_tweak")
2910 .num_columns(2)
2911 .show(ui, |ui| {
2912 let FontTweak {
2913 scale,
2914 y_offset_factor,
2915 y_offset,
2916 hinting_override,
2917 coords,
2918 } = self;
2919
2920 ui.label("Scale");
2921 let speed = *scale * 0.01;
2922 ui.add(DragValue::new(scale).range(0.01..=10.0).speed(speed));
2923 ui.end_row();
2924
2925 ui.label("y_offset_factor");
2926 ui.add(DragValue::new(y_offset_factor).speed(-0.0025));
2927 ui.end_row();
2928
2929 ui.label("y_offset");
2930 ui.add(DragValue::new(y_offset).speed(-0.02));
2931 ui.end_row();
2932
2933 ui.label("hinting_override");
2934 ComboBox::from_id_salt("hinting_override")
2935 .selected_text(match hinting_override {
2936 None => "None",
2937 Some(true) => "Enable",
2938 Some(false) => "Disable",
2939 })
2940 .show_ui(ui, |ui| {
2941 ui.selectable_value(hinting_override, None, "None");
2942 ui.selectable_value(hinting_override, Some(true), "Enable");
2943 ui.selectable_value(hinting_override, Some(false), "Disable");
2944 });
2945 ui.end_row();
2946
2947 ui.label("coords");
2948 ui.end_row();
2949 let mut to_remove = None;
2950 for (i, (tag, value)) in coords.as_mut().iter_mut().enumerate() {
2951 let tag_text = ui.ctx().data_mut(|data| {
2952 let tag = *tag;
2953 Arc::clone(data.get_temp_mut_or_insert_with(ui.id().with(i), move || {
2954 Arc::new(Mutex::new(tag.to_string()))
2955 }))
2956 });
2957
2958 let tag_text = &mut *tag_text.lock();
2959 let response = ui.text_edit_singleline(tag_text);
2960 if response.changed()
2961 && let Ok(new_tag) = Tag::new_checked(tag_text.as_bytes())
2962 {
2963 *tag = new_tag;
2964 }
2965 if !response.has_focus()
2968 && Tag::new_checked(tag_text.as_bytes()).ok() != Some(*tag)
2969 {
2970 *tag_text = tag.to_string();
2971 }
2972
2973 ui.add(DragValue::new(value));
2974 if ui.small_button("🗑").clicked() {
2975 to_remove = Some(i);
2976 }
2977 ui.end_row();
2978 }
2979 if let Some(i) = to_remove {
2980 coords.remove(i);
2981 }
2982 if ui.button("Add coord").clicked() {
2983 coords.push(b"wght", 0.0);
2984 }
2985 if ui.button("Clear coords").clicked() {
2986 coords.clear();
2987 }
2988 ui.end_row();
2989
2990 if ui.button("Reset").clicked() {
2991 *self = Default::default();
2992 }
2993 })
2994 .response;
2995
2996 if *self != original {
2997 response.mark_changed();
2998 }
2999
3000 response
3001 }
3002}