mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40: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;
|
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
|
// The size available for the content
|
||||||
let available_inner_size = available_size - frame.total_margin().sum();
|
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 {
|
impl Prepared {
|
||||||
pub(crate) fn state(&self) -> &AreaState {
|
|
||||||
&self.state
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn state_mut(&mut self) -> &mut AreaState {
|
pub(crate) fn state_mut(&mut self) -> &mut AreaState {
|
||||||
&mut self.state
|
&mut self.state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::hash::Hash;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle,
|
Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle,
|
||||||
TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetText, WidgetType,
|
TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, WidgetInfo, WidgetText, WidgetType, emath,
|
||||||
emath, epaint, pos2, remap, remap_clamp, vec2,
|
epaint, pos2, remap, remap_clamp, vec2,
|
||||||
};
|
};
|
||||||
use emath::GuiRounding as _;
|
use emath::GuiRounding as _;
|
||||||
use epaint::{Shape, StrokeKind};
|
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.
|
/// Will toggle when clicked, etc.
|
||||||
fn show_default_button_indented(&mut self, ui: &mut Ui) -> Response {
|
fn show_default_button_indented(&mut self, ui: &mut Ui) -> Response {
|
||||||
self.show_button_indented(ui, paint_default_icon)
|
self.show_button_indented(ui, paint_default_icon)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ pub struct Resize {
|
|||||||
pub(crate) min_size: Vec2,
|
pub(crate) min_size: Vec2,
|
||||||
pub(crate) max_size: Vec2,
|
pub(crate) max_size: Vec2,
|
||||||
|
|
||||||
default_size: Vec2,
|
pub(crate) default_size: Vec2,
|
||||||
|
|
||||||
with_stroke: bool,
|
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.
|
// 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 emath::GuiRounding as _;
|
||||||
use epaint::{CornerRadiusF32, RectShape};
|
use epaint::CornerRadiusF32;
|
||||||
|
|
||||||
use crate::collapsing_header::CollapsingState;
|
use crate::collapsing_header::CollapsingState;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
@@ -33,9 +31,9 @@ use super::{Area, Frame, Resize, ScrollArea, area, resize};
|
|||||||
/// Note that this is NOT a native OS window.
|
/// Note that this is NOT a native OS window.
|
||||||
/// To create a new native OS window, use [`crate::Context::show_viewport_deferred`].
|
/// To create a new native OS window, use [`crate::Context::show_viewport_deferred`].
|
||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct Window<'open> {
|
pub struct Window<'a> {
|
||||||
title: WidgetText,
|
title: Atoms<'a>,
|
||||||
open: Option<&'open mut bool>,
|
open: Option<&'a mut bool>,
|
||||||
area: Area,
|
area: Area,
|
||||||
frame: Option<Frame>,
|
frame: Option<Frame>,
|
||||||
resize: Resize,
|
resize: Resize,
|
||||||
@@ -44,14 +42,15 @@ pub struct Window<'open> {
|
|||||||
default_open: bool,
|
default_open: bool,
|
||||||
with_title_bar: bool,
|
with_title_bar: bool,
|
||||||
fade_out: 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.
|
/// 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)`.
|
/// 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.
|
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
|
||||||
pub fn new(title: impl Into<WidgetText>) -> Self {
|
pub fn new(title: impl IntoAtoms<'a>) -> Self {
|
||||||
let title = title.into().fallback_text_style(TextStyle::Heading);
|
let title: Atoms<'_> = title.into_atoms();
|
||||||
let area = Area::new(Id::new(title.text())).kind(UiKind::Window);
|
let area = Area::new(Id::new(title.text())).kind(UiKind::Window);
|
||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
@@ -61,12 +60,13 @@ impl<'open> Window<'open> {
|
|||||||
resize: Resize::default()
|
resize: Resize::default()
|
||||||
.with_stroke(false)
|
.with_stroke(false)
|
||||||
.min_size([96.0, 32.0])
|
.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),
|
scroll: ScrollArea::neither().auto_shrink(false),
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
default_open: true,
|
default_open: true,
|
||||||
with_title_bar: true,
|
with_title_bar: true,
|
||||||
fade_out: 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 `*open == true`, the window will have a close button.
|
||||||
/// * If the close button is pressed, `*open` will be set to `false`.
|
/// * If the close button is pressed, `*open` will be set to `false`.
|
||||||
#[inline]
|
#[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.open = Some(open);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -213,6 +213,9 @@ impl<'open> Window<'open> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set minimum size of the window, equivalent to calling both `min_width` and `min_height`.
|
/// 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]
|
#[inline]
|
||||||
pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
|
pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
|
||||||
self.resize = self.resize.min_size(min_size);
|
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`.
|
/// 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]
|
#[inline]
|
||||||
pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
|
pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
|
||||||
self.resize = self.resize.max_size(max_size);
|
self.resize = self.resize.max_size(max_size);
|
||||||
@@ -320,6 +326,9 @@ impl<'open> Window<'open> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set initial size of the window.
|
/// Set initial size of the window.
|
||||||
|
///
|
||||||
|
/// The size refers to the *outer* window size, including frame margins, stroke,
|
||||||
|
/// and the title bar.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
|
pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
|
||||||
let default_size: Vec2 = default_size.into();
|
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.
|
/// 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]
|
#[inline]
|
||||||
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
|
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
|
||||||
self.resize = self.resize.fixed_size(size);
|
self.resize = self.resize.fixed_size(size);
|
||||||
@@ -399,6 +411,7 @@ impl<'open> Window<'open> {
|
|||||||
pub fn auto_sized(mut self) -> Self {
|
pub fn auto_sized(mut self) -> Self {
|
||||||
self.resize = self.resize.auto_sized();
|
self.resize = self.resize.auto_sized();
|
||||||
self.scroll = ScrollArea::neither();
|
self.scroll = ScrollArea::neither();
|
||||||
|
self.auto_sized = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,13 +486,12 @@ impl Window<'_> {
|
|||||||
default_open,
|
default_open,
|
||||||
with_title_bar,
|
with_title_bar,
|
||||||
fade_out,
|
fade_out,
|
||||||
|
auto_sized,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let style = ctx.global_style();
|
let style = ctx.global_style();
|
||||||
|
|
||||||
let header_color =
|
let window_frame = frame.unwrap_or_else(|| Frame::window(&style));
|
||||||
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 is_explicitly_closed = matches!(open, Some(false));
|
let is_explicitly_closed = matches!(open, Some(false));
|
||||||
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
|
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 on_top = Some(area_layer_id) == ctx.top_layer_id();
|
||||||
let mut area = area.begin(ctx);
|
let mut area = area.begin(ctx);
|
||||||
|
|
||||||
area.with_widget_info(|| WidgetInfo::labeled(WidgetType::Window, true, title.text()));
|
area.with_widget_info(|| {
|
||||||
|
WidgetInfo::labeled(
|
||||||
// Calculate roughly how much larger the full window inner size is compared to the content rect
|
WidgetType::Window,
|
||||||
let (title_bar_height_with_margin, title_content_spacing) = if with_title_bar {
|
true,
|
||||||
let title_bar_inner_height = ctx
|
title.text().as_deref().unwrap_or(""),
|
||||||
.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)
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Prevent window from becoming larger than the constrain rect.
|
// 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 constrain_rect = area.constrain_rect();
|
||||||
let max_width = constrain_rect.width();
|
let max_width = constrain_rect.width();
|
||||||
let max_height =
|
let max_height = constrain_rect.height();
|
||||||
constrain_rect.height() - title_bar_height_with_margin - title_content_spacing;
|
|
||||||
resize.max_size.x = resize.max_size.x.min(max_width);
|
resize.max_size.x = resize.max_size.x.min(max_width);
|
||||||
resize.max_size.y = resize.max_size.y.min(max_height);
|
resize.max_size.y = resize.max_size.y.min(max_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First check for resize to avoid frame delay:
|
// The user-supplied min/max/default sizes on `Window` refer to the *outer* window size
|
||||||
let last_frame_outer_rect = area.state().rect();
|
// (the total footprint, including frame margins, stroke, and title bar). `Resize` sizes
|
||||||
let resize_interaction = do_resize_interaction(
|
// the title bar + inner content area, so we subtract the extra frame margin (the part
|
||||||
ctx,
|
// outside of `Resize`).
|
||||||
possible,
|
|
||||||
area.id(),
|
|
||||||
area_layer_id,
|
|
||||||
last_frame_outer_rect,
|
|
||||||
window_frame,
|
|
||||||
);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let margins = window_frame.total_margin().sum()
|
let frame_margin = window_frame.total_margin().sum();
|
||||||
+ vec2(0.0, title_bar_height_with_margin + title_content_spacing);
|
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_response(
|
resize.default_size = (resize.default_size - frame_margin).at_least(Vec2::ZERO);
|
||||||
resize_interaction,
|
|
||||||
ctx,
|
|
||||||
margins,
|
|
||||||
area_layer_id,
|
|
||||||
&mut area,
|
|
||||||
resize_id,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut area_content_ui = area.content_ui(ctx);
|
let mut area_content_ui = area.content_ui(ctx);
|
||||||
|
|
||||||
if is_open {
|
if is_open {
|
||||||
// `Area` already takes care of fade-in animations,
|
// `Area` already takes care of fade-in animations,
|
||||||
// so we only need to handle fade-out animations here.
|
// so we only need to handle fade-out animations here.
|
||||||
@@ -573,55 +558,33 @@ impl Window<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let content_inner = {
|
let content_inner = {
|
||||||
// BEGIN FRAME --------------------------------
|
let outer_response = window_frame.show(&mut area_content_ui, |ui| {
|
||||||
let mut frame = window_frame.begin(&mut area_content_ui);
|
resize.show(ui, |ui| {
|
||||||
|
if with_title_bar {
|
||||||
let show_close_button = open.is_some();
|
title_ui(
|
||||||
|
ui,
|
||||||
let where_to_put_header_background = &area_content_ui.painter().add(Shape::Noop);
|
title,
|
||||||
|
window_frame,
|
||||||
let title_bar = if with_title_bar {
|
&mut collapsing,
|
||||||
let title_bar = TitleBar::new(
|
collapsible,
|
||||||
&frame.content_ui,
|
on_top,
|
||||||
title,
|
open.as_deref_mut(),
|
||||||
show_close_button,
|
auto_sized,
|
||||||
collapsible,
|
);
|
||||||
window_frame,
|
}
|
||||||
title_bar_height_with_margin,
|
collapsing
|
||||||
);
|
.show_body_unindented(ui, |ui| {
|
||||||
resize.min_size.x = resize.min_size.x.at_least(title_bar.inner_rect.width()); // Prevent making window smaller than title bar width
|
if scroll.is_any_scroll_enabled() {
|
||||||
|
scroll.show(ui, add_contents).inner
|
||||||
frame.content_ui.set_min_size(title_bar.inner_rect.size());
|
} else {
|
||||||
|
add_contents(ui)
|
||||||
// Skip the title bar (and separator):
|
}
|
||||||
if is_collapsed {
|
})
|
||||||
frame.content_ui.add_space(title_bar.inner_rect.height());
|
.map(|inner| inner.inner)
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.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.
|
// Do resize interaction _again_, to move their widget rectangles on TOP of the rest of the window.
|
||||||
let resize_interaction = do_resize_interaction(
|
let resize_interaction = do_resize_interaction(
|
||||||
@@ -629,7 +592,7 @@ impl Window<'_> {
|
|||||||
possible,
|
possible,
|
||||||
area.id(),
|
area.id(),
|
||||||
area_layer_id,
|
area_layer_id,
|
||||||
last_frame_outer_rect,
|
outer_rect,
|
||||||
window_frame,
|
window_frame,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -641,50 +604,25 @@ impl Window<'_> {
|
|||||||
resize_interaction,
|
resize_interaction,
|
||||||
);
|
);
|
||||||
|
|
||||||
// END FRAME --------------------------------
|
{
|
||||||
|
let margins = window_frame.total_margin().sum();
|
||||||
|
|
||||||
if let Some(mut title_bar) = title_bar {
|
resize_response(
|
||||||
title_bar.inner_rect = outer_rect.shrink(window_frame.stroke.width);
|
resize_interaction,
|
||||||
title_bar.inner_rect.max.y =
|
ctx,
|
||||||
title_bar.inner_rect.min.y + title_bar_height_with_margin;
|
margins,
|
||||||
|
area_layer_id,
|
||||||
if on_top && area_content_ui.visuals().window_highlight_topmost {
|
&mut area,
|
||||||
let mut round =
|
resize_id,
|
||||||
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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// END FRAME --------------------------------
|
||||||
|
|
||||||
collapsing.store(ctx);
|
collapsing.store(ctx);
|
||||||
|
|
||||||
paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction);
|
paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction);
|
||||||
|
|
||||||
content_inner
|
outer_response.inner
|
||||||
};
|
};
|
||||||
|
|
||||||
let full_response = area.end(ctx, area_content_ui);
|
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 side_grab_radius = style.interaction.resize_grab_radius_side;
|
||||||
let corner_grab_radius = style.interaction.resize_grab_radius_corner;
|
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))
|
Rect::from_min_max(a, b).expand2(vec2(side_grab_radius, -corner_grab_radius))
|
||||||
};
|
};
|
||||||
let horizontal_rect = |a: Pos2, b: Pos2| {
|
let horizontal_rect = |a: Pos2, b: Pos2| {
|
||||||
@@ -1009,14 +947,14 @@ fn do_resize_interaction(
|
|||||||
|
|
||||||
if possible.resize_right {
|
if possible.resize_right {
|
||||||
let response = side_response(
|
let response = side_response(
|
||||||
vetrtical_rect(rect.right_top(), rect.right_bottom()),
|
vertical_rect(rect.right_top(), rect.right_bottom()),
|
||||||
id.with("right"),
|
id.with("right"),
|
||||||
);
|
);
|
||||||
right |= response;
|
right |= response;
|
||||||
}
|
}
|
||||||
if possible.resize_left {
|
if possible.resize_left {
|
||||||
let response = side_response(
|
let response = side_response(
|
||||||
vetrtical_rect(rect.left_top(), rect.left_bottom()),
|
vertical_rect(rect.left_top(), rect.left_bottom()),
|
||||||
id.with("left"),
|
id.with("left"),
|
||||||
);
|
);
|
||||||
left |= response;
|
left |= response;
|
||||||
@@ -1177,176 +1115,165 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction)
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
struct TitleBar {
|
/// Show the window titlebar.
|
||||||
window_frame: Frame,
|
///
|
||||||
|
/// 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
|
let mut atoms = Atoms::default();
|
||||||
title_galley: Arc<Galley>,
|
|
||||||
|
|
||||||
/// Size of the title bar in an expanded state. This size become known only
|
let button_size = Vec2::splat(ui.spacing().icon_width);
|
||||||
/// after expanding window and painting its content.
|
|
||||||
///
|
|
||||||
/// Does not include the stroke, nor the separator line between the title bar and the window contents.
|
|
||||||
inner_rect: Rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TitleBar {
|
// Since the heading height is higher than the button size, we need to allocate the buttons
|
||||||
fn new(
|
// with the headers height as size, otherwise they'd look slightly off-center.
|
||||||
ui: &Ui,
|
// The shrink is then used to render the buttons with the right size.
|
||||||
title: WidgetText,
|
let heading_font_height =
|
||||||
show_close_button: bool,
|
ui.fonts_mut(|f| f.row_height(&TextStyle::Heading.resolve(ui.style())));
|
||||||
collapsible: bool,
|
let button_allocation_size = Vec2::splat(heading_font_height);
|
||||||
window_frame: Frame,
|
let button_shrink = (button_allocation_size - button_size) / 2.0;
|
||||||
title_bar_height_with_margin: f32,
|
|
||||||
) -> Self {
|
|
||||||
if false {
|
|
||||||
ui.debug_painter()
|
|
||||||
.debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 expanded = collapsing.openness(ui.ctx()) > 0.0;
|
||||||
let button_size = Vec2::splat(ui.spacing().icon_width.at_most(inner_height));
|
|
||||||
|
|
||||||
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(
|
atoms.push_right(Atom::grow());
|
||||||
ui,
|
|
||||||
Some(crate::TextWrapMode::Extend),
|
|
||||||
f32::INFINITY,
|
|
||||||
TextStyle::Heading,
|
|
||||||
);
|
|
||||||
|
|
||||||
let minimum_width = if collapsible || show_close_button {
|
if !auto_sized
|
||||||
// If at least one button is shown we make room for both buttons (since title should be centered):
|
&& !title.any_shrink()
|
||||||
2.0 * (left_pad + button_size.x + item_spacing.x) + title_galley.size().x
|
&& 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 {
|
} 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 {
|
layout = layout.min_size(Vec2::new(min_width, 0.0));
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finishes painting of the title bar when the window content size already known.
|
let layout_response = layout.show(&mut child_ui);
|
||||||
///
|
|
||||||
/// # 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;
|
|
||||||
|
|
||||||
if false {
|
let mut title_click_rect = layout_response.response.rect + frame.total_margin();
|
||||||
ui.debug_painter()
|
|
||||||
.debug_rect(self.inner_rect, Color32::RED, "TitleBar");
|
|
||||||
}
|
|
||||||
|
|
||||||
if collapsible {
|
// Collapse triangle icon
|
||||||
// Show collapse-button:
|
if collapsible && let Some(rect) = layout_response.rect(collapse_atom_id) {
|
||||||
let button_center = Align2::LEFT_CENTER
|
let rect = rect.shrink2(button_shrink);
|
||||||
.align_size_within_rect(Vec2::splat(self.inner_rect.height()), self.inner_rect)
|
title_click_rect = title_click_rect.with_min_x(rect.max.x);
|
||||||
.center();
|
let icon_response = child_ui.interact(
|
||||||
let button_size = Vec2::splat(ui.spacing().icon_width);
|
rect,
|
||||||
let button_rect = Rect::from_center_size(button_center, button_size);
|
child_ui.auto_id_with("collapse_button"),
|
||||||
let button_rect = button_rect.round_ui();
|
Sense::click(),
|
||||||
|
|
||||||
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(),
|
|
||||||
);
|
);
|
||||||
|
icon_response.widget_info(|| {
|
||||||
if let Some(content_response) = content_response {
|
WidgetInfo::labeled(
|
||||||
// Paint separator between title and content:
|
WidgetType::Button,
|
||||||
let content_rect = content_response.rect;
|
child_ui.is_enabled(),
|
||||||
if false {
|
if collapsing.is_open() { "Hide" } else { "Show" },
|
||||||
ui.debug_painter()
|
)
|
||||||
.debug_rect(content_rect, Color32::RED, "content_rect");
|
});
|
||||||
}
|
if icon_response.clicked() {
|
||||||
let y = title_inner_rect.bottom() + window_frame.stroke.width / 2.0;
|
collapsing.toggle(&child_ui);
|
||||||
|
|
||||||
// To verify the sanity of this, use a very wide window stroke
|
|
||||||
ui.painter()
|
|
||||||
.hline(title_inner_rect.x_range(), y, window_frame.stroke);
|
|
||||||
}
|
}
|
||||||
|
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:
|
// Close button
|
||||||
let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0));
|
if let Some(open) = open
|
||||||
|
&& let Some(rect) = layout_response.rect(close_atom_id)
|
||||||
if false {
|
{
|
||||||
ui.debug_painter()
|
let rect = rect.shrink2(button_shrink);
|
||||||
.debug_rect(double_click_rect, Color32::GREEN, "double_click_rect");
|
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 collapsible
|
||||||
|
&& child_ui
|
||||||
if ui
|
.interact(
|
||||||
.interact(double_click_rect, id, Sense::CLICK)
|
title_click_rect,
|
||||||
|
child_ui.auto_id_with("window_title_click"),
|
||||||
|
Sense::click(),
|
||||||
|
)
|
||||||
.double_clicked()
|
.double_clicked()
|
||||||
&& collapsible
|
{
|
||||||
{
|
collapsing.toggle(&child_ui);
|
||||||
collapsing.toggle(ui);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paints the "Close" button at the right side of the title bar
|
child_ui.set_clip_rect(Rect::EVERYTHING);
|
||||||
/// and processes clicks on it.
|
let mut header_frame = frame.shadow(Shadow::NONE);
|
||||||
///
|
if active {
|
||||||
/// The button is square and its size is determined by the
|
header_frame = header_frame.fill(ui.visuals().widgets.open.weak_bg_fill);
|
||||||
/// [`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)
|
|
||||||
}
|
}
|
||||||
|
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.
|
/// 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 epaint::text::{IntoTag, TextFormat, VariationCoords};
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
@@ -692,22 +691,6 @@ impl WidgetText {
|
|||||||
self.map_rich_text(|text| text.background_color(background_color))
|
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(
|
pub fn into_layout_job(
|
||||||
self,
|
self,
|
||||||
style: &Style,
|
style: &Style,
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:10d64e017d1d0eba736a4471d28b1602a0cb69d8e2ab53f4ee604b01c9343116
|
oid sha256:7c777cd6b36219b92c88ebf5987f9239a5750d25be4a87a58841bcf6db259599
|
||||||
size 32475
|
size 32422
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ab0d730ce0cf5f1d79947601def4f60c0a015e23a5dcd780df65c7ddc7ae7156
|
oid sha256:157996b151dd0f450abca6a9cbad4c8237e236300546521e50f95637d9a89c05
|
||||||
size 27194
|
size 27206
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6fccbda741a056ae30a7bfd7497f7b0adfb337bdb885d247fbdfef43cc24b54c
|
oid sha256:2d941c979def6f30fd227c144d20c7f138f9c06c3338d563ac075c5e17e9b795
|
||||||
size 26948
|
size 26911
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:26d247655398bae33c724ad3c3bdcab330d194093b07442708d5069e256f636b
|
oid sha256:6df3c0a298d48a5f2e1b36207909e20ea545a72a97461a3ae0792d21e0554f93
|
||||||
size 76542
|
size 76567
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e3389491f9f5c54cfc2e295abe76ad53f223c31e9489e1d07a8323cf14fcf37d
|
oid sha256:e1aa359d3d2767ad0d0ecb635152371b50b3d551f486fb45c6f7fa96969bc8f1
|
||||||
size 62628
|
size 62291
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6e42e801758bb9e6130a4e94bd0857d6f254e68a7dedebec5af5cb7f7d896068
|
oid sha256:05527c073b1ee2f6a15052a9097d1ad515331ff32d572cf510086f9ebb7a7bbc
|
||||||
size 27822
|
size 27253
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:41e68fd3a679e2a6e3dd81129de5aa1ded3a8f24cc7b1f2ba0b876240d309d9b
|
oid sha256:9aa1b0f9a1ff5167b7f66894e6d38112e73bd3b6a692b4b468fb6a75717d01bd
|
||||||
size 21019
|
size 21009
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3db502ec416b322e0f98e9737faa52d5d2fd308d47649710fffa0b0bc5996f52
|
oid sha256:04f66c848c6f24607d19b809f411a9287ad45067b18da40cf48a77ff65ae9d91
|
||||||
size 10783
|
size 10875
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c39ae3420fe01d696a032d8d052c405c5623a9208fc673f5cf09188b1e6a539b
|
oid sha256:157424625dbb855e116a29a008172f29697a37b03512ef06a2e92e502d5e4128
|
||||||
size 115447
|
size 101816
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:19322248e8335301b2ce31fc1f1352993a374dda945d227784fecea2ee831761
|
oid sha256:79ee28a9c0d8ef80d53560584312a33569f668e0a49f6ad5d277ffef507f2818
|
||||||
size 25088
|
size 25116
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:98f8865d866a6f28ae3e3a16c815770ed691a031b1d06f7d3662a7e94f564606
|
oid sha256:ff02fa99b92059d7b4f8286b8a2fd2e27a7f8f005328da97f0b9786aac9bbb17
|
||||||
size 99318
|
size 99316
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:39208c3c2c95f68fb37880b011f866bedd8dbccee33d163a725cb2a5cc6bb1b3
|
oid sha256:165821b21d980c98612a45cf54d9ca3578dec023ab0e2194d2166db087019cc3
|
||||||
size 18290
|
size 18293
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d251ffd6c34e4ffa7b5de7fe5ae57a2d8f48ba7c2e03711da2c3f1a3fd84648c
|
oid sha256:2027f21fdb132542e01fc592d3150aa876cf2ebb4a4268b8c170ff4f523657f0
|
||||||
size 113998
|
size 114074
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4d959e17ee5c8a32534ab98b6f08db884b2fbf25476d8dc2dc90edc79bd87ea4
|
oid sha256:03c4660ce72440f5ebf3db05b4bce1e8caf1899907723d30699d9d65d442f562
|
||||||
size 25821
|
size 24540
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c923f523cc77d678929f294b360f60b9f546ddec66d5317ad0eb44bd61a5f927
|
oid sha256:3e2f29678c41e45bedc916dbe845f05c343bfad5528a4143c32cac6c3dee41e7
|
||||||
size 51733
|
size 50512
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c9c98d7bfa08e22e217dd9e7031cc49e4b4486f1a9fdd223bd122be07af72365
|
oid sha256:d70cd39499c8f9e0edf689b642ef7cc98115c751bf787305f92323f42aae3fd4
|
||||||
size 22550
|
size 21814
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:68afa93605427e12fe527f3ca9613095664b4983f1f585a60f14bc2370c0a1f2
|
oid sha256:7705ef738605c31bbf067363f5544ce57e698ab869feb8a88e4be4eabf4de7b6
|
||||||
size 47224
|
size 47087
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:33959851f1bd2386fcca8fb5700133d14d90db5a8f783fbfaa9f3aebb7b0d5b2
|
oid sha256:345ab9ac3586c3cb9d3b02d46c590a81c7bc31778a3b631874520e57b6694076
|
||||||
size 23119
|
size 22994
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7b918565d66594fe53ed62bb14e357d3489335f3d037ccb088f198d944eb367d
|
oid sha256:6e00e1dd95278a003c383f5a3f16d25ee4943073171d862db50cb17e90e421bc
|
||||||
size 65307
|
size 60272
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f8c3c2912a3a11892e65f94792c77c79400a81d8c913109d39e8ce12f5b095c6
|
oid sha256:58d56dce5f1e9766dbfdca4ea98fb71f7062b23a0ee4b08ce7f76056513aa9a5
|
||||||
size 33503
|
size 33440
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0c2c7b0d4913b59ef932f6d6349ddab5cc8619b2e8e9a8b5eb3e055a62e6ed60
|
oid sha256:353d92d99e9c2350267a43bff7bb9100d3109c82f8d54f9f7a9e3a312ea0c4e6
|
||||||
size 38382
|
size 35378
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9567f56f2dd030608798347e1ab755d95730ba5c5dd1721f1c61147be7216e87
|
oid sha256:218cce13bd96c97aca6a529ba49baaeda5a126cf657e6af1788646cb845725d0
|
||||||
size 18304
|
size 17686
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d19d694e6a70ab6acb45668b391751e82f7beab19f9918e23821c667e8cc9cdb
|
oid sha256:fc1d270e0171cc055fe99579d3f7ab52c63783c793c59aaf8c82027bf1dd3ab8
|
||||||
size 249733
|
size 216484
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9e1c73e28020371429b3e03d540f72dbf886fd40f0ba08bb194e868bbb3c95ff
|
oid sha256:3edbe6debf700364949ef481ba1e8b2319db624ddc316570b6180636dcd88935
|
||||||
size 57230
|
size 57262
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c2facd3881e6b107a0dcce9d6e00008a3c9b0f31ad270c35357a87e487180f56
|
oid sha256:ebe84ae10b084df7d8373bc4499c5d50a1446a6b707e8220c786a198341d76c8
|
||||||
size 19814
|
size 19189
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0ad81eb762150360368a97858ef30bb0d5aff72e71743fa40c3fd4d70ec84cdc
|
oid sha256:141271330d4cef517c4ec2c4c2c41306f8b5de386738381a0e4446fa89df7cdf
|
||||||
size 33400
|
size 30450
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c22130891755ac095d73e0494f11ee8e89d0fd0c31a321d3afb969648ece11ef
|
oid sha256:be289c58296a7656c8550d25ff92b8cd0b03c5c9c527b9ca4a38acda8bbe3ac1
|
||||||
size 23675
|
size 23807
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:465f33e53ebe15b776e2d6d0710f13f2453f00bab1f6ef4319b3491f1d1d3a26
|
oid sha256:10e4658168d6a93779463ebb81520f821cbf6eac59391c60d30f5d40ebdf910d
|
||||||
size 173487
|
size 152623
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:001f7a1310ddf37be4d9a7f56a95a3079f713b741824a348544561bb16c291fa
|
oid sha256:ad81eeb593663cb26295e7fc51987b85a7084b32504e26364f54f9c2129cb790
|
||||||
size 118614
|
size 118048
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8105f7c2b519716cc0a45dffd5f08980f53f35c6c6b788592e1d82506cdacccc
|
oid sha256:3c8aaf89b6d5dad6ae7788b58f642f5d80384777ca80dc3819f1aefc5867b148
|
||||||
size 26665
|
size 26086
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a9e83d5e83e3003830f7f719b02dd93273733a9b72d388aa42083387d02c1a20
|
oid sha256:66f35217b0becce12e251c4e7063c31ac92e080340a0318891771fdff54593db
|
||||||
size 76310
|
size 67388
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3d46c87417c49c8462fac6c488e46b1482bbc75f70fe8f7af8391f0d5d28dac3
|
oid sha256:97452108d7809775756f1a229644076f5b4c75a6a6d8b8edc082c2448ae35e94
|
||||||
size 70271
|
size 60701
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:661570ed4bdf24d52ab049e7d3cf22ef4d50542ee5486d133e0a618a6146da42
|
oid sha256:29e23e0e45a6e557e569358c89a34dc768ff0b33b47112f864ae8ed119ac6e6e
|
||||||
size 98582
|
size 81119
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:94c4af5715992f4dbb5bbec6ce67eec1e2f66cfc078a3e704ec386bdb482cac4
|
oid sha256:7ad81306e153798d72724e37f23a4037bf986f98e0ecbe3e8d294d23a92ef77c
|
||||||
size 30064
|
size 29975
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9d69b9a12e777ee559a481aec012935a5bfb2ca8b0d48725ecd33e7f0880b2b8
|
oid sha256:dde445e82732c45f84acbfa83761348b9b89dd2a4fec500ba5124b0c1e58dd29
|
||||||
size 64273
|
size 63060
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f6c020860fe9cb7503cea548afbf298ebb9dc620133870b528fe7508d04150c8
|
oid sha256:789700adbf20ec5508abdfc95d843197715fbec9f35051799ea18e9927207b30
|
||||||
size 13691
|
size 13650
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d033935ca18ebee7e3c35629233cc3e3a73766ac8c0627fcdd8a12660eed703c
|
oid sha256:ab0a8201038b2b066c5aff1fd1a35bc7aed95e74cf50c293eee4a76d66623822
|
||||||
size 35873
|
size 35171
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:77c9058b036770a644f1bfcf9ed1ba7a29a7c98107b1823474c55ccc2880e9e7
|
oid sha256:f9c8eecaedce2bbcbdc4b7975a349b9f986463f189041c7ad6bed64809f18bcd
|
||||||
size 485880
|
size 445367
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8bd53b56322123940496700cbd2a73e336fd80eaf49dcb19a958888d66570ddb
|
oid sha256:01493c64acb62a24d19c69579a8f8cb8e080a60747792bbbb98e1cccdfe9214f
|
||||||
size 47159
|
size 47175
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d15954e6183558141e05e1b566ec4a794d372503baf436da2a8c8c67299056f1
|
oid sha256:4b069ad508fd3a518df3a39e1740d1786c4af4c3c4a479db04c00f701980efa8
|
||||||
size 48238
|
size 48277
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1738ecf660979888c70b1046dd759fe0b082e4958814fb19077c4fed2fb4bdef
|
oid sha256:e92e72c651e0ad2c0fc38008d24d9ddf28a70e3b35b26898af78e74028e2c251
|
||||||
size 44338
|
size 44357
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dae7239dc065068147387eb313afdbaa3f0df8b81d060dbbc29f5a6a31ad76c8
|
oid sha256:1a5f7bc46c7baf62b559458b3596e386d66bf2281b7c35e5fdb6c5c4a64569f2
|
||||||
size 44309
|
size 44347
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use egui::accesskit::{self, Role};
|
use egui::accesskit::{self, Role};
|
||||||
use egui::{
|
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"))]
|
#[cfg(all(feature = "wgpu", feature = "snapshot"))]
|
||||||
use egui_kittest::SnapshotResults;
|
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"
|
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