diff --git a/CHANGELOG.md b/CHANGELOG.md index 81e84aadbff..e2a583b05b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * `TextStyle` is no longer `Copy` ([#1154](https://github.com/emilk/egui/pull/1154)). * Replaced `TextEdit::text_style` with `TextEdit::font` ([#1154](https://github.com/emilk/egui/pull/1154)). * Replaced `corner_radius: f32` with `rounding: Rounding`, allowing per-corner rounding settings ([#1206](https://github.com/emilk/egui/pull/1206)). +* Replaced Frame's `margin: Vec2` with `margin: Margin`, allowing for different margins on opposing sides ([#1219](https://github.com/emilk/egui/pull/1219)). * `Plot::highlight` now takes a `bool` argument ([#1159](https://github.com/emilk/egui/pull/1159)). * `ScrollArea::show` now returns a `ScrollAreaOutput`, so you might need to add `.inner` after the call to it ([#1166](https://github.com/emilk/egui/pull/1166)). diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index c5c5a61a047..044b96a9a97 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -3,12 +3,55 @@ use crate::{layers::ShapeIdx, *}; use epaint::*; +#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Margin { + pub left: f32, + pub right: f32, + pub top: f32, + pub bottom: f32, +} + +impl Margin { + #[inline] + pub fn same(margin: f32) -> Self { + Self { + left: margin, + right: margin, + top: margin, + bottom: margin, + } + } + + /// Margins with the same size on opposing sides + #[inline] + pub fn symmetric(x: f32, y: f32) -> Self { + Self { + left: x, + right: x, + top: y, + bottom: y, + } + } + + /// Total margins on both sides + pub fn sum(&self) -> Vec2 { + Vec2::new(self.left + self.right, self.top + self.bottom) + } +} + +impl From for Margin { + fn from(v: Vec2) -> Self { + Self::symmetric(v.x, v.y) + } +} + /// Color and margin of a rectangular background of a [`Ui`]. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[must_use = "You should call .show()"] pub struct Frame { /// On each side - pub margin: Vec2, + pub margin: Margin, pub rounding: Rounding, pub shadow: Shadow, pub fill: Color32, @@ -23,7 +66,7 @@ impl Frame { /// For when you want to group a few widgets together within a frame. pub fn group(style: &Style) -> Self { Self { - margin: Vec2::splat(6.0), // symmetric looks best in corners when nesting + margin: Margin::same(6.0), // symmetric looks best in corners when nesting rounding: style.visuals.widgets.noninteractive.rounding, stroke: style.visuals.widgets.noninteractive.bg_stroke, ..Default::default() @@ -32,7 +75,7 @@ impl Frame { pub(crate) fn side_top_panel(style: &Style) -> Self { Self { - margin: Vec2::new(8.0, 2.0), + margin: Margin::symmetric(8.0, 2.0), rounding: Rounding::none(), fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), @@ -42,7 +85,7 @@ impl Frame { pub(crate) fn central_panel(style: &Style) -> Self { Self { - margin: Vec2::new(8.0, 8.0), + margin: Margin::symmetric(8.0, 8.0), rounding: Rounding::none(), fill: style.visuals.window_fill(), stroke: Default::default(), @@ -52,7 +95,7 @@ impl Frame { pub fn window(style: &Style) -> Self { Self { - margin: style.spacing.window_padding, + margin: style.spacing.window_margin, rounding: style.visuals.window_rounding, shadow: style.visuals.window_shadow, fill: style.visuals.window_fill(), @@ -62,7 +105,7 @@ impl Frame { pub fn menu(style: &Style) -> Self { Self { - margin: Vec2::splat(1.0), + margin: Margin::same(1.0), rounding: style.visuals.widgets.noninteractive.rounding, shadow: style.visuals.popup_shadow, fill: style.visuals.window_fill(), @@ -72,7 +115,7 @@ impl Frame { pub fn popup(style: &Style) -> Self { Self { - margin: style.spacing.window_padding, + margin: style.spacing.window_margin, rounding: style.visuals.widgets.noninteractive.rounding, shadow: style.visuals.popup_shadow, fill: style.visuals.window_fill(), @@ -83,7 +126,7 @@ impl Frame { /// dark canvas to draw on pub fn dark_canvas(style: &Style) -> Self { Self { - margin: Vec2::new(10.0, 10.0), + margin: Margin::symmetric(10.0, 10.0), rounding: style.visuals.widgets.noninteractive.rounding, fill: Color32::from_black_alpha(250), stroke: style.visuals.window_stroke(), @@ -109,7 +152,7 @@ impl Frame { } /// Margin on each side of the frame. - pub fn margin(mut self, margin: impl Into) -> Self { + pub fn margin(mut self, margin: impl Into) -> Self { self.margin = margin.into(); self } @@ -137,7 +180,10 @@ impl Frame { pub fn begin(self, ui: &mut Ui) -> Prepared { let where_to_put_background = ui.painter().add(Shape::Noop); let outer_rect_bounds = ui.available_rect_before_wrap(); - let mut inner_rect = outer_rect_bounds.shrink2(self.margin); + + let mut inner_rect = outer_rect_bounds; + inner_rect.min += Vec2::new(self.margin.left, self.margin.top); + inner_rect.max -= Vec2::new(self.margin.right, self.margin.bottom); // Make sure we don't shrink to the negative: inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); @@ -197,7 +243,10 @@ impl Frame { impl Prepared { pub fn outer_rect(&self) -> Rect { - self.content_ui.min_rect().expand2(self.frame.margin) + let mut rect = self.content_ui.min_rect(); + rect.min -= Vec2::new(self.frame.margin.left, self.frame.margin.top); + rect.max += Vec2::new(self.frame.margin.right, self.frame.margin.bottom); + rect } pub fn end(self, ui: &mut Ui) -> Response { diff --git a/egui/src/containers/mod.rs b/egui/src/containers/mod.rs index f151b8e9edd..cc457a511a5 100644 --- a/egui/src/containers/mod.rs +++ b/egui/src/containers/mod.rs @@ -16,7 +16,7 @@ pub use { area::Area, collapsing_header::{CollapsingHeader, CollapsingResponse}, combo_box::*, - frame::Frame, + frame::{Frame, Margin}, panel::{CentralPanel, SidePanel, TopBottomPanel}, popup::*, resize::Resize, diff --git a/egui/src/containers/popup.rs b/egui/src/containers/popup.rs index ffe366721e3..c8cbdb67f96 100644 --- a/egui/src/containers/popup.rs +++ b/egui/src/containers/popup.rs @@ -302,7 +302,7 @@ pub fn popup_below_widget( frame .show(ui, |ui| { ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| { - ui.set_width(widget_response.rect.width() - 2.0 * frame_margin.x); + ui.set_width(widget_response.rect.width() - frame_margin.sum().x); add_contents(ui) }) .inner diff --git a/egui/src/containers/resize.rs b/egui/src/containers/resize.rs index 5dd46dbe4bc..2f31ff0544f 100644 --- a/egui/src/containers/resize.rs +++ b/egui/src/containers/resize.rs @@ -178,7 +178,7 @@ impl Resize { .at_least(self.min_size) .at_most(self.max_size) .at_most( - ui.input().screen_rect().size() - 2.0 * ui.spacing().window_padding, // hack for windows + ui.input().screen_rect().size() - ui.spacing().window_margin.sum(), // hack for windows ); State { diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 9c439b26e5f..4e1eb70d567 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -301,7 +301,7 @@ impl<'open> Window<'open> { } else { 0.0 }; - let margins = 2.0 * frame.margin + vec2(0.0, title_bar_height); + let margins = frame.margin.sum() + vec2(0.0, title_bar_height); interact( window_interaction, diff --git a/egui/src/style.rs b/egui/src/style.rs index a1430850d90..d13548a6b1e 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -2,7 +2,7 @@ #![allow(clippy::if_same_then_else)] -use crate::{color::*, emath::*, FontFamily, FontId, Response, RichText, WidgetText}; +use crate::{color::*, emath::*, FontFamily, FontId, Margin, Response, RichText, WidgetText}; use epaint::{mutex::Arc, Rounding, Shadow, Stroke}; use std::collections::BTreeMap; @@ -220,8 +220,8 @@ pub struct Spacing { /// widgets `A` and `B` you need to change `item_spacing` before adding `A`. pub item_spacing: Vec2, - /// Horizontal and vertical padding within a window frame. - pub window_padding: Vec2, + /// Horizontal and vertical margins within a window frame. + pub window_margin: Margin, /// Button size is text size plus this on each side pub button_padding: Vec2, @@ -528,7 +528,7 @@ impl Default for Spacing { fn default() -> Self { Self { item_spacing: vec2(8.0, 3.0), - window_padding: Vec2::splat(6.0), + window_margin: Margin::same(6.0), button_padding: vec2(4.0, 1.0), indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing` interact_size: vec2(40.0, 18.0), @@ -803,7 +803,7 @@ impl Spacing { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { item_spacing, - window_padding, + window_margin, button_padding, indent, interact_size, @@ -818,7 +818,37 @@ impl Spacing { } = self; ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing")); - ui.add(slider_vec2(window_padding, 0.0..=20.0, "Window padding")); + + let margin_range = 0.0..=20.0; + ui.horizontal(|ui| { + ui.add( + DragValue::new(&mut window_margin.left) + .clamp_range(margin_range.clone()) + .prefix("left: "), + ); + ui.add( + DragValue::new(&mut window_margin.right) + .clamp_range(margin_range.clone()) + .prefix("right: "), + ); + + ui.label("Window margins x"); + }); + + ui.horizontal(|ui| { + ui.add( + DragValue::new(&mut window_margin.top) + .clamp_range(margin_range.clone()) + .prefix("top: "), + ); + ui.add( + DragValue::new(&mut window_margin.bottom) + .clamp_range(margin_range) + .prefix("bottom: "), + ); + ui.label("Window margins y"); + }); + ui.add(slider_vec2(button_padding, 0.0..=20.0, "Button padding")); ui.add(slider_vec2(interact_size, 4.0..=60.0, "Interact size")) .on_hover_text("Minimum size of an interactive widget"); diff --git a/egui/src/widgets/plot/legend.rs b/egui/src/widgets/plot/legend.rs index 71f3dfdcbef..6c60c6a366b 100644 --- a/egui/src/widgets/plot/legend.rs +++ b/egui/src/widgets/plot/legend.rs @@ -239,7 +239,7 @@ impl Widget for &mut LegendWidget { legend_ui .scope(|ui| { let background_frame = Frame { - margin: vec2(8.0, 4.0), + margin: vec2(8.0, 4.0).into(), rounding: ui.style().visuals.window_rounding, shadow: epaint::Shadow::default(), fill: ui.style().visuals.extreme_bg_color,