Skip to main content

egui/
style.rs

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