[go: up one dir, main page]

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    /// Background color behind selected text and other selectable buttons.
1133    pub bg_fill: Color32,
1134
1135    /// Color of selected text.
1136    pub stroke: Stroke,
1137}
1138
1139/// Shape of the handle for sliders and similar widgets.
1140#[derive(Clone, Copy, Debug, PartialEq)]
1141#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1142pub enum HandleShape {
1143    /// Circular handle
1144    Circle,
1145
1146    /// Rectangular handle
1147    Rect {
1148        /// Aspect ratio of the rectangle. Set to < 1.0 to make it narrower.
1149        aspect_ratio: f32,
1150    },
1151}
1152
1153/// The visuals of widgets for different states of interaction.
1154#[derive(Clone, Debug, PartialEq)]
1155#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1156#[cfg_attr(feature = "serde", serde(default))]
1157pub struct Widgets {
1158    /// The style of a widget that you cannot interact with.
1159    /// * `noninteractive.bg_stroke` is the outline of windows.
1160    /// * `noninteractive.bg_fill` is the background color of windows.
1161    /// * `noninteractive.fg_stroke` is the normal text color.
1162    pub noninteractive: WidgetVisuals,
1163
1164    /// The style of an interactive widget, such as a button, at rest.
1165    pub inactive: WidgetVisuals,
1166
1167    /// The style of an interactive widget while you hover it, or when it is highlighted.
1168    ///
1169    /// See [`Response::hovered`], [`Response::highlighted`] and [`Response::highlight`].
1170    pub hovered: WidgetVisuals,
1171
1172    /// The style of an interactive widget as you are clicking or dragging it.
1173    pub active: WidgetVisuals,
1174
1175    /// The style of a button that has an open menu beneath it (e.g. a combo-box)
1176    pub open: WidgetVisuals,
1177}
1178
1179impl Widgets {
1180    pub fn style(&self, response: &Response) -> &WidgetVisuals {
1181        if !response.sense.interactive() {
1182            &self.noninteractive
1183        } else if response.is_pointer_button_down_on() || response.has_focus() || response.clicked()
1184        {
1185            &self.active
1186        } else if response.hovered() || response.highlighted() {
1187            &self.hovered
1188        } else {
1189            &self.inactive
1190        }
1191    }
1192}
1193
1194/// bg = background, fg = foreground.
1195#[derive(Clone, Copy, Debug, PartialEq)]
1196#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1197pub struct WidgetVisuals {
1198    /// Background color of widgets that must have a background fill,
1199    /// such as the slider background, a checkbox background, or a radio button background.
1200    ///
1201    /// Must never be [`Color32::TRANSPARENT`].
1202    pub bg_fill: Color32,
1203
1204    /// Background color of widgets that can _optionally_ have a background fill, such as buttons.
1205    ///
1206    /// May be [`Color32::TRANSPARENT`].
1207    pub weak_bg_fill: Color32,
1208
1209    /// For surrounding rectangle of things that need it,
1210    /// like buttons, the box of the checkbox, etc.
1211    /// Should maybe be called `frame_stroke`.
1212    pub bg_stroke: Stroke,
1213
1214    /// Button frames etc.
1215    pub corner_radius: CornerRadius,
1216
1217    /// Stroke and text color of the interactive part of a component (button text, slider grab, check-mark, …).
1218    pub fg_stroke: Stroke,
1219
1220    /// Make the frame this much larger.
1221    pub expansion: f32,
1222}
1223
1224impl WidgetVisuals {
1225    #[inline(always)]
1226    pub fn text_color(&self) -> Color32 {
1227        self.fg_stroke.color
1228    }
1229
1230    #[deprecated = "Renamed to corner_radius"]
1231    pub fn rounding(&self) -> CornerRadius {
1232        self.corner_radius
1233    }
1234}
1235
1236/// Options for help debug egui by adding extra visualization
1237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1238#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1239#[cfg(debug_assertions)]
1240pub struct DebugOptions {
1241    /// Always show callstack to ui on hover.
1242    ///
1243    /// Useful for figuring out where in the code some UI is being created.
1244    ///
1245    /// Only works in debug builds.
1246    /// Requires the `callstack` feature.
1247    /// Does not work on web.
1248    #[cfg(debug_assertions)]
1249    pub debug_on_hover: bool,
1250
1251    /// Show callstack for the current widget on hover if all modifier keys are pressed down.
1252    ///
1253    /// Useful for figuring out where in the code some UI is being created.
1254    ///
1255    /// Only works in debug builds.
1256    /// Requires the `callstack` feature.
1257    /// Does not work on web.
1258    ///
1259    /// Default is `true` in debug builds, on native, if the `callstack` feature is enabled.
1260    #[cfg(debug_assertions)]
1261    pub debug_on_hover_with_all_modifiers: bool,
1262
1263    /// If we show the hover ui, include where the next widget is placed.
1264    #[cfg(debug_assertions)]
1265    pub hover_shows_next: bool,
1266
1267    /// Show which widgets make their parent wider
1268    pub show_expand_width: bool,
1269
1270    /// Show which widgets make their parent higher
1271    pub show_expand_height: bool,
1272
1273    pub show_resize: bool,
1274
1275    /// Show an overlay on all interactive widgets.
1276    pub show_interactive_widgets: bool,
1277
1278    /// Show interesting widgets under the mouse cursor.
1279    pub show_widget_hits: bool,
1280
1281    /// If true, highlight widgets that are not aligned to [`emath::GUI_ROUNDING`].
1282    ///
1283    /// See [`emath::GuiRounding`] for more.
1284    pub show_unaligned: bool,
1285}
1286
1287#[cfg(debug_assertions)]
1288impl Default for DebugOptions {
1289    fn default() -> Self {
1290        Self {
1291            debug_on_hover: false,
1292            debug_on_hover_with_all_modifiers: cfg!(feature = "callstack")
1293                && !cfg!(target_arch = "wasm32"),
1294            hover_shows_next: false,
1295            show_expand_width: false,
1296            show_expand_height: false,
1297            show_resize: false,
1298            show_interactive_widgets: false,
1299            show_widget_hits: false,
1300            show_unaligned: cfg!(debug_assertions),
1301        }
1302    }
1303}
1304
1305// ----------------------------------------------------------------------------
1306
1307/// The default text styles of the default egui theme.
1308pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
1309    use FontFamily::{Monospace, Proportional};
1310
1311    [
1312        (TextStyle::Small, FontId::new(9.0, Proportional)),
1313        (TextStyle::Body, FontId::new(13.0, Proportional)),
1314        (TextStyle::Button, FontId::new(13.0, Proportional)),
1315        (TextStyle::Heading, FontId::new(18.0, Proportional)),
1316        (TextStyle::Monospace, FontId::new(13.0, Monospace)),
1317    ]
1318    .into()
1319}
1320
1321impl Default for Style {
1322    fn default() -> Self {
1323        #[expect(deprecated)]
1324        Self {
1325            override_font_id: None,
1326            override_text_style: None,
1327            override_text_valign: Some(Align::Center),
1328            text_styles: default_text_styles(),
1329            drag_value_text_style: TextStyle::Button,
1330            number_formatter: NumberFormatter(Arc::new(emath::format_with_decimals_in_range)),
1331            wrap: None,
1332            wrap_mode: None,
1333            spacing: Spacing::default(),
1334            interaction: Interaction::default(),
1335            visuals: Visuals::default(),
1336            animation_time: 6.0 / 60.0, // If we make this too slow, it will be too obvious that our panel animations look like shit :(
1337            #[cfg(debug_assertions)]
1338            debug: Default::default(),
1339            explanation_tooltips: false,
1340            url_in_tooltip: false,
1341            always_scroll_the_only_direction: false,
1342            scroll_animation: ScrollAnimation::default(),
1343            compact_menu_style: true,
1344        }
1345    }
1346}
1347
1348impl Default for Spacing {
1349    fn default() -> Self {
1350        Self {
1351            item_spacing: vec2(8.0, 3.0),
1352            window_margin: Margin::same(6),
1353            menu_margin: Margin::same(6),
1354            button_padding: vec2(4.0, 1.0),
1355            indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing`
1356            interact_size: vec2(40.0, 18.0),
1357            slider_width: 100.0,
1358            slider_rail_height: 8.0,
1359            combo_width: 100.0,
1360            text_edit_width: 280.0,
1361            icon_width: 14.0,
1362            icon_width_inner: 8.0,
1363            icon_spacing: 4.0,
1364            default_area_size: vec2(600.0, 400.0),
1365            tooltip_width: 500.0,
1366            menu_width: 400.0,
1367            menu_spacing: 2.0,
1368            combo_height: 200.0,
1369            scroll: Default::default(),
1370            indent_ends_with_horizontal_line: false,
1371        }
1372    }
1373}
1374
1375impl Default for Interaction {
1376    fn default() -> Self {
1377        Self {
1378            interact_radius: 5.0,
1379            resize_grab_radius_side: 5.0,
1380            resize_grab_radius_corner: 10.0,
1381            show_tooltips_only_when_still: true,
1382            tooltip_delay: 0.5,
1383            tooltip_grace_time: 0.2,
1384            selectable_labels: true,
1385            multi_widget_text_select: true,
1386        }
1387    }
1388}
1389
1390impl Visuals {
1391    /// Default dark theme.
1392    pub fn dark() -> Self {
1393        Self {
1394            dark_mode: true,
1395            text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
1396            override_text_color: None,
1397            weak_text_alpha: 0.6,
1398            weak_text_color: None,
1399            widgets: Widgets::default(),
1400            selection: Selection::default(),
1401            hyperlink_color: Color32::from_rgb(90, 170, 255),
1402            faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
1403            extreme_bg_color: Color32::from_gray(10),            // e.g. TextEdit background
1404            text_edit_bg_color: None, // use `extreme_bg_color` by default
1405            code_bg_color: Color32::from_gray(64),
1406            warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
1407            error_fg_color: Color32::from_rgb(255, 0, 0),  // red
1408
1409            window_corner_radius: CornerRadius::same(6),
1410            window_shadow: Shadow {
1411                offset: [10, 20],
1412                blur: 15,
1413                spread: 0,
1414                color: Color32::from_black_alpha(96),
1415            },
1416            window_fill: Color32::from_gray(27),
1417            window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1418            window_highlight_topmost: true,
1419
1420            menu_corner_radius: CornerRadius::same(6),
1421
1422            panel_fill: Color32::from_gray(27),
1423
1424            popup_shadow: Shadow {
1425                offset: [6, 10],
1426                blur: 8,
1427                spread: 0,
1428                color: Color32::from_black_alpha(96),
1429            },
1430
1431            resize_corner_size: 12.0,
1432
1433            text_cursor: Default::default(),
1434
1435            clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
1436            button_frame: true,
1437            collapsing_header_frame: false,
1438            indent_has_left_vline: true,
1439
1440            striped: false,
1441
1442            slider_trailing_fill: false,
1443            handle_shape: HandleShape::Rect { aspect_ratio: 0.75 },
1444
1445            interact_cursor: None,
1446
1447            image_loading_spinners: true,
1448
1449            numeric_color_space: NumericColorSpace::GammaByte,
1450            disabled_alpha: 0.5,
1451        }
1452    }
1453
1454    /// Default light theme.
1455    pub fn light() -> Self {
1456        Self {
1457            dark_mode: false,
1458            text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
1459            widgets: Widgets::light(),
1460            selection: Selection::light(),
1461            hyperlink_color: Color32::from_rgb(0, 155, 255),
1462            faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
1463            extreme_bg_color: Color32::from_gray(255),           // e.g. TextEdit background
1464            code_bg_color: Color32::from_gray(230),
1465            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.
1466            error_fg_color: Color32::from_rgb(255, 0, 0),  // red
1467
1468            window_shadow: Shadow {
1469                offset: [10, 20],
1470                blur: 15,
1471                spread: 0,
1472                color: Color32::from_black_alpha(25),
1473            },
1474            window_fill: Color32::from_gray(248),
1475            window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
1476
1477            panel_fill: Color32::from_gray(248),
1478
1479            popup_shadow: Shadow {
1480                offset: [6, 10],
1481                blur: 8,
1482                spread: 0,
1483                color: Color32::from_black_alpha(25),
1484            },
1485
1486            text_cursor: TextCursorStyle {
1487                stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
1488                ..Default::default()
1489            },
1490
1491            ..Self::dark()
1492        }
1493    }
1494}
1495
1496impl Default for Visuals {
1497    fn default() -> Self {
1498        Self::dark()
1499    }
1500}
1501
1502impl Selection {
1503    fn dark() -> Self {
1504        Self {
1505            bg_fill: Color32::from_rgb(0, 92, 128),
1506            stroke: Stroke::new(1.0, Color32::from_rgb(192, 222, 255)),
1507        }
1508    }
1509
1510    fn light() -> Self {
1511        Self {
1512            bg_fill: Color32::from_rgb(144, 209, 255),
1513            stroke: Stroke::new(1.0, Color32::from_rgb(0, 83, 125)),
1514        }
1515    }
1516}
1517
1518impl Default for Selection {
1519    fn default() -> Self {
1520        Self::dark()
1521    }
1522}
1523
1524impl Widgets {
1525    pub fn dark() -> Self {
1526        Self {
1527            noninteractive: WidgetVisuals {
1528                weak_bg_fill: Color32::from_gray(27),
1529                bg_fill: Color32::from_gray(27),
1530                bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines
1531                fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
1532                corner_radius: CornerRadius::same(2),
1533                expansion: 0.0,
1534            },
1535            inactive: WidgetVisuals {
1536                weak_bg_fill: Color32::from_gray(60), // button background
1537                bg_fill: Color32::from_gray(60),      // checkbox background
1538                bg_stroke: Default::default(),
1539                fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
1540                corner_radius: CornerRadius::same(2),
1541                expansion: 0.0,
1542            },
1543            hovered: WidgetVisuals {
1544                weak_bg_fill: Color32::from_gray(70),
1545                bg_fill: Color32::from_gray(70),
1546                bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
1547                fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
1548                corner_radius: CornerRadius::same(3),
1549                expansion: 1.0,
1550            },
1551            active: WidgetVisuals {
1552                weak_bg_fill: Color32::from_gray(55),
1553                bg_fill: Color32::from_gray(55),
1554                bg_stroke: Stroke::new(1.0, Color32::WHITE),
1555                fg_stroke: Stroke::new(2.0, Color32::WHITE),
1556                corner_radius: CornerRadius::same(2),
1557                expansion: 1.0,
1558            },
1559            open: WidgetVisuals {
1560                weak_bg_fill: Color32::from_gray(45),
1561                bg_fill: Color32::from_gray(27),
1562                bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
1563                fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
1564                corner_radius: CornerRadius::same(2),
1565                expansion: 0.0,
1566            },
1567        }
1568    }
1569
1570    pub fn light() -> Self {
1571        Self {
1572            noninteractive: WidgetVisuals {
1573                weak_bg_fill: Color32::from_gray(248),
1574                bg_fill: Color32::from_gray(248),
1575                bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines
1576                fg_stroke: Stroke::new(1.0, Color32::from_gray(80)),  // normal text color
1577                corner_radius: CornerRadius::same(2),
1578                expansion: 0.0,
1579            },
1580            inactive: WidgetVisuals {
1581                weak_bg_fill: Color32::from_gray(230), // button background
1582                bg_fill: Color32::from_gray(230),      // checkbox background
1583                bg_stroke: Default::default(),
1584                fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // button text
1585                corner_radius: CornerRadius::same(2),
1586                expansion: 0.0,
1587            },
1588            hovered: WidgetVisuals {
1589                weak_bg_fill: Color32::from_gray(220),
1590                bg_fill: Color32::from_gray(220),
1591                bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
1592                fg_stroke: Stroke::new(1.5, Color32::BLACK),
1593                corner_radius: CornerRadius::same(3),
1594                expansion: 1.0,
1595            },
1596            active: WidgetVisuals {
1597                weak_bg_fill: Color32::from_gray(165),
1598                bg_fill: Color32::from_gray(165),
1599                bg_stroke: Stroke::new(1.0, Color32::BLACK),
1600                fg_stroke: Stroke::new(2.0, Color32::BLACK),
1601                corner_radius: CornerRadius::same(2),
1602                expansion: 1.0,
1603            },
1604            open: WidgetVisuals {
1605                weak_bg_fill: Color32::from_gray(220),
1606                bg_fill: Color32::from_gray(220),
1607                bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
1608                fg_stroke: Stroke::new(1.0, Color32::BLACK),
1609                corner_radius: CornerRadius::same(2),
1610                expansion: 0.0,
1611            },
1612        }
1613    }
1614}
1615
1616impl Default for Widgets {
1617    fn default() -> Self {
1618        Self::dark()
1619    }
1620}
1621
1622// ----------------------------------------------------------------------------
1623
1624use crate::{
1625    Ui,
1626    widgets::{DragValue, Slider, Widget, reset_button},
1627};
1628
1629impl Style {
1630    pub fn ui(&mut self, ui: &mut crate::Ui) {
1631        #[expect(deprecated)]
1632        let Self {
1633            override_font_id,
1634            override_text_style,
1635            override_text_valign,
1636            text_styles,
1637            drag_value_text_style,
1638            number_formatter: _, // can't change callbacks in the UI
1639            wrap: _,
1640            wrap_mode,
1641            spacing,
1642            interaction,
1643            visuals,
1644            animation_time,
1645            #[cfg(debug_assertions)]
1646            debug,
1647            explanation_tooltips,
1648            url_in_tooltip,
1649            always_scroll_the_only_direction,
1650            scroll_animation,
1651            compact_menu_style,
1652        } = self;
1653
1654        crate::Grid::new("_options").show(ui, |ui| {
1655            ui.label("Override font id");
1656            ui.vertical(|ui| {
1657                ui.horizontal(|ui| {
1658                    ui.radio_value(override_font_id, None, "None");
1659                    if ui.radio(override_font_id.is_some(), "override").clicked() {
1660                        *override_font_id = Some(FontId::default());
1661                    }
1662                });
1663                if let Some(override_font_id) = override_font_id {
1664                    crate::introspection::font_id_ui(ui, override_font_id);
1665                }
1666            });
1667            ui.end_row();
1668
1669            ui.label("Override text style");
1670            crate::ComboBox::from_id_salt("override_text_style")
1671                .selected_text(match override_text_style {
1672                    None => "None".to_owned(),
1673                    Some(override_text_style) => override_text_style.to_string(),
1674                })
1675                .show_ui(ui, |ui| {
1676                    ui.selectable_value(override_text_style, None, "None");
1677                    let all_text_styles = ui.style().text_styles();
1678                    for style in all_text_styles {
1679                        let text =
1680                            crate::RichText::new(style.to_string()).text_style(style.clone());
1681                        ui.selectable_value(override_text_style, Some(style), text);
1682                    }
1683                });
1684            ui.end_row();
1685
1686            fn valign_name(valign: Align) -> &'static str {
1687                match valign {
1688                    Align::TOP => "Top",
1689                    Align::Center => "Center",
1690                    Align::BOTTOM => "Bottom",
1691                }
1692            }
1693
1694            ui.label("Override text valign");
1695            crate::ComboBox::from_id_salt("override_text_valign")
1696                .selected_text(match override_text_valign {
1697                    None => "None",
1698                    Some(override_text_valign) => valign_name(*override_text_valign),
1699                })
1700                .show_ui(ui, |ui| {
1701                    ui.selectable_value(override_text_valign, None, "None");
1702                    for align in [Align::TOP, Align::Center, Align::BOTTOM] {
1703                        ui.selectable_value(override_text_valign, Some(align), valign_name(align));
1704                    }
1705                });
1706            ui.end_row();
1707
1708            ui.label("Text style of DragValue");
1709            crate::ComboBox::from_id_salt("drag_value_text_style")
1710                .selected_text(drag_value_text_style.to_string())
1711                .show_ui(ui, |ui| {
1712                    let all_text_styles = ui.style().text_styles();
1713                    for style in all_text_styles {
1714                        let text =
1715                            crate::RichText::new(style.to_string()).text_style(style.clone());
1716                        ui.selectable_value(drag_value_text_style, style, text);
1717                    }
1718                });
1719            ui.end_row();
1720
1721            ui.label("Text Wrap Mode");
1722            crate::ComboBox::from_id_salt("text_wrap_mode")
1723                .selected_text(format!("{wrap_mode:?}"))
1724                .show_ui(ui, |ui| {
1725                    let all_wrap_mode: Vec<Option<TextWrapMode>> = vec![
1726                        None,
1727                        Some(TextWrapMode::Extend),
1728                        Some(TextWrapMode::Wrap),
1729                        Some(TextWrapMode::Truncate),
1730                    ];
1731                    for style in all_wrap_mode {
1732                        let text = crate::RichText::new(format!("{style:?}"));
1733                        ui.selectable_value(wrap_mode, style, text);
1734                    }
1735                });
1736            ui.end_row();
1737
1738            ui.label("Animation duration");
1739            ui.add(
1740                DragValue::new(animation_time)
1741                    .range(0.0..=1.0)
1742                    .speed(0.02)
1743                    .suffix(" s"),
1744            );
1745            ui.end_row();
1746        });
1747
1748        ui.collapsing("🔠 Text styles", |ui| text_styles_ui(ui, text_styles));
1749        ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
1750        ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
1751        ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
1752        ui.collapsing("🔄 Scroll animation", |ui| scroll_animation.ui(ui));
1753
1754        #[cfg(debug_assertions)]
1755        ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
1756
1757        ui.checkbox(compact_menu_style, "Compact menu style");
1758
1759        ui.checkbox(explanation_tooltips, "Explanation tooltips")
1760            .on_hover_text(
1761                "Show explanatory text when hovering DragValue:s and other egui widgets",
1762            );
1763
1764        ui.checkbox(url_in_tooltip, "Show url when hovering links");
1765
1766        ui.checkbox(always_scroll_the_only_direction, "Always scroll the only enabled direction")
1767            .on_hover_text(
1768                "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift",
1769            );
1770
1771        ui.vertical_centered(|ui| reset_button(ui, self, "Reset style"));
1772    }
1773}
1774
1775fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
1776    ui.vertical(|ui| {
1777        crate::Grid::new("text_styles").show(ui, |ui| {
1778            for (text_style, font_id) in &mut *text_styles {
1779                ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
1780                crate::introspection::font_id_ui(ui, font_id);
1781                ui.end_row();
1782            }
1783        });
1784        crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles());
1785    })
1786    .response
1787}
1788
1789impl Spacing {
1790    pub fn ui(&mut self, ui: &mut crate::Ui) {
1791        let Self {
1792            item_spacing,
1793            window_margin,
1794            menu_margin,
1795            button_padding,
1796            indent,
1797            interact_size,
1798            slider_width,
1799            slider_rail_height,
1800            combo_width,
1801            text_edit_width,
1802            icon_width,
1803            icon_width_inner,
1804            icon_spacing,
1805            default_area_size,
1806            tooltip_width,
1807            menu_width,
1808            menu_spacing,
1809            indent_ends_with_horizontal_line,
1810            combo_height,
1811            scroll,
1812        } = self;
1813
1814        Grid::new("spacing")
1815            .num_columns(2)
1816            .spacing([12.0, 8.0])
1817            .striped(true)
1818            .show(ui, |ui| {
1819                ui.label("Item spacing");
1820                ui.add(two_drag_values(item_spacing, 0.0..=20.0));
1821                ui.end_row();
1822
1823                ui.label("Window margin");
1824                ui.add(window_margin);
1825                ui.end_row();
1826
1827                ui.label("Menu margin");
1828                ui.add(menu_margin);
1829                ui.end_row();
1830
1831                ui.label("Button padding");
1832                ui.add(two_drag_values(button_padding, 0.0..=20.0));
1833                ui.end_row();
1834
1835                ui.label("Interact size")
1836                    .on_hover_text("Minimum size of an interactive widget");
1837                ui.add(two_drag_values(interact_size, 4.0..=60.0));
1838                ui.end_row();
1839
1840                ui.label("Indent");
1841                ui.add(DragValue::new(indent).range(0.0..=100.0));
1842                ui.end_row();
1843
1844                ui.label("Slider width");
1845                ui.add(DragValue::new(slider_width).range(0.0..=1000.0));
1846                ui.end_row();
1847
1848                ui.label("Slider rail height");
1849                ui.add(DragValue::new(slider_rail_height).range(0.0..=50.0));
1850                ui.end_row();
1851
1852                ui.label("ComboBox width");
1853                ui.add(DragValue::new(combo_width).range(0.0..=1000.0));
1854                ui.end_row();
1855
1856                ui.label("Default area size");
1857                ui.add(two_drag_values(default_area_size, 0.0..=1000.0));
1858                ui.end_row();
1859
1860                ui.label("TextEdit width");
1861                ui.add(DragValue::new(text_edit_width).range(0.0..=1000.0));
1862                ui.end_row();
1863
1864                ui.label("Tooltip wrap width");
1865                ui.add(DragValue::new(tooltip_width).range(0.0..=1000.0));
1866                ui.end_row();
1867
1868                ui.label("Default menu width");
1869                ui.add(DragValue::new(menu_width).range(0.0..=1000.0));
1870                ui.end_row();
1871
1872                ui.label("Menu spacing")
1873                    .on_hover_text("Horizontal spacing between menus");
1874                ui.add(DragValue::new(menu_spacing).range(0.0..=10.0));
1875                ui.end_row();
1876
1877                ui.label("Checkboxes etc");
1878                ui.vertical(|ui| {
1879                    ui.add(
1880                        DragValue::new(icon_width)
1881                            .prefix("outer icon width:")
1882                            .range(0.0..=60.0),
1883                    );
1884                    ui.add(
1885                        DragValue::new(icon_width_inner)
1886                            .prefix("inner icon width:")
1887                            .range(0.0..=60.0),
1888                    );
1889                    ui.add(
1890                        DragValue::new(icon_spacing)
1891                            .prefix("spacing:")
1892                            .range(0.0..=10.0),
1893                    );
1894                });
1895                ui.end_row();
1896            });
1897
1898        ui.checkbox(
1899            indent_ends_with_horizontal_line,
1900            "End indented regions with a horizontal separator",
1901        );
1902
1903        ui.horizontal(|ui| {
1904            ui.label("Max height of a combo box");
1905            ui.add(DragValue::new(combo_height).range(0.0..=1000.0));
1906        });
1907
1908        ui.collapsing("Scroll Area", |ui| {
1909            scroll.ui(ui);
1910        });
1911
1912        ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing"));
1913    }
1914}
1915
1916impl Interaction {
1917    pub fn ui(&mut self, ui: &mut crate::Ui) {
1918        let Self {
1919            interact_radius,
1920            resize_grab_radius_side,
1921            resize_grab_radius_corner,
1922            show_tooltips_only_when_still,
1923            tooltip_delay,
1924            tooltip_grace_time,
1925            selectable_labels,
1926            multi_widget_text_select,
1927        } = self;
1928
1929        ui.spacing_mut().item_spacing = vec2(12.0, 8.0);
1930
1931        Grid::new("interaction")
1932            .num_columns(2)
1933            .striped(true)
1934            .show(ui, |ui| {
1935                ui.label("interact_radius")
1936                    .on_hover_text("Interact with the closest widget within this radius.");
1937                ui.add(DragValue::new(interact_radius).range(0.0..=20.0));
1938                ui.end_row();
1939
1940                ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize");
1941                ui.add(DragValue::new(resize_grab_radius_side).range(0.0..=20.0));
1942                ui.end_row();
1943
1944                ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize.");
1945                ui.add(DragValue::new(resize_grab_radius_corner).range(0.0..=20.0));
1946                ui.end_row();
1947
1948                ui.label("Tooltip delay").on_hover_text(
1949                    "Delay in seconds before showing tooltips after the mouse stops moving",
1950                );
1951                ui.add(
1952                    DragValue::new(tooltip_delay)
1953                        .range(0.0..=1.0)
1954                        .speed(0.05)
1955                        .suffix(" s"),
1956                );
1957                ui.end_row();
1958
1959                ui.label("Tooltip grace time").on_hover_text(
1960                    "If a tooltip is open and you hover another widget within this grace period, show the next tooltip right away",
1961                );
1962                ui.add(
1963                    DragValue::new(tooltip_grace_time)
1964                        .range(0.0..=1.0)
1965                        .speed(0.05)
1966                        .suffix(" s"),
1967                );
1968                ui.end_row();
1969            });
1970
1971        ui.checkbox(
1972            show_tooltips_only_when_still,
1973            "Only show tooltips if mouse is still",
1974        );
1975
1976        ui.horizontal(|ui| {
1977            ui.checkbox(selectable_labels, "Selectable text in labels");
1978            if *selectable_labels {
1979                ui.checkbox(multi_widget_text_select, "Across multiple labels");
1980            }
1981        });
1982
1983        ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings"));
1984    }
1985}
1986
1987impl Widgets {
1988    pub fn ui(&mut self, ui: &mut crate::Ui) {
1989        let Self {
1990            active,
1991            hovered,
1992            inactive,
1993            noninteractive,
1994            open,
1995        } = self;
1996
1997        ui.collapsing("Noninteractive", |ui| {
1998            ui.label(
1999                "The style of a widget that you cannot interact with, e.g. labels and separators.",
2000            );
2001            noninteractive.ui(ui);
2002        });
2003        ui.collapsing("Interactive but inactive", |ui| {
2004            ui.label("The style of an interactive widget, such as a button, at rest.");
2005            inactive.ui(ui);
2006        });
2007        ui.collapsing("Interactive and hovered", |ui| {
2008            ui.label("The style of an interactive widget while you hover it.");
2009            hovered.ui(ui);
2010        });
2011        ui.collapsing("Interactive and active", |ui| {
2012            ui.label("The style of an interactive widget as you are clicking or dragging it.");
2013            active.ui(ui);
2014        });
2015        ui.collapsing("Open menu", |ui| {
2016            ui.label("The style of an open combo-box or menu button");
2017            open.ui(ui);
2018        });
2019
2020        // ui.vertical_centered(|ui| reset_button(ui, self));
2021    }
2022}
2023
2024impl Selection {
2025    pub fn ui(&mut self, ui: &mut crate::Ui) {
2026        let Self { bg_fill, stroke } = self;
2027        ui.label("Selectable labels");
2028
2029        Grid::new("selectiom").num_columns(2).show(ui, |ui| {
2030            ui.label("Background fill");
2031            ui.color_edit_button_srgba(bg_fill);
2032            ui.end_row();
2033
2034            ui.label("Stroke");
2035            ui.add(stroke);
2036            ui.end_row();
2037        });
2038    }
2039}
2040
2041impl WidgetVisuals {
2042    pub fn ui(&mut self, ui: &mut crate::Ui) {
2043        let Self {
2044            weak_bg_fill,
2045            bg_fill: mandatory_bg_fill,
2046            bg_stroke,
2047            corner_radius,
2048            fg_stroke,
2049            expansion,
2050        } = self;
2051
2052        Grid::new("widget")
2053            .num_columns(2)
2054            .spacing([12.0, 8.0])
2055            .striped(true)
2056            .show(ui, |ui| {
2057                ui.label("Optional background fill")
2058                    .on_hover_text("For buttons, combo-boxes, etc");
2059                ui.color_edit_button_srgba(weak_bg_fill);
2060                ui.end_row();
2061
2062                ui.label("Mandatory background fill")
2063                    .on_hover_text("For checkboxes, sliders, etc");
2064                ui.color_edit_button_srgba(mandatory_bg_fill);
2065                ui.end_row();
2066
2067                ui.label("Background stroke");
2068                ui.add(bg_stroke);
2069                ui.end_row();
2070
2071                ui.label("Corner radius");
2072                ui.add(corner_radius);
2073                ui.end_row();
2074
2075                ui.label("Foreground stroke (text)");
2076                ui.add(fg_stroke);
2077                ui.end_row();
2078
2079                ui.label("Expansion")
2080                    .on_hover_text("make shapes this much larger");
2081                ui.add(DragValue::new(expansion).speed(0.1));
2082                ui.end_row();
2083            });
2084    }
2085}
2086
2087impl Visuals {
2088    pub fn ui(&mut self, ui: &mut crate::Ui) {
2089        let Self {
2090            dark_mode,
2091            text_alpha_from_coverage,
2092            override_text_color: _,
2093            weak_text_alpha,
2094            weak_text_color,
2095            widgets,
2096            selection,
2097            hyperlink_color,
2098            faint_bg_color,
2099            extreme_bg_color,
2100            text_edit_bg_color,
2101            code_bg_color,
2102            warn_fg_color,
2103            error_fg_color,
2104
2105            window_corner_radius,
2106            window_shadow,
2107            window_fill,
2108            window_stroke,
2109            window_highlight_topmost,
2110
2111            menu_corner_radius,
2112
2113            panel_fill,
2114
2115            popup_shadow,
2116
2117            resize_corner_size,
2118
2119            text_cursor,
2120
2121            clip_rect_margin,
2122            button_frame,
2123            collapsing_header_frame,
2124            indent_has_left_vline,
2125
2126            striped,
2127
2128            slider_trailing_fill,
2129            handle_shape,
2130            interact_cursor,
2131
2132            image_loading_spinners,
2133
2134            numeric_color_space,
2135            disabled_alpha,
2136        } = self;
2137
2138        fn ui_optional_color(
2139            ui: &mut Ui,
2140            color: &mut Option<Color32>,
2141            default_value: Color32,
2142            label: impl Into<WidgetText>,
2143        ) -> Response {
2144            let label_response = ui.label(label);
2145
2146            ui.horizontal(|ui| {
2147                let mut set = color.is_some();
2148                ui.checkbox(&mut set, "");
2149                if set {
2150                    let color = color.get_or_insert(default_value);
2151                    ui.color_edit_button_srgba(color);
2152                } else {
2153                    *color = None;
2154                }
2155            });
2156
2157            ui.end_row();
2158
2159            label_response
2160        }
2161
2162        ui.collapsing("Background colors", |ui| {
2163            Grid::new("background_colors")
2164                .num_columns(2)
2165                .show(ui, |ui| {
2166                    fn ui_color(
2167                        ui: &mut Ui,
2168                        color: &mut Color32,
2169                        label: impl Into<WidgetText>,
2170                    ) -> Response {
2171                        let label_response = ui.label(label);
2172                        ui.color_edit_button_srgba(color);
2173                        ui.end_row();
2174                        label_response
2175                    }
2176
2177                    ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
2178                    ui_color(ui, window_fill, "Windows");
2179                    ui_color(ui, panel_fill, "Panels");
2180                    ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
2181                        "Used for faint accentuation of interactive things, like striped grids.",
2182                    );
2183                    ui_color(ui, extreme_bg_color, "Extreme")
2184                        .on_hover_text("Background of plots and paintings");
2185
2186                    ui_optional_color(ui, text_edit_bg_color, *extreme_bg_color, "TextEdit")
2187                        .on_hover_text("Background of TextEdit");
2188                });
2189        });
2190
2191        ui.collapsing("Text color", |ui| {
2192            fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
2193                ui.label(label.into().color(*color));
2194                ui.color_edit_button_srgba(color);
2195                ui.end_row();
2196            }
2197
2198            Grid::new("text_color").num_columns(2).show(ui, |ui| {
2199                ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
2200
2201                ui_text_color(
2202                    ui,
2203                    &mut widgets.inactive.fg_stroke.color,
2204                    "Unhovered button",
2205                );
2206                ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
2207                ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
2208
2209                ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
2210                ui_text_color(ui, error_fg_color, RichText::new("Errors"));
2211
2212                ui_text_color(ui, hyperlink_color, "hyperlink_color");
2213
2214                ui.label(RichText::new("Code background").code())
2215                    .on_hover_ui(|ui| {
2216                        ui.horizontal(|ui| {
2217                            ui.spacing_mut().item_spacing.x = 0.0;
2218                            ui.label("For monospaced inlined text ");
2219                            ui.code("like this");
2220                            ui.label(".");
2221                        });
2222                    });
2223                ui.color_edit_button_srgba(code_bg_color);
2224                ui.end_row();
2225
2226                ui.label("Weak text alpha");
2227                ui.add_enabled(
2228                    weak_text_color.is_none(),
2229                    DragValue::new(weak_text_alpha).speed(0.01).range(0.0..=1.0),
2230                );
2231                ui.end_row();
2232
2233                ui_optional_color(
2234                    ui,
2235                    weak_text_color,
2236                    widgets.noninteractive.text_color(),
2237                    "Weak text color",
2238                );
2239            });
2240
2241            ui.add_space(4.0);
2242
2243            text_alpha_from_coverage_ui(ui, text_alpha_from_coverage);
2244        });
2245
2246        ui.collapsing("Text cursor", |ui| {
2247            text_cursor.ui(ui);
2248        });
2249
2250        ui.collapsing("Window", |ui| {
2251            Grid::new("window")
2252                .num_columns(2)
2253                .spacing([12.0, 8.0])
2254                .striped(true)
2255                .show(ui, |ui| {
2256                    ui.label("Fill");
2257                    ui.color_edit_button_srgba(window_fill);
2258                    ui.end_row();
2259
2260                    ui.label("Stroke");
2261                    ui.add(window_stroke);
2262                    ui.end_row();
2263
2264                    ui.label("Corner radius");
2265                    ui.add(window_corner_radius);
2266                    ui.end_row();
2267
2268                    ui.label("Shadow");
2269                    ui.add(window_shadow);
2270                    ui.end_row();
2271                });
2272
2273            ui.checkbox(window_highlight_topmost, "Highlight topmost Window");
2274        });
2275
2276        ui.collapsing("Menus and popups", |ui| {
2277            Grid::new("menus_and_popups")
2278                .num_columns(2)
2279                .spacing([12.0, 8.0])
2280                .striped(true)
2281                .show(ui, |ui| {
2282                    ui.label("Corner radius");
2283                    ui.add(menu_corner_radius);
2284                    ui.end_row();
2285
2286                    ui.label("Shadow");
2287                    ui.add(popup_shadow);
2288                    ui.end_row();
2289                });
2290        });
2291
2292        ui.collapsing("Widgets", |ui| widgets.ui(ui));
2293        ui.collapsing("Selection", |ui| selection.ui(ui));
2294
2295        ui.collapsing("Misc", |ui| {
2296            ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
2297            ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
2298
2299            ui.checkbox(button_frame, "Button has a frame");
2300            ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
2301            ui.checkbox(
2302                indent_has_left_vline,
2303                "Paint a vertical line to the left of indented regions",
2304            );
2305
2306            ui.checkbox(striped, "Default stripes on grids and tables");
2307
2308            ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
2309
2310            handle_shape.ui(ui);
2311
2312            ComboBox::from_label("Interact cursor")
2313                .selected_text(
2314                    interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")),
2315                )
2316                .show_ui(ui, |ui| {
2317                    ui.selectable_value(interact_cursor, None, "-");
2318
2319                    for cursor in CursorIcon::ALL {
2320                        ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}"))
2321                            .on_hover_cursor(cursor);
2322                    }
2323                })
2324                .response
2325                .on_hover_text("Use this cursor when hovering buttons etc");
2326
2327            ui.checkbox(image_loading_spinners, "Image loading spinners")
2328                .on_hover_text("Show a spinner when an Image is loading");
2329
2330            ui.horizontal(|ui| {
2331                ui.label("Color picker type");
2332                numeric_color_space.toggle_button_ui(ui);
2333            });
2334
2335            ui.add(Slider::new(disabled_alpha, 0.0..=1.0).text("Disabled element alpha"));
2336        });
2337
2338        let dark_mode = *dark_mode;
2339        ui.vertical_centered(|ui| {
2340            reset_button_with(
2341                ui,
2342                self,
2343                "Reset visuals",
2344                if dark_mode {
2345                    Self::dark()
2346                } else {
2347                    Self::light()
2348                },
2349            );
2350        });
2351    }
2352}
2353
2354fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) {
2355    let mut dark_mode_special =
2356        *text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
2357
2358    ui.horizontal(|ui| {
2359        ui.label("Text rendering:");
2360
2361        ui.checkbox(&mut dark_mode_special, "Dark-mode special");
2362
2363        if dark_mode_special {
2364            *text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq;
2365        } else {
2366            let mut gamma = match text_alpha_from_coverage {
2367                AlphaFromCoverage::Linear => 1.0,
2368                AlphaFromCoverage::Gamma(gamma) => *gamma,
2369                AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
2370            };
2371
2372            ui.add(
2373                DragValue::new(&mut gamma)
2374                    .speed(0.01)
2375                    .range(0.1..=4.0)
2376                    .prefix("Gamma: "),
2377            );
2378
2379            if gamma == 1.0 {
2380                *text_alpha_from_coverage = AlphaFromCoverage::Linear;
2381            } else {
2382                *text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
2383            }
2384        }
2385    });
2386}
2387
2388impl TextCursorStyle {
2389    fn ui(&mut self, ui: &mut Ui) {
2390        let Self {
2391            stroke,
2392            preview,
2393            blink,
2394            on_duration,
2395            off_duration,
2396        } = self;
2397
2398        ui.horizontal(|ui| {
2399            ui.label("Stroke");
2400            ui.add(stroke);
2401        });
2402
2403        ui.checkbox(preview, "Preview text cursor on hover");
2404
2405        ui.checkbox(blink, "Blink");
2406
2407        if *blink {
2408            Grid::new("cursor_blink").show(ui, |ui| {
2409                ui.label("On time");
2410                ui.add(
2411                    DragValue::new(on_duration)
2412                        .speed(0.1)
2413                        .range(0.0..=2.0)
2414                        .suffix(" s"),
2415                );
2416                ui.end_row();
2417
2418                ui.label("Off time");
2419                ui.add(
2420                    DragValue::new(off_duration)
2421                        .speed(0.1)
2422                        .range(0.0..=2.0)
2423                        .suffix(" s"),
2424                );
2425                ui.end_row();
2426            });
2427        }
2428    }
2429}
2430
2431#[cfg(debug_assertions)]
2432impl DebugOptions {
2433    pub fn ui(&mut self, ui: &mut crate::Ui) {
2434        let Self {
2435            debug_on_hover,
2436            debug_on_hover_with_all_modifiers,
2437            hover_shows_next,
2438            show_expand_width,
2439            show_expand_height,
2440            show_resize,
2441            show_interactive_widgets,
2442            show_widget_hits,
2443            show_unaligned,
2444        } = self;
2445
2446        {
2447            ui.checkbox(debug_on_hover, "Show widget info on hover.");
2448            ui.checkbox(
2449                debug_on_hover_with_all_modifiers,
2450                "Show widget info on hover if holding all modifier keys",
2451            );
2452
2453            ui.checkbox(hover_shows_next, "Show next widget placement on hover");
2454        }
2455
2456        ui.checkbox(
2457            show_expand_width,
2458            "Show which widgets make their parent wider",
2459        );
2460        ui.checkbox(
2461            show_expand_height,
2462            "Show which widgets make their parent higher",
2463        );
2464        ui.checkbox(show_resize, "Debug Resize");
2465
2466        ui.checkbox(
2467            show_interactive_widgets,
2468            "Show an overlay on all interactive widgets",
2469        );
2470
2471        ui.checkbox(show_widget_hits, "Show widgets under mouse pointer");
2472
2473        ui.checkbox(
2474            show_unaligned,
2475            "Show rectangles not aligned to integer point coordinates",
2476        );
2477
2478        ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options"));
2479    }
2480}
2481
2482// TODO(emilk): improve and standardize
2483fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive<f32>) -> impl Widget + '_ {
2484    move |ui: &mut crate::Ui| {
2485        ui.horizontal(|ui| {
2486            ui.add(
2487                DragValue::new(&mut value.x)
2488                    .range(range.clone())
2489                    .prefix("x: "),
2490            );
2491            ui.add(
2492                DragValue::new(&mut value.y)
2493                    .range(range.clone())
2494                    .prefix("y: "),
2495            );
2496        })
2497        .response
2498    }
2499}
2500
2501impl HandleShape {
2502    pub fn ui(&mut self, ui: &mut Ui) {
2503        ui.horizontal(|ui| {
2504            ui.label("Slider handle");
2505            ui.radio_value(self, Self::Circle, "Circle");
2506            if ui
2507                .radio(matches!(self, Self::Rect { .. }), "Rectangle")
2508                .clicked()
2509            {
2510                *self = Self::Rect { aspect_ratio: 0.5 };
2511            }
2512            if let Self::Rect { aspect_ratio } = self {
2513                ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio"));
2514            }
2515        });
2516    }
2517}
2518
2519/// How to display numeric color values.
2520#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2521#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
2522pub enum NumericColorSpace {
2523    /// RGB is 0-255 in gamma space.
2524    ///
2525    /// Alpha is 0-255 in linear space.
2526    GammaByte,
2527
2528    /// 0-1 in linear space.
2529    Linear,
2530    // TODO(emilk): add Hex as an option
2531}
2532
2533impl NumericColorSpace {
2534    pub fn toggle_button_ui(&mut self, ui: &mut Ui) -> crate::Response {
2535        let tooltip = match self {
2536            Self::GammaByte => "Showing color values in 0-255 gamma space",
2537            Self::Linear => "Showing color values in 0-1 linear space",
2538        };
2539
2540        let mut response = ui.button(self.to_string()).on_hover_text(tooltip);
2541        if response.clicked() {
2542            *self = match self {
2543                Self::GammaByte => Self::Linear,
2544                Self::Linear => Self::GammaByte,
2545            };
2546            response.mark_changed();
2547        }
2548        response
2549    }
2550}
2551
2552impl std::fmt::Display for NumericColorSpace {
2553    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2554        match self {
2555            Self::GammaByte => write!(f, "U8"),
2556            Self::Linear => write!(f, "F"),
2557        }
2558    }
2559}
2560
2561impl Widget for &mut Margin {
2562    fn ui(self, ui: &mut Ui) -> Response {
2563        let mut same = self.is_same();
2564
2565        let response = if same {
2566            ui.horizontal(|ui| {
2567                ui.checkbox(&mut same, "same");
2568
2569                let mut value = self.left;
2570                ui.add(DragValue::new(&mut value).range(0.0..=100.0));
2571                *self = Margin::same(value);
2572            })
2573            .response
2574        } else {
2575            ui.vertical(|ui| {
2576                ui.checkbox(&mut same, "same");
2577
2578                crate::Grid::new("margin").num_columns(2).show(ui, |ui| {
2579                    ui.label("Left");
2580                    ui.add(DragValue::new(&mut self.left).range(0.0..=100.0));
2581                    ui.end_row();
2582
2583                    ui.label("Right");
2584                    ui.add(DragValue::new(&mut self.right).range(0.0..=100.0));
2585                    ui.end_row();
2586
2587                    ui.label("Top");
2588                    ui.add(DragValue::new(&mut self.top).range(0.0..=100.0));
2589                    ui.end_row();
2590
2591                    ui.label("Bottom");
2592                    ui.add(DragValue::new(&mut self.bottom).range(0.0..=100.0));
2593                    ui.end_row();
2594                });
2595            })
2596            .response
2597        };
2598
2599        // Apply the checkbox:
2600        if same {
2601            *self =
2602                Margin::from((self.leftf() + self.rightf() + self.topf() + self.bottomf()) / 4.0);
2603        } else {
2604            // Make sure it is not same:
2605            if self.is_same() {
2606                if self.right == i8::MAX {
2607                    self.right = i8::MAX - 1;
2608                } else {
2609                    self.right += 1;
2610                }
2611            }
2612        }
2613
2614        response
2615    }
2616}
2617
2618impl Widget for &mut CornerRadius {
2619    fn ui(self, ui: &mut Ui) -> Response {
2620        let mut same = self.is_same();
2621
2622        let response = if same {
2623            ui.horizontal(|ui| {
2624                ui.checkbox(&mut same, "same");
2625
2626                let mut cr = self.nw;
2627                ui.add(DragValue::new(&mut cr).range(0.0..=f32::INFINITY));
2628                *self = CornerRadius::same(cr);
2629            })
2630            .response
2631        } else {
2632            ui.vertical(|ui| {
2633                ui.checkbox(&mut same, "same");
2634
2635                crate::Grid::new("Corner radius")
2636                    .num_columns(2)
2637                    .show(ui, |ui| {
2638                        ui.label("NW");
2639                        ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
2640                        ui.end_row();
2641
2642                        ui.label("NE");
2643                        ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
2644                        ui.end_row();
2645
2646                        ui.label("SW");
2647                        ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
2648                        ui.end_row();
2649
2650                        ui.label("SE");
2651                        ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
2652                        ui.end_row();
2653                    });
2654            })
2655            .response
2656        };
2657
2658        // Apply the checkbox:
2659        if same {
2660            *self = CornerRadius::from(self.average());
2661        } else {
2662            // Make sure we aren't same:
2663            if self.is_same() {
2664                if self.average() == 0.0 {
2665                    self.se = 1;
2666                } else {
2667                    self.se -= 1;
2668                }
2669            }
2670        }
2671
2672        response
2673    }
2674}
2675
2676impl Widget for &mut Shadow {
2677    fn ui(self, ui: &mut Ui) -> Response {
2678        let epaint::Shadow {
2679            offset,
2680            blur,
2681            spread,
2682            color,
2683        } = self;
2684
2685        ui.vertical(|ui| {
2686            crate::Grid::new("shadow_ui").show(ui, |ui| {
2687                ui.add(
2688                    DragValue::new(&mut offset[0])
2689                        .speed(1.0)
2690                        .range(-100.0..=100.0)
2691                        .prefix("x: "),
2692                );
2693                ui.add(
2694                    DragValue::new(&mut offset[1])
2695                        .speed(1.0)
2696                        .range(-100.0..=100.0)
2697                        .prefix("y: "),
2698                );
2699                ui.end_row();
2700
2701                ui.add(
2702                    DragValue::new(blur)
2703                        .speed(1.0)
2704                        .range(0.0..=100.0)
2705                        .prefix("blur: "),
2706                );
2707
2708                ui.add(
2709                    DragValue::new(spread)
2710                        .speed(1.0)
2711                        .range(0.0..=100.0)
2712                        .prefix("spread: "),
2713                );
2714            });
2715            ui.color_edit_button_srgba(color);
2716        })
2717        .response
2718    }
2719}
2720
2721impl Widget for &mut Stroke {
2722    fn ui(self, ui: &mut Ui) -> Response {
2723        let Stroke { width, color } = self;
2724
2725        ui.horizontal(|ui| {
2726            ui.add(DragValue::new(width).speed(0.1).range(0.0..=1e9))
2727                .on_hover_text("Width");
2728            ui.color_edit_button_srgba(color);
2729
2730            // stroke preview:
2731            let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size);
2732            let left = stroke_rect.left_center();
2733            let right = stroke_rect.right_center();
2734            ui.painter().line_segment([left, right], (*width, *color));
2735        })
2736        .response
2737    }
2738}
2739
2740impl Widget for &mut crate::Frame {
2741    fn ui(self, ui: &mut Ui) -> Response {
2742        let crate::Frame {
2743            inner_margin,
2744            outer_margin,
2745            corner_radius,
2746            shadow,
2747            fill,
2748            stroke,
2749        } = self;
2750
2751        crate::Grid::new("frame")
2752            .num_columns(2)
2753            .spacing([12.0, 8.0])
2754            .striped(true)
2755            .show(ui, |ui| {
2756                ui.label("Inner margin");
2757                ui.add(inner_margin);
2758                ui.end_row();
2759
2760                ui.label("Outer margin");
2761                // Push Id to avoid clashes in the Margin widget's Grid
2762                ui.push_id("outer", |ui| ui.add(outer_margin));
2763                ui.end_row();
2764
2765                ui.label("Corner radius");
2766                ui.add(corner_radius);
2767                ui.end_row();
2768
2769                ui.label("Shadow");
2770                ui.add(shadow);
2771                ui.end_row();
2772
2773                ui.label("Fill");
2774                ui.color_edit_button_srgba(fill);
2775                ui.end_row();
2776
2777                ui.label("Stroke");
2778                ui.add(stroke);
2779                ui.end_row();
2780            })
2781            .response
2782    }
2783}
2784
2785impl Widget for &mut FontTweak {
2786    fn ui(self, ui: &mut Ui) -> Response {
2787        let original: FontTweak = *self;
2788
2789        let mut response = Grid::new("font_tweak")
2790            .num_columns(2)
2791            .show(ui, |ui| {
2792                let FontTweak {
2793                    scale,
2794                    y_offset_factor,
2795                    y_offset,
2796                } = self;
2797
2798                ui.label("Scale");
2799                let speed = *scale * 0.01;
2800                ui.add(DragValue::new(scale).range(0.01..=10.0).speed(speed));
2801                ui.end_row();
2802
2803                ui.label("y_offset_factor");
2804                ui.add(DragValue::new(y_offset_factor).speed(-0.0025));
2805                ui.end_row();
2806
2807                ui.label("y_offset");
2808                ui.add(DragValue::new(y_offset).speed(-0.02));
2809                ui.end_row();
2810
2811                if ui.button("Reset").clicked() {
2812                    *self = Default::default();
2813                }
2814            })
2815            .response;
2816
2817        if *self != original {
2818            response.mark_changed();
2819        }
2820
2821        response
2822    }
2823}