Refactor layout (#241)

* Fix https://github.com/emilk/egui/issues/222

* Rewrite layout logic

Cursor is now a Rect.

Closes https://github.com/emilk/egui/issues/179
This commit is contained in:
Emil Ernerfeldt 2021-03-20 21:47:19 +01:00 committed by GitHub
parent 5621a46b4b
commit 589bae1211
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 308 additions and 231 deletions

View file

@ -61,6 +61,15 @@ impl GridLayout {
pub(crate) fn new(ui: &Ui, id: Id) -> Self {
let prev_state = ui.memory().grid.get(&id).cloned().unwrap_or_default();
// TODO: respect current layout
let available = ui.placer().max_rect().intersect(ui.cursor());
let initial_x = available.min.x;
assert!(
initial_x.is_finite(),
"Grid not yet available for right-to-left layouts"
);
Self {
ctx: ui.ctx().clone(),
style: ui.style().clone(),
@ -69,7 +78,7 @@ impl GridLayout {
curr_state: State::default(),
spacing: ui.spacing().item_spacing,
striped: false,
initial_x: ui.cursor().x,
initial_x,
min_cell_size: ui.spacing().interact_size,
max_cell_size: Vec2::INFINITY,
col: 0,
@ -95,10 +104,6 @@ impl GridLayout {
}
pub(crate) fn available_rect(&self, region: &Region) -> Rect {
// let mut rect = Rect::from_min_max(region.cursor, region.max_rect.max);
// rect.set_height(rect.height().at_least(self.min_cell_size.y));
// rect
// required for putting CollapsingHeader in anything but the last column:
self.available_rect_finite(region)
}
@ -116,19 +121,21 @@ impl GridLayout {
.unwrap_or(self.min_cell_size.x)
};
let height = region.max_rect_finite().max.y - region.cursor.y;
let available = region.max_rect.intersect(region.cursor);
let height = region.max_rect_finite().max.y - available.top();
let height = height
.at_least(self.min_cell_size.y)
.at_most(self.max_cell_size.y);
Rect::from_min_size(region.cursor, vec2(width, height))
Rect::from_min_size(available.min, vec2(width, height))
}
pub(crate) fn next_cell(&self, cursor: Pos2, child_size: Vec2) -> Rect {
pub(crate) fn next_cell(&self, cursor: Rect, child_size: Vec2) -> Rect {
let width = self.prev_state.col_width(self.col).unwrap_or(0.0);
let height = self.prev_row_height(self.row);
let size = child_size.max(vec2(width, height));
Rect::from_min_size(cursor, size)
Rect::from_min_size(cursor.min, size)
}
pub(crate) fn align_size_within_rect(&self, size: Vec2, frame: Rect) -> Rect {
@ -140,7 +147,7 @@ impl GridLayout {
self.align_size_within_rect(size, frame)
}
pub(crate) fn advance(&mut self, cursor: &mut Pos2, frame_rect: Rect, widget_rect: Rect) {
pub(crate) fn advance(&mut self, cursor: &mut Rect, frame_rect: Rect, widget_rect: Rect) {
let debug_expand_width = self.style.visuals.debug_expand_width;
let debug_expand_height = self.style.visuals.debug_expand_height;
if debug_expand_width || debug_expand_height {
@ -171,14 +178,14 @@ impl GridLayout {
);
self.col += 1;
cursor.x += frame_rect.width() + self.spacing.x;
cursor.min.x += frame_rect.width() + self.spacing.x;
}
pub(crate) fn end_row(&mut self, cursor: &mut Pos2, painter: &Painter) {
pub(crate) fn end_row(&mut self, cursor: &mut Rect, painter: &Painter) {
let row_height = self.prev_row_height(self.row);
cursor.x = self.initial_x;
cursor.y += row_height + self.spacing.y;
cursor.min.x = self.initial_x;
cursor.min.y += row_height + self.spacing.y;
self.col = 0;
self.row += 1;
@ -186,7 +193,7 @@ impl GridLayout {
if let Some(height) = self.prev_state.row_height(self.row) {
// Paint background for coming row:
let size = Vec2::new(self.prev_state.full_width(self.spacing.x), height);
let rect = Rect::from_min_size(*cursor, size);
let rect = Rect::from_min_size(cursor.min, size);
let rect = rect.expand2(0.5 * self.spacing.y * Vec2::Y);
let rect = rect.expand2(2.0 * Vec2::X); // HACK: just looks better with some spacing on the sides

View file

@ -1,4 +1,5 @@
use crate::{emath::*, Align};
use std::f32::INFINITY;
// ----------------------------------------------------------------------------
@ -29,9 +30,16 @@ pub(crate) struct Region {
pub max_rect: Rect,
/// Where the next widget will be put.
///
/// One side of this will always be infinite: the direction in which new widgets will be added.
/// The opposing side is what is incremented.
/// The crossing sides are initialized to `max_rect`.
///
/// So one can think of `cursor` as a constraint on the available region.
///
/// If something has already been added, this will point ot `style.spacing.item_spacing` beyond the latest child.
/// The cursor can thus be `style.spacing.item_spacing` pixels outside of the min_rect.
pub(crate) cursor: Pos2,
pub(crate) cursor: Rect,
}
impl Region {
@ -61,14 +69,18 @@ impl Region {
self.max_rect = self.max_rect.union(rect);
}
/// Ensure we are big enough to contain the given x-coordinate.
/// Ensure we are big enough to contain the given X-coordinate.
/// This is sometimes useful to expand an ui to stretch to a certain place.
pub fn expand_to_include_x(&mut self, x: f32) {
self.min_rect.min.x = self.min_rect.min.x.min(x);
self.min_rect.max.x = self.min_rect.max.x.max(x);
self.min_rect.extend_with_x(x);
self.max_rect.extend_with_x(x);
}
self.max_rect.min.x = self.max_rect.min.x.min(x);
self.max_rect.max.x = self.max_rect.max.x.max(x);
/// Ensure we are big enough to contain the given Y-coordinate.
/// This is sometimes useful to expand an ui to stretch to a certain place.
pub fn expand_to_include_y(&mut self, y: f32) {
self.min_rect.extend_with_y(y);
self.max_rect.extend_with_y(y);
}
}
@ -323,49 +335,55 @@ impl Layout {
self.align2().align_size_within_rect(size, outer)
}
fn initial_cursor(&self, max_rect: Rect) -> Pos2 {
fn initial_cursor(&self, max_rect: Rect) -> Rect {
let mut cursor = max_rect;
match self.main_dir {
Direction::LeftToRight => max_rect.left_top(),
Direction::RightToLeft => max_rect.right_top(),
Direction::TopDown => max_rect.left_top(),
Direction::BottomUp => max_rect.left_bottom(),
Direction::LeftToRight => {
cursor.max.x = INFINITY;
}
Direction::RightToLeft => {
cursor.min.x = -INFINITY;
}
Direction::TopDown => {
cursor.max.y = INFINITY;
}
Direction::BottomUp => {
cursor.min.y = -INFINITY;
}
}
pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region {
let cursor = self.initial_cursor(max_rect);
let min_rect = Rect::from_min_size(cursor, Vec2::ZERO);
Region {
min_rect,
max_rect,
cursor,
cursor
}
pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region {
debug_assert!(!max_rect.any_nan());
let mut region = Region {
min_rect: Rect::NOTHING, // temporary
max_rect,
cursor: self.initial_cursor(max_rect),
};
let seed = self.next_widget_position(&region);
region.min_rect = Rect::from_center_size(seed, Vec2::ZERO);
region
}
pub(crate) fn available_rect_before_wrap(&self, region: &Region) -> Rect {
self.available_from_cursor_max_rect(region.cursor, region.max_rect)
}
fn available_size_before_wrap(&self, region: &Region) -> Vec2 {
self.available_rect_before_wrap(region).size()
}
pub(crate) fn available_rect_before_wrap_finite(&self, region: &Region) -> Rect {
self.available_from_cursor_max_rect(region.cursor, region.max_rect_finite())
}
fn available_size_before_wrap_finite(&self, region: &Region) -> Vec2 {
self.available_rect_before_wrap_finite(region).size()
}
/// Amount of space available for a widget.
/// For wrapping layouts, this is the maximum (after wrap).
pub(crate) fn available_size(&self, r: &Region) -> Vec2 {
if self.main_wrap {
if self.main_dir.is_horizontal() {
vec2(r.max_rect.width(), r.max_rect.bottom() - r.cursor.y)
vec2(r.max_rect.width(), r.cursor.height())
} else {
vec2(r.max_rect.right() - r.cursor.x, r.max_rect.height())
vec2(r.cursor.width(), r.max_rect.height())
}
} else {
self.available_from_cursor_max_rect(r.cursor, r.max_rect)
@ -375,123 +393,159 @@ impl Layout {
/// Given the cursor in the region, how much space is available
/// for the next widget?
fn available_from_cursor_max_rect(&self, cursor: Pos2, max_rect: Rect) -> Rect {
let mut rect = max_rect;
match self.main_dir {
Direction::LeftToRight => {
rect.min.x = cursor.x;
rect.min.y = cursor.y;
}
Direction::RightToLeft => {
rect.max.x = cursor.x;
rect.min.y = cursor.y;
}
Direction::TopDown => {
rect.min.x = cursor.x;
rect.min.y = cursor.y;
}
Direction::BottomUp => {
rect.min.x = cursor.x;
rect.max.y = cursor.y;
}
}
rect
fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect {
max_rect.intersect(cursor)
}
/// Returns where to put the next widget that is of the given size.
/// The returned `frame_rect` `Rect` will always be justified along the cross axis.
/// This is what you then pass to `advance_after_rects`.
/// Use `justify_and_align` to get the inner `widget_rect`.
#[allow(clippy::collapsible_if)]
pub(crate) fn next_space(
&self,
region: &Region,
mut child_size: Vec2,
item_spacing: Vec2,
) -> Rect {
let mut cursor = region.cursor;
pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect {
if self.main_wrap {
let available_size = self.available_size_before_wrap(region);
let available_size = self.available_rect_before_wrap(region).size();
let Region {
mut cursor,
mut max_rect,
min_rect,
} = *region;
match self.main_dir {
Direction::LeftToRight => {
if available_size.x < child_size.x && region.max_rect.left() < cursor.x {
if available_size.x < child_size.x && max_rect.left() < cursor.left() {
// New row
cursor = pos2(
region.max_rect.left(),
region.max_rect.bottom() + item_spacing.y,
let new_row_height = cursor.height().max(child_size.y);
cursor = Rect::from_min_max(
pos2(max_rect.left(), cursor.bottom() + spacing.y),
pos2(INFINITY, cursor.bottom() + spacing.y + new_row_height),
);
max_rect.max.y = max_rect.max.y.max(cursor.max.y);
}
}
Direction::RightToLeft => {
if available_size.x < child_size.x && cursor.x < region.max_rect.right() {
if available_size.x < child_size.x && cursor.right() < max_rect.right() {
// New row
cursor = pos2(
region.max_rect.right(),
region.max_rect.bottom() + item_spacing.y,
let new_row_height = cursor.height().max(child_size.y);
cursor = Rect::from_min_max(
pos2(-INFINITY, cursor.bottom() + spacing.y),
pos2(
max_rect.right(),
cursor.bottom() + spacing.y + new_row_height,
),
);
max_rect.max.y = max_rect.max.y.max(cursor.max.y);
}
}
Direction::TopDown => {
if available_size.y < child_size.y && region.max_rect.top() < cursor.y {
if available_size.y < child_size.y && max_rect.top() < cursor.top() {
// New column
cursor = pos2(
region.max_rect.right() + item_spacing.x,
region.max_rect.top(),
let new_col_width = cursor.width().max(child_size.x);
cursor = Rect::from_min_max(
pos2(cursor.right() + spacing.x, max_rect.top()),
pos2(cursor.right() + spacing.x + new_col_width, INFINITY),
);
max_rect.max.x = max_rect.max.x.max(cursor.max.x);
}
}
Direction::BottomUp => {
if available_size.y < child_size.y && cursor.y < region.max_rect.bottom() {
if available_size.y < child_size.y && cursor.bottom() < max_rect.bottom() {
// New column
cursor = pos2(
region.max_rect.right() + item_spacing.x,
region.max_rect.bottom(),
let new_col_width = cursor.width().max(child_size.x);
cursor = Rect::from_min_max(
pos2(cursor.right() + spacing.x, -INFINITY),
pos2(
cursor.right() + spacing.x + new_col_width,
max_rect.bottom(),
),
);
}
max_rect.max.x = max_rect.max.x.max(cursor.max.x);
}
}
}
let available_size = self.available_size_before_wrap_finite(region);
if self.is_vertical() || self.horizontal_justify() {
child_size.x = child_size.x.at_least(available_size.x); // fill full width
}
if self.is_horizontal() || self.vertical_justify() {
child_size.y = child_size.y.at_least(available_size.y); // fill full height
}
let child_pos = match self.main_dir {
Direction::LeftToRight => cursor,
Direction::RightToLeft => cursor + vec2(-child_size.x, 0.0),
Direction::TopDown => cursor,
Direction::BottomUp => cursor + vec2(0.0, -child_size.y),
// Use the new cursor:
let region = Region {
min_rect,
max_rect,
cursor,
};
Rect::from_min_size(child_pos, child_size)
self.next_frame_ignore_wrap(&region, child_size)
} else {
self.next_frame_ignore_wrap(region, child_size)
}
}
fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect {
let available_rect = self.available_rect_before_wrap_finite(region);
let mut frame_size = child_size;
if self.is_vertical() || self.horizontal_justify() {
frame_size.x = frame_size.x.at_least(available_rect.width()); // fill full width
}
if self.is_horizontal() || self.vertical_justify() {
frame_size.y = frame_size.y.at_least(available_rect.height()); // fill full height
}
let align2 = match self.main_dir {
Direction::LeftToRight => Align2([Align::LEFT, self.vertical_align()]),
Direction::RightToLeft => Align2([Align::RIGHT, self.vertical_align()]),
Direction::TopDown => Align2([self.horizontal_align(), Align::TOP]),
Direction::BottomUp => Align2([self.horizontal_align(), Align::BOTTOM]),
};
align2.align_size_within_rect(frame_size, available_rect)
}
/// Apply justify (fill width/height) and/or alignment after calling `next_space`.
pub(crate) fn justify_and_align(&self, rect: Rect, mut child_size: Vec2) -> Rect {
pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect {
if self.horizontal_justify() {
child_size.x = child_size.x.at_least(rect.width()); // fill full width
child_size.x = child_size.x.at_least(frame.width()); // fill full width
}
if self.vertical_justify() {
child_size.y = child_size.y.at_least(rect.height()); // fill full height
child_size.y = child_size.y.at_least(frame.height()); // fill full height
}
self.align_size_within_rect(child_size, rect)
self.align_size_within_rect(child_size, frame)
}
/// Advance the cursor by this many points.
pub(crate) fn advance_cursor(&self, cursor: &mut Pos2, amount: f32) {
pub(crate) fn next_widget_space_ignore_wrap_justify(
&self,
region: &Region,
size: Vec2,
) -> Rect {
let frame = self.next_frame_ignore_wrap(region, size);
let rect = self.align_size_within_rect(size, frame);
debug_assert!((rect.size() - size).length() < 1.0);
rect
}
/// Where would the next tiny widget be centered?
pub(crate) fn next_widget_position(&self, region: &Region) -> Pos2 {
self.next_widget_space_ignore_wrap_justify(region, Vec2::ZERO)
.center()
}
/// Advance the cursor by this many points, and allocate in region.
pub(crate) fn advance_cursor(&self, region: &mut Region, amount: f32) {
match self.main_dir {
Direction::LeftToRight => cursor.x += amount,
Direction::RightToLeft => cursor.x -= amount,
Direction::TopDown => cursor.y += amount,
Direction::BottomUp => cursor.y -= amount,
Direction::LeftToRight => {
region.cursor.min.x += amount;
region.expand_to_include_x(region.cursor.min.x);
}
Direction::RightToLeft => {
region.cursor.max.x -= amount;
region.expand_to_include_x(region.cursor.max.x);
}
Direction::TopDown => {
region.cursor.min.y += amount;
region.expand_to_include_y(region.cursor.min.y);
}
Direction::BottomUp => {
region.cursor.max.y -= amount;
region.expand_to_include_y(region.cursor.max.y);
}
}
}
@ -501,29 +555,84 @@ impl Layout {
/// * `widget_rect`: the actual rect used by the widget
pub(crate) fn advance_after_rects(
&self,
cursor: &mut Pos2,
cursor: &mut Rect,
frame_rect: Rect,
widget_rect: Rect,
item_spacing: Vec2,
) {
*cursor = match self.main_dir {
Direction::LeftToRight => pos2(widget_rect.right() + item_spacing.x, frame_rect.top()),
Direction::RightToLeft => pos2(widget_rect.left() - item_spacing.x, frame_rect.top()),
Direction::TopDown => pos2(frame_rect.left(), widget_rect.bottom() + item_spacing.y),
Direction::BottomUp => pos2(frame_rect.left(), widget_rect.top() - item_spacing.y),
if self.main_wrap {
if cursor.intersects(frame_rect) {
// make row/column larger if necessary
*cursor = cursor.union(frame_rect);
} else {
// this is a new row or column. We temporarily use NAN for what will be filled in later.
match self.main_dir {
Direction::LeftToRight => {
*cursor = Rect::from_min_max(
pos2(f32::NAN, frame_rect.min.y),
pos2(INFINITY, frame_rect.max.y),
);
}
Direction::RightToLeft => {
*cursor = Rect::from_min_max(
pos2(-INFINITY, frame_rect.min.y),
pos2(f32::NAN, frame_rect.max.y),
);
}
Direction::TopDown => {
*cursor = Rect::from_min_max(
pos2(frame_rect.min.x, f32::NAN),
pos2(frame_rect.max.x, INFINITY),
);
}
Direction::BottomUp => {
*cursor = Rect::from_min_max(
pos2(frame_rect.min.x, -INFINITY),
pos2(frame_rect.max.x, f32::NAN),
);
}
};
}
}
match self.main_dir {
Direction::LeftToRight => {
cursor.min.x = widget_rect.right() + item_spacing.x;
}
Direction::RightToLeft => {
cursor.max.x = widget_rect.left() - item_spacing.x;
}
Direction::TopDown => {
cursor.min.y = widget_rect.bottom() + item_spacing.y;
}
Direction::BottomUp => {
cursor.max.y = widget_rect.top() - item_spacing.y;
}
};
}
/// Move to the next row in a wrapping layout.
/// Otherwise does nothing.
pub(crate) fn end_row(&mut self, region: &mut Region, item_spacing: Vec2) {
if self.main_wrap && self.is_horizontal() {
// New row
region.cursor = pos2(
region.max_rect.left(),
region.max_rect.bottom() + item_spacing.y,
pub(crate) fn end_row(&mut self, region: &mut Region, spacing: Vec2) {
if self.main_wrap {
match self.main_dir {
Direction::LeftToRight => {
let new_top = region.cursor.bottom() + spacing.y;
region.cursor = Rect::from_min_max(
pos2(region.max_rect.left(), new_top),
pos2(INFINITY, new_top + region.cursor.height()),
);
}
Direction::RightToLeft => {
let new_top = region.cursor.bottom() + spacing.y;
region.cursor = Rect::from_min_max(
pos2(-INFINITY, new_top),
pos2(region.max_rect.right(), new_top + region.cursor.height()),
);
}
Direction::TopDown | Direction::BottomUp => {}
}
}
}
}
@ -540,7 +649,7 @@ impl Layout {
) {
use epaint::*;
let cursor = region.cursor;
let cursor = self.next_widget_position(region);
let align;

View file

@ -59,7 +59,7 @@ impl Placer {
self.region.min_rect = min_rect;
}
pub(crate) fn cursor(&self) -> Pos2 {
pub(crate) fn cursor(&self) -> Rect {
self.region.cursor
}
}
@ -108,12 +108,24 @@ impl Placer {
grid.next_cell(self.region.cursor, child_size)
} else {
self.layout
.next_space(&self.region, child_size, item_spacing)
.next_frame(&self.region, child_size, item_spacing)
}
}
/// Where do we expect a zero-sized widget to be placed?
pub(crate) fn next_widget_position(&self) -> Pos2 {
if let Some(grid) = &self.grid {
grid.next_cell(self.region.cursor, Vec2::ZERO).center()
} else {
self.layout.next_widget_position(&self.region)
}
}
/// Apply justify or alignment after calling `next_space`.
pub(crate) fn justify_and_align(&self, rect: Rect, child_size: Vec2) -> Rect {
debug_assert!(!rect.any_nan());
debug_assert!(!child_size.any_nan());
if let Some(grid) = &self.grid {
grid.justify_and_align(rect, child_size)
} else {
@ -128,10 +140,7 @@ impl Placer {
self.grid.is_none(),
"You cannot advance the cursor when in a grid layout"
);
self.layout.advance_cursor(&mut self.region.cursor, amount);
self.region
.expand_to_include_rect(Rect::from_min_size(self.cursor(), Vec2::ZERO));
self.layout.advance_cursor(&mut self.region, amount);
}
/// Advance cursor after a widget was added to a specific rectangle
@ -155,7 +164,7 @@ impl Placer {
item_spacing,
)
}
self.region.expand_to_include_rect(widget_rect);
self.region.expand_to_include_rect(frame_rect); // e.g. for centered layouts: pretend we used whole frame
}
/// Move to the next row in a grid layout or wrapping layout.
@ -180,68 +189,45 @@ impl Placer {
self.region.expand_to_include_x(x);
}
fn next_widget_space_ignore_wrap_justify(&self, size: Vec2) -> Rect {
self.layout
.next_widget_space_ignore_wrap_justify(&self.region, size)
}
/// Set the maximum width of the ui.
/// You won't be able to shrink it below the current minimum size.
pub(crate) fn set_max_width(&mut self, width: f32) {
#![allow(clippy::float_cmp)]
let Self { layout, region, .. } = self;
if layout.main_dir() == Direction::RightToLeft {
debug_assert_eq!(region.min_rect.max.x, region.max_rect.max.x);
region.max_rect.min.x = region.max_rect.max.x - width.at_least(region.min_rect.width());
} else {
debug_assert_eq!(region.min_rect.min.x, region.max_rect.min.x);
region.max_rect.max.x = region.max_rect.min.x + width.at_least(region.min_rect.width());
}
let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0));
let region = &mut self.region;
region.max_rect.min.x = rect.min.x;
region.max_rect.max.x = rect.max.x;
region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much
}
/// Set the maximum height of the ui.
/// You won't be able to shrink it below the current minimum size.
pub(crate) fn set_max_height(&mut self, height: f32) {
#![allow(clippy::float_cmp)]
let Self { layout, region, .. } = self;
if layout.main_dir() == Direction::BottomUp {
debug_assert_eq!(region.min_rect.max.y, region.max_rect.max.y);
region.max_rect.min.y =
region.max_rect.max.y - height.at_least(region.min_rect.height());
} else {
debug_assert_eq!(region.min_rect.min.y, region.max_rect.min.y);
region.max_rect.max.y =
region.max_rect.min.y + height.at_least(region.min_rect.height());
}
let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height));
let region = &mut self.region;
region.max_rect.min.y = rect.min.y;
region.max_rect.max.y = rect.max.y;
region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much
}
/// Set the minimum width of the ui.
/// This can't shrink the ui, only make it larger.
pub(crate) fn set_min_width(&mut self, width: f32) {
#![allow(clippy::float_cmp)]
let Self { layout, region, .. } = self;
if layout.main_dir() == Direction::RightToLeft {
debug_assert_eq!(region.min_rect.max.x, region.max_rect.max.x);
let min_rect = &mut region.min_rect;
min_rect.min.x = min_rect.min.x.min(min_rect.max.x - width);
} else {
debug_assert_eq!(region.min_rect.min.x, region.max_rect.min.x);
let min_rect = &mut region.min_rect;
min_rect.max.x = min_rect.max.x.max(min_rect.min.x + width);
}
region.max_rect = region.max_rect.union(region.min_rect);
let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0));
self.region.expand_to_include_x(rect.min.x);
self.region.expand_to_include_x(rect.max.x);
}
/// Set the minimum height of the ui.
/// This can't shrink the ui, only make it larger.
pub(crate) fn set_min_height(&mut self, height: f32) {
#![allow(clippy::float_cmp)]
let Self { layout, region, .. } = self;
if layout.main_dir() == Direction::BottomUp {
debug_assert_eq!(region.min_rect.max.y, region.max_rect.max.y);
let min_rect = &mut region.min_rect;
min_rect.min.y = min_rect.min.y.min(min_rect.max.y - height);
} else {
debug_assert_eq!(region.min_rect.min.y, region.max_rect.min.y);
let min_rect = &mut region.min_rect;
min_rect.max.y = min_rect.max.y.max(min_rect.min.y + height);
}
region.max_rect = region.max_rect.union(region.min_rect);
let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height));
self.region.expand_to_include_y(rect.min.y);
self.region.expand_to_include_y(rect.max.y);
}
}

View file

@ -73,6 +73,7 @@ impl Ui {
}
pub fn child_ui(&mut self, max_rect: Rect, layout: Layout) -> Self {
debug_assert!(!max_rect.any_nan());
self.next_auto_id = self.next_auto_id.wrapping_add(1);
Ui {
@ -645,7 +646,9 @@ impl Ui {
}
/// Allocate a specific part of the `Ui.
///
/// Ignore the layout of the `Ui: just put my widget here!
/// The layout cursor will advance to past this `rect`.
pub(crate) fn allocate_rect(&mut self, rect: Rect, sense: Sense) -> Response {
let id = self.advance_cursor_after_rect(rect);
self.interact(rect, id, sense)
@ -659,10 +662,19 @@ impl Ui {
Id::new(self.next_auto_id)
}
pub(crate) fn cursor(&self) -> Pos2 {
pub(crate) fn placer(&self) -> &Placer {
&self.placer
}
pub(crate) fn cursor(&self) -> Rect {
self.placer.cursor()
}
/// Where do we expect a zero-sized widget to be placed?
pub(crate) fn next_widget_position(&self) -> Pos2 {
self.placer.next_widget_position()
}
/// Allocated the given space and then adds content to that space.
/// If the contents overflow, more space will be allocated.
/// When finished, the amount of space actually used (`min_rect`) will be allocated.
@ -673,17 +685,15 @@ impl Ui {
add_contents: impl FnOnce(&mut Self) -> R,
) -> InnerResponse<R> {
let item_spacing = self.spacing().item_spacing;
let outer_child_rect = self.placer.next_space(desired_size, item_spacing);
let inner_child_rect = self
.placer
.justify_and_align(outer_child_rect, desired_size);
let frame_rect = self.placer.next_space(desired_size, item_spacing);
let child_rect = self.placer.justify_and_align(frame_rect, desired_size);
let mut child_ui = self.child_ui(inner_child_rect, *self.layout());
let mut child_ui = self.child_ui(child_rect, *self.layout());
let ret = add_contents(&mut child_ui);
let final_child_rect = child_ui.min_rect();
self.placer.advance_after_rects(
outer_child_rect.union(final_child_rect),
frame_rect.union(final_child_rect),
final_child_rect,
item_spacing,
);
@ -740,8 +750,8 @@ impl Ui {
/// });
/// ```
pub fn scroll_to_cursor(&mut self, align: Align) {
let scroll_y = self.cursor().y;
self.ctx().frame_state().scroll_target = Some((scroll_y, align));
let target_y = self.next_widget_position().y;
self.ctx().frame_state().scroll_target = Some((target_y, align));
}
}
@ -1094,8 +1104,7 @@ impl Ui {
let child_rect = self.available_rect_before_wrap();
let mut child_ui = self.child_ui(child_rect, *self.layout());
let ret = add_contents(&mut child_ui);
let size = child_ui.min_size();
let response = self.allocate_response(size, Sense::hover());
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
InnerResponse::new(ret, response)
}
@ -1140,8 +1149,11 @@ impl Ui {
"You can only indent vertical layouts, found {:?}",
self.layout()
);
let indent = vec2(self.spacing().indent, 0.0);
let child_rect = Rect::from_min_max(self.cursor() + indent, self.max_rect().right_bottom()); // TODO: wrong for reversed layouts
let indent = self.spacing().indent;
let mut child_rect = self.placer.available_rect_before_wrap();
child_rect.min.x += indent;
let mut child_ui = Self {
id: self.id.with(id_source),
..self.child_ui(child_rect, *self.layout())
@ -1153,13 +1165,11 @@ impl Ui {
child_ui.advance_cursor(4.0);
}
let size = child_ui.min_size();
// draw a faint line on the left to mark the indented section
let stroke = self.visuals().widgets.noninteractive.bg_stroke;
let left_top = child_rect.min - indent * 0.5;
let left_top = child_rect.min - 0.5 * indent * Vec2::X;
let left_top = self.painter().round_pos_to_pixels(left_top);
let left_bottom = pos2(left_top.x, left_top.y + size.y - 2.0);
let left_bottom = pos2(left_top.x, child_ui.min_rect().bottom() - 2.0);
let left_bottom = self.painter().round_pos_to_pixels(left_bottom);
self.painter.line_segment([left_top, left_bottom], stroke);
if end_with_horizontal_line {
@ -1169,45 +1179,10 @@ impl Ui {
.line_segment([left_bottom, right_bottom], stroke);
}
let response = self.allocate_response(indent + size, Sense::hover());
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
InnerResponse::new(ret, response)
}
#[deprecated]
pub fn left_column(&mut self, width: f32) -> Self {
#[allow(deprecated)]
self.column(Align::Min, width)
}
#[deprecated]
pub fn centered_column(&mut self, width: f32) -> Self {
#[allow(deprecated)]
self.column(Align::Center, width)
}
#[deprecated]
pub fn right_column(&mut self, width: f32) -> Self {
#[allow(deprecated)]
self.column(Align::Max, width)
}
/// A column ui with a given width.
#[deprecated]
pub fn column(&mut self, column_position: Align, width: f32) -> Self {
let x = match column_position {
Align::Min => 0.0,
Align::Center => self.available_width() / 2.0 - width / 2.0,
Align::Max => self.available_width() - width,
};
self.child_ui(
Rect::from_min_size(
self.cursor() + vec2(x, 0.0),
vec2(width, self.available_size_before_wrap().y),
),
*self.layout(),
)
}
/// Start a ui with horizontal layout.
/// After you have called this, the function registers the contents as any other widget.
///
@ -1403,7 +1378,7 @@ impl Ui {
let spacing = self.spacing().item_spacing.x;
let total_spacing = spacing * (num_columns as f32 - 1.0);
let column_width = (self.available_width() - total_spacing) / (num_columns as f32);
let top_left = self.cursor();
let top_left = self.cursor().min;
let mut columns: Vec<Self> = (0..num_columns)
.map(|col_idx| {

View file

@ -263,7 +263,7 @@ impl Widget for Label {
max_width,
);
let pos = pos2(ui.min_rect().left(), ui.cursor().y);
let pos = pos2(ui.min_rect().left(), ui.cursor().top());
assert!(!galley.rows.is_empty(), "Galleys are never empty");
let rect = galley.rows[0].rect().translate(vec2(pos.x, pos.y));