pub mod column;
pub mod row;
pub use column::*;
pub use row::*;
use crate::resize_layout_rect;
use egui::emath::GuiRounding;
use egui::{Id, InnerResponse, Layout, Sense, Ui, UiBuilder, Vec2};
pub struct Container {
pub id: Option<Id>,
pub layout: Layout,
pub padding: egui::Margin,
pub max_size: Vec2,
pub min_size: Vec2,
}
impl Default for Container {
fn default() -> Self {
Self {
id: None,
layout: Layout::default(),
padding: egui::Margin::ZERO,
max_size: Vec2::INFINITY,
min_size: Vec2::ZERO,
}
}
}
impl Container {
#[inline]
pub fn new(layout: Layout) -> Self {
Self {
layout,
..Default::default()
}
}
#[inline]
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self
}
#[inline]
pub fn layout(mut self, layout: Layout) -> Self {
self.layout = layout;
self
}
#[inline]
pub fn padding(mut self, padding: egui::Margin) -> Self {
self.padding = padding;
self
}
#[inline]
pub fn max_size(mut self, max_size: Vec2) -> Self {
self.max_size = max_size;
self
}
#[inline]
pub fn min_size(mut self, min_size: Vec2) -> Self {
self.min_size = min_size;
self
}
pub fn show<R>(
&self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut 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 mut sizing_pass = false;
let available_rect = ui
.available_rect_before_wrap()
.intersect(ui.ctx().content_rect());
let desired_size = ui
.ctx()
.data_mut(|data| data.get_temp(id))
.unwrap_or_else(|| {
sizing_pass = true;
ui.ctx().request_discard("new Container");
available_rect.size()
});
let (_, expected_rect) = ui
.new_child(UiBuilder::new())
.allocate_space(desired_size.max(self.min_size).min(self.max_size));
let content_rect =
resize_layout_rect(expected_rect, available_rect.size(), &self.layout) - self.padding;
let mut content_ui = ui.new_child({
let builder = UiBuilder::new().max_rect(content_rect);
if sizing_pass {
builder
.layout(
self.layout
.with_cross_align(egui::Align::Min)
.with_cross_justify(false),
)
.sizing_pass()
.invisible()
} else {
builder.layout(self.layout)
}
});
let stretch_space = if self.layout.is_horizontal() {
available_rect.width() - desired_size.x
} else {
available_rect.height() - desired_size.y
};
let last_weights = prepare_stretch(&mut content_ui, stretch_space);
let inner = add_contents(&mut content_ui);
let new_rect = content_ui.min_rect() + self.padding;
let response = ui.allocate_rect(
if sizing_pass { new_rect } else { expected_rect },
Sense::hover(),
);
finish_stretch(&mut content_ui, last_weights);
if sizing_pass || new_rect.size() != desired_size {
ui.ctx()
.data_mut(|data| data.insert_temp(id, new_rect.size()));
}
InnerResponse { inner, response }
}
}
const STRETCH_SPACE_ID_SALT: &'static str = "egui_alignments::container::STRETCH_SPACE_ID_SALT";
const STRETCH_WEIGHT_ID_SALT: &'static str = "egui_alignments::container::STRETCH_WEIGHT_ID_SALT";
const STRETCH_WRAPPED_ID_SALT: &'static str = "egui_alignments::container::STRETCH_WRAPPED_ID_SALT";
pub(crate) fn register_stretch(ui: &mut Ui, weight: f32) -> Option<f32> {
if weight <= 0.0 {
return None;
}
let space_id = ui.unique_id().with(STRETCH_SPACE_ID_SALT);
let weight_id = ui.unique_id().with(STRETCH_WEIGHT_ID_SALT);
let spaces: Vec<f32> = ui.data(|data| data.get_temp(space_id))?;
let mut weights: Vec<f32> = ui.data(|data| data.get_temp(weight_id))?;
let index = weights.len();
weights.push(weight);
ui.data_mut(|data| {
data.insert_temp(weight_id, weights);
});
Some(*spaces.get(index)?)
}
fn prepare_stretch(ui: &mut Ui, available_space: f32) -> Option<Vec<f32>> {
let space_id = ui.unique_id().with(STRETCH_SPACE_ID_SALT);
let weight_id = ui.unique_id().with(STRETCH_WEIGHT_ID_SALT);
let wrapped_id = ui.unique_id().with(STRETCH_WRAPPED_ID_SALT);
let (Some(last_spaces), Some(last_weights)) = ui.data(|data| {
(
data.get_temp::<Vec<f32>>(space_id),
data.get_temp::<Vec<f32>>(weight_id),
)
}) else {
ui.data_mut(|data| {
data.insert_temp(space_id, Vec::<f32>::new());
data.insert_temp(weight_id, Vec::<f32>::new());
});
return None;
};
let mut available_space = (available_space + last_spaces.iter().sum::<f32>()).max(0.0);
let wrapped = ui.data(|data| data.get_temp::<bool>(wrapped_id).unwrap_or(false));
if wrapped {
available_space = 0.0;
}
let total_weight: f32 = last_weights.iter().sum();
let mut cumulative_weight = 0.0;
let mut cumulative_rounded = 0.0;
let mut spaces = Vec::with_capacity(last_weights.len());
for weight in last_weights.iter() {
cumulative_weight += weight;
let cumulative_space = available_space * (cumulative_weight / total_weight);
let rounded = cumulative_space.round_ui() - cumulative_rounded;
cumulative_rounded += rounded;
spaces.push(rounded);
}
ui.data_mut(|data| {
data.insert_temp(space_id, spaces);
data.insert_temp(weight_id, Vec::<f32>::new());
});
Some(last_weights)
}
fn finish_stretch(ui: &mut Ui, last_weights: Option<Vec<f32>>) {
let weight_id = ui.unique_id().with(STRETCH_WEIGHT_ID_SALT);
let wrapped_id = ui.unique_id().with(STRETCH_WRAPPED_ID_SALT);
let Some(last_weights) = last_weights else {
return;
};
let Some(new_weights) = ui.data(|r| r.get_temp::<Vec<f32>>(weight_id)) else {
return;
};
let mut wrapped = false;
if ui.layout().main_wrap {
wrapped = if ui.layout().is_horizontal() {
ui.cursor().top() > ui.min_rect().top()
} else {
ui.cursor().left() > ui.min_rect().left()
};
}
ui.data_mut(|data| {
data.insert_temp(wrapped_id, wrapped);
});
if last_weights != new_weights {
ui.ctx().request_discard("container stretch changed");
}
}
pub fn stretch_with_weight(ui: &mut Ui, weight: f32) -> f32 {
let space = register_stretch(ui, weight);
if let Some(space) = space {
if space > 0.0 {
ui.add_space(space);
}
space
} else {
0.0
}
}
#[inline]
pub fn stretch(ui: &mut Ui) -> f32 {
stretch_with_weight(ui, 1.0)
}