1335 lines
43 KiB
Rust
1335 lines
43 KiB
Rust
//! egui theme (spacing, colors, etc).
|
|
|
|
#![allow(clippy::if_same_then_else)]
|
|
|
|
use crate::{color::*, emath::*, FontFamily, FontId, Response, RichText, WidgetText};
|
|
use epaint::{Rounding, Shadow, Stroke};
|
|
use std::collections::BTreeMap;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/// Alias for a [`FontId`] (font of a certain size).
|
|
///
|
|
/// The font is found via look-up in [`Style::text_styles`].
|
|
/// You can use [`TextStyle::resolve`] to do this lookup.
|
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
pub enum TextStyle {
|
|
/// Used when small text is needed.
|
|
Small,
|
|
|
|
/// Normal labels. Easily readable, doesn't take up too much space.
|
|
Body,
|
|
|
|
/// Same size as [`Self::Body`], but used when monospace is important (for code snippets, aligning numbers, etc).
|
|
Monospace,
|
|
|
|
/// Buttons. Maybe slightly bigger than [`Self::Body`].
|
|
///
|
|
/// Signifies that he item can be interacted with.
|
|
Button,
|
|
|
|
/// Heading. Probably larger than [`Self::Body`].
|
|
Heading,
|
|
|
|
/// A user-chosen style, found in [`Style::text_styles`].
|
|
/// ```
|
|
/// egui::TextStyle::Name("footing".into());
|
|
/// ````
|
|
Name(std::sync::Arc<str>),
|
|
}
|
|
|
|
impl std::fmt::Display for TextStyle {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::Small => "Small".fmt(f),
|
|
Self::Body => "Body".fmt(f),
|
|
Self::Monospace => "Monospace".fmt(f),
|
|
Self::Button => "Button".fmt(f),
|
|
Self::Heading => "Heading".fmt(f),
|
|
Self::Name(name) => (*name).fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TextStyle {
|
|
/// Look up this [`TextStyle`] in [`Style::text_styles`].
|
|
pub fn resolve(&self, style: &Style) -> FontId {
|
|
style.text_styles.get(self).cloned().unwrap_or_else(|| {
|
|
panic!(
|
|
"Failed to find {:?} in Style::text_styles. Available styles:\n{:#?}",
|
|
self,
|
|
style.text_styles()
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/// A way to select [`FontId`], either by picking one directly or by using a [`TextStyle`].
|
|
pub enum FontSelection {
|
|
/// Default text style - will use [`TextStyle::Body`], unless
|
|
/// [`Style::override_font_id`] or [`Style::override_text_style`] is set.
|
|
Default,
|
|
|
|
/// Directly select size and font family
|
|
FontId(FontId),
|
|
|
|
/// Use a [`TextStyle`] to look up the [`FontId`] in [`Style::text_styles`].
|
|
Style(TextStyle),
|
|
}
|
|
|
|
impl Default for FontSelection {
|
|
#[inline]
|
|
fn default() -> Self {
|
|
Self::Default
|
|
}
|
|
}
|
|
|
|
impl FontSelection {
|
|
pub fn resolve(self, style: &Style) -> FontId {
|
|
match self {
|
|
Self::Default => {
|
|
if let Some(override_font_id) = &style.override_font_id {
|
|
override_font_id.clone()
|
|
} else if let Some(text_style) = &style.override_text_style {
|
|
text_style.resolve(style)
|
|
} else {
|
|
TextStyle::Body.resolve(style)
|
|
}
|
|
}
|
|
Self::FontId(font_id) => font_id,
|
|
Self::Style(text_style) => text_style.resolve(style),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<FontId> for FontSelection {
|
|
#[inline(always)]
|
|
fn from(font_id: FontId) -> Self {
|
|
Self::FontId(font_id)
|
|
}
|
|
}
|
|
|
|
impl From<TextStyle> for FontSelection {
|
|
#[inline(always)]
|
|
fn from(text_style: TextStyle) -> Self {
|
|
Self::Style(text_style)
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/// Specifies the look and feel of egui.
|
|
///
|
|
/// You can change the visuals of a [`Ui`] with [`Ui::style_mut`]
|
|
/// and of everything with [`crate::Context::set_style`].
|
|
///
|
|
/// If you want to change fonts, use [`crate::Context::set_fonts`] instead.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
pub struct Style {
|
|
/// If set this will change the default [`TextStyle`] for all widgets.
|
|
///
|
|
/// On most widgets you can also set an explicit text style,
|
|
/// which will take precedence over this.
|
|
pub override_text_style: Option<TextStyle>,
|
|
|
|
/// If set this will change the font family and size for all widgets.
|
|
///
|
|
/// On most widgets you can also set an explicit text style,
|
|
/// which will take precedence over this.
|
|
pub override_font_id: Option<FontId>,
|
|
|
|
/// The [`FontFamily`] and size you want to use for a specific [`TextStyle`].
|
|
///
|
|
/// The most convenient way to look something up in this is to use [`TextStyle::resolve`].
|
|
///
|
|
/// If you would like to overwrite app text_styles
|
|
///
|
|
/// ```
|
|
/// # let mut ctx = egui::Context::default();
|
|
/// use egui::FontFamily::Proportional;
|
|
/// use egui::FontId;
|
|
/// use egui::TextStyle::*;
|
|
///
|
|
/// // Get current context style
|
|
/// let mut style = (*ctx.style()).clone();
|
|
///
|
|
/// // Redefine text_styles
|
|
/// style.text_styles = [
|
|
/// (Heading, FontId::new(30.0, Proportional)),
|
|
/// (Name("Heading2".into()), FontId::new(25.0, Proportional)),
|
|
/// (Name("Context".into()), FontId::new(23.0, Proportional)),
|
|
/// (Body, FontId::new(18.0, Proportional)),
|
|
/// (Monospace, FontId::new(14.0, Proportional)),
|
|
/// (Button, FontId::new(14.0, Proportional)),
|
|
/// (Small, FontId::new(10.0, Proportional)),
|
|
/// ].into();
|
|
///
|
|
/// // Mutate global style with above changes
|
|
/// ctx.set_style(style);
|
|
/// ```
|
|
pub text_styles: BTreeMap<TextStyle, FontId>,
|
|
|
|
/// If set, labels buttons wtc will use this to determine whether or not
|
|
/// to wrap the text at the right edge of the [`Ui`] they are in.
|
|
/// By default this is `None`.
|
|
///
|
|
/// * `None`: follow layout
|
|
/// * `Some(true)`: default on
|
|
/// * `Some(false)`: default off
|
|
pub wrap: Option<bool>,
|
|
|
|
/// Sizes and distances between widgets
|
|
pub spacing: Spacing,
|
|
|
|
/// How and when interaction happens.
|
|
pub interaction: Interaction,
|
|
|
|
/// Colors etc.
|
|
pub visuals: Visuals,
|
|
|
|
/// How many seconds a typical animation should last.
|
|
pub animation_time: f32,
|
|
|
|
/// Options to help debug why egui behaves strangely.
|
|
pub debug: DebugOptions,
|
|
|
|
/// Show tooltips explaining [`DragValue`]:s etc when hovered.
|
|
///
|
|
/// This only affects a few egui widgets.
|
|
pub explanation_tooltips: bool,
|
|
}
|
|
|
|
impl Style {
|
|
// TODO(emilk): rename style.interact() to maybe... `style.interactive` ?
|
|
/// Use this style for interactive things.
|
|
/// Note that you must already have a response,
|
|
/// i.e. you must allocate space and interact BEFORE painting the widget!
|
|
pub fn interact(&self, response: &Response) -> &WidgetVisuals {
|
|
self.visuals.widgets.style(response)
|
|
}
|
|
|
|
pub fn interact_selectable(&self, response: &Response, selected: bool) -> WidgetVisuals {
|
|
let mut visuals = *self.visuals.widgets.style(response);
|
|
if selected {
|
|
visuals.bg_fill = self.visuals.selection.bg_fill;
|
|
// visuals.bg_stroke = self.visuals.selection.stroke;
|
|
visuals.fg_stroke = self.visuals.selection.stroke;
|
|
}
|
|
visuals
|
|
}
|
|
|
|
/// Style to use for non-interactive widgets.
|
|
pub fn noninteractive(&self) -> &WidgetVisuals {
|
|
&self.visuals.widgets.noninteractive
|
|
}
|
|
|
|
/// All known text styles.
|
|
pub fn text_styles(&self) -> Vec<TextStyle> {
|
|
self.text_styles.keys().cloned().collect()
|
|
}
|
|
}
|
|
|
|
/// Controls the sizes and distances between widgets.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
pub struct Spacing {
|
|
/// Horizontal and vertical spacing between widgets.
|
|
///
|
|
/// To add extra space between widgets, use [`Ui::add_space`].
|
|
///
|
|
/// `item_spacing` is inserted _after_ adding a widget, so to increase the spacing between
|
|
/// widgets `A` and `B` you need to change `item_spacing` before adding `A`.
|
|
pub item_spacing: Vec2,
|
|
|
|
/// Horizontal and vertical margins within a window frame.
|
|
pub window_margin: Margin,
|
|
|
|
/// Button size is text size plus this on each side
|
|
pub button_padding: Vec2,
|
|
|
|
/// Horizontal and vertical margins within a menu frame.
|
|
pub menu_margin: Margin,
|
|
|
|
/// Indent collapsing regions etc by this much.
|
|
pub indent: f32,
|
|
|
|
/// Minimum size of a [`DragValue`], color picker button, and other small widgets.
|
|
/// `interact_size.y` is the default height of button, slider, etc.
|
|
/// Anything clickable should be (at least) this size.
|
|
pub interact_size: Vec2, // TODO(emilk): rename min_interact_size ?
|
|
|
|
/// Default width of a [`Slider`] and [`ComboBox`](crate::ComboBox).
|
|
pub slider_width: f32, // TODO(emilk): rename big_interact_size ?
|
|
|
|
/// Default width of a [`TextEdit`].
|
|
pub text_edit_width: f32,
|
|
|
|
/// Checkboxes, radio button and collapsing headers have an icon at the start.
|
|
/// This is the width/height of the outer part of this icon (e.g. the BOX of the checkbox).
|
|
pub icon_width: f32,
|
|
|
|
/// Checkboxes, radio button and collapsing headers have an icon at the start.
|
|
/// This is the width/height of the inner part of this icon (e.g. the check of the checkbox).
|
|
pub icon_width_inner: f32,
|
|
|
|
/// Checkboxes, radio button and collapsing headers have an icon at the start.
|
|
/// This is the spacing between the icon and the text
|
|
pub icon_spacing: f32,
|
|
|
|
/// Width of a tooltip (`on_hover_ui`, `on_hover_text` etc).
|
|
pub tooltip_width: f32,
|
|
|
|
/// End indented regions with a horizontal line
|
|
pub indent_ends_with_horizontal_line: bool,
|
|
|
|
/// Height of a combo-box before showing scroll bars.
|
|
pub combo_height: f32,
|
|
|
|
pub scroll_bar_width: f32,
|
|
}
|
|
|
|
impl Spacing {
|
|
/// Returns small icon rectangle and big icon rectangle
|
|
pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) {
|
|
let icon_width = self.icon_width;
|
|
let big_icon_rect = Rect::from_center_size(
|
|
pos2(rect.left() + icon_width / 2.0, rect.center().y),
|
|
vec2(icon_width, icon_width),
|
|
);
|
|
|
|
let small_icon_rect =
|
|
Rect::from_center_size(big_icon_rect.center(), Vec2::splat(self.icon_width_inner));
|
|
|
|
(small_icon_rect, big_icon_rect)
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
pub struct Margin {
|
|
pub left: f32,
|
|
pub right: f32,
|
|
pub top: f32,
|
|
pub bottom: f32,
|
|
}
|
|
|
|
impl Margin {
|
|
#[inline]
|
|
pub fn same(margin: f32) -> Self {
|
|
Self {
|
|
left: margin,
|
|
right: margin,
|
|
top: margin,
|
|
bottom: margin,
|
|
}
|
|
}
|
|
|
|
/// Margins with the same size on opposing sides
|
|
#[inline]
|
|
pub fn symmetric(x: f32, y: f32) -> Self {
|
|
Self {
|
|
left: x,
|
|
right: x,
|
|
top: y,
|
|
bottom: y,
|
|
}
|
|
}
|
|
|
|
/// Total margins on both sides
|
|
pub fn sum(&self) -> Vec2 {
|
|
vec2(self.left + self.right, self.top + self.bottom)
|
|
}
|
|
|
|
pub fn left_top(&self) -> Vec2 {
|
|
vec2(self.left, self.top)
|
|
}
|
|
|
|
pub fn right_bottom(&self) -> Vec2 {
|
|
vec2(self.right, self.bottom)
|
|
}
|
|
}
|
|
|
|
impl From<f32> for Margin {
|
|
fn from(v: f32) -> Self {
|
|
Self::same(v)
|
|
}
|
|
}
|
|
|
|
impl From<Vec2> for Margin {
|
|
fn from(v: Vec2) -> Self {
|
|
Self::symmetric(v.x, v.y)
|
|
}
|
|
}
|
|
|
|
impl std::ops::Add for Margin {
|
|
type Output = Self;
|
|
|
|
fn add(self, other: Self) -> Self {
|
|
Self {
|
|
left: self.left + other.left,
|
|
right: self.right + other.right,
|
|
top: self.top + other.top,
|
|
bottom: self.bottom + other.bottom,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/// How and when interaction happens.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
pub struct Interaction {
|
|
/// Mouse must be this close to the side of a window to resize
|
|
pub resize_grab_radius_side: f32,
|
|
|
|
/// Mouse must be this close to the corner of a window to resize
|
|
pub resize_grab_radius_corner: f32,
|
|
|
|
/// If `false`, tooltips will show up anytime you hover anything, even is mouse is still moving
|
|
pub show_tooltips_only_when_still: bool,
|
|
}
|
|
|
|
/// Controls the visual style (colors etc) of egui.
|
|
///
|
|
/// You can change the visuals of a [`Ui`] with [`Ui::visuals_mut`]
|
|
/// and of everything with [`crate::Context::set_visuals`].
|
|
///
|
|
/// If you want to change fonts, use [`crate::Context::set_fonts`] instead.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
pub struct Visuals {
|
|
/// If true, the visuals are overall dark with light text.
|
|
/// If false, the visuals are overall light with dark text.
|
|
///
|
|
/// NOTE: setting this does very little by itself,
|
|
/// this is more to provide a convenient summary of the rest of the settings.
|
|
pub dark_mode: bool,
|
|
|
|
/// Override default text color for all text.
|
|
///
|
|
/// This is great for setting the color of text for any widget.
|
|
///
|
|
/// If `text_color` is `None` (default), then the text color will be the same as the
|
|
/// foreground stroke color (`WidgetVisuals::fg_stroke`)
|
|
/// and will depend on whether or not the widget is being interacted with.
|
|
///
|
|
/// In the future we may instead modulate
|
|
/// the `text_color` based on whether or not it is interacted with
|
|
/// so that `visuals.text_color` is always used,
|
|
/// but its alpha may be different based on whether or not
|
|
/// it is disabled, non-interactive, hovered etc.
|
|
pub override_text_color: Option<Color32>,
|
|
|
|
/// Visual styles of widgets
|
|
pub widgets: Widgets,
|
|
|
|
pub selection: Selection,
|
|
|
|
/// The color used for [`Hyperlink`],
|
|
pub hyperlink_color: Color32,
|
|
|
|
/// Something just barely different from the background color.
|
|
/// Used for [`crate::Grid::striped`].
|
|
pub faint_bg_color: Color32,
|
|
|
|
/// Very dark or light color (for corresponding theme).
|
|
/// Used as the background of text edits, scroll bars and others things
|
|
/// that needs to look different from other interactive stuff.
|
|
pub extreme_bg_color: Color32,
|
|
|
|
/// Background color behind code-styled monospaced labels.
|
|
pub code_bg_color: Color32,
|
|
|
|
/// A good color for warning text (e.g. orange).
|
|
pub warn_fg_color: Color32,
|
|
|
|
/// A good color for error text (e.g. red).
|
|
pub error_fg_color: Color32,
|
|
|
|
pub window_rounding: Rounding,
|
|
pub window_shadow: Shadow,
|
|
|
|
pub popup_shadow: Shadow,
|
|
|
|
pub resize_corner_size: f32,
|
|
|
|
pub text_cursor_width: f32,
|
|
/// show where the text cursor would be if you clicked
|
|
pub text_cursor_preview: bool,
|
|
|
|
/// Allow child widgets to be just on the border and still have a stroke with some thickness
|
|
pub clip_rect_margin: f32,
|
|
|
|
/// Show a background behind buttons.
|
|
pub button_frame: bool,
|
|
|
|
/// Show a background behind collapsing headers.
|
|
pub collapsing_header_frame: bool,
|
|
}
|
|
|
|
impl Visuals {
|
|
#[inline(always)]
|
|
pub fn noninteractive(&self) -> &WidgetVisuals {
|
|
&self.widgets.noninteractive
|
|
}
|
|
|
|
pub fn text_color(&self) -> Color32 {
|
|
self.override_text_color
|
|
.unwrap_or_else(|| self.widgets.noninteractive.text_color())
|
|
}
|
|
|
|
pub fn weak_text_color(&self) -> Color32 {
|
|
crate::color::tint_color_towards(self.text_color(), self.window_fill())
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn strong_text_color(&self) -> Color32 {
|
|
self.widgets.active.text_color()
|
|
}
|
|
|
|
/// Window background color.
|
|
#[inline(always)]
|
|
pub fn window_fill(&self) -> Color32 {
|
|
self.widgets.noninteractive.bg_fill
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn window_stroke(&self) -> Stroke {
|
|
self.widgets.noninteractive.bg_stroke
|
|
}
|
|
}
|
|
|
|
/// Selected text, selected elements etc
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
pub struct Selection {
|
|
pub bg_fill: Color32,
|
|
pub stroke: Stroke,
|
|
}
|
|
|
|
/// The visuals of widgets for different states of interaction.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
pub struct Widgets {
|
|
/// The style of a widget that you cannot interact with.
|
|
/// * `noninteractive.bg_stroke` is the outline of windows.
|
|
/// * `noninteractive.bg_fill` is the background color of windows.
|
|
/// * `noninteractive.fg_stroke` is the normal text color.
|
|
pub noninteractive: WidgetVisuals,
|
|
|
|
/// The style of an interactive widget, such as a button, at rest.
|
|
pub inactive: WidgetVisuals,
|
|
|
|
/// The style of an interactive widget while you hover it.
|
|
pub hovered: WidgetVisuals,
|
|
|
|
/// The style of an interactive widget as you are clicking or dragging it.
|
|
pub active: WidgetVisuals,
|
|
|
|
/// The style of a button that has an open menu beneath it (e.g. a combo-box)
|
|
pub open: WidgetVisuals,
|
|
}
|
|
|
|
impl Widgets {
|
|
pub fn style(&self, response: &Response) -> &WidgetVisuals {
|
|
if !response.sense.interactive() {
|
|
&self.noninteractive
|
|
} else if response.is_pointer_button_down_on() || response.has_focus() {
|
|
&self.active
|
|
} else if response.hovered() {
|
|
&self.hovered
|
|
} else {
|
|
&self.inactive
|
|
}
|
|
}
|
|
}
|
|
|
|
/// bg = background, fg = foreground.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
pub struct WidgetVisuals {
|
|
/// Background color of widget.
|
|
pub bg_fill: Color32,
|
|
|
|
/// For surrounding rectangle of things that need it,
|
|
/// like buttons, the box of the checkbox, etc.
|
|
/// Should maybe be called `frame_stroke`.
|
|
pub bg_stroke: Stroke,
|
|
|
|
/// Button frames etc.
|
|
pub rounding: Rounding,
|
|
|
|
/// Stroke and text color of the interactive part of a component (button text, slider grab, check-mark, …).
|
|
pub fg_stroke: Stroke,
|
|
|
|
/// Make the frame this much larger.
|
|
pub expansion: f32,
|
|
}
|
|
|
|
impl WidgetVisuals {
|
|
#[inline(always)]
|
|
pub fn text_color(&self) -> Color32 {
|
|
self.fg_stroke.color
|
|
}
|
|
}
|
|
|
|
/// Options for help debug egui by adding extra visualization
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
pub struct DebugOptions {
|
|
/// However over widgets to see their rectangles
|
|
pub debug_on_hover: bool,
|
|
/// Show which widgets make their parent wider
|
|
pub show_expand_width: bool,
|
|
/// Show which widgets make their parent higher
|
|
pub show_expand_height: bool,
|
|
pub show_resize: bool,
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/// The default text styles of the default egui theme.
|
|
pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
|
|
use FontFamily::{Monospace, Proportional};
|
|
|
|
[
|
|
(TextStyle::Small, FontId::new(9.0, Proportional)),
|
|
(TextStyle::Body, FontId::new(12.5, Proportional)),
|
|
(TextStyle::Button, FontId::new(12.5, Proportional)),
|
|
(TextStyle::Heading, FontId::new(18.0, Proportional)),
|
|
(TextStyle::Monospace, FontId::new(12.0, Monospace)),
|
|
]
|
|
.into()
|
|
}
|
|
|
|
impl Default for Style {
|
|
fn default() -> Self {
|
|
Self {
|
|
override_font_id: None,
|
|
override_text_style: None,
|
|
text_styles: default_text_styles(),
|
|
wrap: None,
|
|
spacing: Spacing::default(),
|
|
interaction: Interaction::default(),
|
|
visuals: Visuals::default(),
|
|
animation_time: 1.0 / 12.0,
|
|
debug: Default::default(),
|
|
explanation_tooltips: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Spacing {
|
|
fn default() -> Self {
|
|
Self {
|
|
item_spacing: vec2(8.0, 3.0),
|
|
window_margin: Margin::same(6.0),
|
|
menu_margin: Margin::same(1.0),
|
|
button_padding: vec2(4.0, 1.0),
|
|
indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing`
|
|
interact_size: vec2(40.0, 18.0),
|
|
slider_width: 100.0,
|
|
text_edit_width: 280.0,
|
|
icon_width: 14.0,
|
|
icon_width_inner: 8.0,
|
|
icon_spacing: 4.0,
|
|
tooltip_width: 600.0,
|
|
combo_height: 200.0,
|
|
scroll_bar_width: 8.0,
|
|
indent_ends_with_horizontal_line: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Interaction {
|
|
fn default() -> Self {
|
|
Self {
|
|
resize_grab_radius_side: 5.0,
|
|
resize_grab_radius_corner: 10.0,
|
|
show_tooltips_only_when_still: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Visuals {
|
|
/// Default dark theme.
|
|
pub fn dark() -> Self {
|
|
Self {
|
|
dark_mode: true,
|
|
override_text_color: None,
|
|
widgets: Widgets::default(),
|
|
selection: Selection::default(),
|
|
hyperlink_color: Color32::from_rgb(90, 170, 255),
|
|
faint_bg_color: Color32::from_gray(35),
|
|
extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background
|
|
code_bg_color: Color32::from_gray(64),
|
|
warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
|
|
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
|
window_rounding: Rounding::same(6.0),
|
|
window_shadow: Shadow::big_dark(),
|
|
popup_shadow: Shadow::small_dark(),
|
|
resize_corner_size: 12.0,
|
|
text_cursor_width: 2.0,
|
|
text_cursor_preview: false,
|
|
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
|
|
button_frame: true,
|
|
collapsing_header_frame: false,
|
|
}
|
|
}
|
|
|
|
/// Default light theme.
|
|
pub fn light() -> Self {
|
|
Self {
|
|
dark_mode: false,
|
|
widgets: Widgets::light(),
|
|
selection: Selection::light(),
|
|
hyperlink_color: Color32::from_rgb(0, 155, 255),
|
|
faint_bg_color: Color32::from_gray(242),
|
|
extreme_bg_color: Color32::from_gray(255), // e.g. TextEdit background
|
|
code_bg_color: Color32::from_gray(230),
|
|
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.
|
|
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
|
window_shadow: Shadow::big_light(),
|
|
popup_shadow: Shadow::small_light(),
|
|
..Self::dark()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Visuals {
|
|
fn default() -> Self {
|
|
Self::dark()
|
|
}
|
|
}
|
|
|
|
impl Selection {
|
|
fn dark() -> Self {
|
|
Self {
|
|
bg_fill: Color32::from_rgb(0, 92, 128),
|
|
stroke: Stroke::new(1.0, Color32::from_rgb(192, 222, 255)),
|
|
}
|
|
}
|
|
|
|
fn light() -> Self {
|
|
Self {
|
|
bg_fill: Color32::from_rgb(144, 209, 255),
|
|
stroke: Stroke::new(1.0, Color32::from_rgb(0, 83, 125)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Selection {
|
|
fn default() -> Self {
|
|
Self::dark()
|
|
}
|
|
}
|
|
|
|
impl Widgets {
|
|
pub fn dark() -> Self {
|
|
Self {
|
|
noninteractive: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(27), // window background
|
|
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines, windows outlines
|
|
fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
|
|
rounding: Rounding::same(2.0),
|
|
expansion: 0.0,
|
|
},
|
|
inactive: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(60), // button background
|
|
bg_stroke: Default::default(),
|
|
fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
|
|
rounding: Rounding::same(2.0),
|
|
expansion: 0.0,
|
|
},
|
|
hovered: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(70),
|
|
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
|
|
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
|
|
rounding: Rounding::same(3.0),
|
|
expansion: 1.0,
|
|
},
|
|
active: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(55),
|
|
bg_stroke: Stroke::new(1.0, Color32::WHITE),
|
|
fg_stroke: Stroke::new(2.0, Color32::WHITE),
|
|
rounding: Rounding::same(2.0),
|
|
expansion: 1.0,
|
|
},
|
|
open: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(27),
|
|
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
|
fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
|
|
rounding: Rounding::same(2.0),
|
|
expansion: 0.0,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn light() -> Self {
|
|
Self {
|
|
noninteractive: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(248), // window background - should be distinct from TextEdit background
|
|
bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines, windows outlines
|
|
fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), // normal text color
|
|
rounding: Rounding::same(2.0),
|
|
expansion: 0.0,
|
|
},
|
|
inactive: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(230), // button background
|
|
bg_stroke: Default::default(),
|
|
fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // button text
|
|
rounding: Rounding::same(2.0),
|
|
expansion: 0.0,
|
|
},
|
|
hovered: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(220),
|
|
bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
|
|
fg_stroke: Stroke::new(1.5, Color32::BLACK),
|
|
rounding: Rounding::same(3.0),
|
|
expansion: 1.0,
|
|
},
|
|
active: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(165),
|
|
bg_stroke: Stroke::new(1.0, Color32::BLACK),
|
|
fg_stroke: Stroke::new(2.0, Color32::BLACK),
|
|
rounding: Rounding::same(2.0),
|
|
expansion: 1.0,
|
|
},
|
|
open: WidgetVisuals {
|
|
bg_fill: Color32::from_gray(220),
|
|
bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
|
|
fg_stroke: Stroke::new(1.0, Color32::BLACK),
|
|
rounding: Rounding::same(2.0),
|
|
expansion: 0.0,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Widgets {
|
|
fn default() -> Self {
|
|
Self::dark()
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
use crate::{widgets::*, Ui};
|
|
|
|
impl Style {
|
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
|
let Self {
|
|
override_font_id,
|
|
override_text_style,
|
|
text_styles,
|
|
wrap: _,
|
|
spacing,
|
|
interaction,
|
|
visuals,
|
|
animation_time,
|
|
debug,
|
|
explanation_tooltips,
|
|
} = self;
|
|
|
|
visuals.light_dark_radio_buttons(ui);
|
|
|
|
crate::Grid::new("_options").show(ui, |ui| {
|
|
ui.label("Override font id:");
|
|
ui.horizontal(|ui| {
|
|
ui.radio_value(override_font_id, None, "None");
|
|
if ui.radio(override_font_id.is_some(), "override").clicked() {
|
|
*override_font_id = Some(FontId::default());
|
|
}
|
|
if let Some(override_font_id) = override_font_id {
|
|
crate::introspection::font_id_ui(ui, override_font_id);
|
|
}
|
|
});
|
|
ui.end_row();
|
|
|
|
ui.label("Override text style:");
|
|
crate::ComboBox::from_id_source("Override text style")
|
|
.selected_text(match override_text_style {
|
|
None => "None".to_owned(),
|
|
Some(override_text_style) => override_text_style.to_string(),
|
|
})
|
|
.show_ui(ui, |ui| {
|
|
ui.selectable_value(override_text_style, None, "None");
|
|
let all_text_styles = ui.style().text_styles();
|
|
for style in all_text_styles {
|
|
let text =
|
|
crate::RichText::new(style.to_string()).text_style(style.clone());
|
|
ui.selectable_value(override_text_style, Some(style), text);
|
|
}
|
|
});
|
|
ui.end_row();
|
|
|
|
ui.label("Animation duration:");
|
|
ui.add(
|
|
Slider::new(animation_time, 0.0..=1.0)
|
|
.clamp_to_range(true)
|
|
.suffix(" s"),
|
|
);
|
|
ui.end_row();
|
|
});
|
|
|
|
ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles));
|
|
ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
|
|
ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
|
|
ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
|
|
ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
|
|
|
|
ui.checkbox(explanation_tooltips, "Explanation tooltips")
|
|
.on_hover_text(
|
|
"Show explanatory text when hovering DragValue:s and other egui widgets",
|
|
);
|
|
|
|
ui.vertical_centered(|ui| reset_button(ui, self));
|
|
}
|
|
}
|
|
|
|
fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
|
|
ui.vertical(|ui| {
|
|
crate::Grid::new("text_styles").show(ui, |ui| {
|
|
for (text_style, font_id) in text_styles.iter_mut() {
|
|
ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
|
|
crate::introspection::font_id_ui(ui, font_id);
|
|
ui.end_row();
|
|
}
|
|
});
|
|
crate::reset_button_with(ui, text_styles, default_text_styles());
|
|
})
|
|
.response
|
|
}
|
|
|
|
impl Spacing {
|
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
|
let Self {
|
|
item_spacing,
|
|
window_margin,
|
|
menu_margin,
|
|
button_padding,
|
|
indent,
|
|
interact_size,
|
|
slider_width,
|
|
text_edit_width,
|
|
icon_width,
|
|
icon_width_inner,
|
|
icon_spacing,
|
|
tooltip_width,
|
|
indent_ends_with_horizontal_line,
|
|
combo_height,
|
|
scroll_bar_width,
|
|
} = self;
|
|
|
|
ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing"));
|
|
|
|
let margin_range = 0.0..=20.0;
|
|
ui.horizontal(|ui| {
|
|
ui.add(
|
|
DragValue::new(&mut window_margin.left)
|
|
.clamp_range(margin_range.clone())
|
|
.prefix("left: "),
|
|
);
|
|
ui.add(
|
|
DragValue::new(&mut window_margin.right)
|
|
.clamp_range(margin_range.clone())
|
|
.prefix("right: "),
|
|
);
|
|
|
|
ui.label("Window margins x");
|
|
});
|
|
|
|
ui.horizontal(|ui| {
|
|
ui.add(
|
|
DragValue::new(&mut window_margin.top)
|
|
.clamp_range(margin_range.clone())
|
|
.prefix("top: "),
|
|
);
|
|
ui.add(
|
|
DragValue::new(&mut window_margin.bottom)
|
|
.clamp_range(margin_range.clone())
|
|
.prefix("bottom: "),
|
|
);
|
|
ui.label("Window margins y");
|
|
});
|
|
|
|
ui.horizontal(|ui| {
|
|
ui.add(
|
|
DragValue::new(&mut menu_margin.left)
|
|
.clamp_range(margin_range.clone())
|
|
.prefix("left: "),
|
|
);
|
|
ui.add(
|
|
DragValue::new(&mut menu_margin.right)
|
|
.clamp_range(margin_range.clone())
|
|
.prefix("right: "),
|
|
);
|
|
|
|
ui.label("Menu margins x");
|
|
});
|
|
|
|
ui.horizontal(|ui| {
|
|
ui.add(
|
|
DragValue::new(&mut menu_margin.top)
|
|
.clamp_range(margin_range.clone())
|
|
.prefix("top: "),
|
|
);
|
|
ui.add(
|
|
DragValue::new(&mut menu_margin.bottom)
|
|
.clamp_range(margin_range)
|
|
.prefix("bottom: "),
|
|
);
|
|
ui.label("Menu margins y");
|
|
});
|
|
|
|
ui.add(slider_vec2(button_padding, 0.0..=20.0, "Button padding"));
|
|
ui.add(slider_vec2(interact_size, 4.0..=60.0, "Interact size"))
|
|
.on_hover_text("Minimum size of an interactive widget");
|
|
ui.horizontal(|ui| {
|
|
ui.add(DragValue::new(indent).clamp_range(0.0..=100.0));
|
|
ui.label("Indent");
|
|
});
|
|
ui.horizontal(|ui| {
|
|
ui.add(DragValue::new(slider_width).clamp_range(0.0..=1000.0));
|
|
ui.label("Slider width");
|
|
});
|
|
ui.horizontal(|ui| {
|
|
ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0));
|
|
ui.label("TextEdit width");
|
|
});
|
|
ui.horizontal(|ui| {
|
|
ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0));
|
|
ui.label("Scroll-bar width width");
|
|
});
|
|
|
|
ui.horizontal(|ui| {
|
|
ui.label("Checkboxes etc:");
|
|
ui.add(
|
|
DragValue::new(icon_width)
|
|
.prefix("outer icon width:")
|
|
.clamp_range(0.0..=60.0),
|
|
);
|
|
ui.add(
|
|
DragValue::new(icon_width_inner)
|
|
.prefix("inner icon width:")
|
|
.clamp_range(0.0..=60.0),
|
|
);
|
|
ui.add(
|
|
DragValue::new(icon_spacing)
|
|
.prefix("spacing:")
|
|
.clamp_range(0.0..=10.0),
|
|
);
|
|
});
|
|
|
|
ui.horizontal(|ui| {
|
|
ui.add(DragValue::new(tooltip_width).clamp_range(0.0..=1000.0));
|
|
ui.label("Tooltip wrap width");
|
|
});
|
|
|
|
ui.checkbox(
|
|
indent_ends_with_horizontal_line,
|
|
"End indented regions with a horizontal separator",
|
|
);
|
|
|
|
ui.horizontal(|ui| {
|
|
ui.label("Max height of a combo box");
|
|
ui.add(DragValue::new(combo_height).clamp_range(0.0..=1000.0));
|
|
});
|
|
|
|
ui.vertical_centered(|ui| reset_button(ui, self));
|
|
}
|
|
}
|
|
|
|
impl Interaction {
|
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
|
let Self {
|
|
resize_grab_radius_side,
|
|
resize_grab_radius_corner,
|
|
show_tooltips_only_when_still,
|
|
} = self;
|
|
ui.add(Slider::new(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side"));
|
|
ui.add(
|
|
Slider::new(resize_grab_radius_corner, 0.0..=20.0).text("resize_grab_radius_corner"),
|
|
);
|
|
ui.checkbox(
|
|
show_tooltips_only_when_still,
|
|
"Only show tooltips if mouse is still",
|
|
);
|
|
|
|
ui.vertical_centered(|ui| reset_button(ui, self));
|
|
}
|
|
}
|
|
|
|
impl Widgets {
|
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
|
let Self {
|
|
active,
|
|
hovered,
|
|
inactive,
|
|
noninteractive,
|
|
open,
|
|
} = self;
|
|
|
|
ui.collapsing("Noninteractive", |ui| {
|
|
ui.label(
|
|
"The style of a widget that you cannot interact with, e.g. labels and separators.",
|
|
);
|
|
noninteractive.ui(ui);
|
|
});
|
|
ui.collapsing("Interactive but inactive", |ui| {
|
|
ui.label("The style of an interactive widget, such as a button, at rest.");
|
|
inactive.ui(ui);
|
|
});
|
|
ui.collapsing("Interactive and hovered", |ui| {
|
|
ui.label("The style of an interactive widget while you hover it.");
|
|
hovered.ui(ui);
|
|
});
|
|
ui.collapsing("Interactive and active", |ui| {
|
|
ui.label("The style of an interactive widget as you are clicking or dragging it.");
|
|
active.ui(ui);
|
|
});
|
|
ui.collapsing("Open menu", |ui| {
|
|
ui.label("The style of an open combo-box or menu button");
|
|
open.ui(ui);
|
|
});
|
|
|
|
// ui.vertical_centered(|ui| reset_button(ui, self));
|
|
}
|
|
}
|
|
|
|
impl Selection {
|
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
|
let Self { bg_fill, stroke } = self;
|
|
ui.label("Selectable labels");
|
|
ui_color(ui, bg_fill, "background fill");
|
|
stroke_ui(ui, stroke, "stroke");
|
|
}
|
|
}
|
|
|
|
impl WidgetVisuals {
|
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
|
let Self {
|
|
bg_fill,
|
|
bg_stroke,
|
|
rounding,
|
|
fg_stroke,
|
|
expansion,
|
|
} = self;
|
|
ui_color(ui, bg_fill, "background fill");
|
|
stroke_ui(ui, bg_stroke, "background stroke");
|
|
|
|
rounding_ui(ui, rounding);
|
|
|
|
stroke_ui(ui, fg_stroke, "foreground stroke (text)");
|
|
ui.add(Slider::new(expansion, -5.0..=5.0).text("expansion"))
|
|
.on_hover_text("make shapes this much larger");
|
|
}
|
|
}
|
|
|
|
impl Visuals {
|
|
/// Show radio-buttons to switch between light and dark mode.
|
|
pub fn light_dark_radio_buttons(&mut self, ui: &mut crate::Ui) {
|
|
ui.horizontal(|ui| {
|
|
ui.selectable_value(self, Self::light(), "☀ Light");
|
|
ui.selectable_value(self, Self::dark(), "🌙 Dark");
|
|
});
|
|
}
|
|
|
|
/// Show small toggle-button for light and dark mode.
|
|
#[must_use]
|
|
pub fn light_dark_small_toggle_button(&self, ui: &mut crate::Ui) -> Option<Self> {
|
|
#![allow(clippy::collapsible_else_if)]
|
|
if self.dark_mode {
|
|
if ui
|
|
.add(Button::new("☀").frame(false))
|
|
.on_hover_text("Switch to light mode")
|
|
.clicked()
|
|
{
|
|
return Some(Self::light());
|
|
}
|
|
} else {
|
|
if ui
|
|
.add(Button::new("🌙").frame(false))
|
|
.on_hover_text("Switch to dark mode")
|
|
.clicked()
|
|
{
|
|
return Some(Self::dark());
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
|
let Self {
|
|
dark_mode: _,
|
|
override_text_color: _,
|
|
widgets,
|
|
selection,
|
|
hyperlink_color,
|
|
faint_bg_color,
|
|
extreme_bg_color,
|
|
code_bg_color,
|
|
warn_fg_color,
|
|
error_fg_color,
|
|
window_rounding,
|
|
window_shadow,
|
|
popup_shadow,
|
|
resize_corner_size,
|
|
text_cursor_width,
|
|
text_cursor_preview,
|
|
clip_rect_margin,
|
|
button_frame,
|
|
collapsing_header_frame,
|
|
} = self;
|
|
|
|
ui.collapsing("Background Colors", |ui| {
|
|
ui_color(ui, &mut widgets.inactive.bg_fill, "Buttons");
|
|
ui_color(ui, &mut widgets.noninteractive.bg_fill, "Windows");
|
|
ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
|
|
"Used for faint accentuation of interactive things, like striped grids.",
|
|
);
|
|
ui_color(ui, extreme_bg_color, "Extreme")
|
|
.on_hover_text("Background of plots and paintings");
|
|
});
|
|
|
|
ui.collapsing("Window", |ui| {
|
|
// Common shortcuts
|
|
ui_color(ui, &mut widgets.noninteractive.bg_fill, "Fill");
|
|
stroke_ui(ui, &mut widgets.noninteractive.bg_stroke, "Outline");
|
|
|
|
rounding_ui(ui, window_rounding);
|
|
|
|
shadow_ui(ui, window_shadow, "Shadow");
|
|
shadow_ui(ui, popup_shadow, "Shadow (small menus and popups)");
|
|
});
|
|
|
|
ui.collapsing("Widgets", |ui| widgets.ui(ui));
|
|
ui.collapsing("Selection", |ui| selection.ui(ui));
|
|
|
|
ui.horizontal(|ui| {
|
|
ui_color(
|
|
ui,
|
|
&mut widgets.noninteractive.fg_stroke.color,
|
|
"Text color",
|
|
);
|
|
ui_color(ui, warn_fg_color, RichText::new("Warnings"));
|
|
ui_color(ui, error_fg_color, RichText::new("Errors"));
|
|
});
|
|
|
|
ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(|ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.spacing_mut().item_spacing.x = 0.0;
|
|
ui.label("For monospaced inlined text ");
|
|
ui.code("like this");
|
|
ui.label(".");
|
|
});
|
|
});
|
|
|
|
ui_color(ui, hyperlink_color, "hyperlink_color");
|
|
ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
|
|
ui.add(Slider::new(text_cursor_width, 0.0..=4.0).text("text_cursor_width"));
|
|
ui.checkbox(text_cursor_preview, "Preview text cursor on hover");
|
|
ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
|
|
|
|
ui.checkbox(button_frame, "Button has a frame");
|
|
ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
|
|
|
|
ui.vertical_centered(|ui| reset_button(ui, self));
|
|
}
|
|
}
|
|
|
|
impl DebugOptions {
|
|
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
|
let Self {
|
|
debug_on_hover,
|
|
show_expand_width: debug_expand_width,
|
|
show_expand_height: debug_expand_height,
|
|
show_resize: debug_resize,
|
|
} = self;
|
|
|
|
ui.checkbox(debug_on_hover, "Show debug info on hover");
|
|
ui.checkbox(
|
|
debug_expand_width,
|
|
"Show which widgets make their parent wider",
|
|
);
|
|
ui.checkbox(
|
|
debug_expand_height,
|
|
"Show which widgets make their parent higher",
|
|
);
|
|
ui.checkbox(debug_resize, "Debug Resize");
|
|
|
|
ui.vertical_centered(|ui| reset_button(ui, self));
|
|
}
|
|
}
|
|
|
|
// TODO(emilk): improve and standardize `slider_vec2`
|
|
fn slider_vec2<'a>(
|
|
value: &'a mut Vec2,
|
|
range: std::ops::RangeInclusive<f32>,
|
|
text: &'a str,
|
|
) -> impl Widget + 'a {
|
|
move |ui: &mut crate::Ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.add(
|
|
DragValue::new(&mut value.x)
|
|
.clamp_range(range.clone())
|
|
.prefix("x: "),
|
|
);
|
|
ui.add(
|
|
DragValue::new(&mut value.y)
|
|
.clamp_range(range.clone())
|
|
.prefix("y: "),
|
|
);
|
|
ui.label(text);
|
|
})
|
|
.response
|
|
}
|
|
}
|
|
|
|
fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into<WidgetText>) -> Response {
|
|
ui.horizontal(|ui| {
|
|
ui.color_edit_button_srgba(srgba);
|
|
ui.label(label);
|
|
})
|
|
.response
|
|
}
|
|
|
|
fn rounding_ui(ui: &mut Ui, rounding: &mut Rounding) {
|
|
const MAX: f32 = 20.0;
|
|
let mut same = rounding.is_same();
|
|
ui.group(|ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.label("Rounding: ");
|
|
ui.radio_value(&mut same, true, "Same");
|
|
ui.radio_value(&mut same, false, "Separate");
|
|
});
|
|
|
|
if same {
|
|
let mut cr = rounding.nw;
|
|
ui.add(Slider::new(&mut cr, 0.0..=MAX));
|
|
*rounding = Rounding::same(cr);
|
|
} else {
|
|
ui.add(Slider::new(&mut rounding.nw, 0.0..=MAX).text("North-West"));
|
|
ui.add(Slider::new(&mut rounding.ne, 0.0..=MAX).text("North-East"));
|
|
ui.add(Slider::new(&mut rounding.sw, 0.0..=MAX).text("South-West"));
|
|
ui.add(Slider::new(&mut rounding.se, 0.0..=MAX).text("South-East"));
|
|
if rounding.is_same() {
|
|
rounding.se *= 1.00001;
|
|
}
|
|
}
|
|
});
|
|
}
|