Add support for reversed layouts

This commit is contained in:
Emil Ernerfeldt 2020-05-13 22:24:32 +02:00
parent d4204f03c0
commit cd1bbddaca
3 changed files with 99 additions and 31 deletions

View File

@ -446,6 +446,7 @@ use crate::layout::*;
struct LayoutExample { struct LayoutExample {
dir: Direction, dir: Direction,
align: Option<Align>, // None == jusitifed align: Option<Align>, // None == jusitifed
reversed: bool,
} }
impl Default for LayoutExample { impl Default for LayoutExample {
@ -453,6 +454,7 @@ impl Default for LayoutExample {
Self { Self {
dir: Direction::Vertical, dir: Direction::Vertical,
align: Some(Align::Center), align: Some(Align::Center),
reversed: false,
} }
} }
} }
@ -465,7 +467,12 @@ impl LayoutExample {
} }
pub fn contents_ui(&mut self, ui: &mut Ui) { pub fn contents_ui(&mut self, ui: &mut Ui) {
ui.set_layout(Layout::from_dir_align(self.dir, self.align)); let layout = Layout::from_dir_align(self.dir, self.align);
if self.reversed {
ui.set_layout(layout.reverse());
} else {
ui.set_layout(layout);
}
ui.add(label!("Available space: {:?}", ui.available().size())); ui.add(label!("Available space: {:?}", ui.available().size()));
if ui.add(Button::new("Reset")).clicked { if ui.add(Button::new("Reset")).clicked {
@ -485,6 +492,8 @@ impl LayoutExample {
} }
} }
ui.add(Checkbox::new(&mut self.reversed, "Reversed"));
ui.add(Separator::new()); ui.add(Separator::new());
ui.add(label!("Align:")); ui.add(label!("Align:"));

View File

@ -64,6 +64,9 @@ pub struct Layout {
/// For horizontal layouts: put things to top, center or bottom? /// For horizontal layouts: put things to top, center or bottom?
/// None means justified, which means full width (vertical layout) or height (horizontal layouts). /// None means justified, which means full width (vertical layout) or height (horizontal layouts).
align: Option<Align>, align: Option<Align>,
/// Lay out things in reversed order, i.e. from the right or bottom-up.
reversed: bool,
} }
impl Default for Layout { impl Default for Layout {
@ -71,6 +74,7 @@ impl Default for Layout {
Self { Self {
dir: Direction::Vertical, dir: Direction::Vertical,
align: Some(Align::Min), align: Some(Align::Min),
reversed: false,
} }
} }
} }
@ -78,13 +82,18 @@ impl Default for Layout {
impl Layout { impl Layout {
/// None align means justified, e.g. fill full width/height. /// None align means justified, e.g. fill full width/height.
pub fn from_dir_align(dir: Direction, align: Option<Align>) -> Self { pub fn from_dir_align(dir: Direction, align: Option<Align>) -> Self {
Self { dir, align } Self {
dir,
align,
reversed: false,
}
} }
pub fn vertical(align: Align) -> Self { pub fn vertical(align: Align) -> Self {
Self { Self {
dir: Direction::Vertical, dir: Direction::Vertical,
align: Some(align), align: Some(align),
reversed: false,
} }
} }
@ -92,19 +101,47 @@ impl Layout {
Self { Self {
dir: Direction::Horizontal, dir: Direction::Horizontal,
align: Some(align), align: Some(align),
reversed: false,
} }
} }
/// Full-width layout. /// Full-width layout.
/// Nice for menues etc where each button is full width. /// Nice for menues etc where each button is full width.
pub fn justified(dir: Direction) -> Self { pub fn justified(dir: Direction) -> Self {
Self { dir, align: None } Self {
dir,
align: None,
reversed: false,
}
}
#[must_use]
pub fn reverse(self) -> Self {
Self {
dir: self.dir,
align: self.align,
reversed: !self.reversed,
}
} }
pub fn dir(&self) -> Direction { pub fn dir(&self) -> Direction {
self.dir self.dir
} }
pub fn is_reversed(&self) -> bool {
self.reversed
}
/// Given the cursor in the region, how much space is available
/// for the next widget?
pub fn available(&self, cursor: Pos2, rect: Rect) -> Rect {
if self.reversed {
Rect::from_min_max(rect.min, cursor)
} else {
Rect::from_min_max(cursor, rect.max)
}
}
/// Reserve this much space and move the cursor. /// Reserve this much space and move the cursor.
/// Returns where to put the widget. /// Returns where to put the widget.
/// ///
@ -126,15 +163,12 @@ impl Layout {
) -> Rect { ) -> Rect {
let available_size = available_size.max(child_size); let available_size = available_size.max(child_size);
let mut child_pos = *cursor; let mut child_move = Vec2::default();
let mut cursor_change = Vec2::default();
if self.dir == Direction::Horizontal { if self.dir == Direction::Horizontal {
if let Some(align) = self.align { if let Some(align) = self.align {
if align != Align::Min { child_move.y += match align {
debug_assert!(available_size.y.is_finite());
debug_assert!(child_size.y.is_finite());
}
child_pos.y += match align {
Align::Min => 0.0, Align::Min => 0.0,
Align::Center => 0.5 * (available_size.y - child_size.y), Align::Center => 0.5 * (available_size.y - child_size.y),
Align::Max => available_size.y - child_size.y, Align::Max => available_size.y - child_size.y,
@ -144,16 +178,11 @@ impl Layout {
child_size.y = child_size.y.max(available_size.y); child_size.y = child_size.y.max(available_size.y);
} }
cursor.x += child_size.x; cursor_change.x += child_size.x;
cursor.x += style.item_spacing.x; // Where to put next thing, if there is a next thing cursor_change.x += style.item_spacing.x; // Where to put next thing, if there is a next thing
} else { } else {
if let Some(align) = self.align { if let Some(align) = self.align {
if align != Align::Min { child_move.x += match align {
debug_assert!(available_size.y.is_finite());
debug_assert!(child_size.y.is_finite());
}
child_pos.x += match align {
Align::Min => 0.0, Align::Min => 0.0,
Align::Center => 0.5 * (available_size.x - child_size.x), Align::Center => 0.5 * (available_size.x - child_size.x),
Align::Max => available_size.x - child_size.x, Align::Max => available_size.x - child_size.x,
@ -162,10 +191,31 @@ impl Layout {
// justified: fill full width // justified: fill full width
child_size.x = child_size.x.max(available_size.x); child_size.x = child_size.x.max(available_size.x);
}; };
cursor.y += child_size.y; cursor_change.y += child_size.y;
cursor.y += style.item_spacing.y; // Where to put next thing, if there is a next thing cursor_change.y += style.item_spacing.y; // Where to put next thing, if there is a next thing
} }
Rect::from_min_size(child_pos, child_size) if self.is_reversed() {
// reverse: cursor starts at bottom right corner of new widget.
let child_pos = if self.dir == Direction::Horizontal {
pos2(
cursor.x - child_size.x,
cursor.y - available_size.y + child_move.y,
)
} else {
pos2(
cursor.x - available_size.x + child_move.x,
cursor.y - child_size.y,
)
};
// let child_pos = *cursor - child_move - child_size;
*cursor -= cursor_change;
Rect::from_min_size(child_pos, child_size)
} else {
let child_pos = *cursor + child_move;
*cursor += cursor_change;
Rect::from_min_size(child_pos, child_size)
}
} }
} }

View File

@ -164,7 +164,17 @@ impl Ui {
self.desired_rect.max.max(self.child_bounds.max) self.desired_rect.max.max(self.child_bounds.max)
} }
pub fn finite_bottom_right(&self) -> Pos2 { /// Position and current size of the ui.
/// The size is the maximum of the origional (minimum/desired) size and
/// the size of the containted children.
pub fn rect(&self) -> Rect {
Rect::from_min_max(self.top_left(), self.bottom_right())
}
/// This is like `rect()`, but will never be infinite.
/// If the desired rect is infinite ("be as big as you want")
/// this will be bounded by child bounds.
pub fn rect_finite(&self) -> Rect {
let mut bottom_right = self.child_bounds.max; let mut bottom_right = self.child_bounds.max;
if self.desired_rect.max.x.is_finite() { if self.desired_rect.max.x.is_finite() {
bottom_right.x = bottom_right.x.max(self.desired_rect.max.x); bottom_right.x = bottom_right.x.max(self.desired_rect.max.x);
@ -172,14 +182,8 @@ impl Ui {
if self.desired_rect.max.y.is_finite() { if self.desired_rect.max.y.is_finite() {
bottom_right.y = bottom_right.y.max(self.desired_rect.max.y); bottom_right.y = bottom_right.y.max(self.desired_rect.max.y);
} }
bottom_right
}
/// Position and current size of the ui. Rect::from_min_max(self.top_left(), bottom_right)
/// The size is the maximum of the origional (minimum/desired) size and
/// the size of the containted children.
pub fn rect(&self) -> Rect {
Rect::from_min_max(self.top_left(), self.bottom_right())
} }
/// Set the width of the ui. /// Set the width of the ui.
@ -232,14 +236,14 @@ impl Ui {
/// An infinite rectangle should be interpred as "as much as you want". /// An infinite rectangle should be interpred as "as much as you want".
/// In most layouts the next widget will be put in the top left corner of this `Rect`. /// In most layouts the next widget will be put in the top left corner of this `Rect`.
pub fn available(&self) -> Rect { pub fn available(&self) -> Rect {
Rect::from_min_max(self.cursor, self.bottom_right()) self.layout.available(self.cursor, self.rect())
} }
/// This is like `available()`, but will never be infinite. /// This is like `available()`, but will never be infinite.
/// Use this for components that want to grow without bounds (but shouldn't). /// 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`. /// In most layouts the next widget will be put in the top left corner of this `Rect`.
pub fn available_finite(&self) -> Rect { pub fn available_finite(&self) -> Rect {
Rect::from_min_max(self.cursor, self.finite_bottom_right()) self.layout.available(self.cursor, self.rect_finite())
} }
pub fn layout(&self) -> &Layout { pub fn layout(&self) -> &Layout {
@ -249,6 +253,11 @@ impl Ui {
// TODO: remove // TODO: remove
pub fn set_layout(&mut self, layout: Layout) { pub fn set_layout(&mut self, layout: Layout) {
self.layout = layout; self.layout = layout;
// TODO: remove this HACK:
if layout.is_reversed() {
self.cursor = self.rect_finite().max;
}
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------