221 lines
6.6 KiB
Rust
221 lines
6.6 KiB
Rust
use serde_derive::{Deserialize, Serialize};
|
|
|
|
use crate::{math::*, style::Style};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum Direction {
|
|
Horizontal,
|
|
Vertical,
|
|
}
|
|
|
|
impl Default for Direction {
|
|
fn default() -> Direction {
|
|
Direction::Vertical
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum Align {
|
|
/// Left/Top
|
|
Min,
|
|
|
|
/// Note: requires a bounded/known available_width.
|
|
Center,
|
|
|
|
/// Right/Bottom
|
|
/// Note: requires a bounded/known available_width.
|
|
Max,
|
|
}
|
|
|
|
impl Default for Align {
|
|
fn default() -> Align {
|
|
Align::Min
|
|
}
|
|
}
|
|
|
|
/// Used e.g. to anchor a piece of text to a part of the rectangle.
|
|
/// Give a position within the rect, specified by the aligns
|
|
pub fn align_rect(rect: Rect, align: (Align, Align)) -> Rect {
|
|
let x = match align.0 {
|
|
Align::Min => rect.left(),
|
|
Align::Center => rect.left() - 0.5 * rect.width(),
|
|
Align::Max => rect.left() - rect.width(),
|
|
};
|
|
let y = match align.1 {
|
|
Align::Min => rect.top(),
|
|
Align::Center => rect.top() - 0.5 * rect.height(),
|
|
Align::Max => rect.top() - rect.height(),
|
|
};
|
|
Rect::from_min_size(pos2(x, y), rect.size())
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
|
|
pub struct Layout {
|
|
/// Lay out things horizontally or vertically?
|
|
dir: Direction,
|
|
|
|
/// For vertical layouts: put things to left, center or right?
|
|
/// For horizontal layouts: put things to top, center or bottom?
|
|
/// None means justified, which means full width (vertical layout) or height (horizontal layouts).
|
|
align: Option<Align>,
|
|
|
|
/// Lay out things in reversed order, i.e. from the right or bottom-up.
|
|
reversed: bool,
|
|
}
|
|
|
|
impl Default for Layout {
|
|
fn default() -> Self {
|
|
Self {
|
|
dir: Direction::Vertical,
|
|
align: Some(Align::Min),
|
|
reversed: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Layout {
|
|
/// None align means justified, e.g. fill full width/height.
|
|
pub fn from_dir_align(dir: Direction, align: Option<Align>) -> Self {
|
|
Self {
|
|
dir,
|
|
align,
|
|
reversed: false,
|
|
}
|
|
}
|
|
|
|
pub fn vertical(align: Align) -> Self {
|
|
Self {
|
|
dir: Direction::Vertical,
|
|
align: Some(align),
|
|
reversed: false,
|
|
}
|
|
}
|
|
|
|
pub fn horizontal(align: Align) -> Self {
|
|
Self {
|
|
dir: Direction::Horizontal,
|
|
align: Some(align),
|
|
reversed: false,
|
|
}
|
|
}
|
|
|
|
/// Full-width layout.
|
|
/// Nice for menues etc where each button is full width.
|
|
pub fn justified(dir: Direction) -> Self {
|
|
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 {
|
|
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.
|
|
/// Returns where to put the widget.
|
|
///
|
|
/// # How sizes are negotiated
|
|
/// Each widget should have a *minimum desired size* and a *desired size*.
|
|
/// When asking for space, ask AT LEAST for you minimum, and don't ask for more than you need.
|
|
/// If you want to fill the space, ask about `available().size()` and use that.
|
|
///
|
|
/// You may get MORE space than you asked for, for instance
|
|
/// 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,
|
|
style: &Style,
|
|
available_size: Vec2,
|
|
mut child_size: Vec2,
|
|
) -> Rect {
|
|
let available_size = available_size.max(child_size);
|
|
|
|
let mut child_move = Vec2::default();
|
|
let mut cursor_change = Vec2::default();
|
|
|
|
if self.dir == Direction::Horizontal {
|
|
if let Some(align) = self.align {
|
|
child_move.y += match align {
|
|
Align::Min => 0.0,
|
|
Align::Center => 0.5 * (available_size.y - child_size.y),
|
|
Align::Max => available_size.y - child_size.y,
|
|
};
|
|
} else {
|
|
// justified: fill full height
|
|
child_size.y = child_size.y.max(available_size.y);
|
|
}
|
|
|
|
cursor_change.x += child_size.x;
|
|
cursor_change.x += style.item_spacing.x; // Where to put next thing, if there is a next thing
|
|
} else {
|
|
if let Some(align) = self.align {
|
|
child_move.x += match align {
|
|
Align::Min => 0.0,
|
|
Align::Center => 0.5 * (available_size.x - child_size.x),
|
|
Align::Max => available_size.x - child_size.x,
|
|
};
|
|
} else {
|
|
// justified: fill full width
|
|
child_size.x = child_size.x.max(available_size.x);
|
|
};
|
|
cursor_change.y += child_size.y;
|
|
cursor_change.y += style.item_spacing.y; // Where to put next thing, if there is a next thing
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|