use egui::{containers::*, emath::*, Align, Id, Key, Layout, Order, Response, Ui};
#[derive(Clone, Default, Debug)]
struct State {
size: Vec2,
}
pub(crate) fn popup_under_widget<R>(
ui: &Ui,
popup_id: Id,
widget_response: &Response,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
if !ui.memory(|mem| mem.is_popup_open(popup_id)) {
return None;
}
let state: Option<State> = ui.data_mut(|d| d.get_temp(popup_id));
if state.is_none() {
ui.ctx().request_repaint();
}
let mut state = state.unwrap_or_default();
let rect = Rect {
min: widget_response.rect.left_bottom(),
max: widget_response.rect.left_bottom() + state.size,
};
let inner = Area::new(popup_id)
.order(Order::Foreground)
.fixed_pos(constrain_window_rect_to_area(ui.ctx(), rect, None).min)
.movable(true)
.show(ui.ctx(), |ui| {
let frame = Frame::popup(ui.style());
let frame_margin = frame.inner_margin + frame.outer_margin;
let result = frame
.show(ui, |ui| {
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
ui.set_width(widget_response.rect.width() - frame_margin.sum().x);
add_contents(ui)
})
.inner
})
.inner;
state.size = ui.min_rect().size();
result
})
.inner;
ui.data_mut(|d| *d.get_temp_mut_or_default(popup_id) = state);
if ui.input(|i| i.key_pressed(Key::Escape)) || widget_response.clicked_elsewhere() {
ui.memory_mut(|mem| mem.close_popup());
}
Some(inner)
}
pub(crate) fn constrain_window_rect_to_area(
ctx: &egui::Context,
window: Rect,
area: Option<Rect>,
) -> Rect {
let mut area = area.unwrap_or_else(|| ctx.available_rect());
if window.width() > area.width() {
area.max.x = ctx.input(|i| i.screen_rect()).max.x;
area.min.x = ctx.input(|i| i.screen_rect()).min.x;
}
if window.height() > area.height() {
area.max.y = ctx.input(|i| i.screen_rect()).max.y;
area.min.y = ctx.input(|i| i.screen_rect()).min.y;
}
let mut pos = window.min;
let margin_x = (window.width() - area.width()).at_least(0.0);
let margin_y = (window.height() - area.height()).at_least(0.0);
pos.x = pos.x.at_most(area.right() + margin_x - window.width()); pos.x = pos.x.at_least(area.left() - margin_x); pos.y = pos.y.at_most(area.bottom() + margin_y - window.height()); pos.y = pos.y.at_least(area.top() - margin_y); Rect::from_min_size(pos, window.size())
}