#![allow(clippy::if_same_then_else)]
use crate::{
color::*,
math::*,
paint::{Stroke, TextStyle},
types::*,
};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Style {
pub body_text_style: TextStyle,
pub spacing: Spacing,
pub interaction: Interaction,
pub visuals: Visuals,
pub animation_time: f32,
}
impl Style {
pub fn interact(&self, response: &Response) -> &WidgetVisuals {
self.visuals.widgets.style(response)
}
pub fn noninteractive(&self) -> &WidgetVisuals {
&self.visuals.widgets.noninteractive
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Spacing {
pub item_spacing: Vec2,
pub window_padding: Vec2,
pub button_padding: Vec2,
pub indent: f32,
pub interact_size: Vec2,
pub slider_width: f32,
pub text_edit_width: f32,
pub icon_width: f32,
pub icon_spacing: f32,
pub tooltip_width: f32,
}
impl Spacing {
pub fn icon_rectangles(&self, rect: Rect) -> (Rect, Rect) {
let box_side = self.icon_width;
let big_icon_rect = Rect::from_center_size(
pos2(rect.left() + box_side / 2.0, rect.center().y),
vec2(box_side, box_side),
);
let small_rect_side = 8.0;
let small_icon_rect =
Rect::from_center_size(big_icon_rect.center(), Vec2::splat(small_rect_side));
(small_icon_rect, big_icon_rect)
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Interaction {
pub resize_grab_radius_side: f32,
pub resize_grab_radius_corner: f32,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Visuals {
pub override_text_color: Option<Srgba>,
pub widgets: Widgets,
pub dark_bg_color: Srgba,
pub window_corner_radius: f32,
pub resize_corner_size: f32,
pub cursor_blink_hz: f32,
pub text_cursor_width: f32,
pub clip_rect_margin: f32,
pub debug_widget_rects: bool,
pub debug_resize: bool,
}
impl Visuals {
pub fn noninteractive(&self) -> &WidgetVisuals {
&self.widgets.noninteractive
}
pub fn text_color(&self) -> Srgba {
self.override_text_color
.unwrap_or_else(|| self.widgets.noninteractive.text_color())
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Widgets {
pub active: WidgetVisuals,
pub hovered: WidgetVisuals,
pub inactive: WidgetVisuals,
pub disabled: WidgetVisuals,
pub noninteractive: WidgetVisuals,
}
impl Widgets {
pub fn style(&self, response: &Response) -> &WidgetVisuals {
if response.active || response.has_kb_focus {
&self.active
} else if response.sense == Sense::nothing() {
&self.disabled
} 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: Srgba,
pub bg_stroke: Stroke,
pub corner_radius: f32,
pub fg_fill: Srgba,
pub fg_stroke: Stroke,
}
impl WidgetVisuals {
pub fn text_color(&self) -> Srgba {
self.fg_stroke.color
}
}
impl Default for Style {
fn default() -> Self {
Self {
body_text_style: TextStyle::Body,
spacing: Spacing::default(),
interaction: Interaction::default(),
visuals: Visuals::default(),
animation_time: 1.0 / 15.0,
}
}
}
impl Default for Spacing {
fn default() -> Self {
Self {
item_spacing: vec2(8.0, 3.0),
window_padding: vec2(6.0, 6.0),
button_padding: vec2(3.0, 1.0),
indent: 25.0,
interact_size: vec2(40.0, 20.0),
slider_width: 140.0,
text_edit_width: 280.0,
icon_width: 16.0,
icon_spacing: 1.0,
tooltip_width: 400.0,
}
}
}
impl Default for Interaction {
fn default() -> Self {
Self {
resize_grab_radius_side: 5.0,
resize_grab_radius_corner: 10.0,
}
}
}
impl Default for Visuals {
fn default() -> Self {
Self {
override_text_color: None,
widgets: Default::default(),
dark_bg_color: Srgba::black_alpha(140),
window_corner_radius: 10.0,
resize_corner_size: 12.0,
cursor_blink_hz: 0.0,
text_cursor_width: 2.0,
clip_rect_margin: 3.0,
debug_widget_rects: false,
debug_resize: false,
}
}
}
impl Default for Widgets {
fn default() -> Self {
Self {
active: WidgetVisuals {
bg_fill: Srgba::black_alpha(128),
bg_stroke: Stroke::new(2.0, WHITE),
corner_radius: 4.0,
fg_fill: srgba(120, 120, 200, 255),
fg_stroke: Stroke::new(2.0, WHITE),
},
hovered: WidgetVisuals {
bg_fill: Rgba::luminance_alpha(0.06, 0.5).into(),
bg_stroke: Stroke::new(1.0, Rgba::white_alpha(0.5)),
corner_radius: 4.0,
fg_fill: srgba(100, 100, 150, 255),
fg_stroke: Stroke::new(1.5, Srgba::gray(240)),
},
inactive: WidgetVisuals {
bg_fill: Rgba::luminance_alpha(0.04, 0.5).into(),
bg_stroke: Stroke::new(1.0, Rgba::white_alpha(0.06)),
corner_radius: 4.0,
fg_fill: srgba(60, 60, 80, 255),
fg_stroke: Stroke::new(1.0, Srgba::gray(200)),
},
disabled: WidgetVisuals {
bg_fill: TRANSPARENT,
bg_stroke: Stroke::new(0.5, Srgba::gray(70)),
corner_radius: 4.0,
fg_fill: srgba(50, 50, 50, 255),
fg_stroke: Stroke::new(1.0, Srgba::gray(128)),
},
noninteractive: WidgetVisuals {
bg_stroke: Stroke::new(1.0, Rgba::white_alpha(0.06)),
bg_fill: Rgba::luminance_alpha(0.010, 0.975).into(),
corner_radius: 4.0,
fg_fill: Default::default(),
fg_stroke: Stroke::new(1.0, Srgba::gray(160)),
},
}
}
}
use crate::{widgets::*, Ui};
impl Style {
pub fn ui(&mut self, ui: &mut crate::Ui) {
if ui.button("Reset").clicked {
*self = Default::default();
}
let Self {
body_text_style,
spacing,
interaction,
visuals,
animation_time,
} = self;
ui.horizontal(|ui| {
ui.label("Default text style:");
for &value in &[TextStyle::Body, TextStyle::Monospace] {
ui.radio_value(body_text_style, value, format!("{:?}", value));
}
});
ui.collapsing("Spacing", |ui| spacing.ui(ui));
ui.collapsing("Interaction", |ui| interaction.ui(ui));
ui.collapsing("Visuals", |ui| visuals.ui(ui));
ui.add(Slider::f32(animation_time, 0.0..=1.0).text("animation_time"));
}
}
impl Spacing {
pub fn ui(&mut self, ui: &mut crate::Ui) {
if ui.button("Reset").clicked {
*self = Default::default();
}
let Self {
item_spacing,
window_padding,
button_padding,
indent,
interact_size,
slider_width,
text_edit_width,
icon_width,
icon_spacing,
tooltip_width,
} = self;
ui_slider_vec2(ui, item_spacing, 0.0..=10.0, "item_spacing");
ui_slider_vec2(ui, window_padding, 0.0..=10.0, "window_padding");
ui_slider_vec2(ui, button_padding, 0.0..=10.0, "button_padding");
ui_slider_vec2(ui, interact_size, 0.0..=60.0, "interact_size")
.on_hover_text("Minimum size of an interactive widget");
ui.add(Slider::f32(indent, 0.0..=100.0).text("indent"));
ui.add(Slider::f32(slider_width, 0.0..=1000.0).text("slider_width"));
ui.add(Slider::f32(text_edit_width, 0.0..=1000.0).text("text_edit_width"));
ui.add(Slider::f32(icon_width, 0.0..=60.0).text("icon_width"));
ui.add(Slider::f32(icon_spacing, 0.0..=10.0).text("icon_spacing"));
ui.add(Slider::f32(tooltip_width, 0.0..=10.0).text("tooltip_width"));
}
}
impl Interaction {
pub fn ui(&mut self, ui: &mut crate::Ui) {
if ui.button("Reset").clicked {
*self = Default::default();
}
let Self {
resize_grab_radius_side,
resize_grab_radius_corner,
} = self;
ui.add(Slider::f32(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side"));
ui.add(
Slider::f32(resize_grab_radius_corner, 0.0..=20.0).text("resize_grab_radius_corner"),
);
}
}
impl Widgets {
pub fn ui(&mut self, ui: &mut crate::Ui) {
if ui.button("Reset").clicked {
*self = Default::default();
}
let Self {
active,
hovered,
inactive,
disabled,
noninteractive,
} = self;
ui.collapsing("noninteractive", |ui| noninteractive.ui(ui));
ui.collapsing("interactive & disabled", |ui| disabled.ui(ui));
ui.collapsing("interactive & inactive", |ui| inactive.ui(ui));
ui.collapsing("interactive & hovered", |ui| hovered.ui(ui));
ui.collapsing("interactive & active", |ui| active.ui(ui));
}
}
impl WidgetVisuals {
pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self {
bg_fill,
bg_stroke,
corner_radius,
fg_fill,
fg_stroke,
} = self;
ui_color(ui, bg_fill, "bg_fill");
bg_stroke.ui(ui, "bg_stroke");
ui.add(Slider::f32(corner_radius, 0.0..=10.0).text("corner_radius"));
ui_color(ui, fg_fill, "fg_fill");
fg_stroke.ui(ui, "fg_stroke (text)");
}
}
impl Visuals {
pub fn ui(&mut self, ui: &mut crate::Ui) {
if ui.button("Reset").clicked {
*self = Default::default();
}
let Self {
override_text_color: _,
widgets,
dark_bg_color,
window_corner_radius,
resize_corner_size,
cursor_blink_hz,
text_cursor_width,
clip_rect_margin,
debug_widget_rects,
debug_resize,
} = self;
ui.collapsing("widgets", |ui| widgets.ui(ui));
ui_color(ui, dark_bg_color, "dark_bg_color");
ui.add(Slider::f32(window_corner_radius, 0.0..=20.0).text("window_corner_radius"));
ui.add(Slider::f32(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
ui.add(Slider::f32(cursor_blink_hz, 0.0..=4.0).text("cursor_blink_hz"));
ui.add(Slider::f32(text_cursor_width, 0.0..=2.0).text("text_cursor_width"));
ui.add(Slider::f32(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
ui.checkbox(debug_widget_rects, "Paint debug rectangles around widgets");
ui.checkbox(debug_resize, "Debug Resize");
}
}
impl Stroke {
pub fn ui(&mut self, ui: &mut crate::Ui, text: &str) {
let Self { width, color } = self;
ui.horizontal(|ui| {
ui.add(DragValue::f32(width).speed(0.1).range(0.0..=5.0))
.on_hover_text("Width");
ui.color_edit_button_srgba(color);
ui.label(text);
let stroke_rect = ui.allocate_space(ui.style().spacing.interact_size);
let left = stroke_rect.left_center();
let right = stroke_rect.right_center();
ui.painter().line_segment([left, right], (*width, *color));
});
}
}
fn ui_slider_vec2(
ui: &mut Ui,
value: &mut Vec2,
range: std::ops::RangeInclusive<f32>,
text: &str,
) -> Response {
ui.horizontal(|ui| {
ui.add(Slider::f32(&mut value.x, range.clone()).text("w"));
ui.add(Slider::f32(&mut value.y, range.clone()).text("h"));
ui.label(text);
})
.1
}
fn ui_color(ui: &mut Ui, srgba: &mut Srgba, text: &str) {
ui.horizontal(|ui| {
ui.color_edit_button_srgba(srgba);
ui.label(text);
});
}