use egui::{
Align, Align2, Direction, Layout, Rect, Response, Sense, Shape, UiBuilder, Vec2,
emath::TSTransform,
};
use crate::{
containers::frame::StyledFrame, impl_style_builders, style::shared_style::SharedStyle,
};
type LayerFn<'a> = Box<dyn FnOnce(&mut egui::Ui) + 'a>;
struct Layer<'a> {
offset: Vec2,
align: Option<Align2>,
fixed_size: Option<Vec2>,
render: LayerFn<'a>,
}
pub struct StyledStack<'a> {
layers: Vec<Layer<'a>>,
sense: Option<Sense>,
style: SharedStyle,
}
impl<'a> Default for StyledStack<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> StyledStack<'a> {
pub fn new() -> Self {
Self {
layers: Vec::new(),
sense: None,
style: SharedStyle::default(),
}
}
pub fn layer(mut self, f: impl FnOnce(&mut egui::Ui) + 'a) -> Self {
self.layers.push(Layer {
offset: Vec2::ZERO,
align: None,
fixed_size: None,
render: Box::new(f),
});
self
}
pub fn layer_offset(mut self, offset: Vec2, f: impl FnOnce(&mut egui::Ui) + 'a) -> Self {
self.layers.push(Layer {
offset,
align: None,
fixed_size: None,
render: Box::new(f),
});
self
}
pub fn layer_aligned(mut self, align: Align2, f: impl FnOnce(&mut egui::Ui) + 'a) -> Self {
self.layers.push(Layer {
offset: Vec2::ZERO,
align: Some(align),
fixed_size: None,
render: Box::new(f),
});
self
}
pub fn layer_fixed(
mut self,
size: Vec2,
align: Align2,
f: impl FnOnce(&mut egui::Ui) + 'a,
) -> Self {
self.layers.push(Layer {
offset: Vec2::ZERO,
align: Some(align),
fixed_size: Some(size),
render: Box::new(f),
});
self
}
pub fn sense(mut self, sense: Sense) -> Self {
self.sense = Some(sense);
self
}
pub fn show(self, ui: &mut egui::Ui) -> Response {
if self.style.visible == Some(false) {
ui.set_invisible();
}
let sense = self.sense.unwrap_or(Sense::hover());
let layers = self.layers;
let render = move |ui: &mut egui::Ui| {
let layer_id = ui.layer_id();
let start = ui.painter().add(Shape::Noop);
let content = ui.ctx().content_rect();
let origin = content.min;
let available = content.size();
let mut union: Option<Rect> = None;
for layer in layers {
let Layer {
offset,
align,
fixed_size,
render,
} = layer;
let (max_rect, layout) = match (fixed_size, align) {
(Some(size), Some(a)) => {
(Rect::from_min_size(origin, size), Some(layout_for(a)))
}
(None, Some(a)) => {
let frame = union.unwrap_or(Rect::from_min_size(origin, Vec2::ZERO));
(frame.translate(offset), Some(layout_for(a)))
}
(None, None) => (
Rect::from_min_size(origin + offset, available),
Some(Layout::top_down(Align::Min)),
),
(Some(_), None) => unreachable!("fixed_size is always paired with align"),
};
let mut builder = UiBuilder::new().max_rect(max_rect);
if let Some(layout) = layout {
builder = builder.layout(layout);
}
let mut child = ui.new_child(builder);
render(&mut child);
let contribution = match fixed_size {
Some(size) => Rect::from_min_size(origin, size),
None => child.min_rect().translate(-offset),
};
union = Some(match union {
None => contribution,
Some(u) => u.union(contribution),
});
}
let union = union.unwrap_or(Rect::from_min_size(origin, Vec2::ZERO));
let (final_rect, response) = ui.allocate_exact_size(union.size(), sense);
let end = ui.painter().add(Shape::Noop);
let delta = final_rect.min - union.min;
if delta != Vec2::ZERO {
ui.ctx().graphics_mut(|g| {
g.entry(layer_id).transform_range(
start,
end,
TSTransform::from_translation(delta),
);
});
}
response
};
if self.style.has_frame_styles() {
StyledFrame {
style: self.style,
align: None,
justify: None,
gap: None,
fill_size: None,
}
.show(ui, render)
.inner
} else {
ui.scope(render).inner
}
}
}
fn layout_for(align: Align2) -> Layout {
let Align2([ax, ay]) = align;
match (ax, ay) {
(Align::Center, Align::Center) => Layout::centered_and_justified(Direction::TopDown),
(_, Align::Center) => {
let dir = if ax == Align::Max {
Direction::RightToLeft
} else {
Direction::LeftToRight
};
Layout::from_main_dir_and_cross_align(dir, Align::Center)
}
(Align::Center, _) => {
let dir = if ay == Align::Max {
Direction::BottomUp
} else {
Direction::TopDown
};
Layout::from_main_dir_and_cross_align(dir, Align::Center)
}
(_, _) => {
let dir = if ay == Align::Max {
Direction::BottomUp
} else {
Direction::TopDown
};
Layout::from_main_dir_and_cross_align(dir, ax)
}
}
}
impl_style_builders!(StyledStack<'_>);
crate::impl_styled_widget!(['a], StyledStack<'a>);