diff --git a/crates/egui/src/atomics/atom_layout.rs b/crates/egui/src/atomics/atom_layout.rs index 7894273f3..1b44c986b 100644 --- a/crates/egui/src/atomics/atom_layout.rs +++ b/crates/egui/src/atomics/atom_layout.rs @@ -226,7 +226,7 @@ impl<'a> AtomLayout<'a> { max_size.x = f32::INFINITY; } - let available_size = ui.available_size().at_most(max_size); + let available_size = ui.available_size().at_most(max_size).at_least(min_size); // The size available for the content let available_inner_size = available_size - frame.total_margin().sum(); diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 09488058d..10be6307a 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -594,10 +594,6 @@ fn round_area_position(ctx: &Context, pos: Pos2) -> Pos2 { } impl Prepared { - pub(crate) fn state(&self) -> &AreaState { - &self.state - } - pub(crate) fn state_mut(&mut self) -> &mut AreaState { &mut self.state } diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index de3581288..d921a5cbf 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -2,8 +2,8 @@ use std::hash::Hash; use crate::{ Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle, - TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetText, WidgetType, - emath, epaint, pos2, remap, remap_clamp, vec2, + TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, WidgetInfo, WidgetText, WidgetType, emath, + epaint, pos2, remap, remap_clamp, vec2, }; use emath::GuiRounding as _; use epaint::{Shape, StrokeKind}; @@ -81,30 +81,6 @@ impl CollapsingState { } } - /// Will toggle when clicked, etc. - pub(crate) fn show_default_button_with_size( - &mut self, - ui: &mut Ui, - button_size: Vec2, - ) -> Response { - let (_id, rect) = ui.allocate_space(button_size); - let response = ui.interact(rect, self.id, Sense::click()); - response.widget_info(|| { - WidgetInfo::labeled( - WidgetType::Button, - ui.is_enabled(), - if self.is_open() { "Hide" } else { "Show" }, - ) - }); - - if response.clicked() { - self.toggle(ui); - } - let openness = self.openness(ui.ctx()); - paint_default_icon(ui, openness, &response); - response - } - /// Will toggle when clicked, etc. fn show_default_button_indented(&mut self, ui: &mut Ui) -> Response { self.show_button_indented(ui, paint_default_icon) diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index c537ac140..679b92c8e 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -50,7 +50,7 @@ pub struct Resize { pub(crate) min_size: Vec2, pub(crate) max_size: Vec2, - default_size: Vec2, + pub(crate) default_size: Vec2, with_stroke: bool, } diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 9f25d6131..2c249254f 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -1,9 +1,7 @@ // WARNING: the code in here is horrible. It is a behemoth that needs breaking up into simpler parts. -use std::sync::Arc; - use emath::GuiRounding as _; -use epaint::{CornerRadiusF32, RectShape}; +use epaint::CornerRadiusF32; use crate::collapsing_header::CollapsingState; use crate::*; @@ -33,9 +31,9 @@ use super::{Area, Frame, Resize, ScrollArea, area, resize}; /// Note that this is NOT a native OS window. /// To create a new native OS window, use [`crate::Context::show_viewport_deferred`]. #[must_use = "You should call .show()"] -pub struct Window<'open> { - title: WidgetText, - open: Option<&'open mut bool>, +pub struct Window<'a> { + title: Atoms<'a>, + open: Option<&'a mut bool>, area: Area, frame: Option, resize: Resize, @@ -44,14 +42,15 @@ pub struct Window<'open> { default_open: bool, with_title_bar: bool, fade_out: bool, + auto_sized: bool, } -impl<'open> Window<'open> { +impl<'a> Window<'a> { /// The window title is used as a unique [`Id`] and must be unique, and should not change. /// This is true even if you disable the title bar with `.title_bar(false)`. /// If you need a changing title, you must call `window.id(…)` with a fixed id. - pub fn new(title: impl Into) -> Self { - let title = title.into().fallback_text_style(TextStyle::Heading); + pub fn new(title: impl IntoAtoms<'a>) -> Self { + let title: Atoms<'_> = title.into_atoms(); let area = Area::new(Id::new(title.text())).kind(UiKind::Window); Self { title, @@ -61,12 +60,13 @@ impl<'open> Window<'open> { resize: Resize::default() .with_stroke(false) .min_size([96.0, 32.0]) - .default_size([340.0, 420.0]), // Default inner size of a window + .default_size([340.0, 420.0]), // Default outer size of a window (includes frame margins, stroke, and title bar) scroll: ScrollArea::neither().auto_shrink(false), collapsible: true, default_open: true, with_title_bar: true, fade_out: true, + auto_sized: false, } } @@ -118,7 +118,7 @@ impl<'open> Window<'open> { /// * If `*open == true`, the window will have a close button. /// * If the close button is pressed, `*open` will be set to `false`. #[inline] - pub fn open(mut self, open: &'open mut bool) -> Self { + pub fn open(mut self, open: &'a mut bool) -> Self { self.open = Some(open); self } @@ -213,6 +213,9 @@ impl<'open> Window<'open> { } /// Set minimum size of the window, equivalent to calling both `min_width` and `min_height`. + /// + /// The size refers to the *outer* window size, including the frame's `inner_margin`, + /// `outer_margin`, `stroke`, and the title bar. #[inline] pub fn min_size(mut self, min_size: impl Into) -> Self { self.resize = self.resize.min_size(min_size); @@ -234,6 +237,9 @@ impl<'open> Window<'open> { } /// Set maximum size of the window, equivalent to calling both `max_width` and `max_height`. + /// + /// The size refers to the *outer* window size, including the frame's `inner_margin`, + /// `outer_margin`, `stroke`, and the title bar. #[inline] pub fn max_size(mut self, max_size: impl Into) -> Self { self.resize = self.resize.max_size(max_size); @@ -320,6 +326,9 @@ impl<'open> Window<'open> { } /// Set initial size of the window. + /// + /// The size refers to the *outer* window size, including frame margins, stroke, + /// and the title bar. #[inline] pub fn default_size(mut self, default_size: impl Into) -> Self { let default_size: Vec2 = default_size.into(); @@ -345,6 +354,9 @@ impl<'open> Window<'open> { } /// Sets the window size and prevents it from being resized by dragging its edges. + /// + /// The size refers to the *outer* window size, including the frame's `inner_margin`, + /// `outer_margin`, `stroke`, and the title bar. #[inline] pub fn fixed_size(mut self, size: impl Into) -> Self { self.resize = self.resize.fixed_size(size); @@ -399,6 +411,7 @@ impl<'open> Window<'open> { pub fn auto_sized(mut self) -> Self { self.resize = self.resize.auto_sized(); self.scroll = ScrollArea::neither(); + self.auto_sized = true; self } @@ -473,13 +486,12 @@ impl Window<'_> { default_open, with_title_bar, fade_out, + auto_sized, } = self; let style = ctx.global_style(); - let header_color = - frame.map_or_else(|| style.visuals.widgets.open.weak_bg_fill, |f| f.fill); - let mut window_frame = frame.unwrap_or_else(|| Frame::window(&style)); + let window_frame = frame.unwrap_or_else(|| Frame::window(&style)); let is_explicitly_closed = matches!(open, Some(false)); let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); @@ -507,64 +519,37 @@ impl Window<'_> { let on_top = Some(area_layer_id) == ctx.top_layer_id(); let mut area = area.begin(ctx); - area.with_widget_info(|| WidgetInfo::labeled(WidgetType::Window, true, title.text())); - - // 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 title_bar_inner_height = ctx - .fonts_mut(|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.corner_radius.ne = window_frame.corner_radius.ne.clamp(0, half_height); - window_frame.corner_radius.nw = window_frame.corner_radius.nw.clamp(0, half_height); - - 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) - }; + area.with_widget_info(|| { + WidgetInfo::labeled( + WidgetType::Window, + true, + title.text().as_deref().unwrap_or(""), + ) + }); { // Prevent window from becoming larger than the constrain rect. + // `resize.max_size` is still in outer-window coordinates here, matching `constrain_rect`. let constrain_rect = area.constrain_rect(); let max_width = constrain_rect.width(); - let max_height = - constrain_rect.height() - title_bar_height_with_margin - title_content_spacing; + let max_height = constrain_rect.height(); resize.max_size.x = resize.max_size.x.min(max_width); resize.max_size.y = resize.max_size.y.min(max_height); } - // First check for resize to avoid frame delay: - let last_frame_outer_rect = area.state().rect(); - let resize_interaction = do_resize_interaction( - ctx, - possible, - area.id(), - area_layer_id, - last_frame_outer_rect, - window_frame, - ); - + // The user-supplied min/max/default sizes on `Window` refer to the *outer* window size + // (the total footprint, including frame margins, stroke, and title bar). `Resize` sizes + // the title bar + inner content area, so we subtract the extra frame margin (the part + // outside of `Resize`). { - 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 frame_margin = window_frame.total_margin().sum(); + resize.min_size = (resize.min_size - frame_margin).at_least(Vec2::ZERO); + resize.max_size = (resize.max_size - frame_margin).at_least(Vec2::ZERO); + resize.default_size = (resize.default_size - frame_margin).at_least(Vec2::ZERO); } let mut area_content_ui = area.content_ui(ctx); + if is_open { // `Area` already takes care of fade-in animations, // so we only need to handle fade-out animations here. @@ -573,55 +558,33 @@ impl Window<'_> { } let content_inner = { - // BEGIN FRAME -------------------------------- - 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); - - let title_bar = if with_title_bar { - let title_bar = TitleBar::new( - &frame.content_ui, - title, - show_close_button, - collapsible, - window_frame, - title_bar_height_with_margin, - ); - 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 - }; - - let (content_inner, content_response) = collapsing - .show_body_unindented(&mut frame.content_ui, |ui| { - resize.show(ui, |ui| { - if scroll.is_any_scroll_enabled() { - scroll.show(ui, add_contents).inner - } else { - add_contents(ui) - } - }) + let outer_response = window_frame.show(&mut area_content_ui, |ui| { + resize.show(ui, |ui| { + if with_title_bar { + title_ui( + ui, + title, + window_frame, + &mut collapsing, + collapsible, + on_top, + open.as_deref_mut(), + auto_sized, + ); + } + collapsing + .show_body_unindented(ui, |ui| { + if scroll.is_any_scroll_enabled() { + scroll.show(ui, add_contents).inner + } else { + add_contents(ui) + } + }) + .map(|inner| inner.inner) }) - .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response))); + }); - let outer_rect = frame.end(&mut area_content_ui).rect; + let outer_rect = outer_response.response.rect; // Do resize interaction _again_, to move their widget rectangles on TOP of the rest of the window. let resize_interaction = do_resize_interaction( @@ -629,7 +592,7 @@ impl Window<'_> { possible, area.id(), area_layer_id, - last_frame_outer_rect, + outer_rect, window_frame, ); @@ -641,50 +604,25 @@ impl Window<'_> { resize_interaction, ); - // END FRAME -------------------------------- + { + let margins = window_frame.total_margin().sum(); - 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; - - if on_top && area_content_ui.visuals().window_highlight_topmost { - let mut round = - window_frame.corner_radius - window_frame.stroke.width.round() as u8; - - if !is_collapsed { - round.se = 0; - round.sw = 0; - } - - area_content_ui.painter().set( - *where_to_put_header_background, - RectShape::filled(title_bar.inner_rect, round, header_color), - ); - } - - if false { - ctx.debug_painter().debug_rect( - title_bar.inner_rect, - Color32::LIGHT_BLUE, - "title_bar.rect", - ); - } - - title_bar.ui( - &mut area_content_ui, - content_response.as_ref(), - open.as_deref_mut(), - &mut collapsing, - collapsible, + resize_response( + resize_interaction, + ctx, + margins, + area_layer_id, + &mut area, + resize_id, ); } + // END FRAME -------------------------------- collapsing.store(ctx); paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction); - content_inner + outer_response.inner }; let full_response = area.end(ctx, area_content_ui); @@ -992,7 +930,7 @@ fn do_resize_interaction( let side_grab_radius = style.interaction.resize_grab_radius_side; let corner_grab_radius = style.interaction.resize_grab_radius_corner; - let vetrtical_rect = |a: Pos2, b: Pos2| { + let vertical_rect = |a: Pos2, b: Pos2| { Rect::from_min_max(a, b).expand2(vec2(side_grab_radius, -corner_grab_radius)) }; let horizontal_rect = |a: Pos2, b: Pos2| { @@ -1009,14 +947,14 @@ fn do_resize_interaction( if possible.resize_right { let response = side_response( - vetrtical_rect(rect.right_top(), rect.right_bottom()), + vertical_rect(rect.right_top(), rect.right_bottom()), id.with("right"), ); right |= response; } if possible.resize_left { let response = side_response( - vetrtical_rect(rect.left_top(), rect.left_bottom()), + vertical_rect(rect.left_top(), rect.left_bottom()), id.with("left"), ); left |= response; @@ -1177,176 +1115,165 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction) // ---------------------------------------------------------------------------- -struct TitleBar { - window_frame: Frame, +/// Show the window titlebar. +/// +/// Should be placed inside a `Frame::window`. The [`Frame`] it was placed inside should be passed as +/// an arg and will be used to paint the divider line at the bottom and the highlighted background +/// when `active` is true. +#[expect(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] +fn title_ui( + ui: &mut Ui, + mut title: Atoms<'_>, + frame: Frame, + collapsing: &mut CollapsingState, + collapsible: bool, + active: bool, + open: Option<&mut bool>, + auto_sized: bool, +) -> Response { + let shape_idx = ui.painter().add(Shape::Noop); - /// Prepared text in the title - title_galley: Arc, + let mut atoms = Atoms::default(); - /// Size of the title bar in an expanded state. This size become known only - /// 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, -} + let button_size = Vec2::splat(ui.spacing().icon_width); -impl TitleBar { - fn new( - ui: &Ui, - title: WidgetText, - show_close_button: bool, - collapsible: bool, - window_frame: Frame, - title_bar_height_with_margin: f32, - ) -> Self { - if false { - ui.debug_painter() - .debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect"); - } + // Since the heading height is higher than the button size, we need to allocate the buttons + // with the headers height as size, otherwise they'd look slightly off-center. + // The shrink is then used to render the buttons with the right size. + let heading_font_height = + ui.fonts_mut(|f| f.row_height(&TextStyle::Heading.resolve(ui.style()))); + let button_allocation_size = Vec2::splat(heading_font_height); + let button_shrink = (button_allocation_size - button_size) / 2.0; - let inner_height = title_bar_height_with_margin - window_frame.inner_margin.sum().y; + let collapse_atom_id = Id::new("__window_collapse_button"); + let close_atom_id = Id::new("__window_close_button"); - let item_spacing = ui.spacing().item_spacing; - let button_size = Vec2::splat(ui.spacing().icon_width.at_most(inner_height)); + let expanded = collapsing.openness(ui.ctx()) > 0.0; - 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) + if collapsible { + atoms.push_right(Atom::custom(collapse_atom_id, button_allocation_size)); + } - let title_galley = title.into_galley( - ui, - Some(crate::TextWrapMode::Extend), - f32::INFINITY, - TextStyle::Heading, - ); + atoms.push_right(Atom::grow()); - 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 + if !auto_sized + && !title.any_shrink() + && let Some(first_text) = title + .iter_mut() + .find(|a| matches!(a.kind, AtomKind::Text(..))) + { + first_text.shrink = true; + } + atoms.extend_right(title); + + atoms.push_right(Atom::grow()); + + if open.is_some() { + atoms.push_right(Atom::custom(close_atom_id, button_allocation_size)); + } + + let spacing = ui.spacing().item_spacing.x; + + let mut child_ui = ui.new_child(UiBuilder::new()); + + let mut layout = AtomLayout::new(atoms) + .gap(spacing) + .fallback_font(TextStyle::Heading) + .wrap_mode(TextWrapMode::Truncate); + + if expanded { + let min_width = if auto_sized { + // During auto size, the resize is essentially disabled, meaning we don't get an + // available_width we can rely on. Instead, check of large the content grew last frame + // and use that for sizing the title bar. Unfortunately this adds a frame delay. + ui.response().rect.width() } else { - left_pad + title_galley.size().x + left_pad + child_ui.available_width() }; - let min_inner_size = vec2(minimum_width, inner_height); - let min_rect = Rect::from_min_size(ui.min_rect().min, min_inner_size); - if false { - ui.debug_painter() - .debug_rect(min_rect, Color32::LIGHT_BLUE, "min_rect"); - } - - Self { - window_frame, - title_galley, - inner_rect: min_rect, // First estimate - will be refined later - } + layout = layout.min_size(Vec2::new(min_width, 0.0)); } - /// Finishes painting of the title bar when the window content size already known. - /// - /// # Parameters - /// - /// - `ui`: - /// - `outer_rect`: - /// - `content_response`: if `None`, window is collapsed at this frame, otherwise contains - /// a result of rendering the window content - /// - `open`: if `None`, no "Close" button will be rendered, otherwise renders and processes - /// the "Close" button and writes a `false` if window was closed - /// - `collapsing`: holds the current expanding state. Can be changed by double click on the - /// title if `collapsible` is `true` - /// - `collapsible`: if `true`, double click on the title bar will be handled for a change - /// of `collapsing` state - fn ui( - self, - ui: &mut Ui, - content_response: Option<&Response>, - open: Option<&mut bool>, - collapsing: &mut CollapsingState, - collapsible: bool, - ) { - let window_frame = self.window_frame; - let title_inner_rect = self.inner_rect; + let layout_response = layout.show(&mut child_ui); - if false { - ui.debug_painter() - .debug_rect(self.inner_rect, Color32::RED, "TitleBar"); - } + let mut title_click_rect = layout_response.response.rect + frame.total_margin(); - 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_ui(); - - ui.scope_builder(UiBuilder::new().max_rect(button_rect), |ui| { - collapsing.show_default_button_with_size(ui, button_size); - }); - } - - if let Some(open) = open { - // Add close button now that we know our full width: - if self.close_button_ui(ui).clicked() { - *open = false; - } - } - - let text_pos = - 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, - Arc::clone(&self.title_galley), - ui.visuals().text_color(), + // Collapse triangle icon + if collapsible && let Some(rect) = layout_response.rect(collapse_atom_id) { + let rect = rect.shrink2(button_shrink); + title_click_rect = title_click_rect.with_min_x(rect.max.x); + let icon_response = child_ui.interact( + rect, + child_ui.auto_id_with("collapse_button"), + Sense::click(), ); - - if let Some(content_response) = content_response { - // Paint separator between title and content: - let content_rect = content_response.rect; - if false { - ui.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); + icon_response.widget_info(|| { + WidgetInfo::labeled( + WidgetType::Button, + child_ui.is_enabled(), + if collapsing.is_open() { "Hide" } else { "Show" }, + ) + }); + if icon_response.clicked() { + collapsing.toggle(&child_ui); } + let openness = collapsing.openness(child_ui.ctx()); + crate::collapsing_header::paint_default_icon(&mut child_ui, openness, &icon_response); + } - // Don't cover the close- and collapse buttons: - let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0)); - - if false { - ui.debug_painter() - .debug_rect(double_click_rect, Color32::GREEN, "double_click_rect"); + // Close button + if let Some(open) = open + && let Some(rect) = layout_response.rect(close_atom_id) + { + let rect = rect.shrink2(button_shrink); + title_click_rect = title_click_rect.with_max_x(rect.min.x); + if close_button(&mut child_ui, rect).clicked() { + *open = false; } + } - let id = ui.unique_id().with("__window_title_bar"); - - if ui - .interact(double_click_rect, id, Sense::CLICK) + if collapsible + && child_ui + .interact( + title_click_rect, + child_ui.auto_id_with("window_title_click"), + Sense::click(), + ) .double_clicked() - && collapsible - { - collapsing.toggle(ui); - } + { + collapsing.toggle(&child_ui); } - /// Paints the "Close" button at the right side of the title bar - /// and processes clicks on it. - /// - /// 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 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) + child_ui.set_clip_rect(Rect::EVERYTHING); + let mut header_frame = frame.shadow(Shadow::NONE); + if active { + header_frame = header_frame.fill(ui.visuals().widgets.open.weak_bg_fill); } + if expanded { + header_frame.corner_radius.sw = 0; + header_frame.corner_radius.se = 0; + } + child_ui + .painter() + .set(shape_idx, header_frame.paint(layout_response.rect)); + + let mut advance_rect = child_ui.min_rect(); + + if auto_sized { + // We may not allocate in the horizontal direction as that would break auto sizing. + // Allocate a rect with 0 width: + advance_rect = advance_rect.with_max_x(advance_rect.min.x); + } + if expanded { + // Account for the margin of the title frame + the margin of the window contents + // - the default ui spacing egui would add on this call + advance_rect.max.y += frame.total_margin().bottom + frame.inner_margin.top as f32 + - child_ui.spacing().item_spacing.y; + } + + ui.advance_cursor_after_rect(advance_rect); + + layout_response.response } /// Paints the "Close" button of the window and processes clicks on it. diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 13e8175a0..8670398fa 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,4 +1,3 @@ -use emath::GuiRounding as _; use epaint::text::{IntoTag, TextFormat, VariationCoords}; use std::fmt::Formatter; use std::{borrow::Cow, sync::Arc}; @@ -692,22 +691,6 @@ impl WidgetText { self.map_rich_text(|text| text.background_color(background_color)) } - /// Returns a value rounded to [`emath::GUI_ROUNDING`]. - pub(crate) fn font_height(&self, fonts: &mut epaint::FontsView<'_>, style: &Style) -> f32 { - match self { - Self::Text(_) => fonts.row_height(&FontSelection::Default.resolve(style)), - Self::RichText(text) => text.font_height(fonts, style), - Self::LayoutJob(job) => job.font_height(fonts), - Self::Galley(galley) => { - if let Some(placed_row) = galley.rows.first() { - placed_row.height().round_ui() - } else { - galley.size().y.round_ui() - } - } - } - } - pub fn into_layout_job( self, style: &Style, diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png b/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png index 96d7706ee..aa52b8bda 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Bézier Curve.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10d64e017d1d0eba736a4471d28b1602a0cb69d8e2ab53f4ee604b01c9343116 -size 32475 +oid sha256:7c777cd6b36219b92c88ebf5987f9239a5750d25be4a87a58841bcf6db259599 +size 32422 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Clipboard Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Clipboard Test.png index c9a05a629..faadcbef3 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Clipboard Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Clipboard Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab0d730ce0cf5f1d79947601def4f60c0a015e23a5dcd780df65c7ddc7ae7156 -size 27194 +oid sha256:157996b151dd0f450abca6a9cbad4c8237e236300546521e50f95637d9a89c05 +size 27206 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 6134dfad9..12b0c24d3 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:6fccbda741a056ae30a7bfd7497f7b0adfb337bdb885d247fbdfef43cc24b54c -size 26948 +oid sha256:2d941c979def6f30fd227c144d20c7f138f9c06c3338d563ac075c5e17e9b795 +size 26911 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 d43e856aa..17f557c8d 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:26d247655398bae33c724ad3c3bdcab330d194093b07442708d5069e256f636b -size 76542 +oid sha256:6df3c0a298d48a5f2e1b36207909e20ea545a72a97461a3ae0792d21e0554f93 +size 76567 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png index 60d3993ef..2954609c5 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Cursor Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3389491f9f5c54cfc2e295abe76ad53f223c31e9489e1d07a8323cf14fcf37d -size 62628 +oid sha256:e1aa359d3d2767ad0d0ecb635152371b50b3d551f486fb45c6f7fa96969bc8f1 +size 62291 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 c59717683..ec34487f1 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:6e42e801758bb9e6130a4e94bd0857d6f254e68a7dedebec5af5cb7f7d896068 -size 27822 +oid sha256:05527c073b1ee2f6a15052a9097d1ad515331ff32d572cf510086f9ebb7a7bbc +size 27253 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 f3c65337f..d5f1d13c5 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:41e68fd3a679e2a6e3dd81129de5aa1ded3a8f24cc7b1f2ba0b876240d309d9b -size 21019 +oid sha256:9aa1b0f9a1ff5167b7f66894e6d38112e73bd3b6a692b4b468fb6a75717d01bd +size 21009 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 6a050e0e8..99850aa43 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:3db502ec416b322e0f98e9737faa52d5d2fd308d47649710fffa0b0bc5996f52 -size 10783 +oid sha256:04f66c848c6f24607d19b809f411a9287ad45067b18da40cf48a77ff65ae9d91 +size 10875 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 b187b4cfd..449c2eac0 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:c39ae3420fe01d696a032d8d052c405c5623a9208fc673f5cf09188b1e6a539b -size 115447 +oid sha256:157424625dbb855e116a29a008172f29697a37b03512ef06a2e92e502d5e4128 +size 101816 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index 658cb0922..bacae8846 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:19322248e8335301b2ce31fc1f1352993a374dda945d227784fecea2ee831761 -size 25088 +oid sha256:79ee28a9c0d8ef80d53560584312a33569f668e0a49f6ad5d277ffef507f2818 +size 25116 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Grid Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Grid Test.png index f6f9a3cda..d7a4c8e1e 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Grid Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Grid Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98f8865d866a6f28ae3e3a16c815770ed691a031b1d06f7d3662a7e94f564606 -size 99318 +oid sha256:ff02fa99b92059d7b4f8286b8a2fd2e27a7f8f005328da97f0b9786aac9bbb17 +size 99316 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index ab7de6c2d..b81ed23e0 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:39208c3c2c95f68fb37880b011f866bedd8dbccee33d163a725cb2a5cc6bb1b3 -size 18290 +oid sha256:165821b21d980c98612a45cf54d9ca3578dec023ab0e2194d2166db087019cc3 +size 18293 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png index b899d7356..e9aebce92 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/ID Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d251ffd6c34e4ffa7b5de7fe5ae57a2d8f48ba7c2e03711da2c3f1a3fd84648c -size 113998 +oid sha256:2027f21fdb132542e01fc592d3150aa876cf2ebb4a4268b8c170ff4f523657f0 +size 114074 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png index 5b3a63aeb..9b1a5e30c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Input Event History.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d959e17ee5c8a32534ab98b6f08db884b2fbf25476d8dc2dc90edc79bd87ea4 -size 25821 +oid sha256:03c4660ce72440f5ebf3db05b4bce1e8caf1899907723d30699d9d65d442f562 +size 24540 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png index 85c205be0..c1627ebba 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c923f523cc77d678929f294b360f60b9f546ddec66d5317ad0eb44bd61a5f927 -size 51733 +oid sha256:3e2f29678c41e45bedc916dbe845f05c343bfad5528a4143c32cac6c3dee41e7 +size 50512 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 b7d365f3a..7653e9170 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:c9c98d7bfa08e22e217dd9e7031cc49e4b4486f1a9fdd223bd122be07af72365 -size 22550 +oid sha256:d70cd39499c8f9e0edf689b642ef7cc98115c751bf787305f92323f42aae3fd4 +size 21814 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png index a4a82635b..710b51b78 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Layout Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68afa93605427e12fe527f3ca9613095664b4983f1f585a60f14bc2370c0a1f2 -size 47224 +oid sha256:7705ef738605c31bbf067363f5544ce57e698ab869feb8a88e4be4eabf4de7b6 +size 47087 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Manual Layout Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Manual Layout Test.png index b0cb0c27b..89e4a647b 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Manual Layout Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Manual Layout Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33959851f1bd2386fcca8fb5700133d14d90db5a8f783fbfaa9f3aebb7b0d5b2 -size 23119 +oid sha256:345ab9ac3586c3cb9d3b02d46c590a81c7bc31778a3b631874520e57b6694076 +size 22994 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 28e6d1845..817eebf8f 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:7b918565d66594fe53ed62bb14e357d3489335f3d037ccb088f198d944eb367d -size 65307 +oid sha256:6e00e1dd95278a003c383f5a3f16d25ee4943073171d862db50cb17e90e421bc +size 60272 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png index 7f0357df1..32dc226c9 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:f8c3c2912a3a11892e65f94792c77c79400a81d8c913109d39e8ce12f5b095c6 -size 33503 +oid sha256:58d56dce5f1e9766dbfdca4ea98fb71f7062b23a0ee4b08ce7f76056513aa9a5 +size 33440 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 59609d76a..b3c10c6a2 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:0c2c7b0d4913b59ef932f6d6349ddab5cc8619b2e8e9a8b5eb3e055a62e6ed60 -size 38382 +oid sha256:353d92d99e9c2350267a43bff7bb9100d3109c82f8d54f9f7a9e3a312ea0c4e6 +size 35378 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index 4d4b5c355..9a5859bf5 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:9567f56f2dd030608798347e1ab755d95730ba5c5dd1721f1c61147be7216e87 -size 18304 +oid sha256:218cce13bd96c97aca6a529ba49baaeda5a126cf657e6af1788646cb845725d0 +size 17686 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 553f8aa0b..2437ae223 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:d19d694e6a70ab6acb45668b391751e82f7beab19f9918e23821c667e8cc9cdb -size 249733 +oid sha256:fc1d270e0171cc055fe99579d3f7ab52c63783c793c59aaf8c82027bf1dd3ab8 +size 216484 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png index 6a18dc9e5..c452f78da 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Popups.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Popups.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e1c73e28020371429b3e03d540f72dbf886fd40f0ba08bb194e868bbb3c95ff -size 57230 +oid sha256:3edbe6debf700364949ef481ba1e8b2319db624ddc316570b6180636dcd88935 +size 57262 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png b/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png index 92d19c470..db3c561ce 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/SVG Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2facd3881e6b107a0dcce9d6e00008a3c9b0f31ad270c35357a87e487180f56 -size 19814 +oid sha256:ebe84ae10b084df7d8373bc4499c5d50a1446a6b707e8220c786a198341d76c8 +size 19189 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index de52524ac..16b02f86d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ad81eb762150360368a97858ef30bb0d5aff72e71743fa40c3fd4d70ec84cdc -size 33400 +oid sha256:141271330d4cef517c4ec2c4c2c41306f8b5de386738381a0e4446fa89df7cdf +size 30450 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png index 14603f2f2..603404a9b 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:c22130891755ac095d73e0494f11ee8e89d0fd0c31a321d3afb969648ece11ef -size 23675 +oid sha256:be289c58296a7656c8550d25ff92b8cd0b03c5c9c527b9ca4a38acda8bbe3ac1 +size 23807 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 525f23435..c9b177a18 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:465f33e53ebe15b776e2d6d0710f13f2453f00bab1f6ef4319b3491f1d1d3a26 -size 173487 +oid sha256:10e4658168d6a93779463ebb81520f821cbf6eac59391c60d30f5d40ebdf910d +size 152623 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index 34ec70a65..0f2836dd1 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:001f7a1310ddf37be4d9a7f56a95a3079f713b741824a348544561bb16c291fa -size 118614 +oid sha256:ad81eeb593663cb26295e7fc51987b85a7084b32504e26364f54f9c2129cb790 +size 118048 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index b9dea21f0..9473551aa 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:8105f7c2b519716cc0a45dffd5f08980f53f35c6c6b788592e1d82506cdacccc -size 26665 +oid sha256:3c8aaf89b6d5dad6ae7788b58f642f5d80384777ca80dc3819f1aefc5867b148 +size 26086 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index 909b766b9..d64d3fca7 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:a9e83d5e83e3003830f7f719b02dd93273733a9b72d388aa42083387d02c1a20 -size 76310 +oid sha256:66f35217b0becce12e251c4e7063c31ac92e080340a0318891771fdff54593db +size 67388 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png index b1ed6bcde..ad87167a3 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tessellation Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d46c87417c49c8462fac6c488e46b1482bbc75f70fe8f7af8391f0d5d28dac3 -size 70271 +oid sha256:97452108d7809775756f1a229644076f5b4c75a6a6d8b8edc082c2448ae35e94 +size 60701 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 0bd5b9ceb..bda94ab51 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:661570ed4bdf24d52ab049e7d3cf22ef4d50542ee5486d133e0a618a6146da42 -size 98582 +oid sha256:29e23e0e45a6e557e569358c89a34dc768ff0b33b47112f864ae8ed119ac6e6e +size 81119 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index 839a15faa..d5513b0ec 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:94c4af5715992f4dbb5bbec6ce67eec1e2f66cfc078a3e704ec386bdb482cac4 -size 30064 +oid sha256:7ad81306e153798d72724e37f23a4037bf986f98e0ecbe3e8d294d23a92ef77c +size 29975 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index bea2050ab..b3447e66a 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:9d69b9a12e777ee559a481aec012935a5bfb2ca8b0d48725ecd33e7f0880b2b8 -size 64273 +oid sha256:dde445e82732c45f84acbfa83761348b9b89dd2a4fec500ba5124b0c1e58dd29 +size 63060 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 728dc59b6..a605d136d 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:f6c020860fe9cb7503cea548afbf298ebb9dc620133870b528fe7508d04150c8 -size 13691 +oid sha256:789700adbf20ec5508abdfc95d843197715fbec9f35051799ea18e9927207b30 +size 13650 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 951a6fd41..ecc93a7d4 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:d033935ca18ebee7e3c35629233cc3e3a73766ac8c0627fcdd8a12660eed703c -size 35873 +oid sha256:ab0a8201038b2b066c5aff1fd1a35bc7aed95e74cf50c293eee4a76d66623822 +size 35171 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png index 933343f25..adc67e7ab 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Resize Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77c9058b036770a644f1bfcf9ed1ba7a29a7c98107b1823474c55ccc2880e9e7 -size 485880 +oid sha256:f9c8eecaedce2bbcbdc4b7975a349b9f986463f189041c7ad6bed64809f18bcd +size 445367 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index d1abde4fa..b2d76916a 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:8bd53b56322123940496700cbd2a73e336fd80eaf49dcb19a958888d66570ddb -size 47159 +oid sha256:01493c64acb62a24d19c69579a8f8cb8e080a60747792bbbb98e1cccdfe9214f +size 47175 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index 8f6976ea5..0995b1814 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:d15954e6183558141e05e1b566ec4a794d372503baf436da2a8c8c67299056f1 -size 48238 +oid sha256:4b069ad508fd3a518df3a39e1740d1786c4af4c3c4a479db04c00f701980efa8 +size 48277 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index ab4a99a12..182b7336d 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:1738ecf660979888c70b1046dd759fe0b082e4958814fb19077c4fed2fb4bdef -size 44338 +oid sha256:e92e72c651e0ad2c0fc38008d24d9ddf28a70e3b35b26898af78e74028e2c251 +size 44357 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 cbac0d342..7d1519d01 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:dae7239dc065068147387eb313afdbaa3f0df8b81d060dbbc29f5a6a31ad76c8 -size 44309 +oid sha256:1a5f7bc46c7baf62b559458b3596e386d66bf2281b7c35e5fdb6c5c4a64569f2 +size 44347 diff --git a/crates/egui_kittest/tests/regression_tests.rs b/crates/egui_kittest/tests/regression_tests.rs index e6f056da4..d68920974 100644 --- a/crates/egui_kittest/tests/regression_tests.rs +++ b/crates/egui_kittest/tests/regression_tests.rs @@ -1,6 +1,7 @@ use egui::accesskit::{self, Role}; use egui::{ - Button, ComboBox, Image, Label, Modifiers, Popup, Pos2, Rect, Vec2, Widget as _, Window, + Align2, Button, ComboBox, FontId, Image, Label, Modifiers, Popup, Pos2, Rect, Stroke, + StrokeKind, Vec2, Widget as _, Window, }; #[cfg(all(feature = "wgpu", feature = "snapshot"))] use egui_kittest::SnapshotResults; @@ -511,3 +512,91 @@ fn window_resize_wraps_to_content_min_width() { window past the non-wrapping label's natural width" ); } + +/// Ensure that the size passed to window is actually treated as outer size (including +/// margins and borders). +#[test] +fn window_fixed_size_is_outer_size() { + use egui::{Color32, Frame, Margin, Pos2, Shape}; + + let outer_pos = Pos2::new(50.0, 50.0); + let outer_size = Vec2::new(300.0, 200.0); + let outer_margin = Margin::same(10); + let expected_rect = Rect::from_min_size(outer_pos, outer_size); + + let mut harness = Harness::builder() + .with_size(Vec2::new(800.0, 600.0)) + .build_ui(move |ui| { + let frame = Frame::window(ui.style()).outer_margin(outer_margin); + Window::new("size_test") + .frame(frame) + .fixed_pos(outer_pos) + .fixed_size(outer_size) + .show(ui.ctx(), |ui| { + // Fill the available space so `Resize` doesn't auto-shrink the window + // below the requested fixed size. + ui.allocate_space(ui.available_size()); + }); + + // Paint a debug rect on top of everything that marks the expected outer + // window rect. In the snapshot this should line up exactly with the + // painted window frame. + let painter = ui.ctx().debug_painter(); + painter.rect_stroke( + expected_rect, + 0.0, + Stroke::new(2.0, Color32::RED), + StrokeKind::Outside, + ); + painter.text( + expected_rect.left_top() + Vec2::new(0.0, -4.0), + Align2::LEFT_BOTTOM, + "should perfectly match the outer window size/position", + FontId::default(), + Color32::RED, + ); + + // Also paint the expected *visible frame* rect (outer rect shrunk by the + // frame's outer_margin). In the snapshot this should line up exactly with + // the painted window frame. + let expected_frame_rect = expected_rect - outer_margin; + painter.debug_rect( + expected_frame_rect, + Color32::GREEN, + "should perfectly match the painted window frame", + ); + }); + + harness.run(); + + #[cfg(all(feature = "wgpu", feature = "snapshot"))] + harness.snapshot("window_outer_size"); + + fn collect_filled_rect_sizes(shape: &Shape, out: &mut Vec) { + match shape { + // Skip stroke-only rects (fill == TRANSPARENT), so the debug overlay + // doesn't trivially satisfy the size check. + Shape::Rect(r) if r.fill != Color32::TRANSPARENT => out.push(r.rect.size()), + Shape::Vec(v) => v.iter().for_each(|s| collect_filled_rect_sizes(s, out)), + _ => {} + } + } + + let mut sizes = Vec::new(); + for clipped in &harness.output().shapes { + collect_filled_rect_sizes(&clipped.shape, &mut sizes); + } + + // The shape will have the inner size + let painted_size = outer_size - outer_margin.sum(); + let found = sizes + .iter() + .any(|s| (s.x - painted_size.x).abs() < 0.5 && (s.y - painted_size.y).abs() < 0.5); + + assert!( + found, + "expected a filled RectShape with size {painted_size:?} (outer size {outer_size:?} \ + minus outer margin {outer_margin:?}) in the paint output, but no painted rect matched. \ + Found filled-rect sizes: {sizes:?}" + ); +} diff --git a/crates/egui_kittest/tests/snapshots/window_outer_size.png b/crates/egui_kittest/tests/snapshots/window_outer_size.png new file mode 100644 index 000000000..bae4f22cb --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/window_outer_size.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cba6fbd64df18b2a41635af59c1f50d1352b4de8ddefd7d5389a4f9e518b6c86 +size 23543