use emath::{Align, Align2, Rect, Vec2};
use crate::{AsIdSalt, Direction, IdSalt, InnerResponse, Ui, UiBuilder};
#[must_use = "You should call .show()"]
#[derive(Clone, Copy, Debug)]
pub struct Aligned {
align2: Align2,
id_salt: Option<IdSalt>,
}
impl Aligned {
pub fn new(align2: Align2) -> Self {
Self {
align2,
id_salt: None,
}
}
#[inline]
pub fn id_salt(mut self, id_salt: impl AsIdSalt) -> Self {
self.id_salt = Some(IdSalt::new(id_salt));
self
}
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
let Self { align2, id_salt } = self;
let id_salt = id_salt.unwrap_or_else(|| IdSalt::new("aligned"));
let id = ui.make_persistent_id(id_salt);
let size_id = id.with("size");
let available_rect = ui.available_rect_before_wrap();
let cursor_before = ui.cursor();
let last_size: Option<Vec2> = ui.data(|d| d.get_temp(size_id));
let content_rect = if let Some(size) = last_size {
let left_top = place(align2, size, available_rect).left_top();
Rect::from_min_size(left_top, available_rect.size())
} else {
available_rect
};
let mut builder = UiBuilder::new().scope_id(id).max_rect(content_rect);
if last_size.is_none() {
builder = builder.invisible();
}
let response = ui.scope_builder(builder, add_contents);
let size = response.response.rect.size();
if last_size != Some(size) {
ui.ctx().request_discard("Aligned: size changed");
}
ui.data_mut(|d| d.insert_temp(size_id, size));
let placed_rect = place(align2, size, available_rect);
let main_dir = ui.layout().main_dir();
let axis = usize::from(main_dir.is_vertical()); let spacing = ui.spacing().item_spacing[axis];
let mut cursor = cursor_before;
match (main_dir, align2.0[axis]) {
(Direction::LeftToRight | Direction::TopDown, Align::Max) => {
cursor.max[axis] = placed_rect.min[axis] - spacing;
ui.set_cursor(cursor);
}
(Direction::RightToLeft | Direction::BottomUp, Align::Min) => {
cursor.min[axis] = placed_rect.max[axis] + spacing;
ui.set_cursor(cursor);
}
_ => {}
}
response
}
}
fn place(align2: Align2, size: Vec2, available_rect: Rect) -> Rect {
let left_top = align2
.align_size_within_rect(size, available_rect)
.left_top()
.max(available_rect.left_top());
Rect::from_min_size(left_top, size)
}
#[cfg(test)]
mod tests {
use emath::vec2;
use crate::{Align2, Aligned, Context, FontDefinitions, Rect};
fn run_frame(ctx: &Context) -> (Rect, Rect) {
let mut rects = None;
let output = ctx.run_ui(Default::default(), |ui| {
let available_rect = ui.available_rect_before_wrap();
let response = Aligned::new(Align2::RIGHT_BOTTOM).show(ui, |ui| {
ui.allocate_space(vec2(30.0, 20.0));
});
rects = Some((response.response.rect, available_rect));
});
output.drop_without_applying_deltas();
rects.expect("The closure was not called")
}
#[test]
fn contents_end_up_aligned() {
let ctx = Context::default();
ctx.set_fonts(FontDefinitions::empty());
run_frame(&ctx);
let (content_rect, available_rect) = run_frame(&ctx);
assert_eq!(content_rect.size(), vec2(30.0, 20.0));
assert!((content_rect.right() - available_rect.right()).abs() < 0.01);
assert!((content_rect.bottom() - available_rect.bottom()).abs() < 0.01);
}
}