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:
parent
5621a46b4b
commit
589bae1211
5 changed files with 308 additions and 231 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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(®ion);
|
||||
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(®ion, 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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue