|
use core::f32; |
|
use std::collections::{HashMap, HashSet}; |
|
use std::sync::{Arc, Mutex}; |
|
|
|
use egui::util::IdTypeMap; |
|
use egui::{Button, Id, Pos2, Response, Ui}; |
|
use taffy::prelude::*; |
|
|
|
pub use taffy; |
|
|
|
struct TaffyState { |
|
taffy: TaffyTree<Context>, |
|
|
|
last_size: egui::Vec2, |
|
items: HashMap<egui::Id, NodeId>, |
|
} |
|
|
|
impl TaffyState { |
|
pub fn new() -> Self { |
|
Self { |
|
taffy: TaffyTree::new(), |
|
last_size: egui::Vec2::ZERO, |
|
items: HashMap::default(), |
|
} |
|
} |
|
|
|
fn layout(&self, node_id: NodeId) -> Layout { |
|
*self.taffy.layout(node_id).unwrap() |
|
} |
|
} |
|
|
|
pub struct TaffyPass<'a> { |
|
main_id: Id, |
|
|
|
ui: &'a mut Ui, |
|
|
|
current_id: Id, |
|
current_node: Option<NodeId>, |
|
current_node_index: usize, |
|
last_child_count: usize, |
|
parent_rect: egui::Rect, |
|
|
|
used_items: HashSet<egui::Id>, |
|
|
|
root_rect: egui::Rect, |
|
available_space: Option<Size<AvailableSpace>>, |
|
} |
|
|
|
impl<'a> TaffyPass<'a> { |
|
#[inline] |
|
pub fn root_id(&self) -> Id { |
|
self.main_id |
|
} |
|
|
|
#[inline] |
|
pub fn current_id(&self) -> Id { |
|
self.current_id |
|
} |
|
|
|
fn with_state<T>(id: egui::Id, ctx: egui::Context, f: impl FnOnce(&mut TaffyState) -> T) -> T { |
|
let state = ctx.data_mut(|data: &mut IdTypeMap| { |
|
let state: Arc<Mutex<TaffyState>> = data |
|
.get_temp_mut_or_insert_with(id, || Arc::new(Mutex::new(TaffyState::new()))) |
|
.clone(); |
|
state |
|
}); |
|
|
|
let mut state = state.lock().unwrap(); |
|
|
|
f(&mut state) |
|
} |
|
|
|
pub fn new<T>( |
|
ui: &'a mut Ui, |
|
id: Id, |
|
root_rect: egui::Rect, |
|
available_space: Option<Size<AvailableSpace>>, |
|
style: Style, |
|
f: impl FnOnce(&mut TaffyPass<'a>) -> T, |
|
) -> TaffyReturn<T> { |
|
let mut this = Self { |
|
main_id: id, |
|
ui, |
|
current_node: None, |
|
current_node_index: 0, |
|
last_child_count: 0, |
|
parent_rect: root_rect, |
|
used_items: Default::default(), |
|
root_rect, |
|
available_space, |
|
current_id: id, |
|
}; |
|
|
|
this.add_children(id, style, |state| { |
|
let resp = f(state); |
|
let container = state.recalculate(); |
|
TaffyReturn { |
|
inner: resp, |
|
container, |
|
} |
|
}) |
|
} |
|
|
|
fn add_child_node(&mut self, id: egui::Id, style: taffy::Style) -> (NodeId, TaffyContainerUi) { |
|
if self.used_items.contains(&id) { |
|
tracing::error!("Taffy layout id collision!"); |
|
} |
|
|
|
Self::with_state(self.main_id, self.ui.ctx().clone(), |state| { |
|
let child_idx = self.current_node_index; |
|
self.current_node_index += 1; |
|
|
|
self.used_items.insert(id); |
|
let mut first_frame = false; |
|
|
|
let node_id = if let Some(node_id) = state.items.get(&id).copied() { |
|
if state.taffy.style(node_id).unwrap() != &style { |
|
state.taffy.set_style(node_id, style).unwrap(); |
|
} |
|
node_id |
|
} else { |
|
first_frame = true; |
|
let node = state.taffy.new_leaf(style).unwrap(); |
|
state.items.insert(id, node); |
|
node |
|
}; |
|
|
|
if let Some(current_node) = self.current_node { |
|
if child_idx < self.last_child_count { |
|
if state.taffy.child_at_index(current_node, child_idx).unwrap() != node_id { |
|
state |
|
.taffy |
|
.replace_child_at_index(current_node, child_idx, node_id) |
|
.unwrap(); |
|
} |
|
} else { |
|
state.taffy.add_child(current_node, node_id).unwrap(); |
|
self.last_child_count += 1; |
|
} |
|
} |
|
|
|
let container = TaffyContainerUi { |
|
layout: state.layout(node_id), |
|
parent_rect: self.parent_rect, |
|
first_frame, |
|
}; |
|
|
|
(node_id, container) |
|
}) |
|
} |
|
|
|
pub fn add_children<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: Style, |
|
f: impl FnOnce(&mut TaffyPass<'a>) -> T, |
|
) -> T { |
|
self.add_children_inner(id, style, Option::<fn(&mut egui::Ui)>::None, f) |
|
} |
|
|
|
pub fn add_children_with_background<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: Style, |
|
f: impl FnOnce(&mut TaffyPass<'a>) -> T, |
|
) -> T { |
|
self.add_children_with_ui( |
|
id, |
|
style, |
|
|ui| { |
|
egui::Frame::popup(ui.style()).show(ui, |ui| { |
|
let available_space = ui.available_size(); |
|
let (id, rect) = ui.allocate_space(available_space); |
|
let _response = ui.interact(rect, id, egui::Sense::click_and_drag()); |
|
// Must catch mouse events |
|
}); |
|
}, |
|
f, |
|
) |
|
} |
|
|
|
pub fn add_children_with_ui<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: Style, |
|
content: impl FnOnce(&mut egui::Ui), |
|
f: impl FnOnce(&mut TaffyPass<'a>) -> T, |
|
) -> T { |
|
self.add_children_inner(id, style, Some(content), f) |
|
} |
|
|
|
fn add_children_inner<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: Style, |
|
content: Option<impl FnOnce(&mut egui::Ui)>, |
|
f: impl FnOnce(&mut TaffyPass<'a>) -> T, |
|
) -> T { |
|
let (node_id, render_options) = self.add_child_node(id, style); |
|
|
|
let stored_id = self.current_id; |
|
let stored_node = self.current_node; |
|
let stored_node_index = self.current_node_index; |
|
let stored_last_child_count = self.last_child_count; |
|
let stored_parent_rect = self.parent_rect; |
|
|
|
Self::with_state(self.main_id, self.ui.ctx().clone(), |state| { |
|
self.current_node = Some(node_id); |
|
self.current_node_index = 0; |
|
self.last_child_count = state.taffy.child_count(node_id); |
|
|
|
let max_rect = render_options.full_container(); |
|
self.parent_rect = if max_rect.any_nan() { |
|
self.parent_rect |
|
} else { |
|
max_rect |
|
}; |
|
self.current_id = id; |
|
}); |
|
|
|
if let Some(content) = content { |
|
let max_rect = render_options.full_container(); |
|
if !max_rect.any_nan() { |
|
let mut child_ui = self.ui.new_child( |
|
egui::UiBuilder::new() |
|
.id_salt(id.with("background")) |
|
.max_rect(max_rect), |
|
); |
|
content(&mut child_ui); |
|
} |
|
} |
|
let resp = f(self); |
|
|
|
Self::with_state(self.main_id, self.ui.ctx().clone(), |state| { |
|
let mut current_cnt = state.taffy.child_count(node_id); |
|
|
|
while current_cnt > self.last_child_count { |
|
state |
|
.taffy |
|
.remove_child_at_index(node_id, current_cnt - 1) |
|
.unwrap(); |
|
current_cnt -= 1; |
|
} |
|
}); |
|
|
|
self.current_id = stored_id; |
|
self.current_node = stored_node; |
|
self.current_node_index = stored_node_index; |
|
self.last_child_count = stored_last_child_count; |
|
self.parent_rect = stored_parent_rect; |
|
|
|
resp |
|
} |
|
|
|
pub fn add_simple<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: taffy::Style, |
|
content: impl FnOnce(&mut Ui) -> T, |
|
) -> T { |
|
self.add_simple_finite(id, style, content) |
|
} |
|
|
|
pub fn add_simple_finite<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: taffy::Style, |
|
content: impl FnOnce(&mut Ui) -> T, |
|
) -> T { |
|
self.add_container(id, style, |ui, _params| { |
|
let inner = content(ui); |
|
TaffyContainerResponse { |
|
inner, |
|
min_size: ui.min_size(), |
|
intrinsic_size: None, |
|
max_size: ui.min_size(), |
|
infinite: egui::Vec2b::FALSE, |
|
scroll_area: false, |
|
} |
|
}) |
|
} |
|
|
|
pub fn add_simple_infinite<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: taffy::Style, |
|
content: impl FnOnce(&mut Ui) -> T, |
|
) -> T { |
|
self.add_container(id, style, |ui, _params| { |
|
let inner = content(ui); |
|
TaffyContainerResponse { |
|
inner, |
|
min_size: ui.min_size(), |
|
intrinsic_size: None, |
|
max_size: ui.min_size(), |
|
infinite: egui::Vec2b::TRUE, |
|
scroll_area: false, |
|
} |
|
}) |
|
} |
|
|
|
pub fn add_container<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: taffy::Style, |
|
content: impl FnOnce(&mut Ui, TaffyContainerUi) -> TaffyContainerResponse<T>, |
|
) -> T { |
|
let parent_node = self.current_node.unwrap(); |
|
let (nodeid, mut render_options) = self.add_child_node(id, style.clone()); |
|
|
|
let mut ui_builder = egui::UiBuilder::new() |
|
.max_rect(render_options.inner_container()) |
|
.id_salt(id.with("_ui")) |
|
.layout(Default::default()); |
|
|
|
let parent_style = Self::with_state(self.main_id, self.ui.ctx().clone(), |state| { |
|
state.taffy.style(parent_node).unwrap().clone() |
|
}); |
|
|
|
// Inner boxes are always vertical by default |
|
ui_builder.layout.as_mut().unwrap().main_dir = egui::Direction::TopDown; |
|
|
|
if parent_style.flex_direction == FlexDirection::Column { |
|
for align_item in [parent_style.align_items, style.align_self] { |
|
match align_item { |
|
Some(align) => match align { |
|
AlignItems::Start => {} |
|
AlignItems::End => {} |
|
AlignItems::FlexStart => {} |
|
AlignItems::FlexEnd => {} |
|
AlignItems::Center => { |
|
ui_builder.layout = Some( |
|
ui_builder |
|
.layout |
|
.unwrap_or_default() |
|
.with_cross_align(egui::Align::Center), |
|
); |
|
} |
|
AlignItems::Baseline => todo!(), |
|
AlignItems::Stretch => { |
|
ui_builder.layout = Some( |
|
ui_builder |
|
.layout |
|
.unwrap_or_default() |
|
.with_cross_justify(true), |
|
); |
|
} |
|
}, |
|
None => {} |
|
} |
|
} |
|
} |
|
|
|
// TODO: Handle correctly case where max_rect has NaN values |
|
if ui_builder.max_rect.unwrap().any_nan() { |
|
render_options.first_frame = true; |
|
ui_builder = ui_builder.max_rect(self.parent_rect); |
|
} |
|
|
|
if render_options.first_frame { |
|
ui_builder = ui_builder.sizing_pass().invisible(); |
|
} |
|
|
|
let mut child_ui = self.ui.new_child(ui_builder); |
|
let resp = content(&mut child_ui, render_options); |
|
|
|
Self::with_state(self.main_id, self.ui.ctx().clone(), |state| { |
|
let min_size = if let Some(intrinsic_size) = resp.intrinsic_size { |
|
resp.min_size.min(intrinsic_size).ceil() |
|
} else { |
|
resp.min_size.ceil() |
|
}; |
|
|
|
let mut max_size = resp.max_size; |
|
max_size = max_size.max(min_size); |
|
|
|
let new_content = Context { |
|
min_size, |
|
max_size, |
|
infinite: resp.infinite, |
|
scroll_area: resp.scroll_area, |
|
}; |
|
if state.taffy.get_node_context(nodeid) != Some(&new_content) { |
|
state |
|
.taffy |
|
.set_node_context(nodeid, Some(new_content)) |
|
.unwrap(); |
|
} |
|
}); |
|
resp.inner |
|
} |
|
|
|
pub fn add_scroll_area_with_background<T>( |
|
&mut self, |
|
id: egui::Id, |
|
mut style: taffy::Style, |
|
content: impl FnOnce(&mut Ui) -> T, |
|
) -> T { |
|
style.min_size = taffy::Size { |
|
width: Dimension::Length(0.), |
|
height: Dimension::Length(0.), |
|
}; |
|
self.add_children_with_background(id, style, move |taffy| { |
|
let s = LengthPercentageAuto::Length( |
|
0.3 * taffy.ui.text_style_height(&egui::TextStyle::Body), |
|
); |
|
let mut style = taffy::Style::default(); |
|
style.margin = Rect { |
|
left: s, |
|
right: s, |
|
top: s, |
|
bottom: s, |
|
}; |
|
taffy.add_scroll_area(taffy.id_with("taffy_inner_scroll_area"), style, content) |
|
}) |
|
} |
|
|
|
pub fn add_scroll_area<T>( |
|
&mut self, |
|
id: egui::Id, |
|
style: taffy::Style, |
|
content: impl FnOnce(&mut Ui) -> T, |
|
) -> T { |
|
self.add_scroll_area_ext(id, style, true, content) |
|
} |
|
|
|
pub fn add_scroll_area_ext<T>( |
|
&mut self, |
|
id: egui::Id, |
|
mut style: taffy::Style, |
|
limit: bool, |
|
content: impl FnOnce(&mut Ui) -> T, |
|
) -> T { |
|
style.overflow = taffy::Point { |
|
x: taffy::Overflow::Visible, |
|
y: taffy::Overflow::Hidden, |
|
}; |
|
style.display = taffy::Display::Block; |
|
style.min_size = Size { |
|
width: Dimension::Length(0.), |
|
height: Dimension::Length(0.), |
|
}; |
|
if limit { |
|
style.max_size.height = Dimension::Length(self.root_rect.height() * 0.7); |
|
} |
|
self.add_children(id, style, |taffy| { |
|
let layout = Self::with_state(taffy.main_id, taffy.ui.ctx().clone(), |state| { |
|
state |
|
.taffy |
|
.layout(taffy.current_node.unwrap()) |
|
.unwrap() |
|
.clone() |
|
}); |
|
|
|
let style = taffy::Style { |
|
..Default::default() |
|
}; |
|
|
|
taffy.add_container(taffy.id_with("inner"), style, |ui, _params| { |
|
let mut real_min_size = None; |
|
let scroll_area = egui::ScrollArea::both() |
|
.id_salt(ui.id().with("scroll_area")) |
|
.max_width(ui.available_width()) |
|
.min_scrolled_width(layout.size.width) |
|
.max_width(layout.size.width) |
|
.min_scrolled_height(layout.size.height) |
|
.max_height(layout.size.height) |
|
.show(ui, |ui| { |
|
let resp = content(ui); |
|
real_min_size = Some(ui.min_size()); |
|
resp |
|
}); |
|
|
|
let potential_frame_size = scroll_area.content_size; |
|
|
|
let max_size = egui::Vec2 { |
|
x: potential_frame_size.x, |
|
y: potential_frame_size.y, |
|
}; |
|
|
|
TaffyContainerResponse { |
|
inner: scroll_area.inner, |
|
min_size: real_min_size.unwrap_or(max_size), |
|
intrinsic_size: None, |
|
max_size, |
|
infinite: egui::Vec2b::FALSE, |
|
scroll_area: true, |
|
} |
|
}) |
|
}) |
|
// }) |
|
} |
|
|
|
fn recalculate(&mut self) -> TaffyContainerUi { |
|
let root_rect = self.root_rect; |
|
let available_space = self.available_space.unwrap_or(Size { |
|
width: AvailableSpace::Definite(root_rect.width()), |
|
height: AvailableSpace::Definite(root_rect.height()), |
|
}); |
|
|
|
let current_node = self.current_node.unwrap(); |
|
Self::with_state(self.main_id, self.ui.ctx().clone(), |state| { |
|
// Remove unused nodes |
|
state.items.retain(|k, v| { |
|
if self.used_items.contains(k) { |
|
return true; |
|
} |
|
if let Some(parent) = state.taffy.parent(*v) { |
|
// Mark parent nodes "dirty" |
|
state.taffy.remove_child(parent, *v).unwrap(); |
|
} |
|
state.taffy.remove(*v).unwrap(); |
|
return false; |
|
}); |
|
self.used_items.clear(); |
|
|
|
let taffy = &mut state.taffy; |
|
|
|
if taffy.dirty(current_node).unwrap() || state.last_size != root_rect.size() { |
|
// let ctx = self.ui.ctx(); |
|
|
|
state.last_size = root_rect.size(); |
|
taffy |
|
.compute_layout_with_measure( |
|
current_node, |
|
available_space, |
|
|_known_size: Size<Option<f32>>, |
|
available_space: Size<AvailableSpace>, |
|
_id, |
|
context, |
|
_style| |
|
-> Size<f32> { |
|
let context = context.copied().unwrap_or(Context { |
|
min_size: egui::Vec2::ZERO, |
|
max_size: egui::Vec2::ZERO, |
|
scroll_area: false, |
|
infinite: egui::Vec2b::FALSE, |
|
}); |
|
|
|
let Context { |
|
mut min_size, |
|
mut max_size, |
|
scroll_area: _, |
|
infinite, |
|
} = context; |
|
|
|
// if scroll_area { |
|
// min_size = egui::Vec2::ZERO; |
|
// } |
|
|
|
if min_size.any_nan() { |
|
min_size = egui::Vec2::ZERO; |
|
} |
|
if max_size.any_nan() { |
|
max_size = root_rect.size(); |
|
} |
|
|
|
let max_size = egui::Vec2 { |
|
x: infinite |
|
.x |
|
.then_some(root_rect.width()) |
|
.unwrap_or(max_size.x), |
|
y: infinite |
|
.y |
|
.then_some(root_rect.height()) |
|
.unwrap_or(max_size.y), |
|
}; |
|
|
|
let width = match available_space.width { |
|
AvailableSpace::Definite(num) => { |
|
num.clamp(min_size.x, max_size.x.max(min_size.x)) |
|
} |
|
AvailableSpace::MinContent => min_size.x, |
|
AvailableSpace::MaxContent => max_size.x, |
|
}; |
|
let height = match available_space.height { |
|
AvailableSpace::Definite(num) => { |
|
num.clamp(min_size.y, max_size.y.max(min_size.y)) |
|
} |
|
AvailableSpace::MinContent => min_size.y, |
|
AvailableSpace::MaxContent => max_size.y, |
|
}; |
|
|
|
#[allow(clippy::let_and_return)] |
|
let final_size = Size { width, height }; |
|
|
|
// println!( |
|
// "{:?} {:?} {:?} {:?} {:?} {:?}", |
|
// _id, min_size, max_size, available_space, final_size, _known_size, |
|
// ); |
|
|
|
final_size |
|
}, |
|
) |
|
.unwrap(); |
|
// taffy.print_tree(current_node); |
|
|
|
tracing::trace!("Taffy recalculation done!"); |
|
self.ui.ctx().request_discard("Taffy recalculation"); |
|
} |
|
|
|
TaffyContainerUi { |
|
parent_rect: root_rect, |
|
layout: state.layout(current_node), |
|
first_frame: false, |
|
} |
|
}) |
|
} |
|
|
|
pub fn add_widget_with_transform_response_style( |
|
&mut self, |
|
f: impl FnOnce(&mut egui::Ui) -> Response, |
|
transform: impl FnOnce( |
|
TaffyContainerResponse<Response>, |
|
&egui::Ui, |
|
) -> TaffyContainerResponse<Response>, |
|
style: Style, |
|
) -> Response { |
|
self.add_container( |
|
self.current_id().with(self.current_node_index), |
|
style, |
|
|ui, _params| { |
|
let response = f(ui); |
|
|
|
let resp = TaffyContainerResponse { |
|
min_size: response.rect.size(), |
|
intrinsic_size: response.intrinsic_size, |
|
max_size: response.rect.size(), |
|
infinite: egui::Vec2b::FALSE, |
|
inner: response, |
|
scroll_area: false, |
|
}; |
|
|
|
transform(resp, ui) |
|
}, |
|
) |
|
} |
|
|
|
pub fn add_widget_with_transform_response( |
|
&mut self, |
|
f: impl FnOnce(&mut egui::Ui) -> Response, |
|
transform: impl FnOnce( |
|
TaffyContainerResponse<Response>, |
|
&egui::Ui, |
|
) -> TaffyContainerResponse<Response>, |
|
) -> Response { |
|
self.add_widget_with_transform_response_style(f, transform, Default::default()) |
|
} |
|
|
|
#[inline] |
|
pub fn add_widget_with_transform( |
|
&mut self, |
|
widget: impl egui::Widget, |
|
transform: impl FnOnce( |
|
TaffyContainerResponse<Response>, |
|
&egui::Ui, |
|
) -> TaffyContainerResponse<Response>, |
|
) -> Response { |
|
self.add_widget_with_transform_response(|ui| ui.add(widget), transform) |
|
} |
|
|
|
#[inline] |
|
pub fn add_widget_with_transform_style( |
|
&mut self, |
|
widget: impl egui::Widget, |
|
style: Style, |
|
transform: impl FnOnce( |
|
TaffyContainerResponse<Response>, |
|
&egui::Ui, |
|
) -> TaffyContainerResponse<Response>, |
|
) -> Response { |
|
self.add_widget_with_transform_response_style(|ui| ui.add(widget), transform, style) |
|
} |
|
|
|
#[inline] |
|
pub fn add_widget(&mut self, widget: impl egui::Widget) -> Response { |
|
self.add_widget_with_transform(widget, identity_transform) |
|
} |
|
|
|
#[inline] |
|
pub fn add_widget_with_response(&mut self, ui: impl FnOnce(&mut Ui) -> Response) -> Response { |
|
self.add_widget_with_transform_response(ui, identity_transform) |
|
} |
|
|
|
#[inline] |
|
pub fn label(&mut self, text: impl Into<egui::WidgetText>) -> Response { |
|
egui::Label::new(text).taffy_ui(self) |
|
} |
|
|
|
pub fn heading(&mut self, text: impl Into<egui::RichText>) -> Response { |
|
egui::Label::new(text.into().heading()).taffy_ui(self) |
|
} |
|
|
|
pub fn separator(&mut self) -> Response { |
|
TaffySeparator::default().taffy_ui(self) |
|
} |
|
|
|
#[inline] |
|
pub fn ui(&self) -> &&'a mut Ui { |
|
&self.ui |
|
} |
|
|
|
#[inline] |
|
pub fn id_with(&self, child: impl std::hash::Hash) -> egui::Id { |
|
self.current_id().with(child) |
|
} |
|
|
|
pub fn root_rect(&self) -> egui::Rect { |
|
self.root_rect |
|
} |
|
} |
|
|
|
fn identity_transform<T>( |
|
value: TaffyContainerResponse<T>, |
|
_ui: &egui::Ui, |
|
) -> TaffyContainerResponse<T> { |
|
value |
|
} |
|
|
|
pub struct TaffyReturn<T> { |
|
pub inner: T, |
|
pub container: TaffyContainerUi, |
|
} |
|
|
|
#[derive(PartialEq, Default, Clone, Copy)] |
|
struct Context { |
|
pub min_size: egui::Vec2, |
|
pub max_size: egui::Vec2, |
|
pub scroll_area: bool, |
|
infinite: egui::Vec2b, |
|
} |
|
|
|
/// Helper to show the inner content of a container. |
|
pub struct TaffyContainerUi { |
|
parent_rect: egui::Rect, |
|
layout: taffy::Layout, |
|
first_frame: bool, |
|
} |
|
|
|
impl TaffyContainerUi { |
|
pub fn full_container(&self) -> egui::Rect { |
|
let layout = &self.layout; |
|
let rect = egui::Rect::from_min_size( |
|
Pos2::new(layout.location.x, layout.location.y), |
|
egui::Vec2::new(layout.size.width, layout.size.height), |
|
); |
|
rect.translate(self.parent_rect.min.to_vec2()) |
|
} |
|
|
|
pub fn inner_container(&self) -> egui::Rect { |
|
let layout = &self.layout; |
|
|
|
let size = layout.size |
|
- Size { |
|
width: layout.padding.left + layout.padding.right, |
|
height: layout.padding.top + layout.padding.bottom, |
|
}; |
|
|
|
let rect = egui::Rect::from_min_size( |
|
Pos2::new( |
|
layout.location.x + layout.padding.left, |
|
layout.location.y + layout.padding.top, |
|
), |
|
egui::Vec2::new(size.width, size.height), |
|
); |
|
rect.translate(self.parent_rect.min.to_vec2()) |
|
} |
|
} |
|
|
|
pub struct TaffyContainerResponse<T> { |
|
pub inner: T, |
|
pub min_size: egui::Vec2, |
|
pub intrinsic_size: Option<egui::Vec2>, |
|
pub max_size: egui::Vec2, |
|
pub infinite: egui::Vec2b, |
|
pub scroll_area: bool, |
|
} |
|
|
|
/// Implement this trait for a widget to make it usable in a flex container. |
|
/// |
|
/// The reason there is a separate trait is that we need to measure the content size independently |
|
/// of the frame size. (The content will stay at it's intrinsic size while the frame will be |
|
/// stretched according to the flex layout.) |
|
/// |
|
/// If your widget has no frmae you don't need to implement this trait and can use |
|
/// [`crate::TaffyWidget`]. |
|
/// |
|
/// Trait idea taken from egui_flex |
|
pub trait TaffyWidget { |
|
/// The response type of the widget |
|
type Response; |
|
/// Show your widget here. Use the provided [`Ui`] to draw the container (e.g. using a [`egui::Frame`]) |
|
/// and in the frame ui use [`TaffyContainerUi::content`] to draw your widget. |
|
fn taffy_ui(self, taffy: &mut TaffyPass) -> Self::Response; |
|
} |
|
|
|
mod egui_widgets { |
|
use super::{TaffyPass, TaffyWidget}; |
|
use egui::widgets::{ |
|
Checkbox, DragValue, Hyperlink, Image, ImageButton, Label, Link, ProgressBar, RadioButton, |
|
SelectableLabel, Slider, Spinner, TextEdit, |
|
}; |
|
macro_rules! impl_widget { |
|
($($widget:ty),*) => { |
|
$( |
|
impl TaffyWidget for $widget { |
|
type Response = egui::Response; |
|
|
|
fn taffy_ui(self, taffy: &mut TaffyPass) -> Self::Response { |
|
taffy.add_widget(self) |
|
} |
|
} |
|
)* |
|
}; |
|
} |
|
impl_widget!( |
|
Label, |
|
Checkbox<'_>, |
|
Image<'_>, |
|
DragValue<'_>, |
|
Hyperlink, |
|
ImageButton<'_>, |
|
ProgressBar, |
|
RadioButton, |
|
Link, |
|
SelectableLabel, |
|
Slider<'_>, |
|
TextEdit<'_>, |
|
Spinner |
|
); |
|
} |
|
|
|
impl TaffyWidget for Button<'_> { |
|
type Response = egui::Response; |
|
|
|
fn taffy_ui(self, taffy: &mut TaffyPass) -> Self::Response { |
|
taffy.add_widget_with_transform(self, |mut val, _ui| { |
|
val.infinite.x = true; |
|
val |
|
}) |
|
} |
|
} |
|
|
|
#[derive(Default)] |
|
pub struct TaffySeparator { |
|
is_horizontal_line: Option<bool>, |
|
separator: egui::Separator, |
|
} |
|
|
|
impl TaffySeparator { |
|
pub fn vertical(mut self) -> Self { |
|
self.is_horizontal_line = Some(false); |
|
self.separator = self.separator.vertical(); |
|
self |
|
} |
|
} |
|
|
|
impl egui::Widget for TaffySeparator { |
|
fn ui(self, ui: &mut Ui) -> Response { |
|
ui.add(self.separator) |
|
} |
|
} |
|
|
|
impl TaffyWidget for TaffySeparator { |
|
type Response = egui::Response; |
|
|
|
fn taffy_ui(self, taffy: &mut TaffyPass) -> Self::Response { |
|
let mut style = taffy::Style::default(); |
|
style.min_size = Size { |
|
width: Dimension::Length(0.), |
|
height: Dimension::Length(0.), |
|
}; |
|
let is_horizontal_line = self.is_horizontal_line; |
|
taffy.add_widget_with_transform_style(self, style, |mut space, ui| { |
|
let is_horizontal_line = |
|
is_horizontal_line.unwrap_or_else(|| !ui.layout().main_dir().is_horizontal()); |
|
if let Some(size) = space.intrinsic_size.as_mut() { |
|
match is_horizontal_line { |
|
true => { |
|
size.x = 0.; |
|
space.infinite.x = true; |
|
} |
|
false => { |
|
size.y = 0.; |
|
space.infinite.y = true; |
|
} |
|
} |
|
} |
|
|
|
space |
|
}) |
|
} |
|
} |