egui/
style.rs

1//! egui theme (spacing, colors, etc).
2
3#![allow(clippy::if_same_then_else)]
4
5use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
6
7use epaint::{Rounding, Shadow, Stroke};
8
9use crate::{
10    ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response,
11    RichText, WidgetText,
12};
13
14/// How to format numbers in e.g. a [`crate::DragValue`].
15#[derive(Clone)]
16pub struct NumberFormatter(
17    Arc<dyn 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String>,
18);
19
20impl NumberFormatter {
21    /// The first argument is the number to be formatted.
22    /// The second argument is the range of the number of decimals to show.
23    ///
24    /// See [`Self::format`] for the meaning of the `decimals` argument.
25    #[inline]
26    pub fn new(
27        formatter: impl 'static + Sync + Send + Fn(f64, RangeInclusive<usize>) -> String,
28    ) -> Self {
29        Self(Arc::new(formatter))
30    }
31
32    /// Format the given number with the given number of decimals.
33    ///
34    /// Decimals are counted after the decimal point.
35    ///
36    /// The minimum number of decimals is usually automatically calculated
37    /// from the sensitivity of the [`crate::DragValue`] and will usually be respected (e.g. include trailing zeroes),
38    /// but if the given value requires more decimals to represent accurately,
39    /// more decimals will be shown, up to the given max.
40    #[inline]
41    pub fn format(&self, value: f64, decimals: RangeInclusive<usize>) -> String {
42        (self.0)(value, decimals)
43    }
44}
45
46impl std::fmt::Debug for NumberFormatter {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.write_str("NumberFormatter")
49    }
50}
51
52impl PartialEq for NumberFormatter {
53    #[inline]
54    fn eq(&self, other: &Self) -> bool {
55        Arc::ptr_eq(&self.0, &other.0)
56    }
57}
58
59// ----------------------------------------------------------------------------
60
61/// Alias for a [`FontId`] (font of a certain size).
62///
63/// The font is found via look-up in [`Style::text_styles`].
64/// You can use [`TextStyle::resolve`] to do this lookup.
65#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
66#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
67pub enum TextStyle {
68    /// Used when small text is needed.
69    Small,
70
71    /// Normal labels. Easily readable, doesn't take up too much space.
72    Body,
73
74    /// Same size as [`Self::Body`], but used when monospace is important (for code snippets, aligning numbers, etc).
75    Monospace,
76
77    /// Buttons. Maybe slightly bigger than [`Self::Body`].
78    ///
79    /// Signifies that he item can be interacted with.
80    Button,
81
82    /// Heading. Probably larger than [`Self::Body`].
83    Heading,
84
85    /// A user-chosen style, found in [`Style::text_styles`].
86    /// ```
87    /// egui::TextStyle::Name("footing".into());
88    /// ````
89    Name(std::sync::Arc<str>),
90}
91
92impl std::fmt::Display for TextStyle {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        match self {
95            Self::Small => "Small".fmt(f),
96            Self::Body => "Body".fmt(f),
97            Self::Monospace => "Monospace".fmt(f),
98            Self::Button => "Button".fmt(f),
99            Self::Heading => "Heading".fmt(f),
100            Self::Name(name) => (*name).fmt(f),
101        }
102    }
103}
104
105impl TextStyle {
106    /// Look up this [`TextStyle`] in [`Style::text_styles`].
107    pub fn resolve(&self, style: &Style) -> FontId {
108        style.text_styles.get(self).cloned().unwrap_or_else(|| {
109            panic!(
110                "Failed to find {:?} in Style::text_styles. Available styles:\n{:#?}",
111                self,
112                style.text_styles()
113            )
114        })
115    }
116}
117
118// ----------------------------------------------------------------------------
119
120/// A way to select [`FontId`], either by picking one directly or by using a [`TextStyle`].
121pub enum FontSelection {
122    /// Default text style - will use [`TextStyle::Body`], unless
123    /// [`Style::override_font_id`] or [`Style::override_text_style`] is set.
124    Default,
125
126    /// Directly select size and font family
127    FontId(FontId),
128
129    /// Use a [`TextStyle`] to look up the [`FontId`] in [`Style::text_styles`].
130    Style(TextStyle),
131}
132
133impl Default for FontSelection {
134    #[inline]
135    fn default() -> Self {
136        Self::Default
137    }
138}
139
140impl FontSelection {
141    pub fn resolve(self, style: &Style) -> FontId {
142        match self {
143            Self::Default => {
144                if let Some(override_font_id) = &style.override_font_id {
145                    override_font_id.clone()
146                } else if let Some(text_style) = &style.override_text_style {
147                    text_style.resolve(style)
148                } else {
149                    TextStyle::Body.resolve(style)
150                }
151            }
152            Self::FontId(font_id) => font_id,
153            Self::Style(text_style) => text_style.resolve(style),
154        }
155    }
156}
157
158impl From<FontId> for FontSelection {
159    #[inline(always)]
160    fn from(font_id: FontId) -> Self {
161        Self::FontId(font_id)
162    }
163}
164
165impl From<TextStyle> for FontSelection {
166    #[inline(always)]
167    fn from(text_style: TextStyle) -> Self {
168        Self::Style(text_style)
169    }
170}
171
172// ----------------------------------------------------------------------------
173
174/// Specifies the look and feel of egui.
175///
176/// You can change the visuals of a [`Ui`] with [`Ui::style_mut`]
177/// and of everything with [`crate::Context::set_style`].
178///
179/// If you want to change fonts, use [`crate::Context::set_fonts`] instead.
180#[derive(Clone, Debug, PartialEq)]
181#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
182#[cfg_attr(feature = "serde", serde(default))]
183pub struct Style {
184    /// If set this will change the default [`TextStyle`] for all widgets.
185    ///
186    /// On most widgets you can also set an explicit text style,
187    /// which will take precedence over this.
188    pub override_text_style: Option<TextStyle>,
189
190    /// If set this will change the font family and size for all widgets.
191    ///
192    /// On most widgets you can also set an explicit text style,
193    /// which will take precedence over this.
194    pub override_font_id: Option<FontId>,
195
196    /// The [`FontFamily`] and size you want to use for a specific [`TextStyle`].
197    ///
198    /// The most convenient way to look something up in this is to use [`TextStyle::resolve`].
199    ///
200    /// If you would like to overwrite app `text_styles`
201    ///
202    /// ```
203    /// # let mut ctx = egui::Context::default();
204    /// use egui::FontFamily::Proportional;
205    /// use egui::FontId;
206    /// use egui::TextStyle::*;
207    ///
208    /// // Get current context style
209    /// let mut style = (*ctx.style()).clone();
210    ///
211    /// // Redefine text_styles
212    /// style.text_styles = [
213    ///   (Heading, FontId::new(30.0, Proportional)),
214    ///   (Name("Heading2".into()), FontId::new(25.0, Proportional)),
215    ///   (Name("Context".into()), FontId::new(23.0, Proportional)),
216    ///   (Body, FontId::new(18.0, Proportional)),
217    ///   (Monospace, FontId::new(14.0, Proportional)),
218    ///   (Button, FontId::new(14.0, Proportional)),
219    ///   (Small, FontId::new(10.0, Proportional)),
220    /// ].into();
221    ///
222    /// // Mutate global style with above changes
223    /// ctx.set_style(style);
224    /// ```
225    pub text_styles: BTreeMap<TextStyle, FontId>,
226
227    /// The style to use for [`DragValue`] text.
228    pub drag_value_text_style: TextStyle,
229
230    /// How to format numbers as strings, e.g. in a [`crate::DragValue`].
231    ///
232    /// You can override this to e.g. add thousands separators.
233    #[cfg_attr(feature = "serde", serde(skip))]
234    pub number_formatter: NumberFormatter,
235
236    /// If set, labels, buttons, etc. will use this to determine whether to wrap the text at the
237    /// right edge of the [`Ui`] they are in. By default, this is `None`.
238    ///
239    /// **Note**: this API is deprecated, use `wrap_mode` instead.
240    ///
241    /// * `None`: use `wrap_mode` instead
242    /// * `Some(true)`: wrap mode defaults to [`crate::TextWrapMode::Wrap`]
243    /// * `Some(false)`: wrap mode defaults to [`crate::TextWrapMode::Extend`]
244    #[deprecated = "Use wrap_mode instead"]
245    pub wrap: Option<bool>,
246
247    /// If set, labels, buttons, etc. will use this to determine whether to wrap or truncate the
248    /// text at the right edge of the [`Ui`] they are in, or to extend it. By default, this is
249    /// `None`.
250    ///
251    /// * `None`: follow layout (with may wrap)
252    /// * `Some(mode)`: use the specified mode as default
253    pub wrap_mode: Option<crate::TextWrapMode>,
254
255    /// Sizes and distances between widgets
256    pub spacing: Spacing,
257
258    /// How and when interaction happens.
259    pub interaction: Interaction,
260
261    /// Colors etc.
262    pub visuals: Visuals,
263
264    /// How many seconds a typical animation should last.
265    pub animation_time: f32,
266
267    /// Options to help debug why egui behaves strangely.
268    ///
269    /// Only available in debug builds.
270    #[cfg(debug_assertions)]
271    pub debug: DebugOptions,
272
273    /// Show tooltips explaining [`DragValue`]:s etc when hovered.
274    ///
275    /// This only affects a few egui widgets.
276    pub explanation_tooltips: bool,
277
278    /// Show the URL of hyperlinks in a tooltip when hovered.
279    pub url_in_tooltip: bool,
280
281    /// If true and scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift
282    pub always_scroll_the_only_direction: bool,
283}
284
285#[test]
286fn style_impl_send_sync() {
287    fn assert_send_sync<T: Send + Sync>() {}
288    assert_send_sync::<Style>();
289}
290
291impl Style {
292    // TODO(emilk): rename style.interact() to maybe… `style.interactive` ?
293    /// Use this style for interactive things.
294    /// Note that you must already have a response,
295    /// i.e. you must allocate space and interact BEFORE painting the widget!
296    pub fn interact(&self, response: &Response) -> &WidgetVisuals {
297        self.visuals.widgets.style(response)
298    }
299
300    pub fn interact_selectable(&self, response: &Response, selected: bool) -> WidgetVisuals {
301        let mut visuals = *self.visuals.widgets.style(response);
302        if selected {
303            visuals.weak_bg_fill = self.visuals.selection.bg_fill;
304            visuals.bg_fill = self.visuals.selection.bg_fill;
305            // visuals.bg_stroke = self.visuals.selection.stroke;
306            visuals.fg_stroke = self.visuals.selection.stroke;
307        }
308        visuals
309    }
310
311    /// Style to use for non-interactive widgets.
312    pub fn noninteractive(&self) -> &WidgetVisuals {
313        &self.visuals.widgets.noninteractive
314    }
315
316    /// All known text styles.
317    pub fn text_styles(&self) -> Vec<TextStyle> {
318        self.text_styles.keys().cloned().collect()
319    }
320}
321
322/// Controls the sizes and distances between widgets.
323#[derive(Clone, Debug, PartialEq)]
324#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
325#[cfg_attr(feature = "serde", serde(default))]
326pub struct Spacing {
327    /// Horizontal and vertical spacing between widgets.
328    ///
329    /// To add extra space between widgets, use [`Ui::add_space`].
330    ///
331    /// `item_spacing` is inserted _after_ adding a widget, so to increase the spacing between
332    /// widgets `A` and `B` you need to change `item_spacing` before adding `A`.
333    pub item_spacing: Vec2,
334
335    /// Horizontal and vertical margins within a window frame.
336    pub window_margin: Margin,
337
338    /// Button size is text size plus this on each side
339    pub button_padding: Vec2,
340
341    /// Horizontal and vertical margins within a menu frame.
342    pub menu_margin: Margin,
343
344    /// Indent collapsing regions etc by this much.
345    pub indent: f32,
346
347    /// Minimum size of a [`DragValue`], color picker button, and other small widgets.
348    /// `interact_size.y` is the default height of button, slider, etc.
349    /// Anything clickable should be (at least) this size.
350    pub interact_size: Vec2, // TODO(emilk): rename min_interact_size ?
351
352    /// Default width of a [`Slider`].
353    pub slider_width: f32,
354
355    /// Default rail height of a [`Slider`].
356    pub slider_rail_height: f32,
357
358    /// Default (minimum) width of a [`ComboBox`].
359    pub combo_width: f32,
360
361    /// Default width of a [`TextEdit`].
362    pub text_edit_width: f32,
363
364    /// Checkboxes, radio button and collapsing headers have an icon at the start.
365    /// This is the width/height of the outer part of this icon (e.g. the BOX of the checkbox).
366    pub icon_width: f32,
367
368    /// Checkboxes, radio button and collapsing headers have an icon at the start.
369    /// This is the width/height of the inner part of this icon (e.g. the check of the checkbox).
370    pub icon_width_inner: f32,
371
372    /// Checkboxes, radio button and collapsing headers have an icon at the start.
373    /// This is the spacing between the icon and the text
374    pub icon_spacing: f32,
375
376    /// The size used for the [`Ui::max_rect`] the first frame.
377    ///
378    /// Text will wrap at this width, and images that expand to fill the available space
379    /// will expand to this size.
380    ///
381    /// If the contents are smaller than this size, the area will shrink to fit the contents.
382    /// If the contents overflow, the area will grow.
383    pub default_area_size: Vec2,
384
385    /// Width of a tooltip (`on_hover_ui`, `on_hover_text` etc).
386    pub tooltip_width: f32,
387
388    /// The default wrapping width of a menu.
389    ///
390    /// Items longer than this will wrap to a new line.
391    pub menu_width: f32,
392
393    /// Horizontal distance between a menu and a submenu.
394    pub menu_spacing: f32,
395
396    /// End indented regions with a horizontal line
397    pub indent_ends_with_horizontal_line: bool,
398
399    /// Height of a combo-box before showing scroll bars.
400    pub combo_height: f32,
401
402    /// Controls the spacing of a [`crate::ScrollArea`].
403    pub scroll: ScrollStyle,
404}
405
406impl Spacing {
407    /// Returns small icon rectangle and big icon rectangle
408    pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) {
409        let icon_width = self.icon_width;
410        let big_icon_rect = Rect::from_center_size(
411            pos2(rect.left() + icon_width / 2.0, rect.center().y),
412            vec2(icon_width, icon_width),
413        );
414
415        let small_icon_rect =
416            Rect::from_center_size(big_icon_rect.center(), Vec2::splat(self.icon_width_inner));
417
418        (small_icon_rect, big_icon_rect)
419    }
420}
421
422// ----------------------------------------------------------------------------
423
424/// Controls the spacing and visuals of a [`crate::ScrollArea`].
425///
426/// There are three presets to chose from:
427/// * [`Self::solid`]
428/// * [`Self::thin`]
429/// * [`Self::floating`]
430#[derive(Clone, Copy, Debug, PartialEq)]
431#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
432#[cfg_attr(feature = "serde", serde(default))]
433pub struct ScrollStyle {
434    /// If `true`, scroll bars float above the content, partially covering it.
435    ///
436    /// If `false`, the scroll bars allocate space, shrinking the area
437    /// available to the contents.
438    ///
439    /// This also changes the colors of the scroll-handle to make
440    /// it more promiment.
441    pub floating: bool,
442
443    /// The width of the scroll bars at it largest.
444    pub bar_width: f32,
445
446    /// Make sure the scroll handle is at least this big
447    pub handle_min_length: f32,
448
449    /// Margin between contents and scroll bar.
450    pub bar_inner_margin: f32,
451
452    /// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
453    /// Only makes sense for non-floating scroll bars.
454    pub bar_outer_margin: f32,
455
456    /// The thin width of floating scroll bars that the user is NOT hovering.
457    ///
458    /// When the user hovers the scroll bars they expand to [`Self::bar_width`].
459    pub floating_width: f32,
460
461    /// How much space is allocated for a floating scroll bar?
462    ///
463    /// Normally this is zero, but you could set this to something small
464    /// like 4.0 and set [`Self::dormant_handle_opacity`] and
465    /// [`Self::dormant_background_opacity`] to e.g. 0.5
466    /// so as to always show a thin scroll bar.
467    pub floating_allocated_width: f32,
468
469    /// If true, use colors with more contrast. Good for floating scroll bars.
470    pub foreground_color: bool,
471
472    /// The opaqueness of the background when the user is neither scrolling
473    /// nor hovering the scroll area.
474    ///
475    /// This is only for floating scroll bars.
476    /// Solid scroll bars are always opaque.
477    pub dormant_background_opacity: f32,
478
479    /// The opaqueness of the background when the user is hovering
480    /// the scroll area, but not the scroll bar.
481    ///
482    /// This is only for floating scroll bars.
483    /// Solid scroll bars are always opaque.
484    pub active_background_opacity: f32,
485
486    /// The opaqueness of the background when the user is hovering
487    /// over the scroll bars.
488    ///
489    /// This is only for floating scroll bars.
490    /// Solid scroll bars are always opaque.
491    pub interact_background_opacity: f32,
492
493    /// The opaqueness of the handle when the user is neither scrolling
494    /// nor hovering the scroll area.
495    ///
496    /// This is only for floating scroll bars.
497    /// Solid scroll bars are always opaque.
498    pub dormant_handle_opacity: f32,
499
500    /// The opaqueness of the handle when the user is hovering
501    /// the scroll area, but not the scroll bar.
502    ///
503    /// This is only for floating scroll bars.
504    /// Solid scroll bars are always opaque.
505    pub active_handle_opacity: f32,
506
507    /// The opaqueness of the handle when the user is hovering
508    /// over the scroll bars.
509    ///
510    /// This is only for floating scroll bars.
511    /// Solid scroll bars are always opaque.
512    pub interact_handle_opacity: f32,
513}
514
515impl Default for ScrollStyle {
516    fn default() -> Self {
517        Self::floating()
518    }
519}
520
521impl ScrollStyle {
522    /// Solid scroll bars that always use up space
523    pub fn solid() -> Self {
524        Self {
525            floating: false,
526            bar_width: 6.0,
527            handle_min_length: 12.0,
528            bar_inner_margin: 4.0,
529            bar_outer_margin: 0.0,
530            floating_width: 2.0,
531            floating_allocated_width: 0.0,
532
533            foreground_color: false,
534
535            dormant_background_opacity: 0.0,
536            active_background_opacity: 0.4,
537            interact_background_opacity: 0.7,
538
539            dormant_handle_opacity: 0.0,
540            active_handle_opacity: 0.6,
541            interact_handle_opacity: 1.0,
542        }
543    }
544
545    /// Thin scroll bars that expand on hover
546    pub fn thin() -> Self {
547        Self {
548            floating: true,
549            bar_width: 10.0,
550            floating_allocated_width: 6.0,
551            foreground_color: false,
552
553            dormant_background_opacity: 1.0,
554            dormant_handle_opacity: 1.0,
555
556            active_background_opacity: 1.0,
557            active_handle_opacity: 1.0,
558
559            // Be translucent when expanded so we can see the content
560            interact_background_opacity: 0.6,
561            interact_handle_opacity: 0.6,
562
563            ..Self::solid()
564        }
565    }
566
567    /// No scroll bars until you hover the scroll area,
568    /// at which time they appear faintly, and then expand
569    /// when you hover the scroll bars.
570    pub fn floating() -> Self {
571        Self {
572            floating: true,
573            bar_width: 10.0,
574            foreground_color: true,
575            floating_allocated_width: 0.0,
576            dormant_background_opacity: 0.0,
577            dormant_handle_opacity: 0.0,
578            ..Self::solid()
579        }
580    }
581
582    /// Width of a solid vertical scrollbar, or height of a horizontal scroll bar, when it is at its widest.
583    pub fn allocated_width(&self) -> f32 {
584        if self.floating {
585            self.floating_allocated_width
586        } else {
587            self.bar_inner_margin + self.bar_width + self.bar_outer_margin
588        }
589    }
590
591    pub fn ui(&mut self, ui: &mut Ui) {
592        ui.horizontal(|ui| {
593            ui.label("Presets:");
594            ui.selectable_value(self, Self::solid(), "Solid");
595            ui.selectable_value(self, Self::thin(), "Thin");
596            ui.selectable_value(self, Self::floating(), "Floating");
597        });
598
599        ui.collapsing("Details", |ui| {
600            self.details_ui(ui);
601        });
602    }
603
604    pub fn details_ui(&mut self, ui: &mut Ui) {
605        let Self {
606            floating,
607            bar_width,
608            handle_min_length,
609            bar_inner_margin,
610            bar_outer_margin,
611            floating_width,
612            floating_allocated_width,
613
614            foreground_color,
615
616            dormant_background_opacity,
617            active_background_opacity,
618            interact_background_opacity,
619            dormant_handle_opacity,
620            active_handle_opacity,
621            interact_handle_opacity,
622        } = self;
623
624        ui.horizontal(|ui| {
625            ui.label("Type:");
626            ui.selectable_value(floating, false, "Solid");
627            ui.selectable_value(floating, true, "Floating");
628        });
629
630        ui.horizontal(|ui| {
631            ui.add(DragValue::new(bar_width).range(0.0..=32.0));
632            ui.label("Full bar width");
633        });
634        if *floating {
635            ui.horizontal(|ui| {
636                ui.add(DragValue::new(floating_width).range(0.0..=32.0));
637                ui.label("Thin bar width");
638            });
639            ui.horizontal(|ui| {
640                ui.add(DragValue::new(floating_allocated_width).range(0.0..=32.0));
641                ui.label("Allocated width");
642            });
643        }
644
645        ui.horizontal(|ui| {
646            ui.add(DragValue::new(handle_min_length).range(0.0..=32.0));
647            ui.label("Minimum handle length");
648        });
649        ui.horizontal(|ui| {
650            ui.add(DragValue::new(bar_outer_margin).range(0.0..=32.0));
651            ui.label("Outer margin");
652        });
653
654        ui.horizontal(|ui| {
655            ui.label("Color:");
656            ui.selectable_value(foreground_color, false, "Background");
657            ui.selectable_value(foreground_color, true, "Foreground");
658        });
659
660        if *floating {
661            crate::Grid::new("opacity").show(ui, |ui| {
662                fn opacity_ui(ui: &mut Ui, opacity: &mut f32) {
663                    ui.add(DragValue::new(opacity).speed(0.01).range(0.0..=1.0));
664                }
665
666                ui.label("Opacity");
667                ui.label("Dormant");
668                ui.label("Active");
669                ui.label("Interacting");
670                ui.end_row();
671
672                ui.label("Background:");
673                opacity_ui(ui, dormant_background_opacity);
674                opacity_ui(ui, active_background_opacity);
675                opacity_ui(ui, interact_background_opacity);
676                ui.end_row();
677
678                ui.label("Handle:");
679                opacity_ui(ui, dormant_handle_opacity);
680                opacity_ui(ui, active_handle_opacity);
681                opacity_ui(ui, interact_handle_opacity);
682                ui.end_row();
683            });
684        } else {
685            ui.horizontal(|ui| {
686                ui.add(DragValue::new(bar_inner_margin).range(0.0..=32.0));
687                ui.label("Inner margin");
688            });
689        }
690    }
691}
692
693// ----------------------------------------------------------------------------
694
695/// How and when interaction happens.
696#[derive(Clone, Debug, PartialEq)]
697#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
698#[cfg_attr(feature = "serde", serde(default))]
699pub struct Interaction {
700    /// How close a widget must be to the mouse to have a chance to register as a click or drag.
701    ///
702    /// If this is larger than zero, it gets easier to hit widgets,
703    /// which is important for e.g. touch screens.
704    pub interact_radius: f32,
705
706    /// Radius of the interactive area of the side of a window during drag-to-resize.
707    pub resize_grab_radius_side: f32,
708
709    /// Radius of the interactive area of the corner of a window during drag-to-resize.
710    pub resize_grab_radius_corner: f32,
711
712    /// If `false`, tooltips will show up anytime you hover anything, even if mouse is still moving
713    pub show_tooltips_only_when_still: bool,
714
715    /// Delay in seconds before showing tooltips after the mouse stops moving
716    pub tooltip_delay: f32,
717
718    /// If you have waited for a tooltip and then hover some other widget within
719    /// this many seconds, then show the new tooltip right away,
720    /// skipping [`Self::tooltip_delay`].
721    ///
722    /// This lets the user quickly move over some dead space to hover the next thing.
723    pub tooltip_grace_time: f32,
724
725    /// Can you select the text on a [`crate::Label`] by default?
726    pub selectable_labels: bool,
727
728    /// Can the user select text that span multiple labels?
729    ///
730    /// The default is `true`, but text selection can be slightly glitchy,
731    /// so you may want to disable it.
732    pub multi_widget_text_select: bool,
733}
734
735/// Look and feel of the text cursor.
736#[derive(Clone, Debug, PartialEq)]
737#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
738#[cfg_attr(feature = "serde", serde(default))]
739pub struct TextCursorStyle {
740    /// The color and width of the text cursor
741    pub stroke: Stroke,
742
743    /// Show where the text cursor would be if you clicked?
744    pub preview: bool,
745
746    /// Should the cursor blink?
747    pub blink: bool,
748
749    /// When blinking, this is how long the cursor is visible.
750    pub on_duration: f32,
751
752    /// When blinking, this is how long the cursor is invisible.
753    pub off_duration: f32,
754}
755
756impl Default for TextCursorStyle {
757    fn default() -> Self {
758        Self {
759            stroke: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), // Dark mode
760            preview: false,
761            blink: true,
762            on_duration: 0.5,
763            off_duration: 0.5,
764        }
765    }
766}
767
768/// Controls the visual style (colors etc) of egui.
769///
770/// You can change the visuals of a [`Ui`] with [`Ui::visuals_mut`]
771/// and of everything with [`crate::Context::set_visuals`].
772///
773/// If you want to change fonts, use [`crate::Context::set_fonts`] instead.
774#[derive(Clone, Debug, PartialEq)]
775#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
776#[cfg_attr(feature = "serde", serde(default))]
777pub struct Visuals {
778    /// If true, the visuals are overall dark with light text.
779    /// If false, the visuals are overall light with dark text.
780    ///
781    /// NOTE: setting this does very little by itself,
782    /// this is more to provide a convenient summary of the rest of the settings.
783    pub dark_mode: bool,
784
785    /// Override default text color for all text.
786    ///
787    /// This is great for setting the color of text for any widget.
788    ///
789    /// If `text_color` is `None` (default), then the text color will be the same as the
790    /// foreground stroke color (`WidgetVisuals::fg_stroke`)
791    /// and will depend on whether or not the widget is being interacted with.
792    ///
793    /// In the future we may instead modulate
794    /// the `text_color` based on whether or not it is interacted with
795    /// so that `visuals.text_color` is always used,
796    /// but its alpha may be different based on whether or not
797    /// it is disabled, non-interactive, hovered etc.
798    pub override_text_color: Option<Color32>,
799
800    /// Visual styles of widgets
801    pub widgets: Widgets,
802
803    pub selection: Selection,
804
805    /// The color used for [`Hyperlink`],
806    pub hyperlink_color: Color32,
807
808    /// Something just barely different from the background color.
809    /// Used for [`crate::Grid::striped`].
810    pub faint_bg_color: Color32,
811
812    /// Very dark or light color (for corresponding theme).
813    /// Used as the background of text edits, scroll bars and others things
814    /// that needs to look different from other interactive stuff.
815    pub extreme_bg_color: Color32,
816
817    /// Background color behind code-styled monospaced labels.
818    pub code_bg_color: Color32,
819
820    /// A good color for warning text (e.g. orange).
821    pub warn_fg_color: Color32,
822
823    /// A good color for error text (e.g. red).
824    pub error_fg_color: Color32,
825
826    pub window_rounding: Rounding,
827    pub window_shadow: Shadow,
828    pub window_fill: Color32,
829    pub window_stroke: Stroke,
830
831    /// Highlight the topmost window.
832    pub window_highlight_topmost: bool,
833
834    pub menu_rounding: Rounding,
835
836    /// Panel background color
837    pub panel_fill: Color32,
838
839    pub popup_shadow: Shadow,
840
841    pub resize_corner_size: f32,
842
843    /// How the text cursor acts.
844    pub text_cursor: TextCursorStyle,
845
846    /// Allow child widgets to be just on the border and still have a stroke with some thickness
847    pub clip_rect_margin: f32,
848
849    /// Show a background behind buttons.
850    pub button_frame: bool,
851
852    /// Show a background behind collapsing headers.
853    pub collapsing_header_frame: bool,
854
855    /// Draw a vertical lien left of indented region, in e.g. [`crate::CollapsingHeader`].
856    pub indent_has_left_vline: bool,
857
858    /// Whether or not Grids and Tables should be striped by default
859    /// (have alternating rows differently colored).
860    pub striped: bool,
861
862    /// Show trailing color behind the circle of a [`Slider`]. Default is OFF.
863    ///
864    /// Enabling this will affect ALL sliders, and can be enabled/disabled per slider with [`Slider::trailing_fill`].
865    pub slider_trailing_fill: bool,
866
867    /// Shape of the handle for sliders and similar widgets.
868    ///
869    /// Changing this will affect ALL sliders, and can be enabled/disabled per slider with [`Slider::handle_shape`].
870    pub handle_shape: HandleShape,
871
872    /// Should the cursor change when the user hovers over an interactive/clickable item?
873    ///
874    /// This is consistent with a lot of browser-based applications (vscode, github
875    /// all turn your cursor into [`CursorIcon::PointingHand`] when a button is
876    /// hovered) but it is inconsistent with native UI toolkits.
877    pub interact_cursor: Option<CursorIcon>,
878
879    /// Show a spinner when loading an image.
880    pub image_loading_spinners: bool,
881
882    /// How to display numeric color values.
883    pub numeric_color_space: NumericColorSpace,
884}
885
886impl Visuals {
887    #[inline(always)]
888    pub fn noninteractive(&self) -> &WidgetVisuals {
889        &self.widgets.noninteractive
890    }
891
892    // Non-interactive text color.
893    pub fn text_color(&self) -> Color32 {
894        self.override_text_color
895            .unwrap_or_else(|| self.widgets.noninteractive.text_color())
896    }
897
898    pub fn weak_text_color(&self) -> Color32 {
899        self.gray_out(self.text_color())
900    }
901
902    #[inline(always)]
903    pub fn strong_text_color(&self) -> Color32 {
904        self.widgets.active.text_color()
905    }
906
907    /// Window background color.
908    #[inline(always)]
909    pub fn window_fill(&self) -> Color32 {
910        self.window_fill
911    }
912
913    #[inline(always)]
914    pub fn window_stroke(&self) -> Stroke {
915        self.window_stroke
916    }
917
918    /// When fading out things, we fade the colors towards this.
919    // TODO(emilk): replace with an alpha
920    #[inline(always)]
921    pub fn fade_out_to_color(&self) -> Color32 {
922        self.widgets.noninteractive.weak_bg_fill
923    }
924
925    /// Returned a "grayed out" version of the given color.
926    #[inline(always)]
927    pub fn gray_out(&self, color: Color32) -> Color32 {
928        crate::ecolor::tint_color_towards(color, self.fade_out_to_color())
929    }
930}
931
932/// Selected text, selected elements etc
933#[derive(Clone, Copy, Debug, PartialEq)]
934#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
935#[cfg_attr(feature = "serde", serde(default))]
936pub struct Selection {
937    pub bg_fill: Color32,
938    pub stroke: Stroke,
939}
940
941/// Shape of the handle for sliders and similar widgets.
942#[derive(Clone, Copy, Debug, PartialEq)]
943#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
944pub enum HandleShape {
945    /// Circular handle
946    Circle,
947
948    /// Rectangular handle
949    Rect {
950        /// Aspect ratio of the rectangle. Set to < 1.0 to make it narrower.
951        aspect_ratio: f32,
952    },
953}
954
955/// The visuals of widgets for different states of interaction.
956#[derive(Clone, Debug, PartialEq)]
957#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
958#[cfg_attr(feature = "serde", serde(default))]
959pub struct Widgets {
960    /// The style of a widget that you cannot interact with.
961    /// * `noninteractive.bg_stroke` is the outline of windows.
962    /// * `noninteractive.bg_fill` is the background color of windows.
963    /// * `noninteractive.fg_stroke` is the normal text color.
964    pub noninteractive: WidgetVisuals,
965
966    /// The style of an interactive widget, such as a button, at rest.
967    pub inactive: WidgetVisuals,
968
969    /// The style of an interactive widget while you hover it, or when it is highlighted.
970    ///
971    /// See [`Response::hovered`], [`Response::highlighted`] and [`Response::highlight`].
972    pub hovered: WidgetVisuals,
973
974    /// The style of an interactive widget as you are clicking or dragging it.
975    pub active: WidgetVisuals,
976
977    /// The style of a button that has an open menu beneath it (e.g. a combo-box)
978    pub open: WidgetVisuals,
979}
980
981impl Widgets {
982    pub fn style(&self, response: &Response) -> &WidgetVisuals {
983        if !response.sense.interactive() {
984            &self.noninteractive
985        } else if response.is_pointer_button_down_on() || response.has_focus() || response.clicked()
986        {
987            &self.active
988        } else if response.hovered() || response.highlighted() {
989            &self.hovered
990        } else {
991            &self.inactive
992        }
993    }
994}
995
996/// bg = background, fg = foreground.
997#[derive(Clone, Copy, Debug, PartialEq)]
998#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
999pub struct WidgetVisuals {
1000    /// Background color of widgets that must have a background fill,
1001    /// such as the slider background, a checkbox background, or a radio button background.
1002    ///
1003    /// Must never be [`Color32::TRANSPARENT`].
1004    pub bg_fill: Color32,
1005
1006    /// Background color of widgets that can _optionally_ have a background fill, such as buttons.
1007    ///
1008    /// May be [`Color32::TRANSPARENT`].
1009    pub weak_bg_fill: Color32,
1010
1011    /// For surrounding rectangle of things that need it,
1012    /// like buttons, the box of the checkbox, etc.
1013    /// Should maybe be called `frame_stroke`.
1014    pub bg_stroke: Stroke,
1015
1016    /// Button frames etc.
1017    pub rounding: Rounding,
1018
1019    /// Stroke and text color of the interactive part of a component (button text, slider grab, check-mark, …).
1020    pub fg_stroke: Stroke,
1021
1022    /// Make the frame this much larger.
1023    pub expansion: f32,
1024}
1025
1026impl WidgetVisuals {
1027    #[inline(always)]
1028    pub fn text_color(&self) -> Color32 {
1029        self.fg_stroke.color
1030    }
1031}
1032
1033/// Options for help debug egui by adding extra visualization
1034#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1035#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1036#[cfg(debug_assertions)]
1037pub struct DebugOptions {
1038    /// Always show callstack to ui on hover.
1039    ///
1040    /// Useful for figuring out where in the code some UI is being created.
1041    ///
1042    /// Only works in debug builds.
1043    /// Requires the `callstack` feature.
1044    /// Does not work on web.
1045    #[cfg(debug_assertions)]
1046    pub debug_on_hover: bool,
1047
1048    /// Show callstack for the current widget on hover if all modifier keys are pressed down.
1049    ///
1050    /// Useful for figuring out where in the code some UI is being created.
1051    ///
1052    /// Only works in debug builds.
1053    /// Requires the `callstack` feature.
1054    /// Does not work on web.
1055    ///
1056    /// Default is `true` in debug builds, on native, if the `callstack` feature is enabled.
1057    #[cfg(debug_assertions)]
1058    pub debug_on_hover_with_all_modifiers: bool,
1059
1060    /// If we show the hover ui, include where the next widget is placed.
1061    #[cfg(debug_assertions)]
1062    pub hover_shows_next: bool,
1063
1064    /// Show which widgets make their parent wider
1065    pub show_expand_width: bool,
1066
1067    /// Show which widgets make their parent higher
1068    pub show_expand_height: bool,
1069
1070    pub show_resize: bool,
1071
1072    /// Show an overlay on all interactive widgets.
1073    pub show_interactive_widgets: bool,
1074
1075    /// Show interesting widgets under the mouse cursor.
1076    pub show_widget_hits: bool,
1077}
1078
1079#[cfg(debug_assertions)]
1080impl Default for DebugOptions {
1081    fn default() -> Self {
1082        Self {
1083            debug_on_hover: false,
1084            debug_on_hover_with_all_modifiers: cfg!(feature = "callstack")
1085                && !cfg!(target_arch = "wasm32"),
1086            hover_shows_next: false,
1087            show_expand_width: false,
1088            show_expand_height: false,
1089            show_resize: false,
1090            show_interactive_widgets: false,
1091            show_widget_hits: false,
1092        }
1093    }
1094}
1095
1096// ----------------------------------------------------------------------------
1097
1098/// The default text styles of the default egui theme.
1099pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
1100    use FontFamily::{Monospace, Proportional};
1101
1102    [
1103        (TextStyle::Small, FontId::new(9.0, Proportional)),
1104        (TextStyle::Body, FontId::new(12.5, Proportional)),
1105        (TextStyle::Button, FontId::new(12.5, Proportional)),
1106        (TextStyle::Heading, FontId::new(18.0, Proportional)),
1107        (TextStyle::Monospace, FontId::new(12.0, Monospace)),
1108    ]
1109    .into()
1110}
1111
1112impl Default for Style {
1113    fn default() -> Self {
1114        #[allow(deprecated)]
1115        Self {
1116            override_font_id: None,
1117            override_text_style: None,
1118            text_styles: default_text_styles(),
1119            drag_value_text_style: TextStyle::Button,
1120            number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
1121            wrap: None,
1122            wrap_mode: None,
1123            spacing: Spacing::default(),
1124            interaction: Interaction::default(),
1125            visuals: Visuals::default(),
1126            animation_time: 1.0 / 12.0,
1127            #[cfg(debug_assertions)]
1128            debug: Default::default(),
1129            explanation_tooltips: false,
1130            url_in_tooltip: false,
1131            always_scroll_the_only_direction: false,
1132        }
1133    }
1134}
1135
1136impl Default for Spacing {
1137    fn default() -> Self {
1138        Self {
1139            item_spacing: vec2(8.0, 3.0),
1140            window_margin: Margin::same(6.0),
1141            menu_margin: Margin::same(6.0),
1142            button_padding: vec2(4.0, 1.0),
1143            indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing`
1144            interact_size: vec2(40.0, 18.0),
1145            slider_width: 100.0,
1146            slider_rail_height: 8.0,
1147            combo_width: 100.0,
1148            text_edit_width: 280.0,
1149            icon_width: 14.0,
1150            icon_width_inner: 8.0,
1151            icon_spacing: 4.0,
1152            default_area_size: vec2(600.0, 400.0),
1153            tooltip_width: 500.0,
1154            menu_width: 400.0,
1155            menu_spacing: 2.0,
1156            combo_height: 200.0,
1157            scroll: Default::default(),
1158            indent_ends_with_horizontal_line: false,
1159        }
1160    }
1161}
1162
1163impl Default for Interaction {
1164    fn default() -> Self {
1165        Self {
1166            interact_radius: 5.0,
1167            resize_grab_radius_side: 5.0,
1168            resize_grab_radius_corner: 10.0,
1169            show_tooltips_only_when_still: true,
1170            tooltip_delay: 0.5,
1171            tooltip_grace_time: 0.2,
1172            selectable_labels: true,
1173            multi_widget_text_select: true,
1174        }
1175    }
1176}
1177
1178impl Visuals {
1179    /// Default dark theme.
1180    pub fn dark() -> Self {
1181        Self {
1182            dark_mode: true,
1183            override_text_color: None,
1184            widgets: Widgets::default(),
1185            selection: Selection::default(),
1186            hyperlink_color: Color32::from_rgb(90, 170, 255),
1187            faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
1188            extreme_bg_color: Color32::from_gray(10),            // e.g. TextEdit background
1189            code_bg_color: Color32::from_gray(64),
1190            warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
1191            error_fg_color: Color32::from_rgb(255, 0, 0),  // red
1192
1193            window_rounding: Rounding::same(6.0),
1194            window_shadow: Shadow {
1195                offset: vec2(10.0, 20.0),
1196                blur: 15.0,
1197                spread: 0.0,
1198                color: Color32::from_black_alpha(96),
1199            },
1200            window_fill: Color32::from_gray(27),
1201            window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1202            window_highlight_topmost: true,
1203
1204            menu_rounding: Rounding::same(6.0),
1205
1206            panel_fill: Color32::from_gray(27),
1207
1208            popup_shadow: Shadow {
1209                offset: vec2(6.0, 10.0),
1210                blur: 8.0,
1211                spread: 0.0,
1212                color: Color32::from_black_alpha(96),
1213            },
1214
1215            resize_corner_size: 12.0,
1216
1217            text_cursor: Default::default(),
1218
1219            clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
1220            button_frame: true,
1221            collapsing_header_frame: false,
1222            indent_has_left_vline: true,
1223
1224            striped: false,
1225
1226            slider_trailing_fill: false,
1227            handle_shape: HandleShape::Circle,
1228
1229            interact_cursor: None,
1230
1231            image_loading_spinners: true,
1232
1233            numeric_color_space: NumericColorSpace::GammaByte,
1234        }
1235    }
1236
1237    /// Default light theme.
1238    pub fn light() -> Self {
1239        Self {
1240            dark_mode: false,
1241            widgets: Widgets::light(),
1242            selection: Selection::light(),
1243            hyperlink_color: Color32::from_rgb(0, 155, 255),
1244            faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
1245            extreme_bg_color: Color32::from_gray(255),           // e.g. TextEdit background
1246            code_bg_color: Color32::from_gray(230),
1247            warn_fg_color: Color32::from_rgb(255, 100, 0), // slightly orange red. it's difficult to find a warning color that pops on bright background.
1248            error_fg_color: Color32::from_rgb(255, 0, 0),  // red
1249
1250            window_shadow: Shadow {
1251                offset: vec2(10.0, 20.0),
1252                blur: 15.0,
1253                spread: 0.0,
1254                color: Color32::from_black_alpha(25),
1255            },
1256            window_fill: Color32::from_gray(248),
1257            window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
1258
1259            panel_fill: Color32::from_gray(248),
1260
1261            popup_shadow: Shadow {
1262                offset: vec2(6.0, 10.0),
1263                blur: 8.0,
1264                spread: 0.0,
1265                color: Color32::from_black_alpha(25),
1266            },
1267
1268            text_cursor: TextCursorStyle {
1269                stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
1270                ..Default::default()
1271            },
1272
1273            ..Self::dark()
1274        }
1275    }
1276}
1277
1278impl Default for Visuals {
1279    fn default() -> Self {
1280        Self::dark()
1281    }
1282}
1283
1284impl Selection {
1285    fn dark() -> Self {
1286        Self {
1287            bg_fill: Color32::from_rgb(0, 92, 128),
1288            stroke: Stroke::new(1.0, Color32::from_rgb(192, 222, 255)),
1289        }
1290    }
1291
1292    fn light() -> Self {
1293        Self {
1294            bg_fill: Color32::from_rgb(144, 209, 255),
1295            stroke: Stroke::new(1.0, Color32::from_rgb(0, 83, 125)),
1296        }
1297    }
1298}
1299
1300impl Default for Selection {
1301    fn default() -> Self {
1302        Self::dark()
1303    }
1304}
1305
1306impl Widgets {
1307    pub fn dark() -> Self {
1308        Self {
1309            noninteractive: WidgetVisuals {
1310                weak_bg_fill: Color32::from_gray(27),
1311                bg_fill: Color32::from_gray(27),
1312                bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines
1313                fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
1314                rounding: Rounding::same(2.0),
1315                expansion: 0.0,
1316            },
1317            inactive: WidgetVisuals {
1318                weak_bg_fill: Color32::from_gray(60), // button background
1319                bg_fill: Color32::from_gray(60),      // checkbox background
1320                bg_stroke: Default::default(),
1321                fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
1322                rounding: Rounding::same(2.0),
1323                expansion: 0.0,
1324            },
1325            hovered: WidgetVisuals {
1326                weak_bg_fill: Color32::from_gray(70),
1327                bg_fill: Color32::from_gray(70),
1328                bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
1329                fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
1330                rounding: Rounding::same(3.0),
1331                expansion: 1.0,
1332            },
1333            active: WidgetVisuals {
1334                weak_bg_fill: Color32::from_gray(55),
1335                bg_fill: Color32::from_gray(55),
1336                bg_stroke: Stroke::new(1.0, Color32::WHITE),
1337                fg_stroke: Stroke::new(2.0, Color32::WHITE),
1338                rounding: Rounding::same(2.0),
1339                expansion: 1.0,
1340            },
1341            open: WidgetVisuals {
1342                weak_bg_fill: Color32::from_gray(45),
1343                bg_fill: Color32::from_gray(27),
1344                bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1345                fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
1346                rounding: Rounding::same(2.0),
1347                expansion: 0.0,
1348            },
1349        }
1350    }
1351
1352    pub fn light() -> Self {
1353        Self {
1354            noninteractive: WidgetVisuals {
1355                weak_bg_fill: Color32::from_gray(248),
1356                bg_fill: Color32::from_gray(248),
1357                bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines
1358                fg_stroke: Stroke::new(1.0, Color32::from_gray(80)),  // normal text color
1359                rounding: Rounding::same(2.0),
1360                expansion: 0.0,
1361            },
1362            inactive: WidgetVisuals {
1363                weak_bg_fill: Color32::from_gray(230), // button background
1364                bg_fill: Color32::from_gray(230),      // checkbox background
1365                bg_stroke: Default::default(),
1366                fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // button text
1367                rounding: Rounding::same(2.0),
1368                expansion: 0.0,
1369            },
1370            hovered: WidgetVisuals {
1371                weak_bg_fill: Color32::from_gray(220),
1372                bg_fill: Color32::from_gray(220),
1373                bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
1374                fg_stroke: Stroke::new(1.5, Color32::BLACK),
1375                rounding: Rounding::same(3.0),
1376                expansion: 1.0,
1377            },
1378            active: WidgetVisuals {
1379                weak_bg_fill: Color32::from_gray(165),
1380                bg_fill: Color32::from_gray(165),
1381                bg_stroke: Stroke::new(1.0, Color32::BLACK),
1382                fg_stroke: Stroke::new(2.0, Color32::BLACK),
1383                rounding: Rounding::same(2.0),
1384                expansion: 1.0,
1385            },
1386            open: WidgetVisuals {
1387                weak_bg_fill: Color32::from_gray(220),
1388                bg_fill: Color32::from_gray(220),
1389                bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
1390                fg_stroke: Stroke::new(1.0, Color32::BLACK),
1391                rounding: Rounding::same(2.0),
1392                expansion: 0.0,
1393            },
1394        }
1395    }
1396}
1397
1398impl Default for Widgets {
1399    fn default() -> Self {
1400        Self::dark()
1401    }
1402}
1403
1404// ----------------------------------------------------------------------------
1405
1406use crate::{widgets::*, Ui};
1407
1408impl Style {
1409    pub fn ui(&mut self, ui: &mut crate::Ui) {
1410        #[allow(deprecated)]
1411        let Self {
1412            override_font_id,
1413            override_text_style,
1414            text_styles,
1415            drag_value_text_style,
1416            number_formatter: _, // can't change callbacks in the UI
1417            wrap: _,
1418            wrap_mode: _,
1419            spacing,
1420            interaction,
1421            visuals,
1422            animation_time,
1423            #[cfg(debug_assertions)]
1424            debug,
1425            explanation_tooltips,
1426            url_in_tooltip,
1427            always_scroll_the_only_direction,
1428        } = self;
1429
1430        visuals.light_dark_radio_buttons(ui);
1431
1432        crate::Grid::new("_options").show(ui, |ui| {
1433            ui.label("Override font id");
1434            ui.vertical(|ui| {
1435                ui.horizontal(|ui| {
1436                    ui.radio_value(override_font_id, None, "None");
1437                    if ui.radio(override_font_id.is_some(), "override").clicked() {
1438                        *override_font_id = Some(FontId::default());
1439                    }
1440                });
1441                if let Some(override_font_id) = override_font_id {
1442                    crate::introspection::font_id_ui(ui, override_font_id);
1443                }
1444            });
1445            ui.end_row();
1446
1447            ui.label("Override text style");
1448            crate::ComboBox::from_id_source("Override text style")
1449                .selected_text(match override_text_style {
1450                    None => "None".to_owned(),
1451                    Some(override_text_style) => override_text_style.to_string(),
1452                })
1453                .show_ui(ui, |ui| {
1454                    ui.selectable_value(override_text_style, None, "None");
1455                    let all_text_styles = ui.style().text_styles();
1456                    for style in all_text_styles {
1457                        let text =
1458                            crate::RichText::new(style.to_string()).text_style(style.clone());
1459                        ui.selectable_value(override_text_style, Some(style), text);
1460                    }
1461                });
1462            ui.end_row();
1463
1464            ui.label("Text style of DragValue");
1465            crate::ComboBox::from_id_source("drag_value_text_style")
1466                .selected_text(drag_value_text_style.to_string())
1467                .show_ui(ui, |ui| {
1468                    let all_text_styles = ui.style().text_styles();
1469                    for style in all_text_styles {
1470                        let text =
1471                            crate::RichText::new(style.to_string()).text_style(style.clone());
1472                        ui.selectable_value(drag_value_text_style, style, text);
1473                    }
1474                });
1475            ui.end_row();
1476
1477            ui.label("Animation duration");
1478            ui.add(
1479                DragValue::new(animation_time)
1480                    .range(0.0..=1.0)
1481                    .speed(0.02)
1482                    .suffix(" s"),
1483            );
1484            ui.end_row();
1485        });
1486
1487        ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles));
1488        ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
1489        ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
1490        ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
1491
1492        #[cfg(debug_assertions)]
1493        ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
1494
1495        ui.checkbox(explanation_tooltips, "Explanation tooltips")
1496            .on_hover_text(
1497                "Show explanatory text when hovering DragValue:s and other egui widgets",
1498            );
1499
1500        ui.checkbox(url_in_tooltip, "Show url when hovering links");
1501
1502        ui.checkbox(always_scroll_the_only_direction, "Always scroll the only enabled direction")
1503            .on_hover_text(
1504                "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift",
1505            );
1506
1507        ui.vertical_centered(|ui| reset_button(ui, self, "Reset style"));
1508    }
1509}
1510
1511fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
1512    ui.vertical(|ui| {
1513        crate::Grid::new("text_styles").show(ui, |ui| {
1514            for (text_style, font_id) in &mut *text_styles {
1515                ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
1516                crate::introspection::font_id_ui(ui, font_id);
1517                ui.end_row();
1518            }
1519        });
1520        crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles());
1521    })
1522    .response
1523}
1524
1525impl Spacing {
1526    pub fn ui(&mut self, ui: &mut crate::Ui) {
1527        let Self {
1528            item_spacing,
1529            window_margin,
1530            menu_margin,
1531            button_padding,
1532            indent,
1533            interact_size,
1534            slider_width,
1535            slider_rail_height,
1536            combo_width,
1537            text_edit_width,
1538            icon_width,
1539            icon_width_inner,
1540            icon_spacing,
1541            default_area_size,
1542            tooltip_width,
1543            menu_width,
1544            menu_spacing,
1545            indent_ends_with_horizontal_line,
1546            combo_height,
1547            scroll,
1548        } = self;
1549
1550        Grid::new("spacing")
1551            .num_columns(2)
1552            .spacing([12.0, 8.0])
1553            .striped(true)
1554            .show(ui, |ui| {
1555                ui.label("Item spacing");
1556                ui.add(two_drag_values(item_spacing, 0.0..=20.0));
1557                ui.end_row();
1558
1559                ui.label("Window margin");
1560                ui.add(window_margin);
1561                ui.end_row();
1562
1563                ui.label("Menu margin");
1564                ui.add(menu_margin);
1565                ui.end_row();
1566
1567                ui.label("Button padding");
1568                ui.add(two_drag_values(button_padding, 0.0..=20.0));
1569                ui.end_row();
1570
1571                ui.label("Interact size")
1572                    .on_hover_text("Minimum size of an interactive widget");
1573                ui.add(two_drag_values(interact_size, 4.0..=60.0));
1574                ui.end_row();
1575
1576                ui.label("Indent");
1577                ui.add(DragValue::new(indent).range(0.0..=100.0));
1578                ui.end_row();
1579
1580                ui.label("Slider width");
1581                ui.add(DragValue::new(slider_width).range(0.0..=1000.0));
1582                ui.end_row();
1583
1584                ui.label("Slider rail height");
1585                ui.add(DragValue::new(slider_rail_height).range(0.0..=50.0));
1586                ui.end_row();
1587
1588                ui.label("ComboBox width");
1589                ui.add(DragValue::new(combo_width).range(0.0..=1000.0));
1590                ui.end_row();
1591
1592                ui.label("Default area size");
1593                ui.add(two_drag_values(default_area_size, 0.0..=1000.0));
1594                ui.end_row();
1595
1596                ui.label("TextEdit width");
1597                ui.add(DragValue::new(text_edit_width).range(0.0..=1000.0));
1598                ui.end_row();
1599
1600                ui.label("Tooltip wrap width");
1601                ui.add(DragValue::new(tooltip_width).range(0.0..=1000.0));
1602                ui.end_row();
1603
1604                ui.label("Default menu width");
1605                ui.add(DragValue::new(menu_width).range(0.0..=1000.0));
1606                ui.end_row();
1607
1608                ui.label("Menu spacing")
1609                    .on_hover_text("Horizontal spacing between menus");
1610                ui.add(DragValue::new(menu_spacing).range(0.0..=10.0));
1611                ui.end_row();
1612
1613                ui.label("Checkboxes etc");
1614                ui.vertical(|ui| {
1615                    ui.add(
1616                        DragValue::new(icon_width)
1617                            .prefix("outer icon width:")
1618                            .range(0.0..=60.0),
1619                    );
1620                    ui.add(
1621                        DragValue::new(icon_width_inner)
1622                            .prefix("inner icon width:")
1623                            .range(0.0..=60.0),
1624                    );
1625                    ui.add(
1626                        DragValue::new(icon_spacing)
1627                            .prefix("spacing:")
1628                            .range(0.0..=10.0),
1629                    );
1630                });
1631                ui.end_row();
1632            });
1633
1634        ui.checkbox(
1635            indent_ends_with_horizontal_line,
1636            "End indented regions with a horizontal separator",
1637        );
1638
1639        ui.horizontal(|ui| {
1640            ui.label("Max height of a combo box");
1641            ui.add(DragValue::new(combo_height).range(0.0..=1000.0));
1642        });
1643
1644        ui.collapsing("Scroll Area", |ui| {
1645            scroll.ui(ui);
1646        });
1647
1648        ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing"));
1649    }
1650}
1651
1652impl Interaction {
1653    pub fn ui(&mut self, ui: &mut crate::Ui) {
1654        let Self {
1655            interact_radius,
1656            resize_grab_radius_side,
1657            resize_grab_radius_corner,
1658            show_tooltips_only_when_still,
1659            tooltip_delay,
1660            tooltip_grace_time,
1661            selectable_labels,
1662            multi_widget_text_select,
1663        } = self;
1664
1665        ui.spacing_mut().item_spacing = vec2(12.0, 8.0);
1666
1667        Grid::new("interaction")
1668            .num_columns(2)
1669            .striped(true)
1670            .show(ui, |ui| {
1671                ui.label("interact_radius")
1672                    .on_hover_text("Interact with the closest widget within this radius.");
1673                ui.add(DragValue::new(interact_radius).range(0.0..=20.0));
1674                ui.end_row();
1675
1676                ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize");
1677                ui.add(DragValue::new(resize_grab_radius_side).range(0.0..=20.0));
1678                ui.end_row();
1679
1680                ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize.");
1681                ui.add(DragValue::new(resize_grab_radius_corner).range(0.0..=20.0));
1682                ui.end_row();
1683
1684                ui.label("Tooltip delay").on_hover_text(
1685                    "Delay in seconds before showing tooltips after the mouse stops moving",
1686                );
1687                ui.add(
1688                    DragValue::new(tooltip_delay)
1689                        .range(0.0..=1.0)
1690                        .speed(0.05)
1691                        .suffix(" s"),
1692                );
1693                ui.end_row();
1694
1695                ui.label("Tooltip grace time").on_hover_text(
1696                    "If a tooltip is open and you hover another widget within this grace period, show the next tooltip right away",
1697                );
1698                ui.add(
1699                    DragValue::new(tooltip_grace_time)
1700                        .range(0.0..=1.0)
1701                        .speed(0.05)
1702                        .suffix(" s"),
1703                );
1704                ui.end_row();
1705            });
1706
1707        ui.checkbox(
1708            show_tooltips_only_when_still,
1709            "Only show tooltips if mouse is still",
1710        );
1711
1712        ui.horizontal(|ui| {
1713            ui.checkbox(selectable_labels, "Selectable text in labels");
1714            if *selectable_labels {
1715                ui.checkbox(multi_widget_text_select, "Across multiple labels");
1716            }
1717        });
1718
1719        ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings"));
1720    }
1721}
1722
1723impl Widgets {
1724    pub fn ui(&mut self, ui: &mut crate::Ui) {
1725        let Self {
1726            active,
1727            hovered,
1728            inactive,
1729            noninteractive,
1730            open,
1731        } = self;
1732
1733        ui.collapsing("Noninteractive", |ui| {
1734            ui.label(
1735                "The style of a widget that you cannot interact with, e.g. labels and separators.",
1736            );
1737            noninteractive.ui(ui);
1738        });
1739        ui.collapsing("Interactive but inactive", |ui| {
1740            ui.label("The style of an interactive widget, such as a button, at rest.");
1741            inactive.ui(ui);
1742        });
1743        ui.collapsing("Interactive and hovered", |ui| {
1744            ui.label("The style of an interactive widget while you hover it.");
1745            hovered.ui(ui);
1746        });
1747        ui.collapsing("Interactive and active", |ui| {
1748            ui.label("The style of an interactive widget as you are clicking or dragging it.");
1749            active.ui(ui);
1750        });
1751        ui.collapsing("Open menu", |ui| {
1752            ui.label("The style of an open combo-box or menu button");
1753            open.ui(ui);
1754        });
1755
1756        // ui.vertical_centered(|ui| reset_button(ui, self));
1757    }
1758}
1759
1760impl Selection {
1761    pub fn ui(&mut self, ui: &mut crate::Ui) {
1762        let Self { bg_fill, stroke } = self;
1763        ui.label("Selectable labels");
1764
1765        Grid::new("selectiom").num_columns(2).show(ui, |ui| {
1766            ui.label("Background fill");
1767            ui.color_edit_button_srgba(bg_fill);
1768            ui.end_row();
1769
1770            ui.label("Stroke");
1771            ui.add(stroke);
1772            ui.end_row();
1773        });
1774    }
1775}
1776
1777impl WidgetVisuals {
1778    pub fn ui(&mut self, ui: &mut crate::Ui) {
1779        let Self {
1780            weak_bg_fill,
1781            bg_fill: mandatory_bg_fill,
1782            bg_stroke,
1783            rounding,
1784            fg_stroke,
1785            expansion,
1786        } = self;
1787
1788        Grid::new("widget")
1789            .num_columns(2)
1790            .spacing([12.0, 8.0])
1791            .striped(true)
1792            .show(ui, |ui| {
1793                ui.label("Optional background fill")
1794                    .on_hover_text("For buttons, combo-boxes, etc");
1795                ui.color_edit_button_srgba(weak_bg_fill);
1796                ui.end_row();
1797
1798                ui.label("Mandatory background fill")
1799                    .on_hover_text("For checkboxes, sliders, etc");
1800                ui.color_edit_button_srgba(mandatory_bg_fill);
1801                ui.end_row();
1802
1803                ui.label("Background stroke");
1804                ui.add(bg_stroke);
1805                ui.end_row();
1806
1807                ui.label("Rounding");
1808                ui.add(rounding);
1809                ui.end_row();
1810
1811                ui.label("Foreground stroke (text)");
1812                ui.add(fg_stroke);
1813                ui.end_row();
1814
1815                ui.label("Expansion")
1816                    .on_hover_text("make shapes this much larger");
1817                ui.add(DragValue::new(expansion).speed(0.1));
1818                ui.end_row();
1819            });
1820    }
1821}
1822
1823impl Visuals {
1824    /// Show radio-buttons to switch between light and dark mode.
1825    pub fn light_dark_radio_buttons(&mut self, ui: &mut crate::Ui) {
1826        ui.horizontal(|ui| {
1827            ui.selectable_value(self, Self::light(), "☀ Light");
1828            ui.selectable_value(self, Self::dark(), "🌙 Dark");
1829        });
1830    }
1831
1832    /// Show small toggle-button for light and dark mode.
1833    #[must_use]
1834    pub fn light_dark_small_toggle_button(&self, ui: &mut crate::Ui) -> Option<Self> {
1835        #![allow(clippy::collapsible_else_if)]
1836        if self.dark_mode {
1837            if ui
1838                .add(Button::new("☀").frame(false))
1839                .on_hover_text("Switch to light mode")
1840                .clicked()
1841            {
1842                return Some(Self::light());
1843            }
1844        } else {
1845            if ui
1846                .add(Button::new("🌙").frame(false))
1847                .on_hover_text("Switch to dark mode")
1848                .clicked()
1849            {
1850                return Some(Self::dark());
1851            }
1852        }
1853        None
1854    }
1855
1856    pub fn ui(&mut self, ui: &mut crate::Ui) {
1857        let Self {
1858            dark_mode: _,
1859            override_text_color: _,
1860            widgets,
1861            selection,
1862            hyperlink_color,
1863            faint_bg_color,
1864            extreme_bg_color,
1865            code_bg_color,
1866            warn_fg_color,
1867            error_fg_color,
1868
1869            window_rounding,
1870            window_shadow,
1871            window_fill,
1872            window_stroke,
1873            window_highlight_topmost,
1874
1875            menu_rounding,
1876
1877            panel_fill,
1878
1879            popup_shadow,
1880
1881            resize_corner_size,
1882
1883            text_cursor,
1884
1885            clip_rect_margin,
1886            button_frame,
1887            collapsing_header_frame,
1888            indent_has_left_vline,
1889
1890            striped,
1891
1892            slider_trailing_fill,
1893            handle_shape,
1894            interact_cursor,
1895
1896            image_loading_spinners,
1897
1898            numeric_color_space,
1899        } = self;
1900
1901        ui.collapsing("Background Colors", |ui| {
1902            ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
1903            ui_color(ui, window_fill, "Windows");
1904            ui_color(ui, panel_fill, "Panels");
1905            ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
1906                "Used for faint accentuation of interactive things, like striped grids.",
1907            );
1908            ui_color(ui, extreme_bg_color, "Extreme")
1909                .on_hover_text("Background of plots and paintings");
1910        });
1911
1912        ui.collapsing("Text color", |ui| {
1913            ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
1914            ui_text_color(
1915                ui,
1916                &mut widgets.inactive.fg_stroke.color,
1917                "Unhovered button",
1918            );
1919            ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
1920            ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
1921
1922            ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
1923            ui_text_color(ui, error_fg_color, RichText::new("Errors"));
1924
1925            ui_text_color(ui, hyperlink_color, "hyperlink_color");
1926
1927            ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(
1928                |ui| {
1929                    ui.horizontal(|ui| {
1930                        ui.spacing_mut().item_spacing.x = 0.0;
1931                        ui.label("For monospaced inlined text ");
1932                        ui.code("like this");
1933                        ui.label(".");
1934                    });
1935                },
1936            );
1937        });
1938
1939        ui.collapsing("Text cursor", |ui| {
1940            text_cursor.ui(ui);
1941        });
1942
1943        ui.collapsing("Window", |ui| {
1944            Grid::new("window")
1945                .num_columns(2)
1946                .spacing([12.0, 8.0])
1947                .striped(true)
1948                .show(ui, |ui| {
1949                    ui.label("Fill");
1950                    ui.color_edit_button_srgba(window_fill);
1951                    ui.end_row();
1952
1953                    ui.label("Stroke");
1954                    ui.add(window_stroke);
1955                    ui.end_row();
1956
1957                    ui.label("Rounding");
1958                    ui.add(window_rounding);
1959                    ui.end_row();
1960
1961                    ui.label("Shadow");
1962                    ui.add(window_shadow);
1963                    ui.end_row();
1964                });
1965
1966            ui.checkbox(window_highlight_topmost, "Highlight topmost Window");
1967        });
1968
1969        ui.collapsing("Menus and popups", |ui| {
1970            Grid::new("menus_and_popups")
1971                .num_columns(2)
1972                .spacing([12.0, 8.0])
1973                .striped(true)
1974                .show(ui, |ui| {
1975                    ui.label("Rounding");
1976                    ui.add(menu_rounding);
1977                    ui.end_row();
1978
1979                    ui.label("Shadow");
1980                    ui.add(popup_shadow);
1981                    ui.end_row();
1982                });
1983        });
1984
1985        ui.collapsing("Widgets", |ui| widgets.ui(ui));
1986        ui.collapsing("Selection", |ui| selection.ui(ui));
1987
1988        ui.collapsing("Misc", |ui| {
1989            ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
1990            ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
1991
1992            ui.checkbox(button_frame, "Button has a frame");
1993            ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
1994            ui.checkbox(
1995                indent_has_left_vline,
1996                "Paint a vertical line to the left of indented regions",
1997            );
1998
1999            ui.checkbox(striped, "Default stripes on grids and tables");
2000
2001            ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
2002
2003            handle_shape.ui(ui);
2004
2005            ComboBox::from_label("Interact cursor")
2006                .selected_text(
2007                    interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")),
2008                )
2009                .show_ui(ui, |ui| {
2010                    ui.selectable_value(interact_cursor, None, "-");
2011
2012                    for cursor in CursorIcon::ALL {
2013                        ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}"))
2014                            .on_hover_cursor(cursor);
2015                    }
2016                })
2017                .response
2018                .on_hover_text("Use this cursor when hovering buttons etc");
2019
2020            ui.checkbox(image_loading_spinners, "Image loading spinners")
2021                .on_hover_text("Show a spinner when an Image is loading");
2022
2023            ui.horizontal(|ui| {
2024                ui.label("Color picker type");
2025                numeric_color_space.toggle_button_ui(ui);
2026            });
2027        });
2028
2029        ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals"));
2030    }
2031}
2032
2033impl TextCursorStyle {
2034    fn ui(&mut self, ui: &mut Ui) {
2035        let Self {
2036            stroke,
2037            preview,
2038            blink,
2039            on_duration,
2040            off_duration,
2041        } = self;
2042
2043        ui.horizontal(|ui| {
2044            ui.label("Stroke");
2045            ui.add(stroke);
2046        });
2047
2048        ui.checkbox(preview, "Preview text cursor on hover");
2049
2050        ui.checkbox(blink, "Blink");
2051
2052        if *blink {
2053            Grid::new("cursor_blink").show(ui, |ui| {
2054                ui.label("On time");
2055                ui.add(
2056                    DragValue::new(on_duration)
2057                        .speed(0.1)
2058                        .range(0.0..=2.0)
2059                        .suffix(" s"),
2060                );
2061                ui.end_row();
2062
2063                ui.label("Off time");
2064                ui.add(
2065                    DragValue::new(off_duration)
2066                        .speed(0.1)
2067                        .range(0.0..=2.0)
2068                        .suffix(" s"),
2069                );
2070                ui.end_row();
2071            });
2072        }
2073    }
2074}
2075
2076#[cfg(debug_assertions)]
2077impl DebugOptions {
2078    pub fn ui(&mut self, ui: &mut crate::Ui) {
2079        let Self {
2080            debug_on_hover,
2081            debug_on_hover_with_all_modifiers,
2082            hover_shows_next,
2083            show_expand_width,
2084            show_expand_height,
2085            show_resize,
2086            show_interactive_widgets,
2087            show_widget_hits,
2088        } = self;
2089
2090        {
2091            ui.checkbox(debug_on_hover, "Show widget info on hover.");
2092            ui.checkbox(
2093                debug_on_hover_with_all_modifiers,
2094                "Show widget info on hover if holding all modifier keys",
2095            );
2096
2097            ui.checkbox(hover_shows_next, "Show next widget placement on hover");
2098        }
2099
2100        ui.checkbox(
2101            show_expand_width,
2102            "Show which widgets make their parent wider",
2103        );
2104        ui.checkbox(
2105            show_expand_height,
2106            "Show which widgets make their parent higher",
2107        );
2108        ui.checkbox(show_resize, "Debug Resize");
2109
2110        ui.checkbox(
2111            show_interactive_widgets,
2112            "Show an overlay on all interactive widgets",
2113        );
2114
2115        ui.checkbox(show_widget_hits, "Show widgets under mouse pointer");
2116
2117        ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options"));
2118    }
2119}
2120
2121// TODO(emilk): improve and standardize
2122fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive<f32>) -> impl Widget + '_ {
2123    move |ui: &mut crate::Ui| {
2124        ui.horizontal(|ui| {
2125            ui.add(
2126                DragValue::new(&mut value.x)
2127                    .range(range.clone())
2128                    .prefix("x: "),
2129            );
2130            ui.add(
2131                DragValue::new(&mut value.y)
2132                    .range(range.clone())
2133                    .prefix("y: "),
2134            );
2135        })
2136        .response
2137    }
2138}
2139
2140fn ui_color(ui: &mut Ui, color: &mut Color32, label: impl Into<WidgetText>) -> Response {
2141    ui.horizontal(|ui| {
2142        ui.color_edit_button_srgba(color);
2143        ui.label(label);
2144    })
2145    .response
2146}
2147
2148fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) -> Response {
2149    ui.horizontal(|ui| {
2150        ui.color_edit_button_srgba(color);
2151        ui.label(label.into().color(*color));
2152    })
2153    .response
2154}
2155
2156impl HandleShape {
2157    pub fn ui(&mut self, ui: &mut Ui) {
2158        ui.horizontal(|ui| {
2159            ui.label("Slider handle");
2160            ui.radio_value(self, Self::Circle, "Circle");
2161            if ui
2162                .radio(matches!(self, Self::Rect { .. }), "Rectangle")
2163                .clicked()
2164            {
2165                *self = Self::Rect { aspect_ratio: 0.5 };
2166            }
2167            if let Self::Rect { aspect_ratio } = self {
2168                ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio"));
2169            }
2170        });
2171    }
2172}
2173
2174/// How to display numeric color values.
2175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2176#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
2177pub enum NumericColorSpace {
2178    /// RGB is 0-255 in gamma space.
2179    ///
2180    /// Alpha is 0-255 in linear space.
2181    GammaByte,
2182
2183    /// 0-1 in linear space.
2184    Linear,
2185    // TODO(emilk): add Hex as an option
2186}
2187
2188impl NumericColorSpace {
2189    pub fn toggle_button_ui(&mut self, ui: &mut Ui) -> crate::Response {
2190        let tooltip = match self {
2191            Self::GammaByte => "Showing color values in 0-255 gamma space",
2192            Self::Linear => "Showing color values in 0-1 linear space",
2193        };
2194
2195        let mut response = ui.button(self.to_string()).on_hover_text(tooltip);
2196        if response.clicked() {
2197            *self = match self {
2198                Self::GammaByte => Self::Linear,
2199                Self::Linear => Self::GammaByte,
2200            };
2201            response.mark_changed();
2202        }
2203        response
2204    }
2205}
2206
2207impl std::fmt::Display for NumericColorSpace {
2208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2209        match self {
2210            Self::GammaByte => write!(f, "U8"),
2211            Self::Linear => write!(f, "F"),
2212        }
2213    }
2214}
2215
2216impl Widget for &mut Margin {
2217    fn ui(self, ui: &mut Ui) -> Response {
2218        let mut same = self.is_same();
2219
2220        let response = if same {
2221            ui.horizontal(|ui| {
2222                ui.checkbox(&mut same, "same");
2223
2224                let mut value = self.left;
2225                ui.add(DragValue::new(&mut value));
2226                *self = Margin::same(value);
2227            })
2228            .response
2229        } else {
2230            ui.vertical(|ui| {
2231                ui.checkbox(&mut same, "same");
2232
2233                crate::Grid::new("margin").num_columns(2).show(ui, |ui| {
2234                    ui.label("Left");
2235                    ui.add(DragValue::new(&mut self.left));
2236                    ui.end_row();
2237
2238                    ui.label("Right");
2239                    ui.add(DragValue::new(&mut self.right));
2240                    ui.end_row();
2241
2242                    ui.label("Top");
2243                    ui.add(DragValue::new(&mut self.top));
2244                    ui.end_row();
2245
2246                    ui.label("Bottom");
2247                    ui.add(DragValue::new(&mut self.bottom));
2248                    ui.end_row();
2249                });
2250            })
2251            .response
2252        };
2253
2254        // Apply the checkbox:
2255        if same {
2256            *self = Margin::same((self.left + self.right + self.top + self.bottom) / 4.0);
2257        } else if self.is_same() {
2258            self.right *= 1.00001; // prevent collapsing into sameness
2259        }
2260
2261        response
2262    }
2263}
2264
2265impl Widget for &mut Rounding {
2266    fn ui(self, ui: &mut Ui) -> Response {
2267        let mut same = self.is_same();
2268
2269        let response = if same {
2270            ui.horizontal(|ui| {
2271                ui.checkbox(&mut same, "same");
2272
2273                let mut cr = self.nw;
2274                ui.add(DragValue::new(&mut cr).range(0.0..=f32::INFINITY));
2275                *self = Rounding::same(cr);
2276            })
2277            .response
2278        } else {
2279            ui.vertical(|ui| {
2280                ui.checkbox(&mut same, "same");
2281
2282                crate::Grid::new("rounding").num_columns(2).show(ui, |ui| {
2283                    ui.label("NW");
2284                    ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
2285                    ui.end_row();
2286
2287                    ui.label("NE");
2288                    ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
2289                    ui.end_row();
2290
2291                    ui.label("SW");
2292                    ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
2293                    ui.end_row();
2294
2295                    ui.label("SE");
2296                    ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
2297                    ui.end_row();
2298                });
2299            })
2300            .response
2301        };
2302
2303        // Apply the checkbox:
2304        if same {
2305            *self = Rounding::same((self.nw + self.ne + self.sw + self.se) / 4.0);
2306        } else if self.is_same() {
2307            self.se *= 1.00001; // prevent collapsing into sameness
2308        }
2309
2310        response
2311    }
2312}
2313
2314impl Widget for &mut Shadow {
2315    fn ui(self, ui: &mut Ui) -> Response {
2316        let epaint::Shadow {
2317            offset,
2318            blur,
2319            spread,
2320            color,
2321        } = self;
2322
2323        ui.vertical(|ui| {
2324            crate::Grid::new("shadow_ui").show(ui, |ui| {
2325                ui.add(
2326                    DragValue::new(&mut offset.x)
2327                        .speed(1.0)
2328                        .range(-100.0..=100.0)
2329                        .prefix("x: "),
2330                );
2331                ui.add(
2332                    DragValue::new(&mut offset.y)
2333                        .speed(1.0)
2334                        .range(-100.0..=100.0)
2335                        .prefix("y: "),
2336                );
2337                ui.end_row();
2338
2339                ui.add(
2340                    DragValue::new(blur)
2341                        .speed(1.0)
2342                        .range(0.0..=100.0)
2343                        .prefix("blur: "),
2344                );
2345
2346                ui.add(
2347                    DragValue::new(spread)
2348                        .speed(1.0)
2349                        .range(0.0..=100.0)
2350                        .prefix("spread: "),
2351                );
2352            });
2353            ui.color_edit_button_srgba(color);
2354        })
2355        .response
2356    }
2357}
2358
2359impl Widget for &mut Stroke {
2360    fn ui(self, ui: &mut Ui) -> Response {
2361        let Stroke { width, color } = self;
2362
2363        ui.horizontal(|ui| {
2364            ui.add(DragValue::new(width).speed(0.1).range(0.0..=f32::INFINITY))
2365                .on_hover_text("Width");
2366            ui.color_edit_button_srgba(color);
2367
2368            // stroke preview:
2369            let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size);
2370            let left = stroke_rect.left_center();
2371            let right = stroke_rect.right_center();
2372            ui.painter().line_segment([left, right], (*width, *color));
2373        })
2374        .response
2375    }
2376}
2377
2378impl Widget for &mut crate::Frame {
2379    fn ui(self, ui: &mut Ui) -> Response {
2380        let crate::Frame {
2381            inner_margin,
2382            outer_margin,
2383            rounding,
2384            shadow,
2385            fill,
2386            stroke,
2387        } = self;
2388
2389        crate::Grid::new("frame")
2390            .num_columns(2)
2391            .spacing([12.0, 8.0])
2392            .striped(true)
2393            .show(ui, |ui| {
2394                ui.label("Inner margin");
2395                ui.add(inner_margin);
2396                ui.end_row();
2397
2398                ui.label("Outer margin");
2399                ui.add(outer_margin);
2400                ui.end_row();
2401
2402                ui.label("Rounding");
2403                ui.add(rounding);
2404                ui.end_row();
2405
2406                ui.label("Shadow");
2407                ui.add(shadow);
2408                ui.end_row();
2409
2410                ui.label("Fill");
2411                ui.color_edit_button_srgba(fill);
2412                ui.end_row();
2413
2414                ui.label("Stroke");
2415                ui.add(stroke);
2416                ui.end_row();
2417            })
2418            .response
2419    }
2420}