use std::{
cell::{Cell, RefCell},
collections::BTreeMap,
sync::atomic::{AtomicBool, Ordering},
time::{Duration, Instant},
};
use crate::context::WidgetRetain;
use super::EguiBridge;
#[derive(Default)]
pub struct SpawnedWidgetContext {
items: RefCell<BTreeMap<(PanelGroup, i32), PanelItem>>,
items_new: RefCell<Vec<NewWidgetItem>>,
#[allow(unused)]
menu_root: RefCell<MenuNode>,
pub hide_all: Cell<bool>,
pub hide_left: Cell<bool>,
pub hide_right: Cell<bool>,
pub hide_center: Cell<bool>,
pub hide_bottom: Cell<bool>,
}
type NewWidgetItem = ((PanelGroup, NewWidgetSlot), Box<FnShowWidget>);
type FnShowWidget = dyn FnMut(&mut egui::Ui) -> WidgetRetain + 'static;
enum NewWidgetSlot {
Specified(i32),
Append,
Prepend,
}
struct PanelItem {
draw: Box<FnShowWidget>,
}
#[allow(unused)]
#[derive(Default)]
struct MenuNode {
children: BTreeMap<String, MenuNode>,
draw: Option<Box<FnShowWidget>>,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum PanelGroup {
#[default]
Left,
Right,
Central,
BottomRight,
BottomLeft,
}
impl PanelGroup {
pub fn range(&self) -> std::ops::RangeInclusive<(Self, i32)> {
(*self, i32::MIN)..=(*self, i32::MAX)
}
}
pub trait FnEguiDraw<R>: FnMut(&mut egui::Ui) -> R + 'static
where
R: Into<WidgetRetain>,
{
}
impl<T, R> FnEguiDraw<R> for T
where
T: FnMut(&mut egui::Ui) -> R + 'static,
R: Into<WidgetRetain> + 'static,
{
}
pub trait CheckExpired: 'static {
fn expired(&self) -> bool;
}
impl<T: 'static> CheckExpired for std::rc::Weak<T> {
fn expired(&self) -> bool {
self.strong_count() == 0
}
}
impl<T: 'static> CheckExpired for std::sync::Weak<T> {
fn expired(&self) -> bool {
self.strong_count() == 0
}
}
impl CheckExpired for std::sync::Arc<AtomicBool> {
fn expired(&self) -> bool {
!self.load(Ordering::Relaxed)
}
}
impl CheckExpired for std::rc::Rc<std::cell::Cell<bool>> {
fn expired(&self) -> bool {
!self.get()
}
}
impl CheckExpired for godot::engine::WeakRef {
fn expired(&self) -> bool {
self.get_ref().is_nil()
}
}
impl CheckExpired for bool {
fn expired(&self) -> bool {
!*self
}
}
pub trait FnEguiDrawExt<L: Into<WidgetRetain>>: Sized + FnEguiDraw<L> {
fn expires_at(mut self, expiration: Instant) -> impl FnEguiDrawExt<WidgetRetain> {
move |ui: &mut egui::Ui| {
if Instant::now() > expiration {
WidgetRetain::Dispose
} else {
self(ui).into()
}
}
}
fn bind<C: CheckExpired>(mut self, owner: impl Into<C>) -> impl FnEguiDrawExt<WidgetRetain> {
let expired = owner.into();
move |ui: &mut egui::Ui| {
if expired.expired() {
WidgetRetain::Dispose
} else {
self(ui).into()
}
}
}
fn once(mut self) -> impl FnEguiDrawExt<WidgetRetain> {
move |ui: &mut egui::Ui| {
let _ = self(ui).into();
WidgetRetain::Dispose
}
}
fn lifespan(self, duration: Duration) -> impl FnEguiDrawExt<WidgetRetain> {
self.expires_at(Instant::now() + duration)
}
}
impl<T, L> FnEguiDrawExt<L> for T
where
T: FnMut(&mut egui::Ui) -> L + 'static,
L: Into<WidgetRetain> + 'static,
{
}
impl SpawnedWidgetContext {
fn _menu_item_insert<T, L>(
&self,
path: impl IntoIterator<Item = T>,
mut widget: impl FnEguiDraw<L>,
) where
T: Into<String>,
L: Into<WidgetRetain>,
{
let mut node = &mut *self.menu_root.borrow_mut();
for seg in path {
let seg = seg.into();
node = node.children.entry(seg).or_default();
}
let show = Box::new(move |ui: &mut _| widget(ui).into());
node.draw.replace(show);
}
pub fn panel_item_insert<L>(&self, panel: PanelGroup, slot: i32, widget: impl FnEguiDraw<L>)
where
L: Into<WidgetRetain>,
{
assert!((i32::MIN >> 1..=i32::MAX >> 1).contains(&slot));
self.impl_push_panel_item(panel, NewWidgetSlot::Specified(slot), widget);
}
pub fn panel_item_push_back<L>(&self, panel: PanelGroup, widget: impl FnEguiDraw<L>)
where
L: Into<WidgetRetain>,
{
self.impl_push_panel_item(panel, NewWidgetSlot::Append, widget);
}
pub fn panel_item_push_front<L>(&self, panel: PanelGroup, widget: impl FnEguiDraw<L>)
where
L: Into<WidgetRetain>,
{
self.impl_push_panel_item(panel, NewWidgetSlot::Prepend, widget);
}
fn impl_push_panel_item<L>(
&self,
panel: PanelGroup,
slot: NewWidgetSlot,
mut widget: impl FnEguiDraw<L>,
) where
L: Into<WidgetRetain>,
{
let show = Box::new(move |ui: &mut _| widget(ui).into());
self.items_new.borrow_mut().push(((panel, slot), show));
}
}
impl SpawnedWidgetContext {
pub(super) fn _start_frame_handle_widgets(&self, ctx: &mut egui::Context) {
if self.hide_all.get() {
return;
}
self.render_widget_items(ctx);
}
fn _render_main_menu(&self, ctx: &egui::Context) {
let mut root = self.menu_root.borrow_mut();
if root.children.is_empty() && root.draw.is_none() {
return;
}
egui::TopBottomPanel::top("%%EguiBridge%%MainMenu").show(ctx, |ui| {
egui::menu::bar(ui, |ui| {
recurse_node(ui, &mut root);
})
});
fn recurse_node(ui: &mut egui::Ui, node: &mut MenuNode) -> WidgetRetain {
if let Some(draw) = &mut node.draw {
if (draw)(ui).disposed() {
node.draw = None;
dbg!("IMALIVE YTET");
}
}
node.children.retain(|key, v| {
let should_retain = ui
.menu_button(key, |ui| !recurse_node(ui, v).disposed())
.inner
.unwrap_or(true);
std::hint::black_box(should_retain);
should_retain
});
if node.draw.is_none() && node.children.is_empty() {
WidgetRetain::Dispose
} else {
WidgetRetain::Retain
}
}
}
fn render_widget_items(&self, ctx: &egui::Context) {
let widgets = &mut *self.items.borrow_mut();
let w = self;
const APPEND_SLOT_LOWER: i32 = i32::MAX >> 1;
const PREPEND_SLOT_UPPER: i32 = i32::MIN >> 1;
for ((group, slot), draw) in w.items_new.borrow_mut().drain(..) {
let slot_index = match slot {
NewWidgetSlot::Specified(idx) => idx,
NewWidgetSlot::Append => {
let idx = widgets
.range(group.range())
.last() .map(|((_, idx), ..)| *idx + 1)
.unwrap_or_default()
.max(APPEND_SLOT_LOWER);
idx
}
NewWidgetSlot::Prepend => {
let idx = widgets
.range(group.range())
.next()
.map(|((_, idx), ..)| *idx - 1)
.unwrap_or_default()
.min(PREPEND_SLOT_UPPER);
idx
}
};
widgets.insert((group, slot_index), PanelItem { draw });
}
let enums = [
PanelGroup::Left,
PanelGroup::Right,
PanelGroup::Central,
PanelGroup::BottomLeft,
PanelGroup::BottomRight,
];
let [has_left, has_right, has_center, has_bottom_left, has_bottom_right] =
enums.map(|x| widgets.range_mut(x.range()).any(|_| true));
let has_top = has_left || has_right || has_center;
let has_bottom = has_bottom_left || has_bottom_right;
let mut disposed = Vec::new();
macro_rules! draw_group {
(#[plain], $ui:expr, $panel:expr) => {{
for (index, item) in widgets.range_mut($panel.range()) {
let retain = (item.draw)($ui);
if retain == WidgetRetain::Dispose {
disposed.push(*index);
}
}
}};
($ui:expr, $panel:expr) => {{
egui::ScrollArea::new([true, true])
.id_source(stringify!($panel))
.show($ui, |ui| {
draw_group!(#[plain], ui, $panel);
});
}};
}
let transparent = egui::Frame::default().fill(egui::Color32::from_black_alpha(0));
let opaque = egui::Frame::default().fill(egui::Color32::from_black_alpha(71));
egui::TopBottomPanel::bottom("%%EguiBridge%%PanelBottom")
.frame(opaque)
.resizable(true)
.show_animated(ctx, has_bottom && !w.hide_bottom.get(), |ui| {
match (has_bottom_left, has_bottom_right) {
(true, true) => {
ui.columns(2, |col| {
draw_group!(&mut col[0], PanelGroup::BottomLeft);
draw_group!(&mut col[1], PanelGroup::BottomRight);
});
}
(true, false) => draw_group!(ui, PanelGroup::BottomLeft),
(false, true) => draw_group!(ui, PanelGroup::BottomRight),
(false, false) => unreachable!(),
}
});
let width = ctx.available_rect().width();
if has_top {
egui::SidePanel::left("%%EguiBridge%%PanelLeft")
.resizable(true)
.frame(opaque)
.max_width(width / 3.)
.show_animated(ctx, has_left && !w.hide_left.get(), |ui| {
draw_group!(ui, PanelGroup::Left)
});
egui::SidePanel::right("%%EguiBridge%%PanelRight")
.resizable(true)
.frame(opaque)
.max_width(width / 3.)
.show_animated(ctx, has_right && !w.hide_right.get(), |ui| {
draw_group!(ui, PanelGroup::Right)
});
if has_center && !w.hide_center.get() {
egui::Window::new("%%EguiBridge%%PanelCenter")
.title_bar(false)
.constrain_to(ctx.available_rect())
.frame(transparent)
.auto_sized()
.anchor(egui::Align2::LEFT_TOP, [0., 0.])
.show(ctx, |ui| {
draw_group!(#[plain], ui, PanelGroup::Central);
});
}
}
if has_top || has_bottom {
egui::Window::new("Visibility")
.id("%%EguiBridge%%Visibility".into())
.title_bar(false)
.auto_sized()
.show(ctx, |ui| {
ui.horizontal(|ui| {
[
(&w.hide_center, "Center"),
(&w.hide_left, "Left"),
(&w.hide_right, "Right"),
(&w.hide_bottom, "Bottom"),
]
.into_iter()
.for_each(|(hide, label)| {
let mut hidden = !hide.get();
ui.checkbox(&mut hidden, label);
hide.set(!hidden);
});
})
});
}
for index in disposed {
assert!(widgets.remove(&index).is_some());
}
}
}