use crate::{emath::*, Align};
#[derive(Clone, Copy, Debug)]
pub(crate) struct Region {
pub min_rect: Rect,
pub max_rect: Rect,
pub(crate) cursor: Pos2,
}
impl Region {
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
}
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);
}
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.max_rect.min.x = self.max_rect.min.x.min(x);
self.max_rect.max.x = self.max_rect.max.x.max(x);
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(rename_all = "snake_case"))]
pub enum Direction {
LeftToRight,
RightToLeft,
TopDown,
BottomUp,
}
impl Direction {
pub fn is_horizontal(self) -> bool {
match self {
Direction::LeftToRight | Direction::RightToLeft => true,
Direction::TopDown | Direction::BottomUp => false,
}
}
pub fn is_vertical(self) -> bool {
match self {
Direction::LeftToRight | Direction::RightToLeft => false,
Direction::TopDown | Direction::BottomUp => true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Layout {
main_dir: Direction,
main_wrap: bool,
main_align: Align,
main_justify: bool,
cross_align: Align,
cross_justify: bool,
}
impl Default for Layout {
fn default() -> Self {
Self {
main_dir: Direction::TopDown,
main_wrap: false,
main_align: Align::TOP,
main_justify: false,
cross_align: Align::LEFT,
cross_justify: false,
}
}
}
impl Layout {
pub fn left_to_right() -> Self {
Self {
main_dir: Direction::LeftToRight,
main_wrap: false,
main_align: Align::Center,
main_justify: false,
cross_align: Align::Center,
cross_justify: false,
}
}
pub fn right_to_left() -> Self {
Self {
main_dir: Direction::RightToLeft,
main_wrap: false,
main_align: Align::Center,
main_justify: false,
cross_align: Align::Center,
cross_justify: false,
}
}
pub fn top_down(cross_align: Align) -> Self {
Self {
main_dir: Direction::TopDown,
main_wrap: false,
main_align: Align::Center,
main_justify: false,
cross_align,
cross_justify: false,
}
}
pub fn top_down_justified(cross_align: Align) -> Self {
Self::top_down(cross_align).with_cross_justify(true)
}
pub fn bottom_up(cross_align: Align) -> Self {
Self {
main_dir: Direction::BottomUp,
main_wrap: false,
main_align: Align::Center,
main_justify: false,
cross_align,
cross_justify: false,
}
}
pub fn from_main_dir_and_cross_align(main_dir: Direction, cross_align: Align) -> Self {
Self {
main_dir,
main_wrap: false,
main_align: Align::Center,
main_justify: false,
cross_align,
cross_justify: false,
}
}
pub fn centered_and_justified(main_dir: Direction) -> Self {
Self {
main_dir,
main_wrap: false,
main_align: Align::Center,
main_justify: true,
cross_align: Align::Center,
cross_justify: true,
}
}
#[deprecated = "Use `top_down`"]
pub fn vertical(cross_align: Align) -> Self {
Self::top_down(cross_align)
}
#[deprecated = "Use `left_to_right`"]
pub fn horizontal(cross_align: Align) -> Self {
Self::left_to_right().with_cross_align(cross_align)
}
pub fn with_main_wrap(self, main_wrap: bool) -> Self {
Self { main_wrap, ..self }
}
pub fn with_cross_align(self, cross_align: Align) -> Self {
Self {
cross_align,
..self
}
}
pub fn with_cross_justify(self, cross_justify: bool) -> Self {
Self {
cross_justify,
..self
}
}
}
impl Layout {
pub fn main_dir(&self) -> Direction {
self.main_dir
}
pub fn main_wrap(&self) -> bool {
self.main_wrap
}
pub fn cross_align(&self) -> Align {
self.cross_align
}
pub fn cross_justify(&self) -> bool {
self.cross_justify
}
pub fn is_horizontal(&self) -> bool {
self.main_dir().is_horizontal()
}
pub fn is_vertical(&self) -> bool {
self.main_dir().is_vertical()
}
pub fn prefer_right_to_left(&self) -> bool {
self.main_dir == Direction::RightToLeft
|| self.main_dir.is_vertical() && self.cross_align == Align::Max
}
fn horizontal_align(&self) -> Align {
if self.is_horizontal() {
self.main_align
} else {
self.cross_align
}
}
fn vertical_align(&self) -> Align {
if self.is_vertical() {
self.main_align
} else {
self.cross_align
}
}
fn align2(&self) -> Align2 {
Align2([self.horizontal_align(), self.vertical_align()])
}
fn horizontal_justify(&self) -> bool {
if self.is_horizontal() {
self.main_justify
} else {
self.cross_justify
}
}
fn vertical_justify(&self) -> bool {
if self.is_vertical() {
self.main_justify
} else {
self.cross_justify
}
}
}
impl Layout {
pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect {
self.align2().align_size_within_rect(size, outer)
}
fn initial_cursor(&self, max_rect: Rect) -> Pos2 {
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(),
}
}
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,
}
}
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()
}
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)
} else {
vec2(r.max_rect.right() - r.cursor.x, r.max_rect.height())
}
} else {
self.available_from_cursor_max_rect(r.cursor, r.max_rect)
.size()
}
}
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
}
#[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;
if self.main_wrap {
let available_size = self.available_size_before_wrap(region);
match self.main_dir {
Direction::LeftToRight => {
if available_size.x < child_size.x && region.max_rect.left() < cursor.x {
cursor = pos2(
region.max_rect.left(),
region.max_rect.bottom() + item_spacing.y,
);
}
}
Direction::RightToLeft => {
if available_size.x < child_size.x && cursor.x < region.max_rect.right() {
cursor = pos2(
region.max_rect.right(),
region.max_rect.bottom() + item_spacing.y,
);
}
}
Direction::TopDown => {
if available_size.y < child_size.y && region.max_rect.top() < cursor.y {
cursor = pos2(
region.max_rect.right() + item_spacing.x,
region.max_rect.top(),
);
}
}
Direction::BottomUp => {
if available_size.y < child_size.y && cursor.y < region.max_rect.bottom() {
cursor = pos2(
region.max_rect.right() + item_spacing.x,
region.max_rect.bottom(),
);
}
}
}
}
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);
}
if self.is_horizontal() || self.vertical_justify() {
child_size.y = child_size.y.at_least(available_size.y);
}
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),
};
Rect::from_min_size(child_pos, child_size)
}
pub(crate) fn justify_and_align(&self, rect: Rect, mut child_size: Vec2) -> Rect {
if self.horizontal_justify() {
child_size.x = child_size.x.at_least(rect.width());
}
if self.vertical_justify() {
child_size.y = child_size.y.at_least(rect.height());
}
self.align_size_within_rect(child_size, rect)
}
pub(crate) fn advance_cursor(&self, cursor: &mut Pos2, 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,
}
}
pub(crate) fn advance_after_rects(
&self,
cursor: &mut Pos2,
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),
};
}
pub(crate) fn end_row(&mut self, region: &mut Region, item_spacing: Vec2) {
if self.main_wrap && self.is_horizontal() {
region.cursor = pos2(
region.max_rect.left(),
region.max_rect.bottom() + item_spacing.y,
);
}
}
}
impl Layout {
pub(crate) fn debug_paint_cursor(
&self,
region: &Region,
stroke: epaint::Stroke,
painter: &crate::Painter,
) {
use epaint::*;
let cursor = region.cursor;
let align;
let l = 64.0;
match self.main_dir {
Direction::LeftToRight => {
painter.arrow(cursor, vec2(l, 0.0), stroke);
align = Align2::LEFT_TOP;
}
Direction::RightToLeft => {
painter.arrow(cursor, vec2(-l, 0.0), stroke);
align = Align2::RIGHT_TOP;
}
Direction::TopDown => {
painter.arrow(cursor, vec2(0.0, l), stroke);
align = Align2::LEFT_TOP;
}
Direction::BottomUp => {
painter.arrow(cursor, vec2(0.0, -l), stroke);
align = Align2::LEFT_BOTTOM;
}
}
painter.text(cursor, align, "cursor", TextStyle::Monospace, stroke.color);
}
}