use egui::{
Align, Align2, Id, InnerResponse, Layout, Margin, Pos2, Rect, Sense, Ui, UiBuilder, Vec2,
};
use crate::resize_layout_rect;
pub trait Alignment {
fn align(self, item_size: Vec2, bounds: Rect) -> Rect;
}
impl Alignment for egui::Align2 {
fn align(self, item_size: Vec2, bounds: Rect) -> Rect {
self.align_size_within_rect(item_size, bounds)
}
}
impl<T> Alignment for T
where
T: FnOnce(Vec2, Rect) -> Rect,
{
fn align(self, item_size: Vec2, bounds: Rect) -> Rect {
self(item_size, bounds)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AllocateType {
None,
Content,
ContentRow,
ContentColumn,
Bounds,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Bounds {
AvailableRect(Vec2),
MaxRect(Margin),
}
impl Bounds {
#[inline]
pub fn available_rect() -> Self {
Bounds::AvailableRect(Vec2::INFINITY)
}
#[inline]
pub fn max_rect() -> Self {
Bounds::MaxRect(0.0.into())
}
}
pub struct Aligner<T: Alignment> {
pub id: Option<Id>,
pub align: T,
pub bounds: Bounds,
pub allocate_type: AllocateType,
pub layout: Option<Layout>,
}
impl Default for Aligner<Align2> {
fn default() -> Self {
Self {
id: None,
align: egui::Align2::LEFT_TOP,
bounds: Bounds::available_rect(),
allocate_type: AllocateType::Content,
layout: None,
}
}
}
impl Aligner<Align2> {
#[inline]
pub fn center() -> Self {
Self::from_align(Align2::CENTER_CENTER)
}
#[inline]
pub fn center_top() -> Self {
Self::from_align(Align2::CENTER_TOP)
}
#[inline]
pub fn center_bottom() -> Self {
Self::from_align(Align2::CENTER_BOTTOM)
}
#[inline]
pub fn left() -> Self {
Self::from_align(Align2::LEFT_CENTER)
}
#[inline]
pub fn left_top() -> Self {
Self::from_align(Align2::LEFT_TOP)
}
#[inline]
pub fn left_bottom() -> Self {
Self::from_align(Align2::LEFT_BOTTOM)
}
#[inline]
pub fn right() -> Self {
Self::from_align(Align2::RIGHT_CENTER)
}
#[inline]
pub fn right_top() -> Self {
Self::from_align(Align2::RIGHT_TOP)
}
#[inline]
pub fn right_bottom() -> Self {
Self::from_align(Align2::RIGHT_BOTTOM)
}
}
impl<T: Alignment> Aligner<T> {
pub fn from_align(align: T) -> Self {
Self {
id: None,
align,
bounds: Bounds::AvailableRect(Vec2::INFINITY),
allocate_type: AllocateType::Content,
layout: None,
}
}
}
impl<T: Alignment> Aligner<T> {
#[inline]
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self
}
#[inline]
pub fn align(mut self, align: T) -> Self {
self.align = align;
self
}
#[inline]
pub fn bounds(mut self, bounds: Bounds) -> Self {
self.bounds = bounds;
self
}
#[inline]
pub fn allocate_type(mut self, allocate_type: AllocateType) -> Self {
self.allocate_type = allocate_type;
self
}
#[inline]
pub fn layout(mut self, layout: Layout) -> Self {
self.layout = Some(layout);
self
}
}
impl<T: Alignment> Aligner<T> {
pub fn show<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> InnerResponse<R> {
let id = self.id.unwrap_or_else(|| {
let id = ui.next_auto_id();
ui.skip_ahead_auto_ids(1);
id
});
let layout = self.layout.unwrap_or(*ui.layout());
let bounds = match self.bounds {
Bounds::AvailableRect(size) => {
ui.new_child(UiBuilder::new())
.allocate_space(size.min(ui.available_size()))
.1
}
Bounds::MaxRect(margin) => ui.max_rect() - margin,
};
let mut memorized = true;
let content_size = ui.ctx().data(|r| r.get_temp(id)).unwrap_or_else(|| {
memorized = false;
bounds.size()
});
let expected_rect = self.align.align(content_size, bounds);
let content_rect = resize_layout_rect(expected_rect, bounds.size(), &layout);
let mut child_ui = ui.new_child({
let builder = UiBuilder::new().max_rect(content_rect).layout(layout);
if memorized {
builder
} else {
ui.ctx().request_discard("new Aligner");
builder.sizing_pass().invisible()
}
});
let inner = add_contents(&mut child_ui);
let content_rect = if memorized {
expected_rect
} else {
content_rect
};
let response = ui.allocate_rect(
match self.allocate_type {
AllocateType::None => Rect::from_min_size(ui.next_widget_position(), Vec2::ZERO),
AllocateType::Content => content_rect,
AllocateType::ContentRow => {
let min = Pos2::new(bounds.left(), content_rect.top());
let max = Pos2::new(bounds.right(), content_rect.bottom());
Rect::from_min_max(min, max)
}
AllocateType::ContentColumn => {
let min = Pos2::new(content_rect.left(), bounds.top());
let max = Pos2::new(content_rect.right(), bounds.bottom());
Rect::from_min_max(min, max)
}
AllocateType::Bounds => bounds,
},
Sense::hover(),
);
let new_size = child_ui.min_size();
if new_size != content_size || !memorized {
ui.ctx().data_mut(|w| w.insert_temp(id, new_size));
}
InnerResponse { inner, response }
}
#[inline]
pub fn show_horizontal<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = if ui.layout().prefer_right_to_left() {
Layout::right_to_left(Align::Center)
} else {
Layout::left_to_right(Align::Center)
}
.with_main_wrap(false);
self.layout(layout).show(ui, add_contents)
}
#[inline]
pub fn show_horizontal_wrapped<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = if ui.layout().prefer_right_to_left() {
Layout::right_to_left(Align::Center)
} else {
Layout::left_to_right(Align::Center)
}
.with_main_wrap(true);
self.layout(layout).show(ui, add_contents)
}
#[inline]
pub fn show_vertical<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = Layout::top_down(Align::Center);
self.layout(layout).show(ui, add_contents)
}
}
#[inline]
pub fn center_horizontal<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = if ui.layout().prefer_right_to_left() {
Layout::right_to_left(Align::Center)
} else {
Layout::left_to_right(Align::Center)
};
Aligner::center().layout(layout).show(ui, add_contents)
}
#[inline]
pub fn center_horizontal_wrapped<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = if ui.layout().prefer_right_to_left() {
Layout::right_to_left(Align::Center)
} else {
Layout::left_to_right(Align::Center)
}
.with_main_wrap(true);
Aligner::center().layout(layout).show(ui, add_contents)
}
#[inline]
pub fn center_vertical<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
Aligner::center()
.layout(Layout::top_down(Align::Center))
.show(ui, add_contents)
}
#[inline]
pub fn top_horizontal<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
let layout = if ui.layout().prefer_right_to_left() {
Layout::right_to_left(Align::TOP)
} else {
Layout::left_to_right(Align::TOP)
};
Aligner::from_align(egui::Align2::CENTER_TOP)
.layout(layout)
.show(ui, add_contents)
}
#[inline]
pub fn top_horizontal_wrapped<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = if ui.layout().prefer_right_to_left() {
Layout::right_to_left(Align::TOP)
} else {
Layout::left_to_right(Align::TOP)
}
.with_main_wrap(true);
Aligner::from_align(Align2::CENTER_TOP)
.layout(layout)
.show(ui, add_contents)
}
#[inline]
pub fn top_vertical<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
ui.vertical_centered(add_contents)
}
#[inline]
pub fn bottom_horizontal<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = if ui.layout().prefer_right_to_left() {
Layout::right_to_left(Align::BOTTOM)
} else {
Layout::left_to_right(Align::BOTTOM)
};
Aligner::from_align(egui::Align2::CENTER_BOTTOM)
.layout(layout)
.show(ui, add_contents)
}
#[inline]
pub fn bottom_horizontal_wrapped<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = if ui.layout().prefer_right_to_left() {
Layout::right_to_left(Align::BOTTOM)
} else {
Layout::left_to_right(Align::BOTTOM)
}
.with_main_wrap(true);
Aligner::from_align(egui::Align2::CENTER_BOTTOM)
.layout(layout)
.show(ui, add_contents)
}
#[inline]
pub fn bottom_vertical<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
ui.with_layout(Layout::bottom_up(Align::Center), add_contents)
}
#[inline]
pub fn left_horizontal<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
ui.horizontal_centered(add_contents)
}
#[inline]
pub fn left_horizontal_wrapped<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = Layout::left_to_right(Align::Center).with_main_wrap(true);
Aligner::from_align(egui::Align2::LEFT_CENTER)
.layout(layout)
.show(ui, add_contents)
}
#[inline]
pub fn left_vertical<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
Aligner::from_align(egui::Align2::LEFT_CENTER)
.layout(Layout::top_down(Align::Min))
.show(ui, add_contents)
}
#[inline]
pub fn right_horizontal<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
ui.with_layout(Layout::right_to_left(Align::Center), add_contents)
}
#[inline]
pub fn right_horizontal_wrapped<R>(
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layout = Layout::right_to_left(Align::Center).with_main_wrap(true);
Aligner::from_align(Align2::RIGHT_CENTER)
.layout(layout)
.show(ui, add_contents)
}
#[inline]
pub fn right_vertical<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
Aligner::from_align(Align2::RIGHT_CENTER)
.layout(Layout::top_down(Align::Max))
.show(ui, add_contents)
}