use egui::{InnerResponse, NumExt as _};
use crate::UiExt as _;
use crate::list_item::navigation::ListItemNavigation;
#[derive(Debug, Clone, Default)]
struct LayoutStatistics {
max_desired_left_column_width: Option<f32>,
is_action_button_used: bool,
max_item_width: Option<f32>,
property_content_max_width: Option<f32>,
}
impl LayoutStatistics {
fn reset(ctx: &egui::Context, scope_id: egui::Id) {
ctx.data_mut(|writer| {
writer.insert_temp(scope_id, Self::default());
});
}
fn read(ctx: &egui::Context, scope_id: egui::Id) -> Self {
if let Some(slf) = ctx.data(|reader| reader.get_temp(scope_id)) {
slf
} else {
ctx.request_discard("Missing re_ui::LayoutStatistics");
Default::default()
}
}
fn update(ui: &egui::Ui, scope_id: egui::Id, update: impl FnOnce(&mut Self)) {
ui.sanity_check();
ui.data_mut(|writer| {
let stats: &mut Self = writer.get_temp_mut_or_default(scope_id);
update(stats);
});
}
}
#[derive(Debug, Clone)]
pub struct LayoutInfo {
pub(crate) left_x: f32,
pub(crate) left_column_width: Option<f32>,
scope_id: egui::Id,
pub(crate) property_content_max_width: Option<f32>,
}
impl Default for LayoutInfo {
fn default() -> Self {
Self {
left_x: 0.0,
left_column_width: None,
scope_id: egui::Id::NULL,
property_content_max_width: None,
}
}
}
impl LayoutInfo {
pub fn register_desired_left_column_width(&self, ui: &egui::Ui, desired_width: f32) {
LayoutStatistics::update(ui, self.scope_id, |stats| {
stats.max_desired_left_column_width = stats
.max_desired_left_column_width
.map(|v| v.max(desired_width))
.or(Some(desired_width));
});
}
pub fn reserve_action_button_space(&self, ui: &egui::Ui, reserve: bool) {
LayoutStatistics::update(ui, self.scope_id, |stats| {
stats.is_action_button_used |= reserve;
});
}
pub(crate) fn register_max_item_width(&self, ui: &egui::Ui, width: f32) {
LayoutStatistics::update(ui, self.scope_id, |stats| {
stats.max_item_width = stats.max_item_width.map(|v| v.max(width)).or(Some(width));
});
}
pub(super) fn register_property_content_max_width(&self, ui: &egui::Ui, width: f32) {
LayoutStatistics::update(ui, self.scope_id, |stats| {
stats.property_content_max_width = stats
.property_content_max_width
.map(|v| v.max(width))
.or(Some(width));
});
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct LayoutInfoStack(Vec<LayoutInfo>);
impl LayoutInfoStack {
fn push(ctx: &egui::Context, state: LayoutInfo) {
ctx.data_mut(|writer| {
let stack: &mut Self = writer.get_temp_mut_or_default(egui::Id::NULL);
stack.0.push(state);
});
}
fn pop(ctx: &egui::Context) -> Option<LayoutInfo> {
ctx.data_mut(|writer| {
let stack: &mut Self = writer.get_temp_mut_or_default(egui::Id::NULL);
stack.0.pop()
})
}
pub(crate) fn top(ctx: &egui::Context) -> LayoutInfo {
ctx.data_mut(|writer| {
let stack: &mut Self = writer.get_temp_mut_or_default(egui::Id::NULL);
let state = stack.0.last();
if state.is_none() {
re_log::warn_once!(
"Attempted to access empty LayoutInfo stack, returning default LayoutInfo. \
Wrap all calls to ListItem in a list_item_scope()."
);
}
re_log::debug_assert!(
state.is_some(),
"ListItem was not wrapped in list_item_scope()"
);
state.cloned().unwrap_or_default()
})
}
}
pub fn list_item_scope<R>(
ui: &mut egui::Ui,
id_salt: impl std::hash::Hash,
content: impl FnOnce(&mut egui::Ui) -> R,
) -> InnerResponse<R> {
ui.sanity_check();
let id_salt = egui::Id::new(id_salt); let scope_id = ui.id().with(id_salt);
let layout_stats = LayoutStatistics::read(ui.ctx(), scope_id);
LayoutStatistics::reset(ui.ctx(), scope_id);
let left_column_width =
if let Some(max_desired_left_column_width) = layout_stats.max_desired_left_column_width {
let available_width = layout_stats
.max_item_width
.unwrap_or_else(|| ui.available_width());
Some(max_desired_left_column_width.at_most(0.7 * available_width))
} else {
None
};
let state = LayoutInfo {
left_x: ui.max_rect().left(),
left_column_width,
scope_id,
property_content_max_width: layout_stats.property_content_max_width,
};
let is_root = ListItemNavigation::init_if_root(ui.ctx());
LayoutInfoStack::push(ui.ctx(), state.clone());
let response = ui.push_id(id_salt, |ui| {
ui.spacing_mut().item_spacing.y = 0.0;
content(ui)
});
LayoutInfoStack::pop(ui.ctx());
if is_root {
ListItemNavigation::end_if_root(ui.ctx());
}
ui.sanity_check();
response
}