egui/
style.rs

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