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