mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 14:20:04 -04:00
⚠️ 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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user