Refactor: move min_rect/max_rect/cursor into struct Region

This commit is contained in:
Emil Ernerfeldt 2020-12-06 10:16:37 +01:00
parent 6e8d5c87a0
commit b0ba66b90d
3 changed files with 187 additions and 124 deletions

View file

@ -2,6 +2,12 @@
TODO-list for the Egui project. If you looking for something to do, look here.
## Layout refactor
* Deprecate `add_custom_contents`
* Fix and test `allocate_ui`
*
## Misc
* Widgets

View file

@ -2,6 +2,68 @@ use crate::{math::*, Align};
// ----------------------------------------------------------------------------
/// This describes the bounds and existing contents of an `Ui`.
/// It is what is used and updated by `Layout` when adding new widgets.
#[derive(Clone, Copy, Debug)]
pub struct Region {
/// This is the minimal size of the `Ui`.
/// When adding new widgets, this will generally expand.
///
/// Always finite.
///
/// The bounding box of all child widgets, but not necessarily a tight bounding box
/// since `Ui` can start with a non-zero min_rect size.
pub min_rect: Rect,
/// The maximum size of this `Ui`. This is a *soft max*
/// meaning new widgets will *try* not to expand beyond it,
/// but if they have to, they will.
///
/// Text will wrap at `max_rect.right()`.
/// Some widgets (like separator lines) will try to fill the full `max_rect` width of the ui.
///
/// `max_rect` will always be at least the size of `min_rect`.
///
/// If the `max_rect` size is zero, it is a signal that child widgets should be as small as possible.
/// If the `max_rect` size is infinite, it is a signal that child widgets should take up as much room as they want.
pub max_rect: Rect,
/// Where the next widget will be put.
/// 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,
}
impl Region {
/// This is like `max_rect`, but will never be infinite.
/// If the desired rect is infinite ("be as big as you want")
/// this will be bounded by `min_rect` instead.
pub fn max_rect_finite(&self) -> Rect {
let mut result = self.max_rect;
if !result.min.x.is_finite() {
result.min.x = self.min_rect.min.x;
}
if !result.min.y.is_finite() {
result.min.y = self.min_rect.min.y;
}
if !result.max.x.is_finite() {
result.max.x = self.min_rect.max.x;
}
if !result.max.y.is_finite() {
result.max.y = self.min_rect.max.y;
}
result
}
/// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect.
pub fn expand_to_include_rect(&mut self, rect: Rect) {
self.min_rect = self.min_rect.union(rect);
self.max_rect = self.max_rect.union(rect);
}
}
// ----------------------------------------------------------------------------
/// `Layout` direction (horizontal or vertical).
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
@ -112,7 +174,7 @@ impl Layout {
self.reversed
}
pub fn initial_cursor(self, max_rect: Rect) -> Pos2 {
fn initial_cursor(self, max_rect: Rect) -> Pos2 {
match self.dir {
Direction::Horizontal => {
if self.reversed {
@ -131,9 +193,27 @@ impl Layout {
}
}
pub 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,
}
}
pub fn available(&self, region: &Region) -> Rect {
self.available_from_cursor_max_rect(region.cursor, region.max_rect)
}
pub fn available_finite(&self, region: &Region) -> Rect {
self.available_from_cursor_max_rect(region.cursor, region.max_rect_finite())
}
/// Given the cursor in the region, how much space is available
/// for the next widget?
pub fn available(self, cursor: Pos2, max_rect: Rect) -> Rect {
fn available_from_cursor_max_rect(self, cursor: Pos2, max_rect: Rect) -> Rect {
let mut rect = max_rect;
match self.dir {
Direction::Horizontal => {
@ -157,46 +237,46 @@ impl Layout {
}
/// Advance the cursor by this many points.
pub fn advance_cursor(self, cursor: &mut Pos2, amount: f32) {
pub fn advance_cursor(self, region: &mut Region, amount: f32) {
match self.dir() {
Direction::Horizontal => {
if self.is_reversed() {
cursor.x -= amount;
region.cursor.x -= amount;
} else {
cursor.x += amount;
region.cursor.x += amount;
}
}
Direction::Vertical => {
if self.is_reversed() {
cursor.y -= amount;
region.cursor.y -= amount;
} else {
cursor.y += amount;
region.cursor.y += amount;
}
}
}
}
/// Advance the cursor by this spacing
pub fn advance_cursor2(self, cursor: &mut Pos2, amount: Vec2) {
pub fn advance_cursor2(self, region: &mut Region, amount: Vec2) {
match self.dir() {
Direction::Horizontal => self.advance_cursor(cursor, amount.x),
Direction::Vertical => self.advance_cursor(cursor, amount.y),
Direction::Horizontal => self.advance_cursor(region, amount.x),
Direction::Vertical => self.advance_cursor(region, amount.y),
}
}
pub fn rect_from_cursor_size(self, cursor: Pos2, size: Vec2) -> Rect {
let mut rect = Rect::from_min_size(cursor, size);
pub fn rect_from_cursor_size(self, region: &Region, size: Vec2) -> Rect {
let mut rect = Rect::from_min_size(region.cursor, size);
match self.dir {
Direction::Horizontal => {
if self.reversed {
rect.min.x = cursor.x - size.x;
rect.min.x = region.cursor.x - size.x;
rect.max.x = rect.min.x - size.x
}
}
Direction::Vertical => {
if self.reversed {
rect.min.y = cursor.y - size.y;
rect.min.y = region.cursor.y - size.y;
rect.max.y = rect.min.y - size.y
}
}
@ -217,12 +297,8 @@ impl Layout {
/// for `Justified` aligned layouts, like in menus.
///
/// You may get LESS space than you asked for if the current layout won't fit what you asked for.
pub fn allocate_space(
self,
cursor: &mut Pos2,
available_size: Vec2,
minimum_child_size: Vec2,
) -> Rect {
pub fn allocate_space(self, region: &mut Region, minimum_child_size: Vec2) -> Rect {
let available_size = self.available_finite(region).size();
let available_size = available_size.at_least(minimum_child_size);
let mut child_size = minimum_child_size;
@ -260,16 +336,16 @@ impl Layout {
}
if self.is_reversed() {
let child_pos = *cursor + child_move;
let child_pos = region.cursor + child_move;
let child_pos = match self.dir {
Direction::Horizontal => child_pos + vec2(-child_size.x, 0.0),
Direction::Vertical => child_pos + vec2(0.0, -child_size.y),
};
*cursor -= cursor_change;
region.cursor -= cursor_change; // TODO: separate call
Rect::from_min_size(child_pos, child_size)
} else {
let child_pos = *cursor + child_move;
*cursor += cursor_change;
let child_pos = region.cursor + child_move;
region.cursor += cursor_change; // TODO: separate call
Rect::from_min_size(child_pos, child_size)
}
}
@ -280,8 +356,11 @@ impl Layout {
/// ## Debug stuff
impl Layout {
/// Shows where the next widget is going to be placed
pub fn debug_paint_cursor(&self, cursor: Pos2, painter: &crate::Painter) {
pub fn debug_paint_cursor(&self, region: &Region, painter: &crate::Painter) {
use crate::paint::*;
let cursor = region.cursor;
let color = color::GREEN;
let stroke = Stroke::new(2.0, color);

View file

@ -22,41 +22,19 @@ pub struct Ui {
/// They are therefore only good for Id:s that has no state.
next_auto_id: u64,
/// Specifies paint layer, clip rectangle and a reference to `Context`.
painter: Painter,
/// This is the minimal size of the `Ui`.
/// When adding new widgets, this will generally expand.
///
/// Always finite.
///
/// The bounding box of all child widgets, but not necessarily a tight bounding box
/// since `Ui` can start with a non-zero min_rect size.
min_rect: Rect,
/// The maximum size of this `Ui`. This is a *soft max*
/// meaning new widgets will *try* not to expand beyond it,
/// but if they have to, they will.
///
/// Text will wrap at `max_rect.right()`.
/// Some widgets (like separator lines) will try to fill the full `max_rect` width of the ui.
///
/// `max_rect` will always be at least the size of `min_rect`.
///
/// If the `max_rect` size is zero, it is a signal that child widgets should be as small as possible.
/// If the `max_rect` size is infinite, it is a signal that child widgets should take up as much room as they want.
max_rect: Rect,
/// Override default style in this ui
/// The `Style` (visuals, spacing, etc) of this ui.
/// Commonly many `Ui`:s share the same `Style`.
/// The `Ui` implements copy-on-write for this.
style: Arc<Style>,
/// The strategy for where to put the next widget.
layout: Layout,
/// Where the next widget will be put.
/// Progresses along self.dir.
/// Initially set to rect.min
/// 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.
cursor: Pos2, // TODO: move into Layout?
/// Sizes/bounds and cursor used by `Layout`.
region: Region,
}
impl Ui {
@ -72,36 +50,28 @@ impl Ui {
) -> Self {
let style = ctx.style();
let layout = Layout::default();
let cursor = layout.initial_cursor(max_rect);
let min_size = Vec2::zero(); // TODO: From Style
let min_rect = layout.rect_from_cursor_size(cursor, min_size);
let region = layout.region_from_max_rect(max_rect);
Ui {
id,
next_auto_id: id.with("auto").value(),
painter: Painter::new(ctx, layer_id, clip_rect),
min_rect,
max_rect,
style,
layout,
cursor,
region,
}
}
pub fn child_ui(&mut self, max_rect: Rect, layout: Layout) -> Self {
self.next_auto_id = self.next_auto_id.wrapping_add(1);
let cursor = layout.initial_cursor(max_rect);
let min_size = Vec2::zero(); // TODO: From Style
let min_rect = layout.rect_from_cursor_size(cursor, min_size);
let region = layout.region_from_max_rect(max_rect);
Ui {
id: self.id.with("child"),
next_auto_id: Id::new(self.next_auto_id).with("child").value(),
painter: self.painter.clone(),
min_rect,
max_rect,
style: self.style.clone(),
layout,
cursor,
region,
}
}
@ -210,12 +180,12 @@ impl Ui {
///
/// This will grow as new widgets are added, but never shrink.
pub fn min_rect(&self) -> Rect {
self.min_rect
self.region.min_rect
}
/// Size of content; same as `min_rect().size()`
pub fn min_size(&self) -> Vec2 {
self.min_rect.size()
self.min_rect().size()
}
/// New widgets will *try* to fit within this rectangle.
@ -226,32 +196,19 @@ impl Ui {
/// If a new widget doesn't fit within the `max_rect` then the
/// `Ui` will make room for it by expanding both `min_rect` and `max_rect`.
pub fn max_rect(&self) -> Rect {
self.max_rect
self.region.max_rect
}
/// Used for animation, kind of hacky
pub(crate) fn force_set_min_rect(&mut self, min_rect: Rect) {
self.min_rect = min_rect;
self.region.min_rect = min_rect;
}
/// This is like `max_rect()`, but will never be infinite.
/// If the desired rect is infinite ("be as big as you want")
/// this will be bounded by `min_rect` instead.
pub fn max_rect_finite(&self) -> Rect {
let mut result = self.max_rect;
if !result.min.x.is_finite() {
result.min.x = self.min_rect.min.x;
}
if !result.min.y.is_finite() {
result.min.y = self.min_rect.min.y;
}
if !result.max.x.is_finite() {
result.max.x = self.min_rect.max.x;
}
if !result.max.y.is_finite() {
result.max.y = self.min_rect.max.y;
}
result
self.region.max_rect_finite()
}
// ------------------------------------------------------------------------
@ -267,11 +224,13 @@ impl Ui {
/// You won't be able to shrink it below the current minimum size.
pub fn set_max_width(&mut self, width: f32) {
if self.layout.dir() == Direction::Horizontal && self.layout.is_reversed() {
debug_assert_eq!(self.min_rect.max.x, self.max_rect.max.x);
self.max_rect.min.x = self.max_rect.max.x - width.at_least(self.min_rect.width());
debug_assert_eq!(self.min_rect().max.x, self.max_rect().max.x);
self.region.max_rect.min.x =
self.region.max_rect.max.x - width.at_least(self.min_rect().width());
} else {
debug_assert_eq!(self.min_rect.min.x, self.max_rect.min.x);
self.max_rect.max.x = self.max_rect.min.x + width.at_least(self.min_rect.width());
debug_assert_eq!(self.min_rect().min.x, self.region.max_rect.min.x);
self.region.max_rect.max.x =
self.region.max_rect.min.x + width.at_least(self.min_rect().width());
}
}
@ -279,11 +238,13 @@ impl Ui {
/// You won't be able to shrink it below the current minimum size.
pub fn set_max_height(&mut self, height: f32) {
if self.layout.dir() == Direction::Vertical && self.layout.is_reversed() {
debug_assert_eq!(self.min_rect.max.y, self.max_rect.max.y);
self.max_rect.min.y = self.max_rect.max.y - height.at_least(self.min_rect.height());
debug_assert_eq!(self.min_rect().max.y, self.region.max_rect.max.y);
self.region.max_rect.min.y =
self.region.max_rect.max.y - height.at_least(self.min_rect().height());
} else {
debug_assert_eq!(self.min_rect.min.y, self.max_rect.min.y);
self.max_rect.max.y = self.max_rect.min.y + height.at_least(self.min_rect.height());
debug_assert_eq!(self.min_rect().min.y, self.region.max_rect.min.y);
self.region.max_rect.max.y =
self.region.max_rect.min.y + height.at_least(self.min_rect().height());
}
}
@ -300,26 +261,30 @@ impl Ui {
/// This can't shrink the ui, only make it larger.
pub fn set_min_width(&mut self, width: f32) {
if self.layout.dir() == Direction::Horizontal && self.layout.is_reversed() {
debug_assert_eq!(self.min_rect.max.x, self.max_rect.max.x);
self.min_rect.min.x = self.min_rect.min.x.min(self.min_rect.max.x - width);
debug_assert_eq!(self.region.min_rect.max.x, self.region.max_rect.max.x);
let min_rect = &mut self.region.min_rect;
min_rect.min.x = min_rect.min.x.min(min_rect.max.x - width);
} else {
debug_assert_eq!(self.min_rect.min.x, self.max_rect.min.x);
self.min_rect.max.x = self.min_rect.max.x.max(self.min_rect.min.x + width);
debug_assert_eq!(self.region.min_rect.min.x, self.region.max_rect.min.x);
let min_rect = &mut self.region.min_rect;
min_rect.max.x = min_rect.max.x.max(min_rect.min.x + width);
}
self.max_rect = self.max_rect.union(self.min_rect);
self.region.max_rect = self.region.max_rect.union(self.min_rect());
}
/// Set the minimum height of the ui.
/// This can't shrink the ui, only make it larger.
pub fn set_min_height(&mut self, height: f32) {
if self.layout.dir() == Direction::Vertical && self.layout.is_reversed() {
debug_assert_eq!(self.min_rect.max.y, self.max_rect.max.y);
self.min_rect.min.y = self.min_rect.min.y.min(self.min_rect.max.y - height);
debug_assert_eq!(self.region.min_rect.max.y, self.region.max_rect.max.y);
let min_rect = &mut self.region.min_rect;
min_rect.min.y = min_rect.min.y.min(min_rect.max.y - height);
} else {
debug_assert_eq!(self.min_rect.min.y, self.max_rect.min.y);
self.min_rect.max.y = self.min_rect.max.y.max(self.min_rect.min.y + height);
debug_assert_eq!(self.region.min_rect.min.y, self.region.max_rect.min.y);
let min_rect = &mut self.region.min_rect;
min_rect.max.y = min_rect.max.y.max(min_rect.min.y + height);
}
self.max_rect = self.max_rect.union(self.min_rect);
self.region.max_rect = self.region.max_rect.union(self.min_rect());
}
// ------------------------------------------------------------------------
@ -339,8 +304,7 @@ impl Ui {
/// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect.
pub fn expand_to_include_rect(&mut self, rect: Rect) {
self.min_rect = self.min_rect.union(rect);
self.max_rect = self.max_rect.union(rect);
self.region.expand_to_include_rect(rect);
}
// ------------------------------------------------------------------------
@ -353,14 +317,14 @@ impl Ui {
/// An infinite rectangle should be interpreted as "as much as you want".
/// In most layouts the next widget will be put in the top left corner of this `Rect`.
pub fn available(&self) -> Rect {
self.layout.available(self.cursor, self.max_rect())
self.layout.available(&self.region)
}
/// This is like `available()`, but will never be infinite.
/// Use this for components that want to grow without bounds (but shouldn't).
/// In most layouts the next widget will be put in the top left corner of this `Rect`.
pub fn available_finite(&self) -> Rect {
self.layout.available(self.cursor, self.max_rect_finite())
self.layout.available_finite(&self.region)
}
}
@ -417,7 +381,7 @@ impl Ui {
/// The direction is dependent on the layout.
/// This is useful for creating some extra space between widgets.
pub fn advance_cursor(&mut self, amount: f32) {
self.layout.advance_cursor(&mut self.cursor, amount);
self.layout.advance_cursor(&mut self.region, amount);
}
/// Reserve this much space and move the cursor.
@ -438,7 +402,7 @@ impl Ui {
let too_wide = desired_size.x > original_size.x;
let too_high = desired_size.y > original_size.y;
let rect = self.reserve_space_impl(desired_size);
let rect = self.allocate_space_impl(desired_size);
let debug_expand_width = self.style().visuals.debug_expand_width;
let debug_expand_height = self.style().visuals.debug_expand_height;
@ -473,17 +437,27 @@ impl Ui {
/// Reserve this much space and move the cursor.
/// Returns where to put the widget.
fn reserve_space_impl(&mut self, child_size: Vec2) -> Rect {
let available_size = self.available_finite().size();
let child_rect = self
.layout
.allocate_space(&mut self.cursor, available_size, child_size);
fn allocate_space_impl(&mut self, child_size: Vec2) -> Rect {
let item_spacing = self.style().spacing.item_spacing;
self.layout.advance_cursor2(&mut self.cursor, item_spacing);
let child_rect = self.layout.allocate_space(&mut self.region, child_size);
self.layout.advance_cursor2(&mut self.region, item_spacing);
self.expand_to_include_rect(child_rect);
self.next_auto_id = self.next_auto_id.wrapping_add(1);
child_rect
}
/// Allocated the given space and then adds content to that space.
/// If the contents overflow, more space will be allocated.
/// At least the amount of space requested will always be allocated.
/// Thus you can ask for a little and use more, but you cannot ask for a lot and use less.
pub fn allocate_ui(&mut self, desired_size: Vec2, add_contents: impl FnOnce(&mut Ui)) -> Rect {
let child_rect = self.allocate_space(desired_size);
let mut child_ui = self.child_ui(child_rect, self.layout);
add_contents(&mut child_ui);
let final_rect = child_ui.region.max_rect;
self.expand_to_include_rect(final_rect);
final_rect
}
}
/// # Adding widgets
@ -705,7 +679,7 @@ impl Ui {
/// will decide how much space will be used in the parent ui.
pub fn add_custom_contents(&mut self, size: Vec2, add_contents: impl FnOnce(&mut Ui)) -> Rect {
let size = size.at_most(self.available().size());
let child_rect = self.layout.rect_from_cursor_size(self.cursor, size);
let child_rect = self.layout.rect_from_cursor_size(&self.region, size);
let mut child_ui = self.child_ui(child_rect, self.layout);
add_contents(&mut child_ui);
self.allocate_space(child_ui.min_size())
@ -731,7 +705,8 @@ impl Ui {
"You can only indent vertical layouts"
);
let indent = vec2(self.style().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 child_rect =
Rect::from_min_max(self.region.cursor + indent, self.max_rect().right_bottom()); // TODO: wrong for reversed layouts
let mut child_ui = Self {
id: self.id.with(id_source),
..self.child_ui(child_rect, self.layout)
@ -773,7 +748,7 @@ impl Ui {
};
self.child_ui(
Rect::from_min_size(
self.cursor + vec2(x, 0.0),
self.region.cursor + vec2(x, 0.0),
vec2(width, self.available().height()),
),
self.layout,
@ -781,13 +756,13 @@ impl Ui {
}
/// Start a ui with horizontal layout.
/// After you have called this, the registers the contents as any other widget.
/// After you have called this, the function registers the contents as any other widget.
///
/// Elements will be centered on the Y axis, i.e.
/// adjusted up and down to lie in the center of the horizontal layout.
/// The initial height is `style.spacing.interact_size.y`.
/// Centering is almost always what you want if you are
/// planning to to mix widgets or just different types of text.
/// planning to to mix widgets or use different types of text.
///
/// The returned `Response` will only have checked for mouse hover
/// but can be used for tooltips (`on_hover_text`).
@ -820,7 +795,9 @@ impl Ui {
initial_size: Vec2,
add_contents: impl FnOnce(&mut Self) -> R,
) -> (R, Response) {
let child_rect = self.layout.rect_from_cursor_size(self.cursor, initial_size);
let child_rect = self
.layout
.rect_from_cursor_size(&self.region, initial_size);
let mut child_ui = self.child_ui(child_rect, layout);
let ret = add_contents(&mut child_ui);
let size = child_ui.min_size();
@ -860,10 +837,11 @@ impl Ui {
let mut columns: Vec<Self> = (0..num_columns)
.map(|col_idx| {
let pos = self.cursor + vec2((col_idx as f32) * (column_width + spacing), 0.0);
let pos =
self.region.cursor + vec2((col_idx as f32) * (column_width + spacing), 0.0);
let child_rect = Rect::from_min_max(
pos,
pos2(pos.x + column_width, self.max_rect.right_bottom().y),
pos2(pos.x + column_width, self.max_rect().right_bottom().y),
);
self.child_ui(child_rect, self.layout)
})
@ -873,7 +851,7 @@ impl Ui {
let mut sum_width = total_spacing;
for column in &columns {
sum_width += column.min_rect.width();
sum_width += column.min_rect().width();
}
let mut max_height = 0.0;
@ -894,6 +872,6 @@ impl Ui {
impl Ui {
/// Shows where the next widget is going to be placed
pub fn debug_paint_cursor(&self) {
self.layout.debug_paint_cursor(self.cursor, &self.painter);
self.layout.debug_paint_cursor(&self.region, &self.painter);
}
}