Style tweaks (#450)

* Tweak style

More compact, less round, less noisy

* Button text is now same size as body text
* The rounder corners are now less rounded
* Collapsing headers no longer have a frame around them
* Combo-boxes looks better when opened
* Slightly more muted colors
* Remove extra line spacing after `\n` (i.e. between paragraphs)

* Thinner scrollbars

* Tweak light mode

* Tweak shadows

* Fix broken doc link

* Add style tweak to CHANGELOG
This commit is contained in:
Emil Ernerfeldt 2021-06-12 15:53:56 +02:00 committed by GitHub
parent a50ddc2703
commit 778bcc1ef7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 365 additions and 166 deletions

View file

@ -24,6 +24,7 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
* Add `ScrollArea::enable_scrolling` to allow freezing scrolling when editing TextEdit widgets within it * Add `ScrollArea::enable_scrolling` to allow freezing scrolling when editing TextEdit widgets within it
### Changed 🔧 ### Changed 🔧
* [Tweaked the default visuals style](https://github.com/emilk/egui/pull/450).
* Plot: Changed `Curve` to `Line`. * Plot: Changed `Curve` to `Line`.
* `TopPanel::top` is now `TopBottomPanel::top`. * `TopPanel::top` is now `TopBottomPanel::top`.
* `SidePanel::left` no longet takes the default width by argument, but by a builder call. * `SidePanel::left` no longet takes the default width by argument, but by a builder call.

View file

@ -246,13 +246,16 @@ impl CollapsingHeader {
let visuals = ui.style().interact(&header_response); let visuals = ui.style().interact(&header_response);
let text_color = visuals.text_color(); let text_color = visuals.text_color();
ui.painter().add(Shape::Rect {
rect: header_response.rect.expand(visuals.expansion), if ui.visuals().collapsing_header_frame {
corner_radius: visuals.corner_radius, ui.painter().add(Shape::Rect {
fill: visuals.bg_fill, rect: header_response.rect.expand(visuals.expansion),
stroke: visuals.bg_stroke, corner_radius: visuals.corner_radius,
// stroke: Default::default(), fill: visuals.bg_fill,
}); stroke: visuals.bg_stroke,
// stroke: Default::default(),
});
}
{ {
let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect); let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect);

View file

@ -176,8 +176,8 @@ fn combo_box(
) -> Response { ) -> Response {
let popup_id = button_id.with("popup"); let popup_id = button_id.with("popup");
let button_active = ui.memory().is_popup_open(popup_id); let is_popup_open = ui.memory().is_popup_open(popup_id);
let button_response = button_frame(ui, button_id, button_active, Sense::click(), |ui| { let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
// We don't want to change width when user selects something new // We don't want to change width when user selects something new
let full_minimum_width = ui.spacing().slider_width; let full_minimum_width = ui.spacing().slider_width;
let icon_size = Vec2::splat(ui.spacing().icon_width); let icon_size = Vec2::splat(ui.spacing().icon_width);
@ -193,10 +193,14 @@ fn combo_box(
let (_, rect) = ui.allocate_space(Vec2::new(width, height)); let (_, rect) = ui.allocate_space(Vec2::new(width, height));
let button_rect = ui.min_rect().expand2(ui.spacing().button_padding); let button_rect = ui.min_rect().expand2(ui.spacing().button_padding);
let response = ui.interact(button_rect, button_id, Sense::click()); let response = ui.interact(button_rect, button_id, Sense::click());
// response.active |= button_active; // response.active |= is_popup_open;
let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect); let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect);
let visuals = ui.style().interact(&response); let visuals = if is_popup_open {
&ui.visuals().widgets.open
} else {
ui.style().interact(&response)
};
paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals); paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals);
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size, rect); let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size, rect);
@ -207,9 +211,8 @@ fn combo_box(
if button_response.clicked() { if button_response.clicked() {
ui.memory().toggle_popup(popup_id); ui.memory().toggle_popup(popup_id);
} }
const MAX_COMBO_HEIGHT: f32 = 128.0;
crate::popup::popup_below_widget(ui, popup_id, &button_response, |ui| { crate::popup::popup_below_widget(ui, popup_id, &button_response, |ui| {
ScrollArea::from_max_height(MAX_COMBO_HEIGHT).show(ui, menu_contents) ScrollArea::from_max_height(ui.spacing().combo_height).show(ui, menu_contents)
}); });
button_response button_response
@ -218,7 +221,7 @@ fn combo_box(
fn button_frame( fn button_frame(
ui: &mut Ui, ui: &mut Ui,
id: Id, id: Id,
button_active: bool, is_popup_open: bool,
sense: Sense, sense: Sense,
add_contents: impl FnOnce(&mut Ui), add_contents: impl FnOnce(&mut Ui),
) -> Response { ) -> Response {
@ -237,9 +240,12 @@ fn button_frame(
let mut outer_rect = content_ui.min_rect().expand2(margin); let mut outer_rect = content_ui.min_rect().expand2(margin);
outer_rect.set_height(outer_rect.height().at_least(interact_size.y)); outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
let mut response = ui.interact(outer_rect, id, sense); let response = ui.interact(outer_rect, id, sense);
response.is_pointer_button_down_on |= button_active; let visuals = if is_popup_open {
let visuals = ui.style().interact(&response); &ui.visuals().widgets.open
} else {
ui.style().interact(&response)
};
ui.painter().set( ui.painter().set(
where_to_put_background, where_to_put_background,

View file

@ -24,8 +24,8 @@ impl Frame {
pub fn group(style: &Style) -> Self { pub fn group(style: &Style) -> Self {
Self { Self {
margin: Vec2::new(8.0, 6.0), margin: Vec2::new(8.0, 6.0),
corner_radius: 4.0, corner_radius: style.visuals.widgets.noninteractive.corner_radius,
stroke: style.visuals.window_stroke(), stroke: style.visuals.widgets.noninteractive.bg_stroke,
..Default::default() ..Default::default()
} }
} }
@ -63,8 +63,8 @@ impl Frame {
pub fn menu(style: &Style) -> Self { pub fn menu(style: &Style) -> Self {
Self { Self {
margin: Vec2::splat(1.0), margin: Vec2::splat(1.0),
corner_radius: 2.0, corner_radius: style.visuals.widgets.noninteractive.corner_radius,
shadow: Shadow::small(), shadow: style.visuals.popup_shadow,
fill: style.visuals.window_fill(), fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(), stroke: style.visuals.window_stroke(),
} }
@ -73,8 +73,8 @@ impl Frame {
pub fn popup(style: &Style) -> Self { pub fn popup(style: &Style) -> Self {
Self { Self {
margin: style.spacing.window_padding, margin: style.spacing.window_padding,
corner_radius: 5.0, corner_radius: style.visuals.widgets.noninteractive.corner_radius,
shadow: Shadow::small(), shadow: style.visuals.popup_shadow,
fill: style.visuals.window_fill(), fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(), stroke: style.visuals.window_stroke(),
} }
@ -84,7 +84,7 @@ impl Frame {
pub fn dark_canvas(style: &Style) -> Self { pub fn dark_canvas(style: &Style) -> Self {
Self { Self {
margin: Vec2::new(10.0, 10.0), margin: Vec2::new(10.0, 10.0),
corner_radius: 5.0, corner_radius: style.visuals.widgets.noninteractive.corner_radius,
fill: Color32::from_black_alpha(250), fill: Color32::from_black_alpha(250),
stroke: style.visuals.window_stroke(), stroke: style.visuals.window_stroke(),
..Default::default() ..Default::default()

View file

@ -357,7 +357,6 @@ impl Prepared {
let margin = animation_t * ui.spacing().item_spacing.x; let margin = animation_t * ui.spacing().item_spacing.x;
let left = inner_rect.right() + margin; let left = inner_rect.right() + margin;
let right = outer_rect.right(); let right = outer_rect.right();
let corner_radius = (right - left) / 2.0;
let top = inner_rect.top(); let top = inner_rect.top();
let bottom = inner_rect.bottom(); let bottom = inner_rect.bottom();
@ -415,7 +414,7 @@ impl Prepared {
pos2(left, from_content(state.offset.y)), pos2(left, from_content(state.offset.y)),
pos2(right, from_content(state.offset.y + inner_rect.height())), pos2(right, from_content(state.offset.y + inner_rect.height())),
); );
let min_handle_height = (2.0 * corner_radius).max(8.0); let min_handle_height = ui.spacing().scroll_bar_width;
if handle_rect.size().y < min_handle_height { if handle_rect.size().y < min_handle_height {
handle_rect = Rect::from_center_size( handle_rect = Rect::from_center_size(
handle_rect.center(), handle_rect.center(),
@ -429,21 +428,17 @@ impl Prepared {
&ui.style().visuals.widgets.inactive &ui.style().visuals.widgets.inactive
}; };
ui.painter().add(epaint::Shape::Rect { ui.painter().add(epaint::Shape::rect_filled(
rect: outer_scroll_rect, outer_scroll_rect,
corner_radius, visuals.corner_radius,
fill: ui.visuals().extreme_bg_color, ui.visuals().extreme_bg_color,
stroke: Default::default(), ));
// fill: visuals.bg_fill,
// stroke: visuals.bg_stroke,
});
ui.painter().add(epaint::Shape::Rect { ui.painter().add(epaint::Shape::rect_filled(
rect: handle_rect.expand(-2.0), handle_rect,
corner_radius, visuals.corner_radius,
fill: visuals.bg_fill, visuals.bg_fill,
stroke: visuals.bg_stroke, ));
});
} }
let size = vec2( let size = vec2(
@ -465,5 +460,5 @@ impl Prepared {
} }
fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 { fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 {
ui.spacing().item_spacing.x + 16.0 ui.spacing().item_spacing.x + ui.spacing().scroll_bar_width
} }

View file

@ -198,12 +198,7 @@ impl GridLayout {
let rect = rect.expand2(0.5 * self.spacing.y * Vec2::Y); let rect = rect.expand2(0.5 * self.spacing.y * Vec2::Y);
let rect = rect.expand2(2.0 * Vec2::X); // HACK: just looks better with some spacing on the sides let rect = rect.expand2(2.0 * Vec2::X); // HACK: just looks better with some spacing on the sides
let color = if self.style.visuals.dark_mode { painter.rect_filled(rect, 2.0, self.style.visuals.faint_bg_color);
Rgba::from_white_alpha(0.0075)
} else {
Rgba::from_black_alpha(0.075)
};
painter.rect_filled(rect, 2.0, color);
} }
} }
} }

View file

@ -132,14 +132,17 @@ impl Widget for &mut epaint::TessellationOptions {
debug_paint_text_rects, debug_paint_text_rects,
debug_ignore_clip_rects, debug_ignore_clip_rects,
} = self; } = self;
ui.checkbox(anti_alias, "Antialias"); ui.checkbox(anti_alias, "Antialias")
ui.checkbox( .on_hover_text("Turn off for small performance gain.");
coarse_tessellation_culling, ui.collapsing("debug", |ui| {
"Do coarse culling in the tessellator", ui.checkbox(
); coarse_tessellation_culling,
ui.checkbox(debug_ignore_clip_rects, "Ignore clip rectangles (debug)"); "Do coarse culling in the tessellator)",
ui.checkbox(debug_paint_clip_rects, "Paint clip rectangles (debug)"); );
ui.checkbox(debug_paint_text_rects, "Paint text bounds (debug)"); ui.checkbox(debug_ignore_clip_rects, "Ignore clip rectangles");
ui.checkbox(debug_paint_clip_rects, "Paint clip rectangles");
ui.checkbox(debug_paint_text_rects, "Paint text bounds");
});
}) })
.response .response
} }

View file

@ -75,7 +75,8 @@ fn menu_impl<'c>(ui: &mut Ui, title: impl ToString, add_contents: Box<dyn FnOnce
let mut button = Button::new(title); let mut button = Button::new(title);
if bar_state.open_menu == Some(menu_id) { if bar_state.open_menu == Some(menu_id) {
button = button.fill(Some(ui.visuals().selection.bg_fill)); button = button.fill(ui.visuals().widgets.open.bg_fill);
button = button.stroke(ui.visuals().widgets.open.bg_stroke);
} }
let button_response = ui.add(button); let button_response = ui.add(button);

View file

@ -117,6 +117,14 @@ pub struct Spacing {
/// Width of a tooltip (`on_hover_ui`, `on_hover_text` etc). /// Width of a tooltip (`on_hover_ui`, `on_hover_text` etc).
pub tooltip_width: f32, 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 { impl Spacing {
@ -188,20 +196,26 @@ pub struct Visuals {
pub selection: Selection, 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). /// Very dark or light color (for corresponding theme).
/// Used as the background of text edits, scroll bars and others things /// Used as the background of text edits, scroll bars and others things
/// that needs to look different from other interactive stuff. /// that needs to look different from other interactive stuff.
pub extreme_bg_color: Color32, pub extreme_bg_color: Color32,
/// The color used for `Hyperlink`,
pub hyperlink_color: Color32,
/// Background color behind code-styled monospaced labels. /// Background color behind code-styled monospaced labels.
pub code_bg_color: Color32, pub code_bg_color: Color32,
pub window_corner_radius: f32, pub window_corner_radius: f32,
pub window_shadow: Shadow, pub window_shadow: Shadow,
pub popup_shadow: Shadow,
pub resize_corner_size: f32, pub resize_corner_size: f32,
pub text_cursor_width: f32, pub text_cursor_width: f32,
@ -210,6 +224,12 @@ pub struct Visuals {
/// Allow child widgets to be just on the border and still have a stroke with some thickness /// Allow child widgets to be just on the border and still have a stroke with some thickness
pub clip_rect_margin: f32, 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 { impl Visuals {
@ -264,6 +284,8 @@ pub struct Widgets {
pub hovered: WidgetVisuals, pub hovered: WidgetVisuals,
/// The style of an interactive widget as you are clicking or dragging it. /// The style of an interactive widget as you are clicking or dragging it.
pub active: WidgetVisuals, 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 { impl Widgets {
@ -344,13 +366,16 @@ impl Default for Spacing {
item_spacing: vec2(8.0, 3.0), item_spacing: vec2(8.0, 3.0),
window_padding: Vec2::splat(6.0), window_padding: Vec2::splat(6.0),
button_padding: vec2(4.0, 1.0), button_padding: vec2(4.0, 1.0),
indent: 25.0, indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing`
interact_size: vec2(40.0, 20.0), interact_size: vec2(40.0, 18.0),
slider_width: 100.0, slider_width: 100.0,
text_edit_width: 280.0, text_edit_width: 280.0,
icon_width: 16.0, icon_width: 14.0,
icon_spacing: 0.0, icon_spacing: 0.0,
tooltip_width: 600.0, tooltip_width: 600.0,
combo_height: 200.0,
scroll_bar_width: 8.0,
indent_ends_with_horizontal_line: false,
} }
} }
} }
@ -373,15 +398,19 @@ impl Visuals {
override_text_color: None, override_text_color: None,
widgets: Widgets::default(), widgets: Widgets::default(),
selection: Selection::default(), selection: Selection::default(),
extreme_bg_color: Color32::from_gray(10),
hyperlink_color: Color32::from_rgb(90, 170, 255), hyperlink_color: Color32::from_rgb(90, 170, 255),
faint_bg_color: Color32::from_gray(24),
extreme_bg_color: Color32::from_gray(10),
code_bg_color: Color32::from_gray(64), code_bg_color: Color32::from_gray(64),
window_corner_radius: 10.0, window_corner_radius: 6.0,
window_shadow: Shadow::big_dark(), window_shadow: Shadow::big_dark(),
popup_shadow: Shadow::small_dark(),
resize_corner_size: 12.0, resize_corner_size: 12.0,
text_cursor_width: 2.0, text_cursor_width: 2.0,
text_cursor_preview: false, text_cursor_preview: false,
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion 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,
} }
} }
@ -391,10 +420,12 @@ impl Visuals {
dark_mode: false, dark_mode: false,
widgets: Widgets::light(), widgets: Widgets::light(),
selection: Selection::light(), selection: Selection::light(),
extreme_bg_color: Color32::from_gray(235), // TODO: rename hyperlink_color: Color32::from_rgb(0, 155, 255),
hyperlink_color: Color32::from_rgb(0, 133, 218), faint_bg_color: Color32::from_gray(240),
extreme_bg_color: Color32::from_gray(250),
code_bg_color: Color32::from_gray(200), code_bg_color: Color32::from_gray(200),
window_shadow: Shadow::big_light(), window_shadow: Shadow::big_light(),
popup_shadow: Shadow::small_light(),
..Self::dark() ..Self::dark()
} }
} }
@ -431,32 +462,39 @@ impl Widgets {
pub fn dark() -> Self { pub fn dark() -> Self {
Self { Self {
noninteractive: WidgetVisuals { noninteractive: WidgetVisuals {
bg_fill: Color32::from_gray(30), // window background bg_fill: Color32::from_gray(27), // window background
bg_stroke: Stroke::new(1.0, Color32::from_gray(65)), // window outline bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines, windows outlines
fg_stroke: Stroke::new(1.0, Color32::from_gray(160)), // normal text color fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
corner_radius: 4.0, corner_radius: 2.0,
expansion: 0.0, expansion: 0.0,
}, },
inactive: WidgetVisuals { inactive: WidgetVisuals {
bg_fill: Color32::from_gray(70), bg_fill: Color32::from_gray(60), // button background
bg_stroke: Default::default(), bg_stroke: Default::default(),
fg_stroke: Stroke::new(1.0, Color32::from_gray(200)), // Should NOT look grayed out! fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
corner_radius: 4.0, corner_radius: 2.0,
expansion: 0.0, expansion: 0.0,
}, },
hovered: WidgetVisuals { hovered: WidgetVisuals {
bg_fill: Color32::from_gray(80), bg_fill: Color32::from_gray(70),
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button 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)), fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
corner_radius: 4.0, corner_radius: 3.0,
expansion: 1.0, expansion: 1.0,
}, },
active: WidgetVisuals { active: WidgetVisuals {
bg_fill: Color32::from_gray(90), bg_fill: Color32::from_gray(55),
bg_stroke: Stroke::new(1.0, Color32::WHITE), bg_stroke: Stroke::new(1.0, Color32::WHITE),
fg_stroke: Stroke::new(2.0, Color32::WHITE), fg_stroke: Stroke::new(2.0, Color32::WHITE),
corner_radius: 4.0, corner_radius: 2.0,
expansion: 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)),
corner_radius: 2.0,
expansion: 0.0,
}, },
} }
} }
@ -464,32 +502,39 @@ impl Widgets {
pub fn light() -> Self { pub fn light() -> Self {
Self { Self {
noninteractive: WidgetVisuals { noninteractive: WidgetVisuals {
bg_fill: Color32::from_gray(220), // window background bg_fill: Color32::from_gray(235), // window background
bg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // window outline bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines, windows outlines
fg_stroke: Stroke::new(1.0, Color32::from_gray(70)), // normal text color fg_stroke: Stroke::new(1.0, Color32::from_gray(100)), // normal text color
corner_radius: 4.0, corner_radius: 2.0,
expansion: 0.0, expansion: 0.0,
}, },
inactive: WidgetVisuals { inactive: WidgetVisuals {
bg_fill: Color32::from_gray(195), bg_fill: Color32::from_gray(215), // button background
bg_stroke: Default::default(), bg_stroke: Default::default(),
fg_stroke: Stroke::new(1.0, Color32::from_gray(55)), // Should NOT look grayed out! fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), // button text
corner_radius: 4.0, corner_radius: 2.0,
expansion: 0.0, expansion: 0.0,
}, },
hovered: WidgetVisuals { hovered: WidgetVisuals {
bg_fill: Color32::from_gray(175), bg_fill: Color32::from_gray(210),
bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
fg_stroke: Stroke::new(2.0, Color32::BLACK), fg_stroke: Stroke::new(1.5, Color32::BLACK),
corner_radius: 4.0, corner_radius: 3.0,
expansion: 1.0, expansion: 1.0,
}, },
active: WidgetVisuals { active: WidgetVisuals {
bg_fill: Color32::from_gray(165), bg_fill: Color32::from_gray(165),
bg_stroke: Stroke::new(1.0, Color32::BLACK), bg_stroke: Stroke::new(1.0, Color32::BLACK),
fg_stroke: Stroke::new(2.0, Color32::BLACK), fg_stroke: Stroke::new(2.0, Color32::BLACK),
corner_radius: 4.0, corner_radius: 2.0,
expansion: 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),
corner_radius: 2.0,
expansion: 0.0,
}, },
} }
} }
@ -520,30 +565,55 @@ impl Style {
visuals.light_dark_radio_buttons(ui); visuals.light_dark_radio_buttons(ui);
ui.horizontal(|ui| { crate::Grid::new("_options").show(ui, |ui| {
ui.label("Default text style:"); ui.label("Default body text style:");
for &value in &[TextStyle::Body, TextStyle::Monospace] { ui.horizontal(|ui| {
ui.radio_value(body_text_style, value, format!("{:?}", value)); for &style in &[TextStyle::Body, TextStyle::Monospace] {
} if ui
}); .add(
RadioButton::new(*body_text_style == style, format!("{:?}", style))
crate::ComboBox::from_label("Global text style override") .text_style(style),
.selected_text(match override_text_style { )
None => "None".to_owned(), .clicked()
Some(override_text_style) => format!("{:?}", override_text_style), {
}) *body_text_style = style;
.show_ui(ui, |ui| { };
ui.selectable_value(override_text_style, None, "None");
for style in TextStyle::all() {
ui.selectable_value(override_text_style, Some(style), format!("{:?}", style));
} }
}); });
ui.end_row();
ui.add( ui.label("Override text style:");
Slider::new(animation_time, 0.0..=1.0) crate::ComboBox::from_id_source("Override text style")
.text("animation durations") .selected_text(match override_text_style {
.suffix(" s"), None => "None".to_owned(),
); Some(override_text_style) => format!("{:?}", override_text_style),
})
.show_ui(ui, |ui| {
ui.selectable_value(override_text_style, None, "None");
for style in TextStyle::all() {
// ui.selectable_value(override_text_style, Some(style), format!("{:?}", style));
let selected = *override_text_style == Some(style);
if ui
.add(
SelectableLabel::new(selected, format!("{:?}", style))
.text_style(style),
)
.clicked()
{
*override_text_style = Some(style);
}
}
});
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("📏 Spacing", |ui| spacing.ui(ui)); ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
ui.collapsing("☝ Interaction", |ui| interaction.ui(ui)); ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
@ -567,19 +637,61 @@ impl Spacing {
icon_width, icon_width,
icon_spacing, icon_spacing,
tooltip_width, tooltip_width,
indent_ends_with_horizontal_line,
combo_height,
scroll_bar_width,
} = self; } = self;
ui.add(slider_vec2(item_spacing, 0.0..=10.0, "item_spacing")); ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing"));
ui.add(slider_vec2(window_padding, 0.0..=10.0, "window_padding")); ui.add(slider_vec2(window_padding, 0.0..=20.0, "Window padding"));
ui.add(slider_vec2(button_padding, 0.0..=10.0, "button_padding")); ui.add(slider_vec2(button_padding, 0.0..=20.0, "Button padding"));
ui.add(slider_vec2(interact_size, 0.0..=60.0, "interact_size")) ui.add(slider_vec2(interact_size, 4.0..=60.0, "Interact size"))
.on_hover_text("Minimum size of an interactive widget"); .on_hover_text("Minimum size of an interactive widget");
ui.add(Slider::new(indent, 0.0..=100.0).text("indent")); ui.horizontal(|ui| {
ui.add(Slider::new(slider_width, 0.0..=1000.0).text("slider_width")); ui.add(DragValue::new(indent).clamp_range(0.0..=100.0));
ui.add(Slider::new(text_edit_width, 0.0..=1000.0).text("text_edit_width")); ui.label("Indent");
ui.add(Slider::new(icon_width, 0.0..=60.0).text("icon_width")); });
ui.add(Slider::new(icon_spacing, 0.0..=10.0).text("icon_spacing")); ui.horizontal(|ui| {
ui.add(Slider::new(tooltip_width, 0.0..=1000.0).text("tooltip_width")); 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("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)); ui.vertical_centered(|ui| reset_button(ui, self));
} }
@ -612,33 +724,40 @@ impl Widgets {
hovered, hovered,
inactive, inactive,
noninteractive, noninteractive,
open,
} = self; } = self;
ui.collapsing("noninteractive", |ui| { ui.collapsing("Noninteractive", |ui| {
ui.label("The style of a widget that you cannot interact with."); ui.label(
"The style of a widget that you cannot interact with, e.g. labels and separators.",
);
noninteractive.ui(ui) noninteractive.ui(ui)
}); });
ui.collapsing("interactive & inactive", |ui| { ui.collapsing("Interactive but inactive", |ui| {
ui.label("The style of an interactive widget, such as a button, at rest."); ui.label("The style of an interactive widget, such as a button, at rest.");
inactive.ui(ui) inactive.ui(ui)
}); });
ui.collapsing("interactive & hovered", |ui| { ui.collapsing("Interactive and hovered", |ui| {
ui.label("The style of an interactive widget while you hover it."); ui.label("The style of an interactive widget while you hover it.");
hovered.ui(ui) hovered.ui(ui)
}); });
ui.collapsing("interactive & active", |ui| { ui.collapsing("Interactive and active", |ui| {
ui.label("The style of an interactive widget as you are clicking or dragging it."); ui.label("The style of an interactive widget as you are clicking or dragging it.");
active.ui(ui) 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)); // ui.vertical_centered(|ui| reset_button(ui, self));
} }
} }
impl Selection { impl Selection {
pub fn ui(&mut self, ui: &mut crate::Ui) { pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self { bg_fill, stroke } = self; let Self { bg_fill, stroke } = self;
ui.label("Selectable labels");
ui_color(ui, bg_fill, "bg_fill"); ui_color(ui, bg_fill, "bg_fill");
stroke_ui(ui, stroke, "stroke"); stroke_ui(ui, stroke, "stroke");
} }
@ -653,12 +772,12 @@ impl WidgetVisuals {
fg_stroke, fg_stroke,
expansion, expansion,
} = self; } = self;
ui_color(ui, bg_fill, "bg_fill"); ui_color(ui, bg_fill, "bg_fill");
stroke_ui(ui, bg_stroke, "bg_stroke"); stroke_ui(ui, bg_stroke, "bg_stroke");
ui.add(Slider::new(corner_radius, 0.0..=10.0).text("corner_radius")); ui.add(Slider::new(corner_radius, 0.0..=10.0).text("corner_radius"));
stroke_ui(ui, fg_stroke, "fg_stroke (text)"); stroke_ui(ui, fg_stroke, "fg_stroke (text)");
ui.add(Slider::new(expansion, -5.0..=5.0).text("expansion")); ui.add(Slider::new(expansion, -5.0..=5.0).text("expansion"))
.on_hover_text("make shapes this much larger");
} }
} }
@ -703,42 +822,66 @@ impl Visuals {
override_text_color: _, override_text_color: _,
widgets, widgets,
selection, selection,
extreme_bg_color,
hyperlink_color, hyperlink_color,
faint_bg_color,
extreme_bg_color,
code_bg_color, code_bg_color,
window_corner_radius, window_corner_radius,
window_shadow, window_shadow,
popup_shadow,
resize_corner_size, resize_corner_size,
text_cursor_width, text_cursor_width,
text_cursor_preview, text_cursor_preview,
clip_rect_margin, clip_rect_margin,
button_frame,
collapsing_header_frame,
} = self; } = self;
ui.collapsing("widgets", |ui| widgets.ui(ui)); ui.collapsing("Background Colors", |ui| {
ui.collapsing("selection", |ui| selection.ui(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.group(|ui| { ui.collapsing("Window", |ui| {
ui.label("Window");
// Common shortcuts // Common shortcuts
ui_color(ui, &mut widgets.noninteractive.bg_fill, "Fill"); ui_color(ui, &mut widgets.noninteractive.bg_fill, "Fill");
stroke_ui(ui, &mut widgets.noninteractive.bg_stroke, "Outline"); stroke_ui(ui, &mut widgets.noninteractive.bg_stroke, "Outline");
ui.add(Slider::new(window_corner_radius, 0.0..=20.0).text("Corner Radius")); ui.add(Slider::new(window_corner_radius, 0.0..=20.0).text("Rounding"));
shadow_ui(ui, window_shadow, "Shadow"); 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_color( ui_color(
ui, ui,
&mut widgets.noninteractive.fg_stroke.color, &mut widgets.noninteractive.fg_stroke.color,
"Text color", "Text color",
); );
ui_color(ui, code_bg_color, Label::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, extreme_bg_color, "extreme_bg_color");
ui_color(ui, hyperlink_color, "hyperlink_color"); ui_color(ui, hyperlink_color, "hyperlink_color");
ui_color(ui, code_bg_color, "code_bg_color");
ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
ui.add(Slider::new(text_cursor_width, 0.0..=2.0).text("text_cursor_width")); ui.add(Slider::new(text_cursor_width, 0.0..=4.0).text("text_cursor_width"));
ui.checkbox(text_cursor_preview, "text_cursor_preview"); 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.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)); ui.vertical_centered(|ui| reset_button(ui, self));
} }
} }
@ -775,17 +918,26 @@ fn slider_vec2<'a>(
) -> impl Widget + 'a { ) -> impl Widget + 'a {
move |ui: &mut crate::Ui| { move |ui: &mut crate::Ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(Slider::new(&mut value.x, range.clone()).text("w")); ui.add(
ui.add(Slider::new(&mut value.y, range.clone()).text("h")); 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); ui.label(text);
}) })
.response .response
} }
} }
fn ui_color(ui: &mut Ui, srgba: &mut Color32, text: &str) { fn ui_color(ui: &mut Ui, srgba: &mut Color32, text: impl Into<Label>) -> Response {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.color_edit_button_srgba(srgba); ui.color_edit_button_srgba(srgba);
ui.label(text); ui.label(text);
}); })
.response
} }

View file

@ -137,6 +137,11 @@ impl Ui {
self.style = style.into(); self.style = style.into();
} }
/// Reset to the default style set in [`Context`].
pub fn reset_style(&mut self) {
self.style = self.ctx().style();
}
/// The current spacing options for this `Ui`. /// The current spacing options for this `Ui`.
/// Short for `ui.style().spacing`. /// Short for `ui.style().spacing`.
#[inline(always)] #[inline(always)]
@ -1317,6 +1322,9 @@ impl Ui {
} }
/// Create a child ui which is indented to the right. /// Create a child ui which is indented to the right.
///
/// The `id_source` here be anything at all.
// TODO: remove `id_source` argument?
#[inline(always)] #[inline(always)]
pub fn indent<R>( pub fn indent<R>(
&mut self, &mut self,
@ -1347,7 +1355,8 @@ impl Ui {
}; };
let ret = add_contents(&mut child_ui); let ret = add_contents(&mut child_ui);
let end_with_horizontal_line = true; let end_with_horizontal_line = self.spacing().indent_ends_with_horizontal_line;
if end_with_horizontal_line { if end_with_horizontal_line {
child_ui.add_space(4.0); child_ui.add_space(4.0);
} }

View file

@ -18,9 +18,10 @@ pub struct Button {
text_style: Option<TextStyle>, text_style: Option<TextStyle>,
/// None means default for interact /// None means default for interact
fill: Option<Color32>, fill: Option<Color32>,
stroke: Option<Stroke>,
sense: Sense, sense: Sense,
small: bool, small: bool,
frame: bool, frame: Option<bool>,
wrap: Option<bool>, wrap: Option<bool>,
min_size: Vec2, min_size: Vec2,
} }
@ -32,10 +33,11 @@ impl Button {
text: text.to_string(), text: text.to_string(),
text_color: None, text_color: None,
text_style: None, text_style: None,
fill: Default::default(), fill: None,
stroke: None,
sense: Sense::click(), sense: Sense::click(),
small: false, small: false,
frame: true, frame: None,
wrap: None, wrap: None,
min_size: Vec2::ZERO, min_size: Vec2::ZERO,
} }
@ -56,8 +58,19 @@ impl Button {
self self
} }
pub fn fill(mut self, fill: Option<Color32>) -> Self { /// Override background fill color. Note that this will override any on-hover effects.
self.fill = fill; /// Calling this will also turn on the frame.
pub fn fill(mut self, fill: impl Into<Color32>) -> Self {
self.fill = Some(fill.into());
self.frame = Some(true);
self
}
/// Override button stroke. Note that this will override any on-hover effects.
/// Calling this will also turn on the frame.
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
self.stroke = Some(stroke.into());
self.frame = Some(true);
self self
} }
@ -70,7 +83,7 @@ impl Button {
/// Turn off the frame /// Turn off the frame
pub fn frame(mut self, frame: bool) -> Self { pub fn frame(mut self, frame: bool) -> Self {
self.frame = frame; self.frame = Some(frame);
self self
} }
@ -116,6 +129,7 @@ impl Button {
text_color, text_color,
text_style, text_style,
fill, fill,
stroke,
sense, sense,
small, small,
frame, frame,
@ -123,6 +137,8 @@ impl Button {
min_size, min_size,
} = self; } = self;
let frame = frame.unwrap_or_else(|| ui.visuals().button_frame);
let text_style = text_style let text_style = text_style
.or(ui.style().override_text_style) .or(ui.style().override_text_style)
.unwrap_or(TextStyle::Button); .unwrap_or(TextStyle::Button);
@ -159,11 +175,12 @@ impl Button {
if frame { if frame {
let fill = fill.unwrap_or(visuals.bg_fill); let fill = fill.unwrap_or(visuals.bg_fill);
let stroke = stroke.unwrap_or(visuals.bg_stroke);
ui.painter().rect( ui.painter().rect(
rect.expand(visuals.expansion), rect.expand(visuals.expansion),
visuals.corner_radius, visuals.corner_radius,
fill, fill,
visuals.bg_stroke, stroke,
); );
} }

View file

@ -67,11 +67,15 @@ fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response {
response response
} }
fn color_button(ui: &mut Ui, color: Color32) -> Response { fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response {
let size = ui.spacing().interact_size; let size = ui.spacing().interact_size;
let (rect, response) = ui.allocate_exact_size(size, Sense::click()); let (rect, response) = ui.allocate_exact_size(size, Sense::click());
response.widget_info(|| WidgetInfo::new(WidgetType::ColorButton)); response.widget_info(|| WidgetInfo::new(WidgetType::ColorButton));
let visuals = ui.style().interact(&response); let visuals = if open {
&ui.visuals().widgets.open
} else {
ui.style().interact(&response)
};
let rect = rect.expand(visuals.expansion); let rect = rect.expand(visuals.expansion);
background_checkers(ui.painter(), rect); background_checkers(ui.painter(), rect);
@ -83,7 +87,7 @@ fn color_button(ui: &mut Ui, color: Color32) -> Response {
let corner_radius = visuals.corner_radius.at_most(2.0); let corner_radius = visuals.corner_radius.at_most(2.0);
ui.painter() ui.painter()
.rect_stroke(rect, corner_radius, (2.0, visuals.bg_fill)); // fill is intentional! .rect_stroke(rect, corner_radius, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border
response response
} }
@ -335,7 +339,9 @@ fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> bool {
pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response { pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {
let pupup_id = ui.auto_id_with("popup"); let pupup_id = ui.auto_id_with("popup");
let mut button_response = color_button(ui, (*hsva).into()).on_hover_text("Click to edit color"); let open = ui.memory().is_popup_open(pupup_id);
let mut button_response =
color_button(ui, (*hsva).into(), open).on_hover_text("Click to edit color");
if button_response.clicked() { if button_response.clicked() {
ui.memory().toggle_popup(pupup_id); ui.memory().toggle_popup(pupup_id);

View file

@ -353,7 +353,7 @@ impl Widget for Plot {
rect, rect,
corner_radius: 2.0, corner_radius: 2.0,
fill: ui.visuals().extreme_bg_color, fill: ui.visuals().extreme_bg_color,
stroke: ui.visuals().window_stroke(), stroke: ui.visuals().widgets.noninteractive.bg_stroke,
}); });
// Legend // Legend

View file

@ -375,7 +375,10 @@ impl<'a> Slider<'a> {
{ {
let value = self.get_value(); let value = self.get_value();
let rail_radius = ui.painter().round_to_pixel((rect.height() / 8.0).max(2.0)); let rail_radius = ui
.painter()
.round_to_pixel((rect.height() / 4.0).at_least(2.0));
let rail_rect = Rect::from_min_max( let rail_rect = Rect::from_min_max(
pos2(rect.left(), rect.center().y - rail_radius), pos2(rect.left(), rect.center().y - rail_radius),
pos2(rect.right(), rect.center().y + rail_radius), pos2(rect.right(), rect.center().y + rail_radius),
@ -385,8 +388,7 @@ impl<'a> Slider<'a> {
let visuals = ui.style().interact(response); let visuals = ui.style().interact(response);
ui.painter().add(Shape::Rect { ui.painter().add(Shape::Rect {
rect: rail_rect, rect: rail_rect,
corner_radius: rail_radius, corner_radius: ui.visuals().widgets.inactive.corner_radius,
fill: ui.visuals().widgets.inactive.bg_fill, fill: ui.visuals().widgets.inactive.bg_fill,
// fill: visuals.bg_fill, // fill: visuals.bg_fill,
// fill: ui.visuals().extreme_bg_color, // fill: ui.visuals().extreme_bg_color,

View file

@ -8,7 +8,7 @@ pub fn easy_mark(ui: &mut Ui, easy_mark: &str) {
pub fn easy_mark_it<'em>(ui: &mut Ui, items: impl Iterator<Item = easy_mark::Item<'em>>) { pub fn easy_mark_it<'em>(ui: &mut Ui, items: impl Iterator<Item = easy_mark::Item<'em>>) {
ui.horizontal_wrapped(|ui| { ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing = Vec2::new(0.0, 0.0); ui.spacing_mut().item_spacing.x = 0.0;
ui.set_row_height(ui.fonts()[TextStyle::Body].row_height()); ui.set_row_height(ui.fonts()[TextStyle::Body].row_height());
for item in items { for item in items {

View file

@ -15,10 +15,18 @@ pub struct Shadow {
impl Shadow { impl Shadow {
/// Tooltips, menus, ... /// Tooltips, menus, ...
pub fn small() -> Self { pub fn small_dark() -> Self {
Self { Self {
extrusion: 8.0, extrusion: 16.0,
color: Color32::from_black_alpha(64), color: Color32::from_black_alpha(96),
}
}
/// Tooltips, menus, ...
pub fn small_light() -> Self {
Self {
extrusion: 16.0,
color: Color32::from_black_alpha(32),
} }
} }

View file

@ -400,7 +400,8 @@ impl Font {
row.y_max += cursor_y; row.y_max += cursor_y;
} }
cursor_y = paragraph_rows.last().unwrap().y_max; cursor_y = paragraph_rows.last().unwrap().y_max;
cursor_y += row_height * 0.2; // Extra spacing between paragraphs. TODO: less hacky
// cursor_y += row_height * 0.2; // Extra spacing between paragraphs.
rows.append(&mut paragraph_rows); rows.append(&mut paragraph_rows);

View file

@ -195,7 +195,7 @@ impl Default for FontDefinitions {
let mut family_and_size = BTreeMap::new(); let mut family_and_size = BTreeMap::new();
family_and_size.insert(TextStyle::Small, (FontFamily::Proportional, 10.0)); family_and_size.insert(TextStyle::Small, (FontFamily::Proportional, 10.0));
family_and_size.insert(TextStyle::Body, (FontFamily::Proportional, 14.0)); family_and_size.insert(TextStyle::Body, (FontFamily::Proportional, 14.0));
family_and_size.insert(TextStyle::Button, (FontFamily::Proportional, 16.0)); family_and_size.insert(TextStyle::Button, (FontFamily::Proportional, 14.0));
family_and_size.insert(TextStyle::Heading, (FontFamily::Proportional, 20.0)); family_and_size.insert(TextStyle::Heading, (FontFamily::Proportional, 20.0));
family_and_size.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0)); // 13 for `ProggyClean` family_and_size.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0)); // 13 for `ProggyClean`