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