mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 14:49:06 -04:00
Atom support for egui::Window Titlebar (#8154)
* part of #7264 * based on https://github.com/emilk/egui/pull/8152 The resize fix allows use to really simplify how the Window Titlebar is rendered. Previously it was using some complex flow to calculate and allocate the height first and then render it later once we knew the windows final width. Since now windows can't shrink past their minimum content widths, I can just show the titlebar inline with the regular content, just outside of the `Resize` container so that it is always visible. This does change what the size of a window means. Before, size was just the size of the contents, while now size (e.g. via min_height) will include the Frames margin and outline, title bar and the contents. Also, the window label now truncates as you shrink the window (meaning windows can now be smaller than their label allows). --------- Co-authored-by: lucasmerlin <8009393+lucasmerlin@users.noreply.github.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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<Frame>,
|
||||
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<WidgetText>) -> 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<Vec2>) -> 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<Vec2>) -> 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<Vec2>) -> 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<Vec2>) -> 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<Galley>,
|
||||
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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:10d64e017d1d0eba736a4471d28b1602a0cb69d8e2ab53f4ee604b01c9343116
|
||||
size 32475
|
||||
oid sha256:7c777cd6b36219b92c88ebf5987f9239a5750d25be4a87a58841bcf6db259599
|
||||
size 32422
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ab0d730ce0cf5f1d79947601def4f60c0a015e23a5dcd780df65c7ddc7ae7156
|
||||
size 27194
|
||||
oid sha256:157996b151dd0f450abca6a9cbad4c8237e236300546521e50f95637d9a89c05
|
||||
size 27206
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6fccbda741a056ae30a7bfd7497f7b0adfb337bdb885d247fbdfef43cc24b54c
|
||||
size 26948
|
||||
oid sha256:2d941c979def6f30fd227c144d20c7f138f9c06c3338d563ac075c5e17e9b795
|
||||
size 26911
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26d247655398bae33c724ad3c3bdcab330d194093b07442708d5069e256f636b
|
||||
size 76542
|
||||
oid sha256:6df3c0a298d48a5f2e1b36207909e20ea545a72a97461a3ae0792d21e0554f93
|
||||
size 76567
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e3389491f9f5c54cfc2e295abe76ad53f223c31e9489e1d07a8323cf14fcf37d
|
||||
size 62628
|
||||
oid sha256:e1aa359d3d2767ad0d0ecb635152371b50b3d551f486fb45c6f7fa96969bc8f1
|
||||
size 62291
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6e42e801758bb9e6130a4e94bd0857d6f254e68a7dedebec5af5cb7f7d896068
|
||||
size 27822
|
||||
oid sha256:05527c073b1ee2f6a15052a9097d1ad515331ff32d572cf510086f9ebb7a7bbc
|
||||
size 27253
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:41e68fd3a679e2a6e3dd81129de5aa1ded3a8f24cc7b1f2ba0b876240d309d9b
|
||||
size 21019
|
||||
oid sha256:9aa1b0f9a1ff5167b7f66894e6d38112e73bd3b6a692b4b468fb6a75717d01bd
|
||||
size 21009
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3db502ec416b322e0f98e9737faa52d5d2fd308d47649710fffa0b0bc5996f52
|
||||
size 10783
|
||||
oid sha256:04f66c848c6f24607d19b809f411a9287ad45067b18da40cf48a77ff65ae9d91
|
||||
size 10875
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c39ae3420fe01d696a032d8d052c405c5623a9208fc673f5cf09188b1e6a539b
|
||||
size 115447
|
||||
oid sha256:157424625dbb855e116a29a008172f29697a37b03512ef06a2e92e502d5e4128
|
||||
size 101816
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:19322248e8335301b2ce31fc1f1352993a374dda945d227784fecea2ee831761
|
||||
size 25088
|
||||
oid sha256:79ee28a9c0d8ef80d53560584312a33569f668e0a49f6ad5d277ffef507f2818
|
||||
size 25116
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:98f8865d866a6f28ae3e3a16c815770ed691a031b1d06f7d3662a7e94f564606
|
||||
size 99318
|
||||
oid sha256:ff02fa99b92059d7b4f8286b8a2fd2e27a7f8f005328da97f0b9786aac9bbb17
|
||||
size 99316
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:39208c3c2c95f68fb37880b011f866bedd8dbccee33d163a725cb2a5cc6bb1b3
|
||||
size 18290
|
||||
oid sha256:165821b21d980c98612a45cf54d9ca3578dec023ab0e2194d2166db087019cc3
|
||||
size 18293
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d251ffd6c34e4ffa7b5de7fe5ae57a2d8f48ba7c2e03711da2c3f1a3fd84648c
|
||||
size 113998
|
||||
oid sha256:2027f21fdb132542e01fc592d3150aa876cf2ebb4a4268b8c170ff4f523657f0
|
||||
size 114074
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4d959e17ee5c8a32534ab98b6f08db884b2fbf25476d8dc2dc90edc79bd87ea4
|
||||
size 25821
|
||||
oid sha256:03c4660ce72440f5ebf3db05b4bce1e8caf1899907723d30699d9d65d442f562
|
||||
size 24540
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c923f523cc77d678929f294b360f60b9f546ddec66d5317ad0eb44bd61a5f927
|
||||
size 51733
|
||||
oid sha256:3e2f29678c41e45bedc916dbe845f05c343bfad5528a4143c32cac6c3dee41e7
|
||||
size 50512
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c9c98d7bfa08e22e217dd9e7031cc49e4b4486f1a9fdd223bd122be07af72365
|
||||
size 22550
|
||||
oid sha256:d70cd39499c8f9e0edf689b642ef7cc98115c751bf787305f92323f42aae3fd4
|
||||
size 21814
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:68afa93605427e12fe527f3ca9613095664b4983f1f585a60f14bc2370c0a1f2
|
||||
size 47224
|
||||
oid sha256:7705ef738605c31bbf067363f5544ce57e698ab869feb8a88e4be4eabf4de7b6
|
||||
size 47087
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:33959851f1bd2386fcca8fb5700133d14d90db5a8f783fbfaa9f3aebb7b0d5b2
|
||||
size 23119
|
||||
oid sha256:345ab9ac3586c3cb9d3b02d46c590a81c7bc31778a3b631874520e57b6694076
|
||||
size 22994
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7b918565d66594fe53ed62bb14e357d3489335f3d037ccb088f198d944eb367d
|
||||
size 65307
|
||||
oid sha256:6e00e1dd95278a003c383f5a3f16d25ee4943073171d862db50cb17e90e421bc
|
||||
size 60272
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f8c3c2912a3a11892e65f94792c77c79400a81d8c913109d39e8ce12f5b095c6
|
||||
size 33503
|
||||
oid sha256:58d56dce5f1e9766dbfdca4ea98fb71f7062b23a0ee4b08ce7f76056513aa9a5
|
||||
size 33440
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0c2c7b0d4913b59ef932f6d6349ddab5cc8619b2e8e9a8b5eb3e055a62e6ed60
|
||||
size 38382
|
||||
oid sha256:353d92d99e9c2350267a43bff7bb9100d3109c82f8d54f9f7a9e3a312ea0c4e6
|
||||
size 35378
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9567f56f2dd030608798347e1ab755d95730ba5c5dd1721f1c61147be7216e87
|
||||
size 18304
|
||||
oid sha256:218cce13bd96c97aca6a529ba49baaeda5a126cf657e6af1788646cb845725d0
|
||||
size 17686
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d19d694e6a70ab6acb45668b391751e82f7beab19f9918e23821c667e8cc9cdb
|
||||
size 249733
|
||||
oid sha256:fc1d270e0171cc055fe99579d3f7ab52c63783c793c59aaf8c82027bf1dd3ab8
|
||||
size 216484
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9e1c73e28020371429b3e03d540f72dbf886fd40f0ba08bb194e868bbb3c95ff
|
||||
size 57230
|
||||
oid sha256:3edbe6debf700364949ef481ba1e8b2319db624ddc316570b6180636dcd88935
|
||||
size 57262
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c2facd3881e6b107a0dcce9d6e00008a3c9b0f31ad270c35357a87e487180f56
|
||||
size 19814
|
||||
oid sha256:ebe84ae10b084df7d8373bc4499c5d50a1446a6b707e8220c786a198341d76c8
|
||||
size 19189
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0ad81eb762150360368a97858ef30bb0d5aff72e71743fa40c3fd4d70ec84cdc
|
||||
size 33400
|
||||
oid sha256:141271330d4cef517c4ec2c4c2c41306f8b5de386738381a0e4446fa89df7cdf
|
||||
size 30450
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c22130891755ac095d73e0494f11ee8e89d0fd0c31a321d3afb969648ece11ef
|
||||
size 23675
|
||||
oid sha256:be289c58296a7656c8550d25ff92b8cd0b03c5c9c527b9ca4a38acda8bbe3ac1
|
||||
size 23807
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:465f33e53ebe15b776e2d6d0710f13f2453f00bab1f6ef4319b3491f1d1d3a26
|
||||
size 173487
|
||||
oid sha256:10e4658168d6a93779463ebb81520f821cbf6eac59391c60d30f5d40ebdf910d
|
||||
size 152623
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:001f7a1310ddf37be4d9a7f56a95a3079f713b741824a348544561bb16c291fa
|
||||
size 118614
|
||||
oid sha256:ad81eeb593663cb26295e7fc51987b85a7084b32504e26364f54f9c2129cb790
|
||||
size 118048
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8105f7c2b519716cc0a45dffd5f08980f53f35c6c6b788592e1d82506cdacccc
|
||||
size 26665
|
||||
oid sha256:3c8aaf89b6d5dad6ae7788b58f642f5d80384777ca80dc3819f1aefc5867b148
|
||||
size 26086
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a9e83d5e83e3003830f7f719b02dd93273733a9b72d388aa42083387d02c1a20
|
||||
size 76310
|
||||
oid sha256:66f35217b0becce12e251c4e7063c31ac92e080340a0318891771fdff54593db
|
||||
size 67388
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3d46c87417c49c8462fac6c488e46b1482bbc75f70fe8f7af8391f0d5d28dac3
|
||||
size 70271
|
||||
oid sha256:97452108d7809775756f1a229644076f5b4c75a6a6d8b8edc082c2448ae35e94
|
||||
size 60701
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:661570ed4bdf24d52ab049e7d3cf22ef4d50542ee5486d133e0a618a6146da42
|
||||
size 98582
|
||||
oid sha256:29e23e0e45a6e557e569358c89a34dc768ff0b33b47112f864ae8ed119ac6e6e
|
||||
size 81119
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:94c4af5715992f4dbb5bbec6ce67eec1e2f66cfc078a3e704ec386bdb482cac4
|
||||
size 30064
|
||||
oid sha256:7ad81306e153798d72724e37f23a4037bf986f98e0ecbe3e8d294d23a92ef77c
|
||||
size 29975
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9d69b9a12e777ee559a481aec012935a5bfb2ca8b0d48725ecd33e7f0880b2b8
|
||||
size 64273
|
||||
oid sha256:dde445e82732c45f84acbfa83761348b9b89dd2a4fec500ba5124b0c1e58dd29
|
||||
size 63060
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f6c020860fe9cb7503cea548afbf298ebb9dc620133870b528fe7508d04150c8
|
||||
size 13691
|
||||
oid sha256:789700adbf20ec5508abdfc95d843197715fbec9f35051799ea18e9927207b30
|
||||
size 13650
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d033935ca18ebee7e3c35629233cc3e3a73766ac8c0627fcdd8a12660eed703c
|
||||
size 35873
|
||||
oid sha256:ab0a8201038b2b066c5aff1fd1a35bc7aed95e74cf50c293eee4a76d66623822
|
||||
size 35171
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:77c9058b036770a644f1bfcf9ed1ba7a29a7c98107b1823474c55ccc2880e9e7
|
||||
size 485880
|
||||
oid sha256:f9c8eecaedce2bbcbdc4b7975a349b9f986463f189041c7ad6bed64809f18bcd
|
||||
size 445367
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8bd53b56322123940496700cbd2a73e336fd80eaf49dcb19a958888d66570ddb
|
||||
size 47159
|
||||
oid sha256:01493c64acb62a24d19c69579a8f8cb8e080a60747792bbbb98e1cccdfe9214f
|
||||
size 47175
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d15954e6183558141e05e1b566ec4a794d372503baf436da2a8c8c67299056f1
|
||||
size 48238
|
||||
oid sha256:4b069ad508fd3a518df3a39e1740d1786c4af4c3c4a479db04c00f701980efa8
|
||||
size 48277
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1738ecf660979888c70b1046dd759fe0b082e4958814fb19077c4fed2fb4bdef
|
||||
size 44338
|
||||
oid sha256:e92e72c651e0ad2c0fc38008d24d9ddf28a70e3b35b26898af78e74028e2c251
|
||||
size 44357
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dae7239dc065068147387eb313afdbaa3f0df8b81d060dbbc29f5a6a31ad76c8
|
||||
size 44309
|
||||
oid sha256:1a5f7bc46c7baf62b559458b3596e386d66bf2281b7c35e5fdb6c5c4a64569f2
|
||||
size 44347
|
||||
|
||||
@@ -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<Vec2>) {
|
||||
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:?}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cba6fbd64df18b2a41635af59c1f50d1352b4de8ddefd7d5389a4f9e518b6c86
|
||||
size 23543
|
||||
Reference in New Issue
Block a user