egui/
style.rs

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