[style] make the default style a lot more compact and sleek

This commit is contained in:
Emil Ernerfeldt 2020-09-01 20:40:54 +02:00
parent 2129a87575
commit e7b098ac56
8 changed files with 89 additions and 64 deletions

View file

@ -45,11 +45,11 @@ TODO-list for the Egui project. If you looking for something to do, look here.
* [x] Pixel-perfect painting (round positions to nearest pixel). * [x] Pixel-perfect painting (round positions to nearest pixel).
* [x] Fix `aa_size`: should be 1, currently fudged at 1.5 * [x] Fix `aa_size`: should be 1, currently fudged at 1.5
* [x] Fix thin rounded corners rendering bug (too bright) * [x] Fix thin rounded corners rendering bug (too bright)
* [x] Smoother animation (e.g. ease-out)? NO: animation are too brief for subtelty
* [ ] Veriy alpha and sRGB correctness * [ ] Veriy alpha and sRGB correctness
* [x] sRGBA decode in fragment shader * [x] sRGBA decode in fragment shader
* [ ] Thin circles look bad * [ ] Thin circles look bad
* [ ] Color picker * [ ] Color picker
* [ ] Smoother animation (e.g. ease-out)?
* Math * Math
* [ ] Change `width.min(max_width)` to `width.at_most(max_width)` * [ ] Change `width.min(max_width)` to `width.at_most(max_width)`

View file

@ -2,7 +2,7 @@ use std::hash::Hash;
use crate::{ use crate::{
layout::Direction, layout::Direction,
paint::{LineStyle, PaintCmd, TextStyle}, paint::{PaintCmd, TextStyle},
widgets::Label, widgets::Label,
*, *,
}; };
@ -105,8 +105,7 @@ impl State {
/// Paint the arrow icon that indicated if the region is open or not /// Paint the arrow icon that indicated if the region is open or not
pub fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) { pub fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
let stroke_color = ui.style().interact(response).stroke_color; let line_style = ui.style().interact(response).line_style();
let stroke_width = ui.style().interact(response).stroke_width;
let rect = response.rect; let rect = response.rect;
@ -124,7 +123,7 @@ pub fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
points, points,
closed: true, closed: true,
fill: Default::default(), fill: Default::default(),
outline: LineStyle::new(stroke_width, stroke_color), outline: line_style,
}); });
} }
@ -183,17 +182,19 @@ impl CollapsingHeader {
let available = ui.available_finite(); let available = ui.available_finite();
let text_pos = available.min + vec2(ui.style().spacing.indent, 0.0); let text_pos = available.min + vec2(ui.style().spacing.indent, 0.0);
let galley = label.layout_width(ui, available.width() - ui.style().spacing.indent); let galley = label.layout_width(ui, available.right() - text_pos.x);
let text_max_x = text_pos.x + galley.size.x; let text_max_x = text_pos.x + galley.size.x;
let desired_width = text_max_x - available.left(); let desired_width = text_max_x - available.left();
let desired_width = desired_width.max(available.width()); let desired_width = desired_width.max(available.width());
let size = vec2( let mut desired_size = vec2(
desired_width, desired_width,
galley.size.y + 2.0 * ui.style().spacing.button_padding.y, galley.size.y + 2.0 * ui.style().spacing.button_padding.y,
); );
desired_size.y = desired_size.y.max(ui.style().spacing.clickable_diameter);
let rect = ui.allocate_space(desired_size);
let rect = rect.expand2(ui.style().spacing.button_expand);
let rect = ui.allocate_space(size);
let response = ui.interact(rect, id, Sense::click()); let response = ui.interact(rect, id, Sense::click());
let text_pos = pos2(text_pos.x, response.rect.center().y - galley.size.y / 2.0); let text_pos = pos2(text_pos.x, response.rect.center().y - galley.size.y / 2.0);

View file

@ -280,7 +280,7 @@ pub fn paint_resize_corner_with_style(ui: &mut Ui, rect: &Rect, style: LineStyle
let corner = painter.round_pos_to_pixels(rect.right_bottom()); let corner = painter.round_pos_to_pixels(rect.right_bottom());
let mut w = 2.0; let mut w = 2.0;
while w < 12.0 { while w <= rect.width() && w <= rect.height() {
painter.line_segment( painter.line_segment(
[pos2(corner.x - w, corner.y), pos2(corner.x, corner.y - w)], [pos2(corner.x - w, corner.y), pos2(corner.x, corner.y - w)],
style, style,

View file

@ -504,9 +504,8 @@ impl Context {
use crate::containers::*; use crate::containers::*;
CollapsingHeader::new("Style") CollapsingHeader::new("Style")
.default_open(false) .default_open(true)
.show(ui, |ui| { .show(ui, |ui| {
self.paint_options.lock().ui(ui);
self.style_ui(ui); self.style_ui(ui);
}); });
@ -518,6 +517,12 @@ impl Context {
self.fonts().texture().ui(ui); self.fonts().texture().ui(ui);
self.set_fonts(font_definitions); self.set_fonts(font_definitions);
}); });
CollapsingHeader::new("Painting")
.default_open(true)
.show(ui, |ui| {
self.paint_options.lock().ui(ui);
});
} }
pub fn inspection_ui(&self, ui: &mut Ui) { pub fn inspection_ui(&self, ui: &mut Ui) {

View file

@ -34,6 +34,11 @@ pub struct Spacing {
/// Button size is text size plus this on each side /// Button size is text size plus this on each side
pub button_padding: Vec2, pub button_padding: Vec2,
/// Expand buttons by this much *after* allocating them.
/// This is then mostly a visual change (but also makes them easier to hit with the mouse).
/// This allows for compact layout where buttons actually eat into item_spacing a bit
pub button_expand: Vec2,
/// Indent collapsing regions etc by this much. /// Indent collapsing regions etc by this much.
pub indent: f32, pub indent: f32,
@ -99,8 +104,8 @@ pub struct Visuals {
pub resize_corner_size: f32, pub resize_corner_size: f32,
/// Blink text cursor by this frequency. If None, always show the cursor. /// Blink text cursor by this frequency. If 0, always show the cursor.
pub cursor_blink_hz: Option<f32>, pub cursor_blink_hz: f32,
pub text_cursor_width: f32, pub text_cursor_width: f32,
/// Allow child widgets to be just on the border and still have an outline with some thickness /// Allow child widgets to be just on the border and still have an outline with some thickness
@ -152,7 +157,7 @@ pub struct WidgetVisuals {
/// When you need a fill. /// When you need a fill.
pub main_fill: Srgba, pub main_fill: Srgba,
/// Stroke and text color of the interactive part of a component (button, slider grab, checkbox, ...) /// Stroke and text color of the interactive part of a component (button text, slider grab, check-mark, ...)
pub stroke_color: Srgba, pub stroke_color: Srgba,
/// For lines etc /// For lines etc
@ -181,11 +186,12 @@ impl Default for Style {
impl Default for Spacing { impl Default for Spacing {
fn default() -> Self { fn default() -> Self {
Self { Self {
item_spacing: vec2(8.0, 4.0), item_spacing: vec2(8.0, 6.0),
window_padding: vec2(6.0, 6.0), window_padding: vec2(6.0, 6.0),
button_padding: vec2(4.0, 1.0), button_padding: vec2(2.0, 0.0),
button_expand: vec2(1.0, 1.0),
indent: 21.0, indent: 21.0,
clickable_diameter: 22.0, clickable_diameter: 14.0, // TODO: automatically higher on touch screens
slider_width: 140.0, slider_width: 140.0,
icon_width: 14.0, icon_width: 14.0,
menu_bar_height: 16.0, menu_bar_height: 16.0,
@ -207,13 +213,13 @@ impl Default for Visuals {
Self { Self {
interacted: Default::default(), interacted: Default::default(),
text_color: Srgba::gray(160), text_color: Srgba::gray(160),
line_width: 1.0, line_width: 0.5,
thin_outline: LineStyle::new(0.5, GRAY), thin_outline: LineStyle::new(0.5, GRAY),
background_fill: Rgba::luminance_alpha(0.013, 0.95).into(), background_fill: Rgba::luminance_alpha(0.013, 0.95).into(),
dark_bg_color: Srgba::black_alpha(140), dark_bg_color: Srgba::black_alpha(140),
window_corner_radius: 10.0, window_corner_radius: 10.0,
resize_corner_size: 16.0, resize_corner_size: 12.0,
cursor_blink_hz: None, // Some(1.0) cursor_blink_hz: 0.0, // 1.0 looks good
text_cursor_width: 2.0, text_cursor_width: 2.0,
clip_rect_margin: 3.0, clip_rect_margin: 3.0,
debug_widget_rects: false, debug_widget_rects: false,
@ -228,30 +234,30 @@ impl Default for Interacted {
active: WidgetVisuals { active: WidgetVisuals {
bg_fill: Srgba::black_alpha(128), bg_fill: Srgba::black_alpha(128),
bg_outline: LineStyle::new(2.0, WHITE), bg_outline: LineStyle::new(2.0, WHITE),
corner_radius: 0.0, corner_radius: 4.0,
main_fill: srgba(120, 120, 200, 255), main_fill: srgba(120, 120, 200, 255),
stroke_color: WHITE, stroke_color: WHITE,
stroke_width: 2.0, stroke_width: 2.0,
}, },
hovered: WidgetVisuals { hovered: WidgetVisuals {
bg_fill: TRANSPARENT, bg_fill: Rgba::luminance_alpha(0.06, 0.5).into(),
bg_outline: LineStyle::new(1.0, WHITE), bg_outline: LineStyle::new(1.0, Rgba::white_alpha(0.5)),
corner_radius: 2.0, corner_radius: 4.0,
main_fill: srgba(100, 100, 150, 255), main_fill: srgba(100, 100, 150, 255),
stroke_color: Srgba::gray(240), stroke_color: Srgba::gray(240),
stroke_width: 1.5, stroke_width: 1.5,
}, },
inactive: WidgetVisuals { inactive: WidgetVisuals {
bg_fill: TRANSPARENT, bg_fill: Rgba::luminance_alpha(0.04, 0.5).into(),
bg_outline: LineStyle::new(1.0, Srgba::gray(128)), bg_outline: LineStyle::new(1.0, Rgba::white_alpha(0.04)),
corner_radius: 4.0, corner_radius: 4.0,
main_fill: srgba(60, 60, 80, 255), main_fill: srgba(60, 60, 80, 255),
stroke_color: Srgba::gray(200), // Mustn't look grayed out! stroke_color: Srgba::gray(200), // Should NOT look grayed out!
stroke_width: 1.0, stroke_width: 0.5,
}, },
disabled: WidgetVisuals { disabled: WidgetVisuals {
bg_fill: TRANSPARENT, bg_fill: TRANSPARENT,
bg_outline: LineStyle::new(0.5, Srgba::gray(128)), bg_outline: LineStyle::new(0.5, Srgba::gray(70)),
corner_radius: 4.0, corner_radius: 4.0,
main_fill: srgba(50, 50, 50, 255), main_fill: srgba(50, 50, 50, 255),
stroke_color: Srgba::gray(128), // Should look grayed out stroke_color: Srgba::gray(128), // Should look grayed out
@ -294,6 +300,7 @@ impl Spacing {
item_spacing, item_spacing,
window_padding, window_padding,
button_padding, button_padding,
button_expand,
indent, indent,
clickable_diameter, clickable_diameter,
slider_width, slider_width,
@ -301,9 +308,10 @@ impl Spacing {
menu_bar_height, menu_bar_height,
} = self; } = self;
ui_slider_vec2(ui, item_spacing, 0.0..=20.0, "item_spacing"); ui_slider_vec2(ui, item_spacing, 0.0..=10.0, "item_spacing");
ui_slider_vec2(ui, window_padding, 0.0..=20.0, "window_padding"); ui_slider_vec2(ui, window_padding, 0.0..=10.0, "window_padding");
ui_slider_vec2(ui, button_padding, 0.0..=20.0, "button_padding"); ui_slider_vec2(ui, button_padding, 0.0..=10.0, "button_padding");
ui_slider_vec2(ui, button_expand, 0.0..=10.0, "button_expand");
ui.add(Slider::f32(indent, 0.0..=100.0).text("indent")); ui.add(Slider::f32(indent, 0.0..=100.0).text("indent"));
ui.add(Slider::f32(clickable_diameter, 0.0..=40.0).text("clickable_diameter")); ui.add(Slider::f32(clickable_diameter, 0.0..=40.0).text("clickable_diameter"));
ui.add(Slider::f32(slider_width, 0.0..=1000.0).text("slider_width")); ui.add(Slider::f32(slider_width, 0.0..=1000.0).text("slider_width"));
@ -400,7 +408,7 @@ impl Visuals {
ui_color(ui, dark_bg_color, "dark_bg_color"); 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(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(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
let _ = cursor_blink_hz; // TODO 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(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.add(Slider::f32(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
@ -416,8 +424,9 @@ impl LineStyle {
pub fn ui(&mut self, ui: &mut crate::Ui, text: &str) { pub fn ui(&mut self, ui: &mut crate::Ui, text: &str) {
let Self { width, color } = self; let Self { width, color } = self;
ui.horizontal_centered(|ui| { ui.horizontal_centered(|ui| {
ui.style_mut().spacing.slider_width /= 2.0;
ui.label(format!("{}: ", text)); ui.label(format!("{}: ", text));
ui.add(Slider::f32(width, 0.0..=10.0).text("width")); ui.add(Slider::f32(width, 0.0..=5.0).text("width"));
ui_color(ui, color, "color"); ui_color(ui, color, "color");
}); });
} }
@ -427,9 +436,8 @@ impl LineStyle {
fn ui_slider_vec2(ui: &mut Ui, value: &mut Vec2, range: std::ops::RangeInclusive<f32>, text: &str) { fn ui_slider_vec2(ui: &mut Ui, value: &mut Vec2, range: std::ops::RangeInclusive<f32>, text: &str) {
ui.horizontal_centered(|ui| { ui.horizontal_centered(|ui| {
ui.label(format!("{}: ", text)); ui.label(format!("{}: ", text));
ui.add(Slider::f32(&mut value.x, range.clone())) ui.add(Slider::f32(&mut value.x, range.clone()).text("w"));
.tooltip_text("x"); ui.add(Slider::f32(&mut value.y, range.clone()).text("h"));
ui.add(Slider::f32(&mut value.y, range)).tooltip_text("y");
}); });
} }

View file

@ -283,12 +283,13 @@ impl Widget for Button {
let id = ui.make_position_id(); let id = ui.make_position_id();
let font = &ui.fonts()[text_style]; let font = &ui.fonts()[text_style];
let galley = font.layout_multiline(text, ui.available().width()); let galley = font.layout_multiline(text, ui.available().width());
let padding = ui.style().spacing.button_padding; let mut desired_size = galley.size + 2.0 * ui.style().spacing.button_padding;
let mut size = galley.size + 2.0 * padding; desired_size.y = desired_size.y.max(ui.style().spacing.clickable_diameter);
size.y = size.y.max(ui.style().spacing.clickable_diameter); let rect = ui.allocate_space(desired_size);
let rect = ui.allocate_space(size); let rect = rect.expand2(ui.style().spacing.button_expand);
let response = ui.interact(rect, id, sense); let response = ui.interact(rect, id, sense);
let text_cursor = response.rect.left_center() + vec2(padding.x, -0.5 * galley.size.y); let text_cursor = response.rect.center() - 0.5 * galley.size;
let fill = fill.unwrap_or(ui.style().interact(&response).bg_fill); let fill = fill.unwrap_or(ui.style().interact(&response).bg_fill);
ui.painter().add(PaintCmd::Rect { ui.painter().add(PaintCmd::Rect {
rect: response.rect, rect: response.rect,
@ -342,18 +343,23 @@ impl<'a> Widget for Checkbox<'a> {
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &ui.fonts()[text_style]; let font = &ui.fonts()[text_style];
let galley = font.layout_single_line(text); let galley = font.layout_single_line(text);
let size = ui.style().spacing.button_padding
+ vec2(ui.style().spacing.icon_width, 0.0) let icon_width = ui.style().spacing.icon_width;
+ galley.size let button_padding = ui.style().spacing.button_padding;
+ ui.style().spacing.button_padding; let mut desired_size =
let rect = ui.allocate_space(size); button_padding + vec2(icon_width, 0.0) + galley.size + button_padding;
desired_size.y = desired_size.y.max(ui.style().spacing.clickable_diameter);
let rect = ui.allocate_space(desired_size);
let response = ui.interact(rect, id, Sense::click()); let response = ui.interact(rect, id, Sense::click());
let text_cursor = response.rect.min
+ ui.style().spacing.button_padding
+ vec2(ui.style().spacing.icon_width, 0.0);
if response.clicked { if response.clicked {
*checked = !*checked; *checked = !*checked;
} }
let text_cursor = pos2(
response.rect.min.x + button_padding.x + icon_width,
response.rect.center().y - 0.5 * galley.size.y,
);
let (small_icon_rect, big_icon_rect) = ui.style().spacing.icon_rectangles(response.rect); let (small_icon_rect, big_icon_rect) = ui.style().spacing.icon_rectangles(response.rect);
ui.painter().add(PaintCmd::Rect { ui.painter().add(PaintCmd::Rect {
rect: big_icon_rect, rect: big_icon_rect,
@ -362,7 +368,7 @@ impl<'a> Widget for Checkbox<'a> {
outline: ui.style().interact(&response).bg_outline, outline: ui.style().interact(&response).bg_outline,
}); });
let stroke_color = ui.style().interact(&response).stroke_color; let line_style = ui.style().interact(&response).line_style();
if *checked { if *checked {
ui.painter().add(PaintCmd::Path { ui.painter().add(PaintCmd::Path {
@ -372,12 +378,12 @@ impl<'a> Widget for Checkbox<'a> {
pos2(small_icon_rect.right(), small_icon_rect.top()), pos2(small_icon_rect.right(), small_icon_rect.top()),
], ],
closed: false, closed: false,
outline: LineStyle::new(ui.style().visuals.line_width, stroke_color), outline: line_style,
fill: Default::default(), fill: Default::default(),
}); });
} }
let text_color = text_color.unwrap_or(stroke_color); let text_color = text_color.unwrap_or(line_style.color);
ui.painter() ui.painter()
.galley(text_cursor, galley, text_style, text_color); .galley(text_cursor, galley, text_style, text_color);
response response
@ -420,15 +426,20 @@ impl Widget for RadioButton {
let text_style = TextStyle::Button; let text_style = TextStyle::Button;
let font = &ui.fonts()[text_style]; let font = &ui.fonts()[text_style];
let galley = font.layout_multiline(text, ui.available().width()); let galley = font.layout_multiline(text, ui.available().width());
let size = ui.style().spacing.button_padding
+ vec2(ui.style().spacing.icon_width, 0.0) let icon_width = ui.style().spacing.icon_width;
+ galley.size let button_padding = ui.style().spacing.button_padding;
+ ui.style().spacing.button_padding; let mut desired_size =
let rect = ui.allocate_space(size); button_padding + vec2(icon_width, 0.0) + galley.size + button_padding;
desired_size.y = desired_size.y.max(ui.style().spacing.clickable_diameter);
let rect = ui.allocate_space(desired_size);
let response = ui.interact(rect, id, Sense::click()); let response = ui.interact(rect, id, Sense::click());
let text_cursor = response.rect.min
+ ui.style().spacing.button_padding let text_cursor = pos2(
+ vec2(ui.style().spacing.icon_width, 0.0); response.rect.min.x + button_padding.x + icon_width,
response.rect.center().y - 0.5 * galley.size.y,
);
let bg_fill = ui.style().interact(&response).bg_fill; let bg_fill = ui.style().interact(&response).bg_fill;
let stroke_color = ui.style().interact(&response).stroke_color; let stroke_color = ui.style().interact(&response).stroke_color;

View file

@ -164,8 +164,8 @@ impl<'a> Slider<'a> {
ui.painter().add(PaintCmd::Rect { ui.painter().add(PaintCmd::Rect {
rect: rail_rect, rect: rail_rect,
corner_radius: rail_radius, corner_radius: rail_radius,
fill: ui.style().visuals.background_fill, fill: ui.style().visuals.interacted.inactive.bg_fill,
outline: LineStyle::new(1.0, Srgba::gray(200)), // TODO outline: ui.style().visuals.interacted.inactive.bg_outline,
}); });
ui.painter().add(PaintCmd::Circle { ui.painter().add(PaintCmd::Circle {

View file

@ -190,7 +190,7 @@ impl<'t> Widget for TextEdit<'t> {
if ui.memory().has_kb_focus(id) { if ui.memory().has_kb_focus(id) {
let cursor_blink_hz = ui.style().visuals.cursor_blink_hz; let cursor_blink_hz = ui.style().visuals.cursor_blink_hz;
let show_cursor = if let Some(cursor_blink_hz) = cursor_blink_hz { let show_cursor = if 0.0 < cursor_blink_hz {
ui.ctx().request_repaint(); // TODO: only when cursor blinks on or off ui.ctx().request_repaint(); // TODO: only when cursor blinks on or off
(ui.input().time * cursor_blink_hz as f64 * 3.0).floor() as i64 % 3 != 0 (ui.input().time * cursor_blink_hz as f64 * 3.0).floor() as i64 % 3 != 0
} else { } else {