⚠️ `Frame` now includes stroke width as part of padding (#5575)

* Part of https://github.com/emilk/egui/issues/4019

`Frame` now includes the width of the stroke as part of its size. From
the new docs:

### `Frame` docs
The total (outer) size of a frame is `content_size + inner_margin +
2*stroke.width + outer_margin`.

Everything within the stroke is filled with the fill color (if any).

```text
+-----------------^-------------------------------------- -+
|                 | outer_margin                           |
|    +------------v----^------------------------------+    |
|    |                 | stroke width                 |    |
|    |    +------------v---^---------------------+    |    |
|    |    |                | inner_margin        |    |    |
|    |    |    +-----------v----------------+    |    |    |
|    |    |    |             ^              |    |    |    |
|    |    |    |             |              |    |    |    |
|    |    |    |<------ content_size ------>|    |    |    |
|    |    |    |             |              |    |    |    |
|    |    |    |             v              |    |    |    |
|    |    |    +------- content_rect -------+    |    |    |
|    |    |                                      |    |    |
|    |    +-------------fill_rect ---------------+    |    |
|    |                                                |    |
|    +----------------- widget_rect ------------------+    |
|                                                          |
+---------------------- outer_rect ------------------------+
```

The four rectangles, from inside to outside, are:
* `content_rect`: the rectangle that is made available to the inner
[`Ui`] or widget.
* `fill_rect`: the rectangle that is filled with the fill color (inside
the stroke, if any).
* `widget_rect`: is the interactive part of the widget (what sense
clicks etc).
* `outer_rect`: what is allocated in the outer [`Ui`], and is what is
returned by [`Response::rect`].

### Notes
This required rewriting a lot of the layout code for `egui::Window`,
which was a massive pain. But now the window margin and stroke width is
properly accounted for everywhere.
This commit is contained in:
Emil Ernerfeldt 2025-01-04 10:29:22 +01:00 committed by GitHub
parent 938d8b0d2e
commit 6607cd95f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 680 additions and 508 deletions

View File

@ -6,7 +6,43 @@ use crate::{
};
use epaint::{Color32, Margin, Marginf, Rect, Rounding, Shadow, Shape, Stroke};
/// Add a background, frame and/or margin to a rectangular background of a [`Ui`].
/// A frame around some content, including margin, colors, etc.
///
/// ## Definitions
/// The total (outer) size of a frame is
/// `content_size + inner_margin + 2 * stroke.width + outer_margin`.
///
/// Everything within the stroke is filled with the fill color (if any).
///
/// ```text
/// +-----------------^-------------------------------------- -+
/// | | outer_margin |
/// | +------------v----^------------------------------+ |
/// | | | stroke width | |
/// | | +------------v---^---------------------+ | |
/// | | | | inner_margin | | |
/// | | | +-----------v----------------+ | | |
/// | | | | ^ | | | |
/// | | | | | | | | |
/// | | | |<------ content_size ------>| | | |
/// | | | | | | | | |
/// | | | | v | | | |
/// | | | +------- content_rect -------+ | | |
/// | | | | | |
/// | | +-------------fill_rect ---------------+ | |
/// | | | |
/// | +----------------- widget_rect ------------------+ |
/// | |
/// +---------------------- outer_rect ------------------------+
/// ```
///
/// The four rectangles, from inside to outside, are:
/// * `content_rect`: the rectangle that is made available to the inner [`Ui`] or widget.
/// * `fill_rect`: the rectangle that is filled with the fill color (inside the stroke, if any).
/// * `widget_rect`: is the interactive part of the widget (what sense clicks etc).
/// * `outer_rect`: what is allocated in the outer [`Ui`], and is what is returned by [`Response::rect`].
///
/// ## Usage
///
/// ```
/// # egui::__run_test_ui(|ui| {
@ -58,19 +94,47 @@ use epaint::{Color32, Margin, Marginf, Rect, Rounding, Shadow, Shape, Stroke};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[must_use = "You should call .show()"]
pub struct Frame {
// Fields are ordered inside-out.
// TODO(emilk): add `min_content_size: Vec2`
//
/// Margin within the painted frame.
///
/// Known as `padding` in CSS.
#[doc(alias = "padding")]
pub inner_margin: Margin,
/// Margin outside the painted frame.
pub outer_margin: Margin,
pub rounding: Rounding,
pub shadow: Shadow,
/// The background fill color of the frame, within the [`Self::stroke`].
///
/// Known as `background` in CSS.
#[doc(alias = "background")]
pub fill: Color32,
/// The width and color of the outline around the frame.
///
/// The width of the stroke is part of the total margin/padding of the frame.
#[doc(alias = "border")]
pub stroke: Stroke,
/// The rounding of the corners of [`Self::stroke`] and [`Self::fill`].
pub rounding: Rounding,
/// Margin outside the painted frame.
///
/// Similar to what is called `margin` in CSS.
/// However, egui does NOT do "Margin Collapse" like in CSS,
/// i.e. when placing two frames next to each other,
/// the distance between their borders is the SUM
/// of their other margins.
/// In CSS the distance would be the MAX of their outer margins.
/// Supporting margin collapse is difficult, and would
/// requires complicating the already complicated egui layout code.
///
/// Consider using [`crate::Spacing::item_spacing`]
/// for adding space between widgets.
pub outer_margin: Margin,
/// Optional drop-shadow behind the frame.
pub shadow: Shadow,
}
#[test]
@ -85,68 +149,72 @@ fn frame_size() {
);
}
/// ## Constructors
impl Frame {
pub fn none() -> Self {
Self::default()
/// No colors, no margins, no border.
///
/// This is also the default.
pub const NONE: Self = Self {
inner_margin: Margin::ZERO,
stroke: Stroke::NONE,
fill: Color32::TRANSPARENT,
rounding: Rounding::ZERO,
outer_margin: Margin::ZERO,
shadow: Shadow::NONE,
};
pub const fn new() -> Self {
Self::NONE
}
#[deprecated = "Use `Frame::NONE` or `Frame::new()` instead."]
pub const fn none() -> Self {
Self::NONE
}
/// For when you want to group a few widgets together within a frame.
pub fn group(style: &Style) -> Self {
Self {
inner_margin: Margin::same(6), // same and symmetric looks best in corners when nesting groups
rounding: style.visuals.widgets.noninteractive.rounding,
stroke: style.visuals.widgets.noninteractive.bg_stroke,
..Default::default()
}
Self::new()
.inner_margin(6)
.rounding(style.visuals.widgets.noninteractive.rounding)
.stroke(style.visuals.widgets.noninteractive.bg_stroke)
}
pub fn side_top_panel(style: &Style) -> Self {
Self {
inner_margin: Margin::symmetric(8, 2),
fill: style.visuals.panel_fill,
..Default::default()
}
Self::new()
.inner_margin(Margin::symmetric(8, 2))
.fill(style.visuals.panel_fill)
}
pub fn central_panel(style: &Style) -> Self {
Self {
inner_margin: Margin::same(8),
fill: style.visuals.panel_fill,
..Default::default()
}
Self::new().inner_margin(8).fill(style.visuals.panel_fill)
}
pub fn window(style: &Style) -> Self {
Self {
inner_margin: style.spacing.window_margin,
rounding: style.visuals.window_rounding,
shadow: style.visuals.window_shadow,
fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(),
..Default::default()
}
Self::new()
.inner_margin(style.spacing.window_margin)
.rounding(style.visuals.window_rounding)
.shadow(style.visuals.window_shadow)
.fill(style.visuals.window_fill())
.stroke(style.visuals.window_stroke())
}
pub fn menu(style: &Style) -> Self {
Self {
inner_margin: style.spacing.menu_margin,
rounding: style.visuals.menu_rounding,
shadow: style.visuals.popup_shadow,
fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(),
..Default::default()
}
Self::new()
.inner_margin(style.spacing.menu_margin)
.rounding(style.visuals.menu_rounding)
.shadow(style.visuals.popup_shadow)
.fill(style.visuals.window_fill())
.stroke(style.visuals.window_stroke())
}
pub fn popup(style: &Style) -> Self {
Self {
inner_margin: style.spacing.menu_margin,
rounding: style.visuals.menu_rounding,
shadow: style.visuals.popup_shadow,
fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(),
..Default::default()
}
Self::new()
.inner_margin(style.spacing.menu_margin)
.rounding(style.visuals.menu_rounding)
.shadow(style.visuals.popup_shadow)
.fill(style.visuals.window_fill())
.stroke(style.visuals.window_stroke())
}
/// A canvas to draw on.
@ -154,57 +222,77 @@ impl Frame {
/// In bright mode this will be very bright,
/// and in dark mode this will be very dark.
pub fn canvas(style: &Style) -> Self {
Self {
inner_margin: Margin::same(2),
rounding: style.visuals.widgets.noninteractive.rounding,
fill: style.visuals.extreme_bg_color,
stroke: style.visuals.window_stroke(),
..Default::default()
}
Self::new()
.inner_margin(2)
.rounding(style.visuals.widgets.noninteractive.rounding)
.fill(style.visuals.extreme_bg_color)
.stroke(style.visuals.window_stroke())
}
/// A dark canvas to draw on.
pub fn dark_canvas(style: &Style) -> Self {
Self {
fill: Color32::from_black_alpha(250),
..Self::canvas(style)
}
Self::canvas(style).fill(Color32::from_black_alpha(250))
}
}
/// ## Builders
impl Frame {
#[inline]
pub fn fill(mut self, fill: Color32) -> Self {
self.fill = fill;
self
}
#[inline]
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
self.stroke = stroke.into();
self
}
#[inline]
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
self.rounding = rounding.into();
self
}
/// Margin within the painted frame.
///
/// Known as `padding` in CSS.
#[doc(alias = "padding")]
#[inline]
pub fn inner_margin(mut self, inner_margin: impl Into<Margin>) -> Self {
self.inner_margin = inner_margin.into();
self
}
/// The background fill color of the frame, within the [`Self::stroke`].
///
/// Known as `background` in CSS.
#[doc(alias = "background")]
#[inline]
pub fn fill(mut self, fill: Color32) -> Self {
self.fill = fill;
self
}
/// The width and color of the outline around the frame.
///
/// The width of the stroke is part of the total margin/padding of the frame.
#[inline]
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
self.stroke = stroke.into();
self
}
/// The rounding of the corners of [`Self::stroke`] and [`Self::fill`].
#[inline]
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
self.rounding = rounding.into();
self
}
/// Margin outside the painted frame.
///
/// Similar to what is called `margin` in CSS.
/// However, egui does NOT do "Margin Collapse" like in CSS,
/// i.e. when placing two frames next to each other,
/// the distance between their borders is the SUM
/// of their other margins.
/// In CSS the distance would be the MAX of their outer margins.
/// Supporting margin collapse is difficult, and would
/// requires complicating the already complicated egui layout code.
///
/// Consider using [`crate::Spacing::item_spacing`]
/// for adding space between widgets.
#[inline]
pub fn outer_margin(mut self, outer_margin: impl Into<Margin>) -> Self {
self.outer_margin = outer_margin.into();
self
}
/// Optional drop-shadow behind the frame.
#[inline]
pub fn shadow(mut self, shadow: Shadow) -> Self {
self.shadow = shadow;
@ -224,11 +312,37 @@ impl Frame {
}
}
/// ## Inspectors
impl Frame {
/// Inner margin plus outer margin.
/// How much extra space the frame uses up compared to the content.
///
/// [`Self::inner_margin`] + [`Self.stroke`]`.width` + [`Self::outer_margin`].
#[inline]
pub fn total_margin(&self) -> Marginf {
Marginf::from(self.inner_margin) + Marginf::from(self.outer_margin)
Marginf::from(self.inner_margin)
+ Marginf::from(self.stroke.width)
+ Marginf::from(self.outer_margin)
}
/// Calculate the `fill_rect` from the `content_rect`.
///
/// This is the rectangle that is filled with the fill color (inside the stroke, if any).
pub fn fill_rect(&self, content_rect: Rect) -> Rect {
content_rect + self.inner_margin
}
/// Calculate the `widget_rect` from the `content_rect`.
///
/// This is the visible and interactive rectangle.
pub fn widget_rect(&self, content_rect: Rect) -> Rect {
content_rect + self.inner_margin + Marginf::from(self.stroke.width)
}
/// Calculate the `outer_rect` from the `content_rect`.
///
/// This is what is allocated in the outer [`Ui`], and is what is returned by [`Response::rect`].
pub fn outer_rect(&self, content_rect: Rect) -> Rect {
content_rect + self.inner_margin + Marginf::from(self.stroke.width) + self.outer_margin
}
}
@ -259,20 +373,18 @@ impl Frame {
let where_to_put_background = ui.painter().add(Shape::Noop);
let outer_rect_bounds = ui.available_rect_before_wrap();
let mut inner_rect = outer_rect_bounds - self.outer_margin - self.inner_margin;
let mut max_content_rect = outer_rect_bounds - self.total_margin();
// Make sure we don't shrink to the negative:
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y);
max_content_rect.max.x = max_content_rect.max.x.max(max_content_rect.min.x);
max_content_rect.max.y = max_content_rect.max.y.max(max_content_rect.min.y);
let content_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::Frame).with_frame(self))
.max_rect(inner_rect),
.max_rect(max_content_rect),
);
// content_ui.set_clip_rect(outer_rect_bounds.shrink(self.stroke.width * 0.5)); // Can't do this since we don't know final size yet
Prepared {
frame: self,
where_to_put_background,
@ -298,32 +410,37 @@ impl Frame {
}
/// Paint this frame as a shape.
///
/// The margin is ignored.
pub fn paint(&self, outer_rect: Rect) -> Shape {
pub fn paint(&self, content_rect: Rect) -> Shape {
let Self {
inner_margin: _,
outer_margin: _,
rounding,
shadow,
fill,
stroke,
rounding,
outer_margin: _,
shadow,
} = *self;
let frame_shape = Shape::Rect(epaint::RectShape::new(outer_rect, rounding, fill, stroke));
let fill_rect = self.fill_rect(content_rect);
let widget_rect = self.widget_rect(content_rect);
let frame_shape = Shape::Rect(epaint::RectShape::new(fill_rect, rounding, fill, stroke));
if shadow == Default::default() {
frame_shape
} else {
let shadow = shadow.as_shape(outer_rect, rounding);
let shadow = shadow.as_shape(widget_rect, rounding);
Shape::Vec(vec![Shape::from(shadow), frame_shape])
}
}
}
impl Prepared {
fn content_with_margin(&self) -> Rect {
self.content_ui.min_rect() + self.frame.inner_margin + self.frame.outer_margin
fn outer_rect(&self) -> Rect {
let content_rect = self.content_ui.min_rect();
content_rect
+ self.frame.inner_margin
+ Marginf::from(self.frame.stroke.width)
+ self.frame.outer_margin
}
/// Allocate the space that was used by [`Self::content_ui`].
@ -332,22 +449,25 @@ impl Prepared {
///
/// This can be called before or after [`Self::paint`].
pub fn allocate_space(&self, ui: &mut Ui) -> Response {
ui.allocate_rect(self.content_with_margin(), Sense::hover())
ui.allocate_rect(self.outer_rect(), Sense::hover())
}
/// Paint the frame.
///
/// This can be called before or after [`Self::allocate_space`].
pub fn paint(&self, ui: &Ui) {
let paint_rect = self.content_ui.min_rect() + self.frame.inner_margin;
let content_rect = self.content_ui.min_rect();
let widget_rect = self.frame.widget_rect(content_rect);
if ui.is_rect_visible(paint_rect) {
let shape = self.frame.paint(paint_rect);
if ui.is_rect_visible(widget_rect) {
let shape = self.frame.paint(content_rect);
ui.painter().set(self.where_to_put_background, shape);
}
}
/// Convenience for calling [`Self::allocate_space`] and [`Self::paint`].
///
/// Returns the outer rect, i.e. including the outer margin.
pub fn end(self, ui: &mut Ui) -> Response {
self.paint(ui);
self.allocate_space(ui)

View File

@ -2,15 +2,11 @@
use std::sync::Arc;
use crate::collapsing_header::CollapsingState;
use crate::{
Align, Align2, Context, CursorIcon, Id, InnerResponse, LayerId, NumExt, Order, Response, Sense,
TextStyle, Ui, UiKind, Vec2b, WidgetInfo, WidgetRect, WidgetText, WidgetType,
};
use emath::GuiRounding as _;
use epaint::{
emath, pos2, vec2, Galley, Pos2, Rect, RectShape, Rounding, Roundingf, Shape, Stroke, Vec2,
};
use epaint::{RectShape, Roundingf};
use crate::collapsing_header::CollapsingState;
use crate::*;
use super::scroll_area::ScrollBarVisibility;
use super::{area, resize, Area, Frame, Resize, ScrollArea};
@ -452,8 +448,6 @@ impl<'open> Window<'open> {
let header_color =
frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill);
let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
// Keep the original inner margin for later use
let window_margin = window_frame.inner_margin;
let is_explicitly_closed = matches!(open, Some(false));
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
@ -483,15 +477,23 @@ impl<'open> Window<'open> {
area.with_widget_info(|| WidgetInfo::labeled(WidgetType::Window, true, title.text()));
// Calculate roughly how much larger the window size is compared to the inner rect
let (title_bar_height, title_content_spacing) = if with_title_bar {
// Calculate roughly how much larger the full window inner size is compared to the content rect
let (title_bar_height_with_margin, title_content_spacing) = if with_title_bar {
let style = ctx.style();
let spacing = window_margin.sum().y;
let height = ctx.fonts(|f| title.font_height(f, &style)) + spacing;
let half_height = (height / 2.0).round() as _;
let title_bar_inner_height = ctx
.fonts(|fonts| title.font_height(fonts, &style))
.at_least(style.spacing.interact_size.y);
let title_bar_inner_height = title_bar_inner_height + window_frame.inner_margin.sum().y;
let half_height = (title_bar_inner_height / 2.0).round() as _;
window_frame.rounding.ne = window_frame.rounding.ne.clamp(0, half_height);
window_frame.rounding.nw = window_frame.rounding.nw.clamp(0, half_height);
(height, spacing)
let title_content_spacing = if is_collapsed {
0.0
} else {
window_frame.stroke.width
};
(title_bar_inner_height, title_content_spacing)
} else {
(0.0, 0.0)
};
@ -500,7 +502,8 @@ impl<'open> Window<'open> {
// Prevent window from becoming larger than the constrain rect.
let constrain_rect = area.constrain_rect();
let max_width = constrain_rect.width();
let max_height = constrain_rect.height() - title_bar_height;
let max_height =
constrain_rect.height() - title_bar_height_with_margin - title_content_spacing;
resize.max_size.x = resize.max_size.x.min(max_width);
resize.max_size.y = resize.max_size.y.min(max_height);
}
@ -508,21 +511,28 @@ impl<'open> Window<'open> {
// First check for resize to avoid frame delay:
let last_frame_outer_rect = area.state().rect();
let resize_interaction = ctx.with_accessibility_parent(area.id(), || {
resize_interaction(ctx, possible, area_layer_id, last_frame_outer_rect)
resize_interaction(
ctx,
possible,
area_layer_id,
last_frame_outer_rect,
window_frame,
)
});
let margins = window_frame.outer_margin.sum()
+ window_frame.inner_margin.sum()
+ vec2(0.0, title_bar_height);
{
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,
);
resize_response(
resize_interaction,
ctx,
margins,
area_layer_id,
&mut area,
resize_id,
);
}
let mut area_content_ui = area.content_ui(ctx);
if is_open {
@ -535,40 +545,43 @@ impl<'open> Window<'open> {
let content_inner = {
ctx.with_accessibility_parent(area.id(), || {
// BEGIN FRAME --------------------------------
let frame_stroke = window_frame.stroke;
let mut frame = window_frame.begin(&mut area_content_ui);
let show_close_button = open.is_some();
let where_to_put_header_background = &area_content_ui.painter().add(Shape::Noop);
// Backup item spacing before the title bar
let item_spacing = frame.content_ui.spacing().item_spacing;
// Use title bar spacing as the item spacing before the content
frame.content_ui.spacing_mut().item_spacing.y = title_content_spacing;
let title_bar = if with_title_bar {
let title_bar = TitleBar::new(
&mut frame.content_ui,
&frame.content_ui,
title,
show_close_button,
&mut collapsing,
collapsible,
window_frame,
title_bar_height_with_margin,
);
resize.min_size.x = resize.min_size.x.at_least(title_bar.rect.width()); // Prevent making window smaller than title bar width
resize.min_size.x = resize.min_size.x.at_least(title_bar.inner_rect.width()); // Prevent making window smaller than title bar width
frame.content_ui.set_min_size(title_bar.inner_rect.size());
// Skip the title bar (and separator):
if is_collapsed {
frame.content_ui.add_space(title_bar.inner_rect.height());
} else {
frame.content_ui.add_space(
title_bar.inner_rect.height()
+ title_content_spacing
+ window_frame.inner_margin.sum().y,
);
}
Some(title_bar)
} else {
None
};
// Remove item spacing after the title bar
frame.content_ui.spacing_mut().item_spacing.y = 0.0;
let (content_inner, mut content_response) = collapsing
let (content_inner, content_response) = collapsing
.show_body_unindented(&mut frame.content_ui, |ui| {
// Restore item spacing for the content
ui.spacing_mut().item_spacing.y = item_spacing.y;
resize.show(ui, |ui| {
if scroll.is_any_scroll_enabled() {
scroll.show(ui, add_contents).inner
@ -584,23 +597,18 @@ impl<'open> Window<'open> {
&area_content_ui,
&possible,
outer_rect,
frame_stroke,
window_frame.rounding,
&window_frame,
resize_interaction,
);
// END FRAME --------------------------------
if let Some(title_bar) = title_bar {
let mut title_rect = Rect::from_min_size(
outer_rect.min,
Vec2 {
x: outer_rect.size().x,
y: title_bar_height,
},
);
title_rect = title_rect.round_to_pixels(area_content_ui.pixels_per_point());
if let Some(mut title_bar) = title_bar {
title_bar.inner_rect = outer_rect.shrink(window_frame.stroke.width);
title_bar.inner_rect.max.y =
title_bar.inner_rect.min.y + title_bar_height_with_margin;
title_bar.inner_rect =
title_bar.inner_rect.round_to_pixels(ctx.pixels_per_point());
if on_top && area_content_ui.visuals().window_highlight_topmost {
let mut round = window_frame.rounding;
@ -612,18 +620,20 @@ impl<'open> Window<'open> {
area_content_ui.painter().set(
*where_to_put_header_background,
RectShape::filled(title_rect, round, header_color),
RectShape::filled(title_bar.inner_rect, round, header_color),
);
};
// Fix title bar separator line position
if let Some(response) = &mut content_response {
response.rect.min.y = outer_rect.min.y + title_bar_height;
if false {
ctx.debug_painter().debug_rect(
title_bar.inner_rect,
Color32::LIGHT_BLUE,
"title_bar.rect",
);
}
title_bar.ui(
&mut area_content_ui,
title_rect,
&content_response,
open,
&mut collapsing,
@ -653,12 +663,11 @@ fn paint_resize_corner(
ui: &Ui,
possible: &PossibleInteractions,
outer_rect: Rect,
stroke: impl Into<Stroke>,
rounding: impl Into<Rounding>,
window_frame: &Frame,
i: ResizeInteraction,
) {
let inactive_stroke = stroke.into();
let rounding = rounding.into();
let rounding = window_frame.rounding;
let (corner, radius, corner_response) = if possible.resize_right && possible.resize_bottom {
(Align2::RIGHT_BOTTOM, rounding.se, i.right & i.bottom)
} else if possible.resize_left && possible.resize_bottom {
@ -694,11 +703,12 @@ fn paint_resize_corner(
} else if corner_response.hover {
ui.visuals().widgets.hovered.fg_stroke
} else {
inactive_stroke
window_frame.stroke
};
let fill_rect = outer_rect.shrink(window_frame.stroke.width);
let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
let corner_rect = corner.align_size_within_rect(corner_size, outer_rect);
let corner_rect = corner.align_size_within_rect(corner_size, fill_rect);
let corner_rect = corner_rect.translate(-offset * corner.to_sign()); // move away from corner
crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke.color, corner);
}
@ -738,7 +748,11 @@ impl PossibleInteractions {
/// Resizing the window edges.
#[derive(Clone, Copy, Debug)]
struct ResizeInteraction {
start_rect: Rect,
/// Outer rect (outside the stroke)
outer_rect: Rect,
window_frame: Frame,
left: SideResponse,
right: SideResponse,
top: SideResponse,
@ -835,13 +849,17 @@ fn resize_response(
ctx.memory_mut(|mem| mem.areas_mut().move_to_top(area_layer_id));
}
/// Acts on outer rect (outside the stroke)
fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Option<Rect> {
if !interaction.any_dragged() {
return None;
}
let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?;
let mut rect = interaction.start_rect; // prevent drift
let mut rect = interaction.outer_rect; // prevent drift
// Put the rect in the center of the stroke:
rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
if interaction.left.drag {
rect.min.x = pointer_pos.x;
@ -855,6 +873,9 @@ fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Opt
rect.max.y = pointer_pos.y;
}
// Return to having the rect outside the stroke:
rect = rect.expand(interaction.window_frame.stroke.width / 2.0);
Some(rect.round_ui())
}
@ -862,11 +883,13 @@ fn resize_interaction(
ctx: &Context,
possible: PossibleInteractions,
layer_id: LayerId,
rect: Rect,
outer_rect: Rect,
window_frame: Frame,
) -> ResizeInteraction {
if !possible.resizable() {
return ResizeInteraction {
start_rect: rect,
outer_rect,
window_frame,
left: Default::default(),
right: Default::default(),
top: Default::default(),
@ -874,6 +897,9 @@ fn resize_interaction(
};
}
// The rect that is in the middle of the stroke:
let rect = outer_rect.shrink(window_frame.stroke.width / 2.0);
let side_response = |rect, id| {
let response = ctx.create_widget(
WidgetRect {
@ -990,7 +1016,8 @@ fn resize_interaction(
}
let interaction = ResizeInteraction {
start_rect: rect,
outer_rect,
window_frame,
left,
right,
top,
@ -1027,6 +1054,18 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction)
}
let rounding = Roundingf::from(ui.visuals().window_rounding);
// Put the rect in the center of the fixed window stroke:
let rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
// Make sure the inner part of the stroke is at a pixel boundary:
let stroke = visuals.bg_stroke;
let half_stroke = stroke.width / 2.0;
let rect = rect
.shrink(half_stroke)
.round_to_pixels(ui.pixels_per_point())
.expand(half_stroke);
let Rect { min, max } = rect;
let mut points = Vec::new();
@ -1083,80 +1122,74 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction)
points.push(pos2(max.x, min.y + rounding.ne));
points.push(pos2(max.x, max.y - rounding.se));
}
ui.painter().add(Shape::line(points, visuals.bg_stroke));
ui.painter().add(Shape::line(points, stroke));
}
// ----------------------------------------------------------------------------
struct TitleBar {
/// A title Id used for dragging windows
id: Id,
window_frame: Frame,
/// Prepared text in the title
title_galley: Arc<Galley>,
/// Size of the title bar in a collapsed state (if window is collapsible),
/// which includes all necessary space for showing the expand button, the
/// title and the close button.
min_rect: Rect,
/// Size of the title bar in an expanded state. This size become known only
/// after expanding window and painting its content
rect: Rect,
/// after expanding window and painting its content.
///
/// Does not include the stroke, nor the separator line between the title bar and the window contents.
inner_rect: Rect,
}
impl TitleBar {
fn new(
ui: &mut Ui,
ui: &Ui,
title: WidgetText,
show_close_button: bool,
collapsing: &mut CollapsingState,
collapsible: bool,
window_frame: Frame,
title_bar_height_with_margin: f32,
) -> Self {
let inner_response = ui.horizontal(|ui| {
let height = ui
.fonts(|fonts| title.font_height(fonts, ui.style()))
.max(ui.spacing().interact_size.y);
ui.set_min_height(height);
if false {
ui.ctx()
.debug_painter()
.debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect");
}
let item_spacing = ui.spacing().item_spacing;
let button_size = Vec2::splat(ui.spacing().icon_width);
let inner_height = title_bar_height_with_margin - window_frame.inner_margin.sum().y;
let pad = ((height - button_size.y) / 2.0).round_ui(); // calculated so that the icon is on the diagonal (if window padding is symmetrical)
let item_spacing = ui.spacing().item_spacing;
let button_size = Vec2::splat(ui.spacing().icon_width.at_most(inner_height));
if collapsible {
ui.add_space(pad);
collapsing.show_default_button_with_size(ui, button_size);
}
let left_pad = ((inner_height - button_size.y) / 2.0).round_ui(); // calculated so that the icon is on the diagonal (if window padding is symmetrical)
let title_galley = title.into_galley(
ui,
Some(crate::TextWrapMode::Extend),
f32::INFINITY,
TextStyle::Heading,
);
let title_galley = title.into_galley(
ui,
Some(crate::TextWrapMode::Extend),
f32::INFINITY,
TextStyle::Heading,
);
let minimum_width = if collapsible || show_close_button {
// If at least one button is shown we make room for both buttons (since title is centered):
2.0 * (pad + button_size.x + item_spacing.x) + title_galley.size().x
} else {
pad + title_galley.size().x + pad
};
let min_rect = Rect::from_min_size(ui.min_rect().min, vec2(minimum_width, height));
let id = ui.advance_cursor_after_rect(min_rect);
let minimum_width = if collapsible || show_close_button {
// If at least one button is shown we make room for both buttons (since title should be centered):
2.0 * (left_pad + button_size.x + item_spacing.x) + title_galley.size().x
} else {
left_pad + title_galley.size().x + left_pad
};
let min_inner_size = vec2(minimum_width, inner_height);
let min_rect = Rect::from_min_size(ui.min_rect().min, min_inner_size);
Self {
id,
title_galley,
min_rect,
rect: Rect::NAN, // Will be filled in later
}
});
if false {
ui.ctx()
.debug_painter()
.debug_rect(min_rect, Color32::LIGHT_BLUE, "min_rect");
}
let title_bar = inner_response.inner;
let rect = inner_response.response.rect;
Self { rect, ..title_bar }
Self {
window_frame,
title_galley,
inner_rect: min_rect, // First estimate - will be refined later
}
}
/// Finishes painting of the title bar when the window content size already known.
@ -1174,17 +1207,34 @@ impl TitleBar {
/// - `collapsible`: if `true`, double click on the title bar will be handled for a change
/// of `collapsing` state
fn ui(
mut self,
self,
ui: &mut Ui,
outer_rect: Rect,
content_response: &Option<Response>,
open: Option<&mut bool>,
collapsing: &mut CollapsingState,
collapsible: bool,
) {
if let Some(content_response) = &content_response {
// Now we know how large we got to be:
self.rect.max.x = self.rect.max.x.max(content_response.rect.max.x);
let window_frame = self.window_frame;
let title_inner_rect = self.inner_rect;
if false {
ui.ctx()
.debug_painter()
.debug_rect(self.inner_rect, Color32::RED, "TitleBar");
}
if collapsible {
// Show collapse-button:
let button_center = Align2::LEFT_CENTER
.align_size_within_rect(Vec2::splat(self.inner_rect.height()), self.inner_rect)
.center();
let button_size = Vec2::splat(ui.spacing().icon_width);
let button_rect = Rect::from_center_size(button_center, button_size);
let button_rect = button_rect.round_to_pixels(ui.pixels_per_point());
ui.allocate_new_ui(UiBuilder::new().max_rect(button_rect), |ui| {
collapsing.show_default_button_with_size(ui, button_size);
});
}
if let Some(open) = open {
@ -1194,9 +1244,9 @@ impl TitleBar {
}
}
let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range());
let text_pos =
emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top();
emath::align::center_size_in_rect(self.title_galley.size(), title_inner_rect)
.left_top();
let text_pos = text_pos - self.title_galley.rect.min.to_vec2();
ui.painter().galley(
text_pos,
@ -1205,22 +1255,35 @@ impl TitleBar {
);
if let Some(content_response) = &content_response {
// paint separator between title and content:
let y = content_response.rect.top();
// let y = lerp(self.rect.bottom()..=content_response.rect.top(), 0.5);
let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
// Workaround: To prevent border infringement,
// the 0.1 value should ideally be calculated using TessellationOptions::feathering_size_in_pixels
// or we could support selectively disabling feathering on line caps
let x_range = outer_rect.x_range().shrink(0.1);
ui.painter().hline(x_range, y, stroke);
// Paint separator between title and content:
let content_rect = content_response.rect;
if false {
ui.ctx()
.debug_painter()
.debug_rect(content_rect, Color32::RED, "content_rect");
}
let y = title_inner_rect.bottom() + window_frame.stroke.width / 2.0;
// To verify the sanity of this, use a very wide window stroke
ui.painter()
.hline(title_inner_rect.x_range(), y, window_frame.stroke);
}
// Don't cover the close- and collapse buttons:
let double_click_rect = self.rect.shrink2(vec2(32.0, 0.0));
let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0));
if false {
ui.ctx().debug_painter().debug_rect(
double_click_rect,
Color32::GREEN,
"double_click_rect",
);
}
let id = ui.unique_id().with("__window_title_bar");
if ui
.interact(double_click_rect, self.id, Sense::click())
.interact(double_click_rect, id, Sense::click())
.double_clicked()
&& collapsible
{
@ -1234,16 +1297,12 @@ impl TitleBar {
/// The button is square and its size is determined by the
/// [`crate::style::Spacing::icon_width`] setting.
fn close_button_ui(&self, ui: &mut Ui) -> Response {
let button_center = Align2::RIGHT_CENTER
.align_size_within_rect(Vec2::splat(self.inner_rect.height()), self.inner_rect)
.center();
let button_size = Vec2::splat(ui.spacing().icon_width);
let pad = (self.rect.height() - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical)
let button_rect = Rect::from_min_size(
pos2(
self.rect.right() - pad - button_size.x,
self.rect.center().y - 0.5 * button_size.y,
),
button_size,
);
let button_rect = Rect::from_center_size(button_center, button_size);
let button_rect = button_rect.round_to_pixels(ui.pixels_per_point());
close_button(ui, button_rect)
}
}

View File

@ -1771,12 +1771,14 @@ impl Ui {
/// Add extra space before the next widget.
///
/// The direction is dependent on the layout.
/// This will be in addition to the [`crate::style::Spacing::item_spacing`].
///
/// This will be in addition to the [`crate::style::Spacing::item_spacing`]
/// that is always added, but `item_spacing` won't be added _again_ by `add_space`.
///
/// [`Self::min_rect`] will expand to contain the space.
#[inline]
pub fn add_space(&mut self, amount: f32) {
self.placer.advance_cursor(amount);
self.placer.advance_cursor(amount.round_ui());
}
/// Show some text.

View File

@ -44,7 +44,11 @@ pub struct FractalClockApp {
impl eframe::App for FractalClockApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default()
.frame(egui::Frame::dark_canvas(&ctx.style()))
.frame(
egui::Frame::dark_canvas(&ctx.style())
.stroke(egui::Stroke::NONE)
.rounding(0),
)
.show(ctx, |ui| {
self.fractal_clock
.ui(ui, self.mock_time.or(Some(crate::seconds_since_midnight())));
@ -293,7 +297,7 @@ impl eframe::App for WrapApp {
let mut cmd = Command::Nothing;
egui::TopBottomPanel::top("wrap_app_top_bar")
.frame(egui::Frame::none().inner_margin(4.0))
.frame(egui::Frame::new().inner_margin(4))
.show(ctx, |ui| {
ui.horizontal_wrapped(|ui| {
ui.visuals_mut().button_frame = false;

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c05cc3d48242e46a391af34cb56f72de7933bf2cead009b6cd477c21867a84e
size 327802
oid sha256:4aeab31841dd95b5e0f4bd0af0c0ba49a862d50836dbafdf2172fbbab950c105
size 327741

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:61212e30fe1fecf5891ddad6ac795df510bfad76b21a7a8a13aa024fdad6d05e
size 93118
oid sha256:0e4a90792a9876da549f3d1da9b057a078400ad15db2cc6e35f4324851137d4e
size 93115

View File

@ -7,19 +7,18 @@ pub struct FrameDemo {
impl Default for FrameDemo {
fn default() -> Self {
Self {
frame: egui::Frame {
inner_margin: 12.0.into(),
outer_margin: 24.0.into(),
rounding: 14.0.into(),
shadow: egui::Shadow {
frame: egui::Frame::new()
.inner_margin(12)
.outer_margin(24)
.rounding(14)
.shadow(egui::Shadow {
offset: [8, 12],
blur: 16,
spread: 0,
color: egui::Color32::from_black_alpha(180),
},
fill: egui::Color32::from_rgba_unmultiplied(97, 0, 255, 128),
stroke: egui::Stroke::new(1.0, egui::Color32::GRAY),
},
})
.fill(egui::Color32::from_rgba_unmultiplied(97, 0, 255, 128))
.stroke(egui::Stroke::new(1.0, egui::Color32::GRAY)),
}
}
}

View File

@ -1,8 +1,8 @@
use super::{Demo, View};
use egui::{
vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Frame, Resize, RichText,
Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window,
vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Resize, RichText, Sense,
Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window,
};
/// Showcase some ui code
@ -512,54 +512,52 @@ fn ui_stack_demo(ui: &mut Ui) {
);
});
let stack = ui.stack().clone();
Frame {
inner_margin: ui.spacing().menu_margin,
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
..Default::default()
}
.show(ui, |ui| {
egui_extras::TableBuilder::new(ui)
.column(egui_extras::Column::auto())
.column(egui_extras::Column::auto())
.header(18.0, |mut header| {
header.col(|ui| {
ui.strong("id");
});
header.col(|ui| {
ui.strong("kind");
});
})
.body(|mut body| {
for node in stack.iter() {
body.row(18.0, |mut row| {
row.col(|ui| {
let response = ui.label(format!("{:?}", node.id));
egui::Frame::new()
.inner_margin(ui.spacing().menu_margin)
.stroke(ui.visuals().widgets.noninteractive.bg_stroke)
.show(ui, |ui| {
egui_extras::TableBuilder::new(ui)
.column(egui_extras::Column::auto())
.column(egui_extras::Column::auto())
.header(18.0, |mut header| {
header.col(|ui| {
ui.strong("id");
});
header.col(|ui| {
ui.strong("kind");
});
})
.body(|mut body| {
for node in stack.iter() {
body.row(18.0, |mut row| {
row.col(|ui| {
let response = ui.label(format!("{:?}", node.id));
if response.hovered() {
ui.ctx().debug_painter().debug_rect(
node.max_rect,
Color32::GREEN,
"max_rect",
);
ui.ctx().debug_painter().circle_filled(
node.min_rect.min,
2.0,
Color32::RED,
);
}
});
if response.hovered() {
ui.ctx().debug_painter().debug_rect(
node.max_rect,
Color32::GREEN,
"max_rect",
);
ui.ctx().debug_painter().circle_filled(
node.min_rect.min,
2.0,
Color32::RED,
);
}
});
row.col(|ui| {
ui.label(if let Some(kind) = node.kind() {
format!("{kind:?}")
} else {
"-".to_owned()
row.col(|ui| {
ui.label(if let Some(kind) = node.kind() {
format!("{kind:?}")
} else {
"-".to_owned()
});
});
});
});
}
});
});
}
});
});
ui.small("Hover on UI's ids to display their origin and max rect.");
}

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf83bead834ec8f88d74b32ae6331715e8c6df183e007e2a16004c019534a30f
size 31810
oid sha256:d4cfd5191dc7046a782ef2350dc8e0547d2702182badcb15b6b928ce077b76c1
size 32154

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0a1099b85a1aaf20f3f1e091bc68259f811737feaefdfcc12acd067eca8f9117
size 27083
oid sha256:e89c730b462c2b60b90f2ac15fe9576e878a4906c223317c51344a0ec2b6d993
size 27564

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6969c6da67ea6cc7ebbbd7a2cc1cb13d4720befe28126367cbf2b2679d037674
size 82363
oid sha256:ea2c944af8bc1be42ec7c00be58dfaa23c92bca8957eda94f2ff10f5b4242562
size 83358

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:332c2af36873d8ccccb36c08fd2e475dc1f18454a3090a851c0889395d4f364f
size 11518
oid sha256:c401ff91fff4051042528d398d2b2270a4ae924570e6332cf8f2c6774c845160
size 11826

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d0d0b1b4d2c4b624904250bc8d6870559f0179e3f7f2d6dc4a4ff256df356237
size 20626
oid sha256:7efc1ff3e4e5bfd4216394f94ee7486c272a9ca1c980789f4ad143f89b0a7103
size 21073

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5fe6166bb8cd5fae0899957e968312b9229a79e423a5d154eda483a149b264d
size 20831
oid sha256:d9c48cf928a17dd0980ba086aa004bde3a0040dcb82752d138c1df34f1ef3d2f
size 21167

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b71e1d109f90e017644dd20b9d84d81e3a6d5195afbd01ba86c85fa248c8b5c5
size 10703
oid sha256:be0f96c700b7662aab5098f8412dae3676116eeed65e70f6b295dd3375b329d0
size 10968

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3c9ba9064f44a4a14405f53316c1c602184caf16cb584d7c1f1912fe59f85ab
size 135712
oid sha256:dc69c76eaa121e9e7782cfbbb68b5a23004d79862bae4af2e3ca3a29eff04bea
size 136467

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:097bd512dd71c17370f6e374312c19e7ab6845f151b3c3565f2a0790b61ee7ba
size 24413
oid sha256:23187a9fb12a3ab7df4e2321aa25b493559923d61e82802f843ee29dcd932f7b
size 24985

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2bdf54573a6b0d2fedd90314f91dd7de22dd13709e8dd699b37ef8935b6adda5
size 17785
oid sha256:7f433f3e8bff38a0aafd7e6cba5c5efe1abf484550a6f9e90008f8f5ea891497
size 18113

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d6328c86b70f3e24aaf87db200d13dfd0baa787dd8431e47621457937f8f5021
size 22552
oid sha256:e5105ecf77852412c0dd904b96f0fec752f22e416df9932df4499d6d5a776f46
size 22865

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:afb57dc0bb8ff839138e36b8780136e0c8da55ff380832538fae0394143807c0
size 65321
oid sha256:ebf0403bd599e5c00c2831f9c4032e8d20420212c9cd7fa875f1ae1cbbc8d3a7
size 65902

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:403fc1d08d79168bc9f7a08ac7f363f2df916547f08311838edfda8a499a9b2d
size 32879
oid sha256:4e690dc73873ab75c030d3c0238e9d5b840f976dd8f4882dc1e930024d217838
size 33323

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cbd490a15377bdd4fd3272f3cd126cbc4fb9ed2f6f84edcbba03dd30dc3f3d99
size 36780
oid sha256:e760210371dbf2a197f96a78d01b7480f0ae05d46bbb4e642276b2eb30847ec2
size 37075

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c31b3998817e345b12b037d7f8bec7641f77d0c7eab7da9a746b7b51c9efc8fb
size 17531
oid sha256:fd02b208d0e4e306bbc9a54f25f5a3d20875a12182cef3224e6daa309b6cf453
size 17898

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4e8963c3ecd0e74fe9641d87101742a0d45c82a582d70e30eb36bc835f5aac06
size 25330
oid sha256:341958da648a7db3374c4337cf057ae8e81c08c4a6de7e4f1cbe9c5b049f2e62
size 25727

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:88b3a50b481942630b5804c60227f23b719dc7e3eb6dbe432c2448cb69332def
size 262141
oid sha256:8934cff7203d19b38df9d91729091ff5d1ad6c8d445fd9c1cb62b6df1bb8cb80
size 263547

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4d5d628b54b28eccac0d9ef21bbdabace0cdf507898707956943e2259df846ca
size 23741
oid sha256:5d61f58138798d701bb8dda2c3240eef69eb350df3168fb3aa4148e4fef3f77a
size 24077

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8763a8f8b4242cbe589cd3282dc5f4b32a84b4e0416fb8072dfefb7879a5d4f6
size 187982
oid sha256:e4006e93663d02fe0f4485d2c163ab2b6feded787bee87ea15616fc0b36136d0
size 188875

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3d61088bf1f992467e8396ac89407fa158d6f44e2d2d196778247f3ff18cb893
size 119759
oid sha256:70b170ba7b8e51d9d9f766d7ce25068fa4265c4127e729af4f1adaacbb745d19
size 120947

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79ebaf9cccd19da2923507b5a553a23edc67382ef59e4b71f01d3bd6cc913539
size 25829
oid sha256:157353a8c9bcb638a8be485489e4a232f348eae3cc4ceefe227d7970c7d1f8b3
size 26256

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:06cbf13841e6ac5fbc57bdae6f5ad9d19718193c344420dedcc0e9d7ed2b8ba9
size 71590
oid sha256:cf0ddd39a45519dcf9027f691e856921c736d18e2eeafd16f0e086720121b6a7
size 72286

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:35e66f211c0b30a684371b520c46dbe4f9d5b6835e053a4eb65f492dd66a9e6c
size 67288
oid sha256:914d37e326087f770994bcf3867a27d88050c57887a2b42c68414d311fa96003
size 67698

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fa9ee8631bfe433ee6fad1fb1651fd6b63e2fb3fbc5f321a5410f7266dc16d09
size 21296
oid sha256:b0fdf8ce329883450e071e4733c3904577999d18ac61c922c7caacbec09dfda7
size 21661

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6475702b1bf2c65fb5196928a8934ade647a6053d6902a836e3d69cb7920091e
size 59874
oid sha256:375b71a8ac5b0e48f3c67a089ef0e8a4fd17f8eb21fa535422099c29c2747e27
size 59991

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d8daaec0c58709298a4594b7b2aa935aa2de327e6b71bd7417c2ba3a6eb060c
size 13020
oid sha256:aa96b1e3733e4af94a6cb6ec765c3f3581df2175e75831eb00bd42df2e7a2708
size 13285

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:29d8d859a8cb11e4b390d45c658ed8ff191c2e542909e12f2385c0cba62baa2d
size 35109
oid sha256:18880dfaf5d198876c4db97ebd6313d59755a3e8298567f2b2fa91dcc21699c5
size 35607

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:17217e600d8a85ec00ecb11f0e5fe69496b30bbf715cc86785cec6e18b8c2fa1
size 48158
oid sha256:d626b310439bff13487548bbba8b516886c13049824a7f5dd902f6dffb3c5ba4
size 48234

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fac50d2327a9e5e99989dd81d5644db86031b91b9c5c68fc17b5ef53ae655048
size 47970
oid sha256:80a2968e211c83639b60e84b805f1327fb37b87144cada672a403c7e92ace8a8
size 48066

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e5e829257b742194742429be0efd326be17ff7f5b9b16f9df58df21e899320bd
size 43963
oid sha256:86df5dc4b4ddd6f44226242b6d9b5e9f2aacd45193ae9f784fb5084a7a509e0b
size 43987

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5da064332c669a860a92a34b101f23e28026d4f07948f7c3e9a40e611f5e284f
size 43986
oid sha256:1138bbc3b7e73cccd555f0fd58c27a5bda4d84484fdc1bd5223fc9802d0c5328
size 44089

View File

@ -56,7 +56,7 @@ impl<'a, State> AppKind<'a, State> {
sizing_pass: bool,
) -> egui::Response {
egui::CentralPanel::default()
.frame(Frame::none())
.frame(Frame::NONE)
.show(ctx, |ui| {
let mut builder = egui::UiBuilder::new();
if sizing_pass {

View File

@ -45,13 +45,11 @@ impl eframe::App for MyApp {
fn custom_window_frame(ctx: &egui::Context, title: &str, add_contents: impl FnOnce(&mut egui::Ui)) {
use egui::{CentralPanel, UiBuilder};
let panel_frame = egui::Frame {
fill: ctx.style().visuals.window_fill(),
rounding: 10.0.into(),
stroke: ctx.style().visuals.widgets.noninteractive.fg_stroke,
outer_margin: 1.0.into(), // so the stroke is within the bounds
..Default::default()
};
let panel_frame = egui::Frame::new()
.fill(ctx.style().visuals.window_fill())
.rounding(10)
.stroke(ctx.style().visuals.widgets.noninteractive.fg_stroke)
.outer_margin(1); // so the stroke is within the bounds
CentralPanel::default().frame(panel_frame).show(ctx, |ui| {
let app_rect = ui.max_rect();

View File

@ -62,27 +62,23 @@ impl eframe::App for MyApp {
// nested frames test
ui.add_space(20.0);
egui::Frame {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(4),
outer_margin: egui::Margin::same(4),
..Default::default()
}
.show(ui, |ui| {
full_span_widget(ui, false);
stack_ui(ui);
egui::Frame {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(8),
outer_margin: egui::Margin::same(6),
..Default::default()
}
egui::Frame::new()
.stroke(ui.visuals().noninteractive().bg_stroke)
.inner_margin(4)
.outer_margin(4)
.show(ui, |ui| {
full_span_widget(ui, false);
stack_ui(ui);
egui::Frame::new()
.stroke(ui.visuals().noninteractive().bg_stroke)
.inner_margin(8)
.outer_margin(6)
.show(ui, |ui| {
full_span_widget(ui, false);
stack_ui(ui);
});
});
});
});
});
@ -126,18 +122,16 @@ impl eframe::App for MyApp {
// Ui nesting test
ui.add_space(20.0);
ui.label("UI nesting test:");
egui::Frame {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(4),
..Default::default()
}
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.scope(stack_ui);
egui::Frame::new()
.stroke(ui.visuals().noninteractive().bg_stroke)
.inner_margin(4)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.scope(stack_ui);
});
});
});
});
// table test
let mut cell_stack = None;
@ -265,106 +259,104 @@ fn stack_ui(ui: &mut egui::Ui) {
}
fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) {
egui::Frame {
stroke: ui.style().noninteractive().fg_stroke,
inner_margin: egui::Margin::same(4),
..Default::default()
}
.show(ui, |ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
egui::Frame::new()
.stroke(ui.style().noninteractive().fg_stroke)
.inner_margin(4)
.show(ui, |ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
egui_extras::TableBuilder::new(ui)
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("id");
});
header.col(|ui| {
ui.strong("kind");
});
header.col(|ui| {
ui.strong("stroke");
});
header.col(|ui| {
ui.strong("inner");
});
header.col(|ui| {
ui.strong("outer");
});
header.col(|ui| {
ui.strong("direction");
});
})
.body(|mut body| {
for node in stack.iter() {
body.row(20.0, |mut row| {
row.col(|ui| {
if ui.label(format!("{:?}", node.id)).hovered() {
ui.ctx().debug_painter().debug_rect(
node.max_rect,
egui::Color32::GREEN,
"max",
);
ui.ctx().debug_painter().circle_filled(
node.min_rect.min,
2.0,
egui::Color32::RED,
);
}
});
row.col(|ui| {
let s = if let Some(kind) = node.kind() {
format!("{kind:?}")
} else {
"-".to_owned()
};
ui.label(s);
});
row.col(|ui| {
let frame = node.frame();
if frame.stroke == egui::Stroke::NONE {
ui.label("-");
} else {
let mut layout_job = egui::text::LayoutJob::default();
layout_job.append(
"",
0.0,
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(ui.style()),
frame.stroke.color,
),
);
layout_job.append(
format!("{}px", frame.stroke.width).as_str(),
0.0,
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(ui.style()),
ui.style().visuals.text_color(),
),
);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.label(layout_job);
}
});
row.col(|ui| {
ui.label(print_margin(&node.frame().inner_margin));
});
row.col(|ui| {
ui.label(print_margin(&node.frame().outer_margin));
});
row.col(|ui| {
ui.label(format!("{:?}", node.layout_direction));
});
egui_extras::TableBuilder::new(ui)
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("id");
});
}
});
});
header.col(|ui| {
ui.strong("kind");
});
header.col(|ui| {
ui.strong("stroke");
});
header.col(|ui| {
ui.strong("inner");
});
header.col(|ui| {
ui.strong("outer");
});
header.col(|ui| {
ui.strong("direction");
});
})
.body(|mut body| {
for node in stack.iter() {
body.row(20.0, |mut row| {
row.col(|ui| {
if ui.label(format!("{:?}", node.id)).hovered() {
ui.ctx().debug_painter().debug_rect(
node.max_rect,
egui::Color32::GREEN,
"max",
);
ui.ctx().debug_painter().circle_filled(
node.min_rect.min,
2.0,
egui::Color32::RED,
);
}
});
row.col(|ui| {
let s = if let Some(kind) = node.kind() {
format!("{kind:?}")
} else {
"-".to_owned()
};
ui.label(s);
});
row.col(|ui| {
let frame = node.frame();
if frame.stroke == egui::Stroke::NONE {
ui.label("-");
} else {
let mut layout_job = egui::text::LayoutJob::default();
layout_job.append(
"",
0.0,
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(ui.style()),
frame.stroke.color,
),
);
layout_job.append(
format!("{}px", frame.stroke.width).as_str(),
0.0,
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(ui.style()),
ui.style().visuals.text_color(),
),
);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.label(layout_job);
}
});
row.col(|ui| {
ui.label(print_margin(&node.frame().inner_margin));
});
row.col(|ui| {
ui.label(print_margin(&node.frame().outer_margin));
});
row.col(|ui| {
ui.label(format!("{:?}", node.layout_direction));
});
});
}
});
});
}
fn print_margin(margin: &egui::Margin) -> String {