diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 62c5d0b5871..64b38582b9c 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -6,7 +6,43 @@ use crate::{ }; use epaint::{Color32, Margin, Marginf, Rect, Rounding, Shadow, Shape, Stroke}; -/// Add a background, frame and/or margin to a rectangular background of a [`Ui`]. +/// A frame around some content, including margin, colors, etc. +/// +/// ## Definitions +/// The total (outer) size of a frame is +/// `content_size + inner_margin + 2 * stroke.width + outer_margin`. +/// +/// Everything within the stroke is filled with the fill color (if any). +/// +/// ```text +/// +-----------------^-------------------------------------- -+ +/// | | outer_margin | +/// | +------------v----^------------------------------+ | +/// | | | stroke width | | +/// | | +------------v---^---------------------+ | | +/// | | | | inner_margin | | | +/// | | | +-----------v----------------+ | | | +/// | | | | ^ | | | | +/// | | | | | | | | | +/// | | | |<------ content_size ------>| | | | +/// | | | | | | | | | +/// | | | | v | | | | +/// | | | +------- content_rect -------+ | | | +/// | | | | | | +/// | | +-------------fill_rect ---------------+ | | +/// | | | | +/// | +----------------- widget_rect ------------------+ | +/// | | +/// +---------------------- outer_rect ------------------------+ +/// ``` +/// +/// The four rectangles, from inside to outside, are: +/// * `content_rect`: the rectangle that is made available to the inner [`Ui`] or widget. +/// * `fill_rect`: the rectangle that is filled with the fill color (inside the stroke, if any). +/// * `widget_rect`: is the interactive part of the widget (what sense clicks etc). +/// * `outer_rect`: what is allocated in the outer [`Ui`], and is what is returned by [`Response::rect`]. +/// +/// ## Usage /// /// ``` /// # egui::__run_test_ui(|ui| { @@ -58,19 +94,47 @@ use epaint::{Color32, Margin, Marginf, Rect, Rounding, Shadow, Shape, Stroke}; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[must_use = "You should call .show()"] pub struct Frame { + // Fields are ordered inside-out. + // TODO(emilk): add `min_content_size: Vec2` + // /// Margin within the painted frame. + /// + /// Known as `padding` in CSS. + #[doc(alias = "padding")] pub inner_margin: Margin, - /// Margin outside the painted frame. - pub outer_margin: Margin, + /// The background fill color of the frame, within the [`Self::stroke`]. + /// + /// Known as `background` in CSS. + #[doc(alias = "background")] + pub fill: Color32, - pub rounding: Rounding, + /// The width and color of the outline around the frame. + /// + /// The width of the stroke is part of the total margin/padding of the frame. + #[doc(alias = "border")] + pub stroke: Stroke, - pub shadow: Shadow, + /// The rounding of the corners of [`Self::stroke`] and [`Self::fill`]. + pub rounding: Rounding, - pub fill: Color32, + /// Margin outside the painted frame. + /// + /// Similar to what is called `margin` in CSS. + /// However, egui does NOT do "Margin Collapse" like in CSS, + /// i.e. when placing two frames next to each other, + /// the distance between their borders is the SUM + /// of their other margins. + /// In CSS the distance would be the MAX of their outer margins. + /// Supporting margin collapse is difficult, and would + /// requires complicating the already complicated egui layout code. + /// + /// Consider using [`crate::Spacing::item_spacing`] + /// for adding space between widgets. + pub outer_margin: Margin, - pub stroke: Stroke, + /// Optional drop-shadow behind the frame. + pub shadow: Shadow, } #[test] @@ -85,68 +149,72 @@ fn frame_size() { ); } +/// ## Constructors impl Frame { - pub fn none() -> Self { - Self::default() + /// No colors, no margins, no border. + /// + /// This is also the default. + pub const NONE: Self = Self { + inner_margin: Margin::ZERO, + stroke: Stroke::NONE, + fill: Color32::TRANSPARENT, + rounding: Rounding::ZERO, + outer_margin: Margin::ZERO, + shadow: Shadow::NONE, + }; + + pub const fn new() -> Self { + Self::NONE + } + + #[deprecated = "Use `Frame::NONE` or `Frame::new()` instead."] + pub const fn none() -> Self { + Self::NONE } /// For when you want to group a few widgets together within a frame. pub fn group(style: &Style) -> Self { - Self { - inner_margin: Margin::same(6), // same and symmetric looks best in corners when nesting groups - rounding: style.visuals.widgets.noninteractive.rounding, - stroke: style.visuals.widgets.noninteractive.bg_stroke, - ..Default::default() - } + Self::new() + .inner_margin(6) + .rounding(style.visuals.widgets.noninteractive.rounding) + .stroke(style.visuals.widgets.noninteractive.bg_stroke) } pub fn side_top_panel(style: &Style) -> Self { - Self { - inner_margin: Margin::symmetric(8, 2), - fill: style.visuals.panel_fill, - ..Default::default() - } + Self::new() + .inner_margin(Margin::symmetric(8, 2)) + .fill(style.visuals.panel_fill) } pub fn central_panel(style: &Style) -> Self { - Self { - inner_margin: Margin::same(8), - fill: style.visuals.panel_fill, - ..Default::default() - } + Self::new().inner_margin(8).fill(style.visuals.panel_fill) } pub fn window(style: &Style) -> Self { - Self { - inner_margin: style.spacing.window_margin, - rounding: style.visuals.window_rounding, - shadow: style.visuals.window_shadow, - fill: style.visuals.window_fill(), - stroke: style.visuals.window_stroke(), - ..Default::default() - } + Self::new() + .inner_margin(style.spacing.window_margin) + .rounding(style.visuals.window_rounding) + .shadow(style.visuals.window_shadow) + .fill(style.visuals.window_fill()) + .stroke(style.visuals.window_stroke()) } pub fn menu(style: &Style) -> Self { - Self { - inner_margin: style.spacing.menu_margin, - rounding: style.visuals.menu_rounding, - shadow: style.visuals.popup_shadow, - fill: style.visuals.window_fill(), - stroke: style.visuals.window_stroke(), - ..Default::default() - } + Self::new() + .inner_margin(style.spacing.menu_margin) + .rounding(style.visuals.menu_rounding) + .shadow(style.visuals.popup_shadow) + .fill(style.visuals.window_fill()) + .stroke(style.visuals.window_stroke()) } pub fn popup(style: &Style) -> Self { - Self { - inner_margin: style.spacing.menu_margin, - rounding: style.visuals.menu_rounding, - shadow: style.visuals.popup_shadow, - fill: style.visuals.window_fill(), - stroke: style.visuals.window_stroke(), - ..Default::default() - } + Self::new() + .inner_margin(style.spacing.menu_margin) + .rounding(style.visuals.menu_rounding) + .shadow(style.visuals.popup_shadow) + .fill(style.visuals.window_fill()) + .stroke(style.visuals.window_stroke()) } /// A canvas to draw on. @@ -154,57 +222,77 @@ impl Frame { /// In bright mode this will be very bright, /// and in dark mode this will be very dark. pub fn canvas(style: &Style) -> Self { - Self { - inner_margin: Margin::same(2), - rounding: style.visuals.widgets.noninteractive.rounding, - fill: style.visuals.extreme_bg_color, - stroke: style.visuals.window_stroke(), - ..Default::default() - } + Self::new() + .inner_margin(2) + .rounding(style.visuals.widgets.noninteractive.rounding) + .fill(style.visuals.extreme_bg_color) + .stroke(style.visuals.window_stroke()) } /// A dark canvas to draw on. pub fn dark_canvas(style: &Style) -> Self { - Self { - fill: Color32::from_black_alpha(250), - ..Self::canvas(style) - } + Self::canvas(style).fill(Color32::from_black_alpha(250)) } } +/// ## Builders impl Frame { + /// Margin within the painted frame. + /// + /// Known as `padding` in CSS. + #[doc(alias = "padding")] + #[inline] + pub fn inner_margin(mut self, inner_margin: impl Into) -> Self { + self.inner_margin = inner_margin.into(); + self + } + + /// The background fill color of the frame, within the [`Self::stroke`]. + /// + /// Known as `background` in CSS. + #[doc(alias = "background")] #[inline] pub fn fill(mut self, fill: Color32) -> Self { self.fill = fill; self } + /// The width and color of the outline around the frame. + /// + /// The width of the stroke is part of the total margin/padding of the frame. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { self.stroke = stroke.into(); self } + /// The rounding of the corners of [`Self::stroke`] and [`Self::fill`]. #[inline] pub fn rounding(mut self, rounding: impl Into) -> Self { self.rounding = rounding.into(); self } - /// Margin within the painted frame. - #[inline] - pub fn inner_margin(mut self, inner_margin: impl Into) -> Self { - self.inner_margin = inner_margin.into(); - self - } - /// Margin outside the painted frame. + /// + /// Similar to what is called `margin` in CSS. + /// However, egui does NOT do "Margin Collapse" like in CSS, + /// i.e. when placing two frames next to each other, + /// the distance between their borders is the SUM + /// of their other margins. + /// In CSS the distance would be the MAX of their outer margins. + /// Supporting margin collapse is difficult, and would + /// requires complicating the already complicated egui layout code. + /// + /// Consider using [`crate::Spacing::item_spacing`] + /// for adding space between widgets. #[inline] pub fn outer_margin(mut self, outer_margin: impl Into) -> Self { self.outer_margin = outer_margin.into(); self } + /// Optional drop-shadow behind the frame. #[inline] pub fn shadow(mut self, shadow: Shadow) -> Self { self.shadow = shadow; @@ -224,11 +312,37 @@ impl Frame { } } +/// ## Inspectors impl Frame { - /// Inner margin plus outer margin. + /// How much extra space the frame uses up compared to the content. + /// + /// [`Self::inner_margin`] + [`Self.stroke`]`.width` + [`Self::outer_margin`]. #[inline] pub fn total_margin(&self) -> Marginf { - Marginf::from(self.inner_margin) + Marginf::from(self.outer_margin) + Marginf::from(self.inner_margin) + + Marginf::from(self.stroke.width) + + Marginf::from(self.outer_margin) + } + + /// Calculate the `fill_rect` from the `content_rect`. + /// + /// This is the rectangle that is filled with the fill color (inside the stroke, if any). + pub fn fill_rect(&self, content_rect: Rect) -> Rect { + content_rect + self.inner_margin + } + + /// Calculate the `widget_rect` from the `content_rect`. + /// + /// This is the visible and interactive rectangle. + pub fn widget_rect(&self, content_rect: Rect) -> Rect { + content_rect + self.inner_margin + Marginf::from(self.stroke.width) + } + + /// Calculate the `outer_rect` from the `content_rect`. + /// + /// This is what is allocated in the outer [`Ui`], and is what is returned by [`Response::rect`]. + pub fn outer_rect(&self, content_rect: Rect) -> Rect { + content_rect + self.inner_margin + Marginf::from(self.stroke.width) + self.outer_margin } } @@ -259,20 +373,18 @@ impl Frame { 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 - self.outer_margin - self.inner_margin; + let mut max_content_rect = outer_rect_bounds - self.total_margin(); // Make sure we don't shrink to the negative: - inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); - inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y); + max_content_rect.max.x = max_content_rect.max.x.max(max_content_rect.min.x); + max_content_rect.max.y = max_content_rect.max.y.max(max_content_rect.min.y); let content_ui = ui.new_child( UiBuilder::new() .ui_stack_info(UiStackInfo::new(UiKind::Frame).with_frame(self)) - .max_rect(inner_rect), + .max_rect(max_content_rect), ); - // content_ui.set_clip_rect(outer_rect_bounds.shrink(self.stroke.width * 0.5)); // Can't do this since we don't know final size yet - Prepared { frame: self, where_to_put_background, @@ -298,32 +410,37 @@ impl Frame { } /// Paint this frame as a shape. - /// - /// The margin is ignored. - pub fn paint(&self, outer_rect: Rect) -> Shape { + pub fn paint(&self, content_rect: Rect) -> Shape { let Self { inner_margin: _, - outer_margin: _, - rounding, - shadow, fill, stroke, + rounding, + outer_margin: _, + shadow, } = *self; - let frame_shape = Shape::Rect(epaint::RectShape::new(outer_rect, rounding, fill, stroke)); + let fill_rect = self.fill_rect(content_rect); + let widget_rect = self.widget_rect(content_rect); + + let frame_shape = Shape::Rect(epaint::RectShape::new(fill_rect, rounding, fill, stroke)); if shadow == Default::default() { frame_shape } else { - let shadow = shadow.as_shape(outer_rect, rounding); + let shadow = shadow.as_shape(widget_rect, rounding); Shape::Vec(vec![Shape::from(shadow), frame_shape]) } } } impl Prepared { - fn content_with_margin(&self) -> Rect { - self.content_ui.min_rect() + self.frame.inner_margin + self.frame.outer_margin + fn outer_rect(&self) -> Rect { + let content_rect = self.content_ui.min_rect(); + content_rect + + self.frame.inner_margin + + Marginf::from(self.frame.stroke.width) + + self.frame.outer_margin } /// Allocate the space that was used by [`Self::content_ui`]. @@ -332,22 +449,25 @@ impl Prepared { /// /// This can be called before or after [`Self::paint`]. pub fn allocate_space(&self, ui: &mut Ui) -> Response { - ui.allocate_rect(self.content_with_margin(), Sense::hover()) + ui.allocate_rect(self.outer_rect(), Sense::hover()) } /// Paint the frame. /// /// This can be called before or after [`Self::allocate_space`]. pub fn paint(&self, ui: &Ui) { - let paint_rect = self.content_ui.min_rect() + self.frame.inner_margin; + let content_rect = self.content_ui.min_rect(); + let widget_rect = self.frame.widget_rect(content_rect); - if ui.is_rect_visible(paint_rect) { - let shape = self.frame.paint(paint_rect); + if ui.is_rect_visible(widget_rect) { + let shape = self.frame.paint(content_rect); ui.painter().set(self.where_to_put_background, shape); } } /// Convenience for calling [`Self::allocate_space`] and [`Self::paint`]. + /// + /// Returns the outer rect, i.e. including the outer margin. pub fn end(self, ui: &mut Ui) -> Response { self.paint(ui); self.allocate_space(ui) diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index d487a27ddb4..f1890feedbe 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -2,15 +2,11 @@ use std::sync::Arc; -use crate::collapsing_header::CollapsingState; -use crate::{ - Align, Align2, Context, CursorIcon, Id, InnerResponse, LayerId, NumExt, Order, Response, Sense, - TextStyle, Ui, UiKind, Vec2b, WidgetInfo, WidgetRect, WidgetText, WidgetType, -}; use emath::GuiRounding as _; -use epaint::{ - emath, pos2, vec2, Galley, Pos2, Rect, RectShape, Rounding, Roundingf, Shape, Stroke, Vec2, -}; +use epaint::{RectShape, Roundingf}; + +use crate::collapsing_header::CollapsingState; +use crate::*; use super::scroll_area::ScrollBarVisibility; use super::{area, resize, Area, Frame, Resize, ScrollArea}; @@ -452,8 +448,6 @@ impl<'open> Window<'open> { let header_color = frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill); let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); - // Keep the original inner margin for later use - let window_margin = window_frame.inner_margin; let is_explicitly_closed = matches!(open, Some(false)); let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); @@ -483,15 +477,23 @@ impl<'open> Window<'open> { area.with_widget_info(|| WidgetInfo::labeled(WidgetType::Window, true, title.text())); - // Calculate roughly how much larger the window size is compared to the inner rect - let (title_bar_height, title_content_spacing) = if with_title_bar { + // Calculate roughly how much larger the full window inner size is compared to the content rect + let (title_bar_height_with_margin, title_content_spacing) = if with_title_bar { let style = ctx.style(); - let spacing = window_margin.sum().y; - let height = ctx.fonts(|f| title.font_height(f, &style)) + spacing; - let half_height = (height / 2.0).round() as _; + let title_bar_inner_height = ctx + .fonts(|fonts| title.font_height(fonts, &style)) + .at_least(style.spacing.interact_size.y); + let title_bar_inner_height = title_bar_inner_height + window_frame.inner_margin.sum().y; + let half_height = (title_bar_inner_height / 2.0).round() as _; window_frame.rounding.ne = window_frame.rounding.ne.clamp(0, half_height); window_frame.rounding.nw = window_frame.rounding.nw.clamp(0, half_height); - (height, spacing) + + let title_content_spacing = if is_collapsed { + 0.0 + } else { + window_frame.stroke.width + }; + (title_bar_inner_height, title_content_spacing) } else { (0.0, 0.0) }; @@ -500,7 +502,8 @@ impl<'open> Window<'open> { // Prevent window from becoming larger than the constrain rect. let constrain_rect = area.constrain_rect(); let max_width = constrain_rect.width(); - let max_height = constrain_rect.height() - title_bar_height; + let max_height = + constrain_rect.height() - title_bar_height_with_margin - title_content_spacing; resize.max_size.x = resize.max_size.x.min(max_width); resize.max_size.y = resize.max_size.y.min(max_height); } @@ -508,21 +511,28 @@ impl<'open> Window<'open> { // First check for resize to avoid frame delay: let last_frame_outer_rect = area.state().rect(); let resize_interaction = ctx.with_accessibility_parent(area.id(), || { - resize_interaction(ctx, possible, area_layer_id, last_frame_outer_rect) + resize_interaction( + ctx, + possible, + area_layer_id, + last_frame_outer_rect, + window_frame, + ) }); - let margins = window_frame.outer_margin.sum() - + window_frame.inner_margin.sum() - + vec2(0.0, title_bar_height); - - resize_response( - resize_interaction, - ctx, - margins, - area_layer_id, - &mut area, - resize_id, - ); + { + let margins = window_frame.total_margin().sum() + + vec2(0.0, title_bar_height_with_margin + title_content_spacing); + + resize_response( + resize_interaction, + ctx, + margins, + area_layer_id, + &mut area, + resize_id, + ); + } let mut area_content_ui = area.content_ui(ctx); if is_open { @@ -535,40 +545,43 @@ impl<'open> Window<'open> { let content_inner = { ctx.with_accessibility_parent(area.id(), || { // BEGIN FRAME -------------------------------- - let frame_stroke = window_frame.stroke; let mut frame = window_frame.begin(&mut area_content_ui); let show_close_button = open.is_some(); let where_to_put_header_background = &area_content_ui.painter().add(Shape::Noop); - // Backup item spacing before the title bar - let item_spacing = frame.content_ui.spacing().item_spacing; - // Use title bar spacing as the item spacing before the content - frame.content_ui.spacing_mut().item_spacing.y = title_content_spacing; - let title_bar = if with_title_bar { let title_bar = TitleBar::new( - &mut frame.content_ui, + &frame.content_ui, title, show_close_button, - &mut collapsing, collapsible, + window_frame, + title_bar_height_with_margin, ); - resize.min_size.x = resize.min_size.x.at_least(title_bar.rect.width()); // Prevent making window smaller than title bar width + resize.min_size.x = resize.min_size.x.at_least(title_bar.inner_rect.width()); // Prevent making window smaller than title bar width + + frame.content_ui.set_min_size(title_bar.inner_rect.size()); + + // Skip the title bar (and separator): + if is_collapsed { + frame.content_ui.add_space(title_bar.inner_rect.height()); + } else { + frame.content_ui.add_space( + title_bar.inner_rect.height() + + title_content_spacing + + window_frame.inner_margin.sum().y, + ); + } + Some(title_bar) } else { None }; - // Remove item spacing after the title bar - frame.content_ui.spacing_mut().item_spacing.y = 0.0; - - let (content_inner, mut content_response) = collapsing + let (content_inner, content_response) = collapsing .show_body_unindented(&mut frame.content_ui, |ui| { - // Restore item spacing for the content - ui.spacing_mut().item_spacing.y = item_spacing.y; - resize.show(ui, |ui| { if scroll.is_any_scroll_enabled() { scroll.show(ui, add_contents).inner @@ -584,23 +597,18 @@ impl<'open> Window<'open> { &area_content_ui, &possible, outer_rect, - frame_stroke, - window_frame.rounding, + &window_frame, resize_interaction, ); // END FRAME -------------------------------- - if let Some(title_bar) = title_bar { - let mut title_rect = Rect::from_min_size( - outer_rect.min, - Vec2 { - x: outer_rect.size().x, - y: title_bar_height, - }, - ); - - title_rect = title_rect.round_to_pixels(area_content_ui.pixels_per_point()); + if let Some(mut title_bar) = title_bar { + title_bar.inner_rect = outer_rect.shrink(window_frame.stroke.width); + title_bar.inner_rect.max.y = + title_bar.inner_rect.min.y + title_bar_height_with_margin; + title_bar.inner_rect = + title_bar.inner_rect.round_to_pixels(ctx.pixels_per_point()); if on_top && area_content_ui.visuals().window_highlight_topmost { let mut round = window_frame.rounding; @@ -612,18 +620,20 @@ impl<'open> Window<'open> { area_content_ui.painter().set( *where_to_put_header_background, - RectShape::filled(title_rect, round, header_color), + RectShape::filled(title_bar.inner_rect, round, header_color), ); }; - // Fix title bar separator line position - if let Some(response) = &mut content_response { - response.rect.min.y = outer_rect.min.y + title_bar_height; + if false { + ctx.debug_painter().debug_rect( + title_bar.inner_rect, + Color32::LIGHT_BLUE, + "title_bar.rect", + ); } title_bar.ui( &mut area_content_ui, - title_rect, &content_response, open, &mut collapsing, @@ -653,12 +663,11 @@ fn paint_resize_corner( ui: &Ui, possible: &PossibleInteractions, outer_rect: Rect, - stroke: impl Into, - rounding: impl Into, + window_frame: &Frame, i: ResizeInteraction, ) { - let inactive_stroke = stroke.into(); - let rounding = rounding.into(); + let rounding = window_frame.rounding; + let (corner, radius, corner_response) = if possible.resize_right && possible.resize_bottom { (Align2::RIGHT_BOTTOM, rounding.se, i.right & i.bottom) } else if possible.resize_left && possible.resize_bottom { @@ -694,11 +703,12 @@ fn paint_resize_corner( } else if corner_response.hover { ui.visuals().widgets.hovered.fg_stroke } else { - inactive_stroke + window_frame.stroke }; + let fill_rect = outer_rect.shrink(window_frame.stroke.width); let corner_size = Vec2::splat(ui.visuals().resize_corner_size); - let corner_rect = corner.align_size_within_rect(corner_size, outer_rect); + let corner_rect = corner.align_size_within_rect(corner_size, fill_rect); let corner_rect = corner_rect.translate(-offset * corner.to_sign()); // move away from corner crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke.color, corner); } @@ -738,7 +748,11 @@ impl PossibleInteractions { /// Resizing the window edges. #[derive(Clone, Copy, Debug)] struct ResizeInteraction { - start_rect: Rect, + /// Outer rect (outside the stroke) + outer_rect: Rect, + + window_frame: Frame, + left: SideResponse, right: SideResponse, top: SideResponse, @@ -835,13 +849,17 @@ fn resize_response( ctx.memory_mut(|mem| mem.areas_mut().move_to_top(area_layer_id)); } +/// Acts on outer rect (outside the stroke) fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Option { if !interaction.any_dragged() { return None; } let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?; - let mut rect = interaction.start_rect; // prevent drift + let mut rect = interaction.outer_rect; // prevent drift + + // Put the rect in the center of the stroke: + rect = rect.shrink(interaction.window_frame.stroke.width / 2.0); if interaction.left.drag { rect.min.x = pointer_pos.x; @@ -855,6 +873,9 @@ fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Opt rect.max.y = pointer_pos.y; } + // Return to having the rect outside the stroke: + rect = rect.expand(interaction.window_frame.stroke.width / 2.0); + Some(rect.round_ui()) } @@ -862,11 +883,13 @@ fn resize_interaction( ctx: &Context, possible: PossibleInteractions, layer_id: LayerId, - rect: Rect, + outer_rect: Rect, + window_frame: Frame, ) -> ResizeInteraction { if !possible.resizable() { return ResizeInteraction { - start_rect: rect, + outer_rect, + window_frame, left: Default::default(), right: Default::default(), top: Default::default(), @@ -874,6 +897,9 @@ fn resize_interaction( }; } + // The rect that is in the middle of the stroke: + let rect = outer_rect.shrink(window_frame.stroke.width / 2.0); + let side_response = |rect, id| { let response = ctx.create_widget( WidgetRect { @@ -990,7 +1016,8 @@ fn resize_interaction( } let interaction = ResizeInteraction { - start_rect: rect, + outer_rect, + window_frame, left, right, top, @@ -1027,6 +1054,18 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction) } let rounding = Roundingf::from(ui.visuals().window_rounding); + + // Put the rect in the center of the fixed window stroke: + let rect = rect.shrink(interaction.window_frame.stroke.width / 2.0); + + // Make sure the inner part of the stroke is at a pixel boundary: + let stroke = visuals.bg_stroke; + let half_stroke = stroke.width / 2.0; + let rect = rect + .shrink(half_stroke) + .round_to_pixels(ui.pixels_per_point()) + .expand(half_stroke); + let Rect { min, max } = rect; let mut points = Vec::new(); @@ -1083,80 +1122,74 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction) points.push(pos2(max.x, min.y + rounding.ne)); points.push(pos2(max.x, max.y - rounding.se)); } - ui.painter().add(Shape::line(points, visuals.bg_stroke)); + + ui.painter().add(Shape::line(points, stroke)); } // ---------------------------------------------------------------------------- struct TitleBar { - /// A title Id used for dragging windows - id: Id, + window_frame: Frame, /// Prepared text in the title title_galley: Arc, - /// Size of the title bar in a collapsed state (if window is collapsible), - /// which includes all necessary space for showing the expand button, the - /// title and the close button. - min_rect: Rect, - /// Size of the title bar in an expanded state. This size become known only - /// after expanding window and painting its content - rect: Rect, + /// after expanding window and painting its content. + /// + /// Does not include the stroke, nor the separator line between the title bar and the window contents. + inner_rect: Rect, } impl TitleBar { fn new( - ui: &mut Ui, + ui: &Ui, title: WidgetText, show_close_button: bool, - collapsing: &mut CollapsingState, collapsible: bool, + window_frame: Frame, + title_bar_height_with_margin: f32, ) -> Self { - let inner_response = ui.horizontal(|ui| { - let height = ui - .fonts(|fonts| title.font_height(fonts, ui.style())) - .max(ui.spacing().interact_size.y); - ui.set_min_height(height); + if false { + ui.ctx() + .debug_painter() + .debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect"); + } - let item_spacing = ui.spacing().item_spacing; - let button_size = Vec2::splat(ui.spacing().icon_width); + let inner_height = title_bar_height_with_margin - window_frame.inner_margin.sum().y; - let pad = ((height - button_size.y) / 2.0).round_ui(); // calculated so that the icon is on the diagonal (if window padding is symmetrical) + let item_spacing = ui.spacing().item_spacing; + let button_size = Vec2::splat(ui.spacing().icon_width.at_most(inner_height)); - if collapsible { - ui.add_space(pad); - collapsing.show_default_button_with_size(ui, button_size); - } + let left_pad = ((inner_height - button_size.y) / 2.0).round_ui(); // calculated so that the icon is on the diagonal (if window padding is symmetrical) - let title_galley = title.into_galley( - ui, - Some(crate::TextWrapMode::Extend), - f32::INFINITY, - TextStyle::Heading, - ); - - let minimum_width = if collapsible || show_close_button { - // If at least one button is shown we make room for both buttons (since title is centered): - 2.0 * (pad + button_size.x + item_spacing.x) + title_galley.size().x - } else { - pad + title_galley.size().x + pad - }; - let min_rect = Rect::from_min_size(ui.min_rect().min, vec2(minimum_width, height)); - let id = ui.advance_cursor_after_rect(min_rect); + let title_galley = title.into_galley( + ui, + Some(crate::TextWrapMode::Extend), + f32::INFINITY, + TextStyle::Heading, + ); - Self { - id, - title_galley, - min_rect, - rect: Rect::NAN, // Will be filled in later - } - }); + let minimum_width = if collapsible || show_close_button { + // If at least one button is shown we make room for both buttons (since title should be centered): + 2.0 * (left_pad + button_size.x + item_spacing.x) + title_galley.size().x + } else { + left_pad + title_galley.size().x + left_pad + }; + let min_inner_size = vec2(minimum_width, inner_height); + let min_rect = Rect::from_min_size(ui.min_rect().min, min_inner_size); - let title_bar = inner_response.inner; - let rect = inner_response.response.rect; + if false { + ui.ctx() + .debug_painter() + .debug_rect(min_rect, Color32::LIGHT_BLUE, "min_rect"); + } - Self { rect, ..title_bar } + Self { + window_frame, + title_galley, + inner_rect: min_rect, // First estimate - will be refined later + } } /// Finishes painting of the title bar when the window content size already known. @@ -1174,17 +1207,34 @@ impl TitleBar { /// - `collapsible`: if `true`, double click on the title bar will be handled for a change /// of `collapsing` state fn ui( - mut self, + self, ui: &mut Ui, - outer_rect: Rect, content_response: &Option, open: Option<&mut bool>, collapsing: &mut CollapsingState, collapsible: bool, ) { - if let Some(content_response) = &content_response { - // Now we know how large we got to be: - self.rect.max.x = self.rect.max.x.max(content_response.rect.max.x); + let window_frame = self.window_frame; + let title_inner_rect = self.inner_rect; + + if false { + ui.ctx() + .debug_painter() + .debug_rect(self.inner_rect, Color32::RED, "TitleBar"); + } + + if collapsible { + // Show collapse-button: + let button_center = Align2::LEFT_CENTER + .align_size_within_rect(Vec2::splat(self.inner_rect.height()), self.inner_rect) + .center(); + let button_size = Vec2::splat(ui.spacing().icon_width); + let button_rect = Rect::from_center_size(button_center, button_size); + let button_rect = button_rect.round_to_pixels(ui.pixels_per_point()); + + ui.allocate_new_ui(UiBuilder::new().max_rect(button_rect), |ui| { + collapsing.show_default_button_with_size(ui, button_size); + }); } if let Some(open) = open { @@ -1194,9 +1244,9 @@ impl TitleBar { } } - let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range()); let text_pos = - emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top(); + emath::align::center_size_in_rect(self.title_galley.size(), title_inner_rect) + .left_top(); let text_pos = text_pos - self.title_galley.rect.min.to_vec2(); ui.painter().galley( text_pos, @@ -1205,22 +1255,35 @@ impl TitleBar { ); if let Some(content_response) = &content_response { - // paint separator between title and content: - let y = content_response.rect.top(); - // let y = lerp(self.rect.bottom()..=content_response.rect.top(), 0.5); - let stroke = ui.visuals().widgets.noninteractive.bg_stroke; - // Workaround: To prevent border infringement, - // the 0.1 value should ideally be calculated using TessellationOptions::feathering_size_in_pixels - // or we could support selectively disabling feathering on line caps - let x_range = outer_rect.x_range().shrink(0.1); - ui.painter().hline(x_range, y, stroke); + // Paint separator between title and content: + let content_rect = content_response.rect; + if false { + ui.ctx() + .debug_painter() + .debug_rect(content_rect, Color32::RED, "content_rect"); + } + let y = title_inner_rect.bottom() + window_frame.stroke.width / 2.0; + + // To verify the sanity of this, use a very wide window stroke + ui.painter() + .hline(title_inner_rect.x_range(), y, window_frame.stroke); } // Don't cover the close- and collapse buttons: - let double_click_rect = self.rect.shrink2(vec2(32.0, 0.0)); + let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0)); + + if false { + ui.ctx().debug_painter().debug_rect( + double_click_rect, + Color32::GREEN, + "double_click_rect", + ); + } + + let id = ui.unique_id().with("__window_title_bar"); if ui - .interact(double_click_rect, self.id, Sense::click()) + .interact(double_click_rect, id, Sense::click()) .double_clicked() && collapsible { @@ -1234,16 +1297,12 @@ impl TitleBar { /// The button is square and its size is determined by the /// [`crate::style::Spacing::icon_width`] setting. fn close_button_ui(&self, ui: &mut Ui) -> Response { + let button_center = Align2::RIGHT_CENTER + .align_size_within_rect(Vec2::splat(self.inner_rect.height()), self.inner_rect) + .center(); let button_size = Vec2::splat(ui.spacing().icon_width); - let pad = (self.rect.height() - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical) - let button_rect = Rect::from_min_size( - pos2( - self.rect.right() - pad - button_size.x, - self.rect.center().y - 0.5 * button_size.y, - ), - button_size, - ); - + let button_rect = Rect::from_center_size(button_center, button_size); + let button_rect = button_rect.round_to_pixels(ui.pixels_per_point()); close_button(ui, button_rect) } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 19fda5d0fee..acc08ca379f 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1771,12 +1771,14 @@ impl Ui { /// Add extra space before the next widget. /// /// The direction is dependent on the layout. - /// This will be in addition to the [`crate::style::Spacing::item_spacing`]. + /// + /// This will be in addition to the [`crate::style::Spacing::item_spacing`] + /// that is always added, but `item_spacing` won't be added _again_ by `add_space`. /// /// [`Self::min_rect`] will expand to contain the space. #[inline] pub fn add_space(&mut self, amount: f32) { - self.placer.advance_cursor(amount); + self.placer.advance_cursor(amount.round_ui()); } /// Show some text. diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 117824e0174..6409eb90227 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -44,7 +44,11 @@ pub struct FractalClockApp { impl eframe::App for FractalClockApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default() - .frame(egui::Frame::dark_canvas(&ctx.style())) + .frame( + egui::Frame::dark_canvas(&ctx.style()) + .stroke(egui::Stroke::NONE) + .rounding(0), + ) .show(ctx, |ui| { self.fractal_clock .ui(ui, self.mock_time.or(Some(crate::seconds_since_midnight()))); @@ -293,7 +297,7 @@ impl eframe::App for WrapApp { let mut cmd = Command::Nothing; egui::TopBottomPanel::top("wrap_app_top_bar") - .frame(egui::Frame::none().inner_margin(4.0)) + .frame(egui::Frame::new().inner_margin(4)) .show(ctx, |ui| { ui.horizontal_wrapped(|ui| { ui.visuals_mut().button_frame = false; diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index 51d271ddd49..d9723049499 100644 --- a/crates/egui_demo_app/tests/snapshots/clock.png +++ b/crates/egui_demo_app/tests/snapshots/clock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c05cc3d48242e46a391af34cb56f72de7933bf2cead009b6cd477c21867a84e -size 327802 +oid sha256:4aeab31841dd95b5e0f4bd0af0c0ba49a862d50836dbafdf2172fbbab950c105 +size 327741 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index 1e51e9f6aa0..b138e53b9b3 100644 --- a/crates/egui_demo_app/tests/snapshots/custom3d.png +++ b/crates/egui_demo_app/tests/snapshots/custom3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61212e30fe1fecf5891ddad6ac795df510bfad76b21a7a8a13aa024fdad6d05e -size 93118 +oid sha256:0e4a90792a9876da549f3d1da9b057a078400ad15db2cc6e35f4324851137d4e +size 93115 diff --git a/crates/egui_demo_lib/src/demo/frame_demo.rs b/crates/egui_demo_lib/src/demo/frame_demo.rs index b772eb7502d..71c5e9ab2fd 100644 --- a/crates/egui_demo_lib/src/demo/frame_demo.rs +++ b/crates/egui_demo_lib/src/demo/frame_demo.rs @@ -7,19 +7,18 @@ pub struct FrameDemo { impl Default for FrameDemo { fn default() -> Self { Self { - frame: egui::Frame { - inner_margin: 12.0.into(), - outer_margin: 24.0.into(), - rounding: 14.0.into(), - shadow: egui::Shadow { + frame: egui::Frame::new() + .inner_margin(12) + .outer_margin(24) + .rounding(14) + .shadow(egui::Shadow { offset: [8, 12], blur: 16, spread: 0, color: egui::Color32::from_black_alpha(180), - }, - fill: egui::Color32::from_rgba_unmultiplied(97, 0, 255, 128), - stroke: egui::Stroke::new(1.0, egui::Color32::GRAY), - }, + }) + .fill(egui::Color32::from_rgba_unmultiplied(97, 0, 255, 128)) + .stroke(egui::Stroke::new(1.0, egui::Color32::GRAY)), } } } diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index 0fd6b7fb632..f3edf39558a 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -1,8 +1,8 @@ use super::{Demo, View}; use egui::{ - vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Frame, Resize, RichText, - Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window, + vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Resize, RichText, Sense, + Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window, }; /// Showcase some ui code @@ -512,54 +512,52 @@ fn ui_stack_demo(ui: &mut Ui) { ); }); let stack = ui.stack().clone(); - Frame { - inner_margin: ui.spacing().menu_margin, - stroke: ui.visuals().widgets.noninteractive.bg_stroke, - ..Default::default() - } - .show(ui, |ui| { - egui_extras::TableBuilder::new(ui) - .column(egui_extras::Column::auto()) - .column(egui_extras::Column::auto()) - .header(18.0, |mut header| { - header.col(|ui| { - ui.strong("id"); - }); - header.col(|ui| { - ui.strong("kind"); - }); - }) - .body(|mut body| { - for node in stack.iter() { - body.row(18.0, |mut row| { - row.col(|ui| { - let response = ui.label(format!("{:?}", node.id)); - - if response.hovered() { - ui.ctx().debug_painter().debug_rect( - node.max_rect, - Color32::GREEN, - "max_rect", - ); - ui.ctx().debug_painter().circle_filled( - node.min_rect.min, - 2.0, - Color32::RED, - ); - } - }); + egui::Frame::new() + .inner_margin(ui.spacing().menu_margin) + .stroke(ui.visuals().widgets.noninteractive.bg_stroke) + .show(ui, |ui| { + egui_extras::TableBuilder::new(ui) + .column(egui_extras::Column::auto()) + .column(egui_extras::Column::auto()) + .header(18.0, |mut header| { + header.col(|ui| { + ui.strong("id"); + }); + header.col(|ui| { + ui.strong("kind"); + }); + }) + .body(|mut body| { + for node in stack.iter() { + body.row(18.0, |mut row| { + row.col(|ui| { + let response = ui.label(format!("{:?}", node.id)); + + if response.hovered() { + ui.ctx().debug_painter().debug_rect( + node.max_rect, + Color32::GREEN, + "max_rect", + ); + ui.ctx().debug_painter().circle_filled( + node.min_rect.min, + 2.0, + Color32::RED, + ); + } + }); - row.col(|ui| { - ui.label(if let Some(kind) = node.kind() { - format!("{kind:?}") - } else { - "-".to_owned() + row.col(|ui| { + ui.label(if let Some(kind) = node.kind() { + format!("{kind:?}") + } else { + "-".to_owned() + }); }); }); - }); - } - }); - }); + } + }); + }); ui.small("Hover on UI's ids to display their origin and max rect."); } diff --git "a/crates/egui_demo_lib/tests/snapshots/demos/B\303\251zier Curve.png" "b/crates/egui_demo_lib/tests/snapshots/demos/B\303\251zier Curve.png" index c4cd0bd1099..09dc7549af0 100644 --- "a/crates/egui_demo_lib/tests/snapshots/demos/B\303\251zier Curve.png" +++ "b/crates/egui_demo_lib/tests/snapshots/demos/B\303\251zier Curve.png" @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf83bead834ec8f88d74b32ae6331715e8c6df183e007e2a16004c019534a30f -size 31810 +oid sha256:d4cfd5191dc7046a782ef2350dc8e0547d2702182badcb15b6b928ce077b76c1 +size 32154 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png index 703c9ef191d..2421711256b 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a1099b85a1aaf20f3f1e091bc68259f811737feaefdfcc12acd067eca8f9117 -size 27083 +oid sha256:e89c730b462c2b60b90f2ac15fe9576e878a4906c223317c51344a0ec2b6d993 +size 27564 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index 0f417f0b45a..99885a8aa81 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6969c6da67ea6cc7ebbbd7a2cc1cb13d4720befe28126367cbf2b2679d037674 -size 82363 +oid sha256:ea2c944af8bc1be42ec7c00be58dfaa23c92bca8957eda94f2ff10f5b4242562 +size 83358 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png b/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png index caa3c3a5d96..d32a46ce76e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:332c2af36873d8ccccb36c08fd2e475dc1f18454a3090a851c0889395d4f364f -size 11518 +oid sha256:c401ff91fff4051042528d398d2b2270a4ae924570e6332cf8f2c6774c845160 +size 11826 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png index e86d10772ef..4f44756deb0 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0d0b1b4d2c4b624904250bc8d6870559f0179e3f7f2d6dc4a4ff256df356237 -size 20626 +oid sha256:7efc1ff3e4e5bfd4216394f94ee7486c272a9ca1c980789f4ad143f89b0a7103 +size 21073 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png index 38199566d27..8a831b65d12 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5fe6166bb8cd5fae0899957e968312b9229a79e423a5d154eda483a149b264d -size 20831 +oid sha256:d9c48cf928a17dd0980ba086aa004bde3a0040dcb82752d138c1df34f1ef3d2f +size 21167 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png index 96adb69423d..05d87fa8b9b 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b71e1d109f90e017644dd20b9d84d81e3a6d5195afbd01ba86c85fa248c8b5c5 -size 10703 +oid sha256:be0f96c700b7662aab5098f8412dae3676116eeed65e70f6b295dd3375b329d0 +size 10968 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png index 10f5d0b40a0..f85436ef791 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3c9ba9064f44a4a14405f53316c1c602184caf16cb584d7c1f1912fe59f85ab -size 135712 +oid sha256:dc69c76eaa121e9e7782cfbbb68b5a23004d79862bae4af2e3ca3a29eff04bea +size 136467 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index e896f366e31..cec95632564 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:097bd512dd71c17370f6e374312c19e7ab6845f151b3c3565f2a0790b61ee7ba -size 24413 +oid sha256:23187a9fb12a3ab7df4e2321aa25b493559923d61e82802f843ee29dcd932f7b +size 24985 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index ea317322420..12d396cb455 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bdf54573a6b0d2fedd90314f91dd7de22dd13709e8dd699b37ef8935b6adda5 -size 17785 +oid sha256:7f433f3e8bff38a0aafd7e6cba5c5efe1abf484550a6f9e90008f8f5ea891497 +size 18113 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png index 68d03d9fd07..576b7b328ef 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6328c86b70f3e24aaf87db200d13dfd0baa787dd8431e47621457937f8f5021 -size 22552 +oid sha256:e5105ecf77852412c0dd904b96f0fec752f22e416df9932df4499d6d5a776f46 +size 22865 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index dcd12b097f2..db0322f86a4 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afb57dc0bb8ff839138e36b8780136e0c8da55ff380832538fae0394143807c0 -size 65321 +oid sha256:ebf0403bd599e5c00c2831f9c4032e8d20420212c9cd7fa875f1ae1cbbc8d3a7 +size 65902 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png index 3341eebff84..20ed40f4b89 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:403fc1d08d79168bc9f7a08ac7f363f2df916547f08311838edfda8a499a9b2d -size 32879 +oid sha256:4e690dc73873ab75c030d3c0238e9d5b840f976dd8f4882dc1e930024d217838 +size 33323 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png index 882279e03b7..68470555be3 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cbd490a15377bdd4fd3272f3cd126cbc4fb9ed2f6f84edcbba03dd30dc3f3d99 -size 36780 +oid sha256:e760210371dbf2a197f96a78d01b7480f0ae05d46bbb4e642276b2eb30847ec2 +size 37075 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index 40cb73fe06a..a2f6c14ad20 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c31b3998817e345b12b037d7f8bec7641f77d0c7eab7da9a746b7b51c9efc8fb -size 17531 +oid sha256:fd02b208d0e4e306bbc9a54f25f5a3d20875a12182cef3224e6daa309b6cf453 +size 17898 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png b/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png index cecfb843283..c8f448facdb 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e8963c3ecd0e74fe9641d87101742a0d45c82a582d70e30eb36bc835f5aac06 -size 25330 +oid sha256:341958da648a7db3374c4337cf057ae8e81c08c4a6de7e4f1cbe9c5b049f2e62 +size 25727 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 6576f9ad5dd..9ce35877bab 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88b3a50b481942630b5804c60227f23b719dc7e3eb6dbe432c2448cb69332def -size 262141 +oid sha256:8934cff7203d19b38df9d91729091ff5d1ad6c8d445fd9c1cb62b6df1bb8cb80 +size 263547 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png index 4c97bde2061..d9f7bc13099 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d5d628b54b28eccac0d9ef21bbdabace0cdf507898707956943e2259df846ca -size 23741 +oid sha256:5d61f58138798d701bb8dda2c3240eef69eb350df3168fb3aa4148e4fef3f77a +size 24077 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 98b10f30d6f..bb0b7a007d1 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8763a8f8b4242cbe589cd3282dc5f4b32a84b4e0416fb8072dfefb7879a5d4f6 -size 187982 +oid sha256:e4006e93663d02fe0f4485d2c163ab2b6feded787bee87ea15616fc0b36136d0 +size 188875 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index 19b7cf0ff35..56d6d37ce3d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d61088bf1f992467e8396ac89407fa158d6f44e2d2d196778247f3ff18cb893 -size 119759 +oid sha256:70b170ba7b8e51d9d9f766d7ce25068fa4265c4127e729af4f1adaacbb745d19 +size 120947 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index 16a54ae0fa8..78a671578ad 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79ebaf9cccd19da2923507b5a553a23edc67382ef59e4b71f01d3bd6cc913539 -size 25829 +oid sha256:157353a8c9bcb638a8be485489e4a232f348eae3cc4ceefe227d7970c7d1f8b3 +size 26256 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index 232a5ec327c..89e92317bfa 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06cbf13841e6ac5fbc57bdae6f5ad9d19718193c344420dedcc0e9d7ed2b8ba9 -size 71590 +oid sha256:cf0ddd39a45519dcf9027f691e856921c736d18e2eeafd16f0e086720121b6a7 +size 72286 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png index cc2da3a5563..90259a56df4 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35e66f211c0b30a684371b520c46dbe4f9d5b6835e053a4eb65f492dd66a9e6c -size 67288 +oid sha256:914d37e326087f770994bcf3867a27d88050c57887a2b42c68414d311fa96003 +size 67698 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index 4704ff75dd7..4bbe0f59b3f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa9ee8631bfe433ee6fad1fb1651fd6b63e2fb3fbc5f321a5410f7266dc16d09 -size 21296 +oid sha256:b0fdf8ce329883450e071e4733c3904577999d18ac61c922c7caacbec09dfda7 +size 21661 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index 3e8a3ba3e93..4f0a7bc17eb 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6475702b1bf2c65fb5196928a8934ade647a6053d6902a836e3d69cb7920091e -size 59874 +oid sha256:375b71a8ac5b0e48f3c67a089ef0e8a4fd17f8eb21fa535422099c29c2747e27 +size 59991 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png b/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png index c39aa9f8571..f96b4ebeeb9 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d8daaec0c58709298a4594b7b2aa935aa2de327e6b71bd7417c2ba3a6eb060c -size 13020 +oid sha256:aa96b1e3733e4af94a6cb6ec765c3f3581df2175e75831eb00bd42df2e7a2708 +size 13285 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png index 5670cef5834..102faa02a23 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29d8d859a8cb11e4b390d45c658ed8ff191c2e542909e12f2385c0cba62baa2d -size 35109 +oid sha256:18880dfaf5d198876c4db97ebd6313d59755a3e8298567f2b2fa91dcc21699c5 +size 35607 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index 0bd63cd50e9..46c12fa2ded 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_1.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17217e600d8a85ec00ecb11f0e5fe69496b30bbf715cc86785cec6e18b8c2fa1 -size 48158 +oid sha256:d626b310439bff13487548bbba8b516886c13049824a7f5dd902f6dffb3c5ba4 +size 48234 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index 8c496d81dd1..5ea3c5b3e81 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fac50d2327a9e5e99989dd81d5644db86031b91b9c5c68fc17b5ef53ae655048 -size 47970 +oid sha256:80a2968e211c83639b60e84b805f1327fb37b87144cada672a403c7e92ace8a8 +size 48066 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index 1cc989ddce5..5bb1fe23eb5 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5e829257b742194742429be0efd326be17ff7f5b9b16f9df58df21e899320bd -size 43963 +oid sha256:86df5dc4b4ddd6f44226242b6d9b5e9f2aacd45193ae9f784fb5084a7a509e0b +size 43987 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index 0246090d4a6..2c868b4400a 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5da064332c669a860a92a34b101f23e28026d4f07948f7c3e9a40e611f5e284f -size 43986 +oid sha256:1138bbc3b7e73cccd555f0fd58c27a5bda4d84484fdc1bd5223fc9802d0c5328 +size 44089 diff --git a/crates/egui_kittest/src/app_kind.rs b/crates/egui_kittest/src/app_kind.rs index 3b5cf9e734c..081490e3d36 100644 --- a/crates/egui_kittest/src/app_kind.rs +++ b/crates/egui_kittest/src/app_kind.rs @@ -56,7 +56,7 @@ impl<'a, State> AppKind<'a, State> { sizing_pass: bool, ) -> egui::Response { egui::CentralPanel::default() - .frame(Frame::none()) + .frame(Frame::NONE) .show(ctx, |ui| { let mut builder = egui::UiBuilder::new(); if sizing_pass { diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 86e82759f40..aefcdc67740 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -45,13 +45,11 @@ impl eframe::App for MyApp { fn custom_window_frame(ctx: &egui::Context, title: &str, add_contents: impl FnOnce(&mut egui::Ui)) { use egui::{CentralPanel, UiBuilder}; - let panel_frame = egui::Frame { - fill: ctx.style().visuals.window_fill(), - rounding: 10.0.into(), - stroke: ctx.style().visuals.widgets.noninteractive.fg_stroke, - outer_margin: 1.0.into(), // so the stroke is within the bounds - ..Default::default() - }; + let panel_frame = egui::Frame::new() + .fill(ctx.style().visuals.window_fill()) + .rounding(10) + .stroke(ctx.style().visuals.widgets.noninteractive.fg_stroke) + .outer_margin(1); // so the stroke is within the bounds CentralPanel::default().frame(panel_frame).show(ctx, |ui| { let app_rect = ui.max_rect(); diff --git a/tests/test_ui_stack/src/main.rs b/tests/test_ui_stack/src/main.rs index a2f09af5cea..47b4ee5cae5 100644 --- a/tests/test_ui_stack/src/main.rs +++ b/tests/test_ui_stack/src/main.rs @@ -62,27 +62,23 @@ impl eframe::App for MyApp { // nested frames test ui.add_space(20.0); - egui::Frame { - stroke: ui.visuals().noninteractive().bg_stroke, - inner_margin: egui::Margin::same(4), - outer_margin: egui::Margin::same(4), - ..Default::default() - } - .show(ui, |ui| { - full_span_widget(ui, false); - stack_ui(ui); - - egui::Frame { - stroke: ui.visuals().noninteractive().bg_stroke, - inner_margin: egui::Margin::same(8), - outer_margin: egui::Margin::same(6), - ..Default::default() - } + egui::Frame::new() + .stroke(ui.visuals().noninteractive().bg_stroke) + .inner_margin(4) + .outer_margin(4) .show(ui, |ui| { full_span_widget(ui, false); stack_ui(ui); + + egui::Frame::new() + .stroke(ui.visuals().noninteractive().bg_stroke) + .inner_margin(8) + .outer_margin(6) + .show(ui, |ui| { + full_span_widget(ui, false); + stack_ui(ui); + }); }); - }); }); }); @@ -126,18 +122,16 @@ impl eframe::App for MyApp { // Ui nesting test ui.add_space(20.0); ui.label("UI nesting test:"); - egui::Frame { - stroke: ui.visuals().noninteractive().bg_stroke, - inner_margin: egui::Margin::same(4), - ..Default::default() - } - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.vertical(|ui| { - ui.scope(stack_ui); + egui::Frame::new() + .stroke(ui.visuals().noninteractive().bg_stroke) + .inner_margin(4) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.scope(stack_ui); + }); }); }); - }); // table test let mut cell_stack = None; @@ -265,106 +259,104 @@ fn stack_ui(ui: &mut egui::Ui) { } fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) { - egui::Frame { - stroke: ui.style().noninteractive().fg_stroke, - inner_margin: egui::Margin::same(4), - ..Default::default() - } - .show(ui, |ui| { - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); - - egui_extras::TableBuilder::new(ui) - .column(Column::auto()) - .column(Column::auto()) - .column(Column::auto()) - .column(Column::auto()) - .column(Column::auto()) - .column(Column::auto()) - .header(20.0, |mut header| { - header.col(|ui| { - ui.strong("id"); - }); - header.col(|ui| { - ui.strong("kind"); - }); - header.col(|ui| { - ui.strong("stroke"); - }); - header.col(|ui| { - ui.strong("inner"); - }); - header.col(|ui| { - ui.strong("outer"); - }); - header.col(|ui| { - ui.strong("direction"); - }); - }) - .body(|mut body| { - for node in stack.iter() { - body.row(20.0, |mut row| { - row.col(|ui| { - if ui.label(format!("{:?}", node.id)).hovered() { - ui.ctx().debug_painter().debug_rect( - node.max_rect, - egui::Color32::GREEN, - "max", - ); - ui.ctx().debug_painter().circle_filled( - node.min_rect.min, - 2.0, - egui::Color32::RED, - ); - } - }); - row.col(|ui| { - let s = if let Some(kind) = node.kind() { - format!("{kind:?}") - } else { - "-".to_owned() - }; - - ui.label(s); - }); - row.col(|ui| { - let frame = node.frame(); - if frame.stroke == egui::Stroke::NONE { - ui.label("-"); - } else { - let mut layout_job = egui::text::LayoutJob::default(); - layout_job.append( - "⬛ ", - 0.0, - egui::TextFormat::simple( - egui::TextStyle::Body.resolve(ui.style()), - frame.stroke.color, - ), - ); - layout_job.append( - format!("{}px", frame.stroke.width).as_str(), - 0.0, - egui::TextFormat::simple( - egui::TextStyle::Body.resolve(ui.style()), - ui.style().visuals.text_color(), - ), - ); - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); - ui.label(layout_job); - } - }); - row.col(|ui| { - ui.label(print_margin(&node.frame().inner_margin)); - }); - row.col(|ui| { - ui.label(print_margin(&node.frame().outer_margin)); - }); - row.col(|ui| { - ui.label(format!("{:?}", node.layout_direction)); - }); + egui::Frame::new() + .stroke(ui.style().noninteractive().fg_stroke) + .inner_margin(4) + .show(ui, |ui| { + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); + + egui_extras::TableBuilder::new(ui) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .header(20.0, |mut header| { + header.col(|ui| { + ui.strong("id"); }); - } - }); - }); + header.col(|ui| { + ui.strong("kind"); + }); + header.col(|ui| { + ui.strong("stroke"); + }); + header.col(|ui| { + ui.strong("inner"); + }); + header.col(|ui| { + ui.strong("outer"); + }); + header.col(|ui| { + ui.strong("direction"); + }); + }) + .body(|mut body| { + for node in stack.iter() { + body.row(20.0, |mut row| { + row.col(|ui| { + if ui.label(format!("{:?}", node.id)).hovered() { + ui.ctx().debug_painter().debug_rect( + node.max_rect, + egui::Color32::GREEN, + "max", + ); + ui.ctx().debug_painter().circle_filled( + node.min_rect.min, + 2.0, + egui::Color32::RED, + ); + } + }); + row.col(|ui| { + let s = if let Some(kind) = node.kind() { + format!("{kind:?}") + } else { + "-".to_owned() + }; + + ui.label(s); + }); + row.col(|ui| { + let frame = node.frame(); + if frame.stroke == egui::Stroke::NONE { + ui.label("-"); + } else { + let mut layout_job = egui::text::LayoutJob::default(); + layout_job.append( + "⬛ ", + 0.0, + egui::TextFormat::simple( + egui::TextStyle::Body.resolve(ui.style()), + frame.stroke.color, + ), + ); + layout_job.append( + format!("{}px", frame.stroke.width).as_str(), + 0.0, + egui::TextFormat::simple( + egui::TextStyle::Body.resolve(ui.style()), + ui.style().visuals.text_color(), + ), + ); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); + ui.label(layout_job); + } + }); + row.col(|ui| { + ui.label(print_margin(&node.frame().inner_margin)); + }); + row.col(|ui| { + ui.label(print_margin(&node.frame().outer_margin)); + }); + row.col(|ui| { + ui.label(format!("{:?}", node.layout_direction)); + }); + }); + } + }); + }); } fn print_margin(margin: &egui::Margin) -> String {