egui/
style.rs

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