#![allow(clippy::if_same_then_else)]
use crate::{ecolor::*, emath::*, FontFamily, FontId, Response, RichText, WidgetText};
use epaint::{Rounding, Shadow, Stroke};
use std::collections::BTreeMap;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum TextStyle {
Small,
Body,
Monospace,
Button,
Heading,
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 {
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()
)
})
}
}
pub enum FontSelection {
Default,
FontId(FontId),
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)
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Style {
pub override_text_style: Option<TextStyle>,
pub override_font_id: Option<FontId>,
pub text_styles: BTreeMap<TextStyle, FontId>,
pub wrap: Option<bool>,
pub spacing: Spacing,
pub interaction: Interaction,
pub visuals: Visuals,
pub animation_time: f32,
pub debug: DebugOptions,
pub explanation_tooltips: bool,
}
impl Style {
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.fg_stroke = self.visuals.selection.stroke;
}
visuals
}
pub fn noninteractive(&self) -> &WidgetVisuals {
&self.visuals.widgets.noninteractive
}
pub fn text_styles(&self) -> Vec<TextStyle> {
self.text_styles.keys().cloned().collect()
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Spacing {
pub item_spacing: Vec2,
pub window_margin: Margin,
pub button_padding: Vec2,
pub menu_margin: Margin,
pub indent: f32,
pub interact_size: Vec2,
pub slider_width: f32,
pub text_edit_width: f32,
pub icon_width: f32,
pub icon_width_inner: f32,
pub icon_spacing: f32,
pub tooltip_width: f32,
pub indent_ends_with_horizontal_line: bool,
pub combo_height: f32,
pub scroll_bar_width: f32,
pub scroll_bar_inner_margin: f32,
pub scroll_bar_outer_margin: f32,
}
impl Spacing {
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,
}
}
#[inline]
pub fn symmetric(x: f32, y: f32) -> Self {
Self {
left: x,
right: x,
top: y,
bottom: y,
}
}
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)
}
pub fn is_same(&self) -> bool {
self.left == self.right && self.left == self.top && self.left == 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,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Interaction {
pub resize_grab_radius_side: f32,
pub resize_grab_radius_corner: f32,
pub show_tooltips_only_when_still: bool,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Visuals {
pub dark_mode: bool,
pub override_text_color: Option<Color32>,
pub widgets: Widgets,
pub selection: Selection,
pub hyperlink_color: Color32,
pub faint_bg_color: Color32,
pub extreme_bg_color: Color32,
pub code_bg_color: Color32,
pub warn_fg_color: Color32,
pub error_fg_color: Color32,
pub window_rounding: Rounding,
pub window_shadow: Shadow,
pub window_fill: Color32,
pub window_stroke: Stroke,
pub panel_fill: Color32,
pub popup_shadow: Shadow,
pub resize_corner_size: f32,
pub text_cursor_width: f32,
pub text_cursor_preview: bool,
pub clip_rect_margin: f32,
pub button_frame: bool,
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 {
self.gray_out(self.text_color())
}
#[inline(always)]
pub fn strong_text_color(&self) -> Color32 {
self.widgets.active.text_color()
}
#[inline(always)]
pub fn window_fill(&self) -> Color32 {
self.window_fill
}
#[inline(always)]
pub fn window_stroke(&self) -> Stroke {
self.window_stroke
}
#[inline(always)]
pub fn fade_out_to_color(&self) -> Color32 {
self.widgets.noninteractive.bg_fill
}
#[inline(always)]
pub fn gray_out(&self, color: Color32) -> Color32 {
crate::ecolor::tint_color_towards(color, self.fade_out_to_color())
}
}
#[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,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Widgets {
pub noninteractive: WidgetVisuals,
pub inactive: WidgetVisuals,
pub hovered: WidgetVisuals,
pub active: WidgetVisuals,
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
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct WidgetVisuals {
pub bg_fill: Color32,
pub bg_stroke: Stroke,
pub rounding: Rounding,
pub fg_stroke: Stroke,
pub expansion: f32,
}
impl WidgetVisuals {
#[inline(always)]
pub fn text_color(&self) -> Color32 {
self.fg_stroke.color
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DebugOptions {
pub debug_on_hover: bool,
pub show_expand_width: bool,
pub show_expand_height: bool,
pub show_resize: bool,
pub show_interactive_widgets: bool,
pub show_blocking_widget: bool,
}
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(6.0),
button_padding: vec2(4.0, 1.0),
indent: 18.0, 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,
scroll_bar_inner_margin: 4.0,
scroll_bar_outer_margin: 0.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: true,
}
}
}
impl Visuals {
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), code_bg_color: Color32::from_gray(64),
warn_fg_color: Color32::from_rgb(255, 143, 0), error_fg_color: Color32::from_rgb(255, 0, 0),
window_rounding: Rounding::same(6.0),
window_shadow: Shadow::big_dark(),
window_fill: Color32::from_gray(27),
window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
panel_fill: Color32::from_gray(27),
popup_shadow: Shadow::small_dark(),
resize_corner_size: 12.0,
text_cursor_width: 2.0,
text_cursor_preview: false,
clip_rect_margin: 3.0, button_frame: true,
collapsing_header_frame: false,
}
}
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), code_bg_color: Color32::from_gray(230),
warn_fg_color: Color32::from_rgb(255, 100, 0), error_fg_color: Color32::from_rgb(255, 0, 0),
window_shadow: Shadow::big_light(),
window_fill: Color32::from_gray(248),
window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
panel_fill: Color32::from_gray(248),
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),
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), rounding: Rounding::same(2.0),
expansion: 0.0,
},
inactive: WidgetVisuals {
bg_fill: Color32::from_gray(60), bg_stroke: Default::default(),
fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), 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)), 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),
bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), rounding: Rounding::same(2.0),
expansion: 0.0,
},
inactive: WidgetVisuals {
bg_fill: Color32::from_gray(230), bg_stroke: Default::default(),
fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), 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)), 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,
scroll_bar_inner_margin,
scroll_bar_outer_margin,
} = self;
ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing"));
margin_ui(ui, "Window margin:", window_margin);
margin_ui(ui, "Menu margin:", menu_margin);
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");
});
ui.horizontal(|ui| {
ui.add(DragValue::new(scroll_bar_inner_margin).clamp_range(0.0..=32.0));
ui.label("Scroll-bar inner margin");
});
ui.horizontal(|ui| {
ui.add(DragValue::new(scroll_bar_outer_margin).clamp_range(0.0..=32.0));
ui.label("Scroll-bar outer margin");
});
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));
}
}
fn margin_ui(ui: &mut Ui, text: &str, margin: &mut Margin) {
let margin_range = 0.0..=20.0;
ui.horizontal(|ui| {
ui.label(text);
let mut same = margin.is_same();
ui.checkbox(&mut same, "Same");
if same {
let mut value = margin.left;
ui.add(DragValue::new(&mut value).clamp_range(margin_range.clone()));
*margin = Margin::same(value);
} else {
if margin.is_same() {
margin.right = margin.left + 1.0;
margin.bottom = margin.left + 2.0;
margin.top = margin.left + 3.0;
}
ui.add(
DragValue::new(&mut margin.left)
.clamp_range(margin_range.clone())
.prefix("L: "),
)
.on_hover_text("Left margin");
ui.add(
DragValue::new(&mut margin.right)
.clamp_range(margin_range.clone())
.prefix("R: "),
)
.on_hover_text("Right margin");
ui.add(
DragValue::new(&mut margin.top)
.clamp_range(margin_range.clone())
.prefix("T: "),
)
.on_hover_text("Top margin");
ui.add(
DragValue::new(&mut margin.bottom)
.clamp_range(margin_range)
.prefix("B: "),
)
.on_hover_text("Bottom margin");
}
});
}
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);
});
}
}
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 {
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");
});
}
#[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,
window_fill,
window_stroke,
panel_fill,
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, window_fill, "Windows");
ui_color(ui, panel_fill, "Panels");
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| {
ui_color(ui, window_fill, "Fill");
stroke_ui(ui, window_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,
show_expand_height,
show_resize,
show_interactive_widgets,
show_blocking_widget,
} = self;
ui.checkbox(debug_on_hover, "Show debug info on hover");
ui.checkbox(
show_expand_width,
"Show which widgets make their parent wider",
);
ui.checkbox(
show_expand_height,
"Show which widgets make their parent higher",
);
ui.checkbox(show_resize, "Debug Resize");
ui.checkbox(
show_interactive_widgets,
"Show an overlay on all interactive widgets",
);
ui.checkbox(
show_blocking_widget,
"Show wha widget blocks the interaction of another widget",
);
ui.vertical_centered(|ui| reset_button(ui, self));
}
}
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;
}
}
});
}