use std::{
cell::{Cell, RefCell},
collections::{hash_map, HashSet, VecDeque},
mem::take,
sync::{
atomic::{
AtomicBool, AtomicU8,
Ordering::{self, Relaxed},
},
mpsc, Arc,
},
thread::ThreadId,
time::{Duration, Instant},
};
use educe::Educe;
use egui::{
mutex::Mutex, CursorIcon, DeferredViewportUiCallback, ViewportBuilder, ViewportClass,
ViewportId, ViewportIdMap,
};
use godot::{
classes::{
self,
control::{LayoutPreset, MouseFilter},
window, CanvasLayer, Control, DisplayServer, ICanvasLayer, WeakRef,
},
global::weakref,
prelude::*,
};
use tap::prelude::{Pipe, Tap};
use with_drop::with_drop;
use crate::{
default,
helpers::{downgrade_gd, try_upgrade_gd, ToCounterpart},
surface,
};
#[derive(GodotClass)]
#[class(base=CanvasLayer, tool, init, rename=GodotEguiBridge)]
pub struct EguiBridge {
base: Base<CanvasLayer>,
share: Arc<SharedContext>,
#[export]
#[var(get, set)]
#[init(val = 13)]
pub max_texture_bits: u8,
textures: surface::TextureLibrary,
surfaces: RefCell<ViewportIdMap<SurfaceContext>>,
setup_scripts: RefCell<Vec<Box<FnDeferredContextAccess>>>,
cursor_shape: RefCell<Option<egui::CursorIcon>>,
tx_bg_task: RefCell<Option<mpsc::Sender<DeferredCommand>>>,
rx_bg_task: RefCell<Option<mpsc::Receiver<DeferredCommand>>>,
root_region_sync: Cell<Option<Gd<WeakRef>>>,
widget_callbacks_first: RefCell<Vec<(i32, Box<FnWidgetCallback>)>>,
widget_callbacks_last: RefCell<Vec<(i32, Box<FnWidgetCallback>)>>,
_non_send_sync: std::marker::PhantomData<*const ()>,
}
type FnWidgetCallback = dyn FnMut(&egui::Context) -> WidgetRetain + 'static;
enum DeferredCommand {
RequestRepaint(ViewportId),
}
#[derive(Clone)]
struct SurfaceContext {
painter: Gd<surface::EguiViewportBridge>,
window: Option<Gd<classes::Window>>,
}
#[derive(Educe)]
#[educe(Default)]
struct SharedContext {
egui: egui::Context,
repaint_queued: AtomicBool,
frame_started: AtomicBool,
raw_input_template: Mutex<egui::RawInput>,
full_output: Mutex<egui::FullOutput>,
spawned_viewports: Mutex<ViewportIdMap<SpawnedViewportContext>>,
viewports: Mutex<ViewportIdMap<ViewportContext>>,
#[educe(Default = std::thread::current().id())]
main_thread_id: ThreadId,
}
struct SpawnedViewportContext {
repaint: Arc<DeferredViewportUiCallback>,
dispose: Arc<Mutex<WidgetRetain>>,
builder: egui::ViewportBuilder,
}
struct ViewportContext {
repaint_at: Option<Instant>,
rx_update: mpsc::Receiver<egui::Event>,
builder: egui::ViewportBuilder,
close_request: Arc<ViewportClose>,
updates: Vec<egui::ViewportCommand>,
paint_this_frame: Option<Vec<egui::ClippedPrimitive>>,
target_ui_scale: f32,
info: egui::ViewportInfo,
}
type ViewportClose = AtomicU8;
const VIEWPORT_CLOSE_NONE: u8 = 0;
const VIEWPORT_CLOSE_REQUESTED: u8 = 1;
const VIEWPORT_CLOSE_PENDING: u8 = 2;
const VIEWPORT_CLOSE_CLOSE: u8 = 3;
type FnDeferredContextAccess = dyn FnOnce(&egui::Context) + 'static;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum WidgetRetain {
Retain,
Dispose,
#[default]
Unspecified,
}
impl WidgetRetain {
pub fn and(self, other: Self) -> Self {
match (self, other) {
(Self::Dispose, _) | (_, Self::Dispose) => Self::Dispose,
(Self::Retain, _) | (_, Self::Retain) => Self::Retain,
_ => Self::Unspecified,
}
}
pub fn disposed(&self) -> bool {
matches!(self, Self::Dispose)
}
}
impl From<bool> for WidgetRetain {
fn from(x: bool) -> Self {
if x {
Self::Retain
} else {
Self::Dispose
}
}
}
impl From<()> for WidgetRetain {
fn from(_: ()) -> Self {
Self::Unspecified
}
}
#[godot_api]
impl ICanvasLayer for EguiBridge {
fn process(&mut self, _dt: f64) {
self.handle_bg_message();
if self.share.repaint_queued.swap(false, Relaxed) {
self.current_frame();
}
if self.share.is_in_frame() {
self.finish_frame();
}
self.handle_bg_message();
}
fn enter_tree(&mut self) {
self.try_initiate();
}
fn exit_tree(&mut self) {
self.try_dispose();
}
}
#[godot_api]
impl EguiBridge {
#[func]
fn __internal_try_start_frame_inner(&self) {
self.try_start_frame();
}
}
impl EguiBridge {
pub fn setup_context(&self, setter: impl FnOnce(&egui::Context) + 'static + Send) {
if self.share.is_in_frame() {
self.setup_scripts.borrow_mut().push(Box::new(setter));
} else {
setter(&self.share.egui);
}
}
pub fn sync_root_region(&self, target: Option<Gd<Control>>) {
if let Some(target) = target {
self.root_region_sync.set(Some(downgrade_gd(target)));
} else {
self.reset_root_region_sync();
}
}
pub fn current_frame(&self) -> &egui::Context {
self.try_start_frame();
&self.share.egui
}
pub fn viewport_immediate<R>(
&self,
id: ViewportId,
builder: ViewportBuilder,
show: impl FnMut(&egui::Context, ViewportClass) -> R,
) -> R {
self.try_start_frame();
let egui = &self.share.egui;
egui.show_viewport_immediate(id, builder, show)
}
pub fn viewport_spawn<L>(
&self,
id: ViewportId,
builder: ViewportBuilder,
show: impl FnMut(&egui::Context) -> L + 'static,
) where
L: Into<WidgetRetain>,
{
self.share.spawned_viewports.lock().pipe(|mut table| {
let dispose = Arc::new(Mutex::new(WidgetRetain::default()));
let show_fn = FnWrapSendSync(show);
let show_fn = Mutex::new(show_fn);
struct FnWrapSendSync<F>(pub F);
unsafe impl<F> Send for FnWrapSendSync<F> {}
table.insert(
id,
SpawnedViewportContext {
dispose: dispose.clone(),
repaint: Arc::new(move |ctx| {
*dispose.lock() = show_fn.lock().0(ctx).into();
}),
builder,
},
)
});
self.queue_try_start_frame();
}
pub fn register_render_callback_first<L>(&self, priority: i32, widget: impl FnEguiDraw<L>)
where
L: Into<WidgetRetain>,
{
self.impl_push_panel_item(true, priority, widget);
}
pub fn register_render_callback_last<L>(&self, priority: i32, widget: impl FnEguiDraw<L>)
where
L: Into<WidgetRetain>,
{
self.impl_push_panel_item(false, priority, widget);
}
fn impl_push_panel_item<L>(&self, first: bool, priority: i32, mut widget: impl FnEguiDraw<L>)
where
L: Into<WidgetRetain>,
{
let show = Box::new(move |ui: &_| widget(ui).into());
let mut arr = if first {
self.widget_callbacks_first.borrow_mut()
} else {
self.widget_callbacks_last.borrow_mut()
};
let insert_index = arr
.binary_search_by_key(&priority, |(p, ..)| *p)
.unwrap_or_else(|x| x);
arr.insert(insert_index, (priority, show));
self.share.repaint_queued.store(true, Relaxed);
}
pub fn viewport_spawn_as_child(
&self,
_id: ViewportId,
_parent: Gd<Control>,
_builder: ViewportBuilder,
_show: impl FnOnce(&egui::Context) -> WidgetRetain + 'static,
) {
}
pub fn attach_node_to_viewport(&self, _id: ViewportId, node: Gd<Node>) -> Result<(), Gd<Node>> {
Err(node)
}
}
impl EguiBridge {
fn try_initiate(&self) {
if self.tx_bg_task.borrow().is_some() {
return;
}
{
let (tx_b, rx_b) = std::sync::mpsc::channel::<DeferredCommand>();
assert!(self.tx_bg_task.replace(Some(tx_b)).is_none());
assert!(self.rx_bg_task.replace(Some(rx_b)).is_none());
};
(&self.share.egui).pipe(|ctx| {
let w_share = Arc::downgrade(&self.share);
ctx.set_embed_viewports(false);
ctx.set_request_repaint_callback({
let w_share = w_share.clone();
move |repaint| {
let Some(share) = w_share.upgrade() else {
godot_print!("Repaint requested for disposed egui bridge: {repaint:?}");
return;
};
share.repaint(repaint);
}
});
});
}
fn handle_bg_message(&self) {
let Some(rx_b) = self.rx_bg_task.borrow_mut().take() else {
return;
};
let ctx = self.share.egui.clone();
for msg in rx_b.try_iter() {
match msg {
DeferredCommand::RequestRepaint(viewport_id) => {
ctx.request_repaint_of(viewport_id);
}
}
}
self.rx_bg_task.replace(Some(rx_b));
}
fn try_dispose(&mut self) {
let Some(_bg) = self.tx_bg_task.take() else {
return;
};
self.rx_bg_task.take();
}
fn try_start_frame(&self) {
assert!(std::thread::current().id() == self.share.main_thread_id);
if !self.share.try_advance_frame() {
return;
}
self.try_initiate();
let w_self = weakref(&self.to_gd().to_variant());
egui::Context::set_immediate_viewport_renderer(move |ctx, mut viewport| {
let Ok(this) = w_self.try_to::<Gd<WeakRef>>() else {
unreachable!();
};
let Ok(this) = this.get_ref().try_to::<Gd<Self>>() else {
return;
};
let this = this.bind();
let p_src = this.share.egui.input(|x| x as *const _);
let p_new = ctx.input(|x| x as *const _);
if p_src != p_new {
return;
}
this.viewport_validate(
viewport.ids.this,
Some((viewport.ids.parent, viewport.builder)),
);
this.viewport_start_frame(viewport.ids.this);
(viewport.viewport_ui_cb)(ctx);
this.viewport_end_frame(viewport.ids.this);
});
let share = self.share.clone();
share
.viewports
.lock()
.pipe(|vp| {
vp.iter()
.map(|(id, value)| (*id, value.info.clone()))
.collect::<egui::ViewportIdMap<_>>()
})
.pipe(|vp| {
let mut inp = share.raw_input_template.lock();
inp.viewports = vp;
inp.time = Some(classes::Time::singleton().get_ticks_usec() as f64 / 1e6);
inp.max_texture_side = Some(1 << (self.max_texture_bits as usize).clamp(8, 16));
inp.modifiers = {
use godot::global::Key as GdKey;
let gd_input = classes::Input::singleton();
let is_pressed = |k: GdKey| gd_input.is_key_pressed(k);
egui::Modifiers {
alt: is_pressed(GdKey::ALT),
ctrl: is_pressed(GdKey::CTRL),
shift: is_pressed(GdKey::SHIFT),
command: is_pressed(GdKey::CTRL),
mac_cmd: is_pressed(GdKey::META),
}
};
});
self.share.egui.set_embed_viewports(
self.base()
.get_viewport()
.unwrap()
.is_embedding_subwindows(),
);
self.viewport_validate(egui::ViewportId::ROOT, None);
'sync: {
let Some(w_target) = self.root_region_sync.take() else {
break 'sync;
};
let Some(target) = try_upgrade_gd::<Control>(w_target.clone()) else {
self.reset_root_region_sync();
break 'sync;
};
self.root_region_sync.set(Some(w_target));
let mut surfaces = self.surfaces.borrow_mut();
let root = surfaces.get_mut(&ViewportId::ROOT).unwrap();
let target_rect = target.get_global_rect();
let root_rect = root.painter.get_global_rect();
let err_pos = target_rect.position - root_rect.position;
let err_size = target_rect.size - root_rect.size;
if err_pos.length_squared() < 1e-4 && err_size.length_squared() < 1e-4 {
break 'sync;
}
root.painter.set_global_position(target_rect.position);
root.painter.set_size(target_rect.size);
}
self.viewport_start_frame(egui::ViewportId::ROOT);
self.invoke_registered_callbacks(true);
}
fn invoke_registered_callbacks(&self, first: bool) {
let get_cb = || {
if first {
self.widget_callbacks_first.borrow_mut()
} else {
self.widget_callbacks_last.borrow_mut()
}
};
let mut callbacks = { take(&mut *get_cb()) };
callbacks.retain_mut(|(_, cb)| !cb(&self.share.egui).disposed());
let mut cbs = get_cb();
let should_sort = !cbs.is_empty() && !callbacks.is_empty();
if should_sort {
cbs.extend(callbacks);
cbs.sort_by_key(|(p, ..)| *p);
} else {
*cbs = callbacks;
}
}
fn reset_root_region_sync(&self) {
self.surfaces
.borrow_mut()
.get_mut(&egui::ViewportId::ROOT)
.unwrap()
.pipe(|x| {
x.painter
.set_anchors_and_offsets_preset(LayoutPreset::FULL_RECT)
});
self.root_region_sync.set(None);
}
fn finish_frame(&mut self) {
let share = self.share.clone();
self.invoke_registered_callbacks(false);
let viewports = take(&mut *share.spawned_viewports.lock()).tap_mut(|viewports| {
viewports.retain(|id, value| {
if *value.dispose.lock() == WidgetRetain::Dispose {
false
} else {
let ui_cb = value.repaint.clone();
share
.egui
.show_viewport_deferred(*id, value.builder.clone(), move |ctx, _| {
ui_cb(ctx);
});
true
}
});
});
viewports.pipe(|mut viewports| {
let mut lock = share.spawned_viewports.lock();
viewports.extend(lock.drain());
*lock = viewports;
});
self.viewport_end_frame(egui::ViewportId::ROOT);
let mut remaining_viewports = share
.viewports
.lock()
.keys()
.copied()
.collect::<HashSet<_>>();
let mut viewports = VecDeque::new();
let now = Instant::now();
loop {
viewports.extend(take(&mut share.full_output.lock().viewport_output));
let Some((vp_id, vp_out)) = viewports.pop_front() else {
break;
};
let scheduled = if let Some(viewport) = share.viewports.lock().get_mut(&vp_id) {
if viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_CLOSE {
continue;
}
viewport.updates.extend(vp_out.commands);
if viewport.repaint_at.is_some_and(|x| x < now) {
viewport.repaint_at = None; true
} else {
viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_REQUESTED
}
} else {
false
};
let _ = remaining_viewports.remove(&vp_id);
self.viewport_validate(vp_id, Some((vp_out.parent, vp_out.builder)));
if let Some(ui_cb) = vp_out.viewport_ui_cb.filter(|_| scheduled) {
self.viewport_start_frame(vp_id);
ui_cb(&self.share.egui);
self.viewport_end_frame(vp_id);
}
}
for id in remaining_viewports {
match share.spawned_viewports.lock().entry(id) {
hash_map::Entry::Occupied(entry) => {
if *entry.get().dispose.lock() == WidgetRetain::Retain {
continue;
}
entry.remove();
}
hash_map::Entry::Vacant(_) => (),
}
Self::free_surface(self.surfaces.borrow_mut().remove(&id));
assert!(share.viewports.lock().remove(&id).is_some());
}
let egui::FullOutput {
platform_output: _,
textures_delta:
egui::TexturesDelta {
set: textures_created,
free: textures_freed,
},
shapes,
pixels_per_point: _,
viewport_output: _,
} = take(&mut *self.share.full_output.lock());
debug_assert!(shapes.is_empty(), "logic error - shape is viewport-wise");
if let Some(cursor) = self.cursor_shape.take() {
type CS = classes::display_server::CursorShape;
let mut ds = DisplayServer::singleton();
ds.cursor_set_shape(match cursor {
egui::CursorIcon::Default => CS::ARROW,
egui::CursorIcon::Help => CS::HELP,
egui::CursorIcon::PointingHand => CS::POINTING_HAND,
egui::CursorIcon::Wait => CS::WAIT,
egui::CursorIcon::Crosshair => CS::CROSS,
egui::CursorIcon::Text => CS::IBEAM,
egui::CursorIcon::VerticalText => CS::IBEAM,
egui::CursorIcon::NotAllowed => CS::FORBIDDEN,
egui::CursorIcon::AllScroll => CS::MOVE,
egui::CursorIcon::ResizeHorizontal => CS::HSIZE,
egui::CursorIcon::ResizeNeSw => CS::BDIAGSIZE,
egui::CursorIcon::ResizeNwSe => CS::FDIAGSIZE,
egui::CursorIcon::ResizeVertical => CS::VSIZE,
egui::CursorIcon::ResizeEast => CS::HSIZE,
egui::CursorIcon::ResizeSouthEast => CS::FDIAGSIZE,
egui::CursorIcon::ResizeSouth => CS::VSIZE,
egui::CursorIcon::ResizeSouthWest => CS::BDIAGSIZE,
egui::CursorIcon::ResizeWest => CS::HSIZE,
egui::CursorIcon::ResizeNorthWest => CS::FDIAGSIZE,
egui::CursorIcon::ResizeNorth => CS::VSIZE,
egui::CursorIcon::ResizeNorthEast => CS::BDIAGSIZE,
egui::CursorIcon::ResizeColumn => CS::HSIZE,
egui::CursorIcon::ResizeRow => CS::VSIZE,
_cursor => CS::ARROW,
});
}
for (id, delta) in textures_created {
self.textures.update_texture(id, delta);
}
for (id, mut paint) in self.surfaces.borrow_mut().clone() {
let Some((primitives, ui_scale)) = self
.share
.viewports
.lock()
.get_mut(&id)
.unwrap()
.pipe(|vp| vp.paint_this_frame.take().map(|x| (x, vp.target_ui_scale)))
else {
continue;
};
paint
.painter
.bind_mut()
.draw(&self.textures, primitives, ui_scale);
}
for id in textures_freed {
self.textures.free_texture(id);
}
self.share.finish_frame();
}
fn free_surface(x: Option<SurfaceContext>) {
if let Some(mut x) = x {
x.painter.queue_free();
if let Some(mut x) = x.window {
x.queue_free();
}
}
}
fn viewport_validate(
&self,
id: ViewportId,
build_with_parent: Option<(ViewportId, ViewportBuilder)>,
) {
let mut surface = with_drop(self.surfaces.borrow_mut().remove(&id), Self::free_surface);
let mut should_rebuild = false;
let mut viewport_lock = self.share.viewports.lock();
let viewport = match viewport_lock.entry(id) {
hash_map::Entry::Occupied(mut entry) => {
if let Some((parent, build)) = build_with_parent {
let entry = entry.get_mut();
let (patch, recreate) = entry.builder.patch(build);
let _ = recreate;
if entry.info.parent.is_some_and(|p| p != parent) {
should_rebuild = true;
entry.updates.splice(.., patch);
} else {
entry.updates.extend(patch);
}
}
entry.into_mut()
}
hash_map::Entry::Vacant(entry) => {
should_rebuild = true;
#[cfg(any())]
godot_print!(
"spawning new EGUI viewport: {id:?} / {}",
build_with_parent
.as_ref()
.map(|x| x.1.title.as_deref().unwrap_or("<unnamed>"))
.unwrap_or("ROOT")
);
let (_tx_update, rx_update) = mpsc::channel();
let mut init = ViewportBuilder::default();
let gd_wnd_parent = build_with_parent
.as_ref()
.map(|x| x.0)
.and_then(|id| {
self.surfaces
.borrow_mut()
.get(&id)
.and_then(|x| x.window.clone())
})
.unwrap_or_else(|| self.base().get_window().expect("not added in tree!"));
let (updates, _) = build_with_parent
.map(|x| {
init.patch(x.1.tap_mut(|init| {
if init.position.is_none() {
let pos = gd_wnd_parent.get_position().to_alternative();
init.position = Some(pos + egui::vec2(25., 25.));
}
if init.inner_size.is_none() {
init.inner_size = Some(egui::vec2(272., 480.));
}
}))
})
.unwrap_or_default();
entry.insert(ViewportContext {
repaint_at: Some(Instant::now()),
rx_update,
close_request: Default::default(),
builder: init,
target_ui_scale: 1.,
updates,
paint_this_frame: None,
info: default(),
})
}
};
if surface.is_none() || should_rebuild {
drop(surface.take());
let (tx_viewport, rx_viewport) = mpsc::channel();
viewport.rx_update = rx_viewport;
let mut gd_painter = surface::EguiViewportBridge::new_alloc();
let ctx = self.share.egui.clone();
gd_painter.bind_mut().initiate(
ctx.clone(),
id,
Box::new(move |ev| {
tx_viewport.send(ev).ok(); }),
);
let tx = self.tx_bg_task.borrow().clone().unwrap();
gd_painter.connect(
"resized",
&Callable::from_fn("Resize", move |_| {
tx.send(DeferredCommand::RequestRepaint(id)).ok();
Variant::nil()
}),
);
let gd_wnd = if id == ViewportId::ROOT {
self.to_gd().add_child(&gd_painter);
gd_painter.set_owner(&self.to_gd());
gd_painter.set_mouse_filter(MouseFilter::IGNORE);
gd_painter.set_process_input(true);
None
} else {
let builder = &viewport.builder;
gd_painter.set_mouse_filter(MouseFilter::PASS);
gd_painter.set_process_input(false);
let mut gd_wnd = classes::Window::new_alloc();
self.to_gd().add_child(&gd_wnd);
gd_wnd.set_owner(&self.to_gd());
gd_wnd.add_child(&gd_painter);
gd_painter.set_owner(&gd_wnd);
let close_req = viewport.close_request.clone();
gd_wnd.connect(
"close_requested",
&Callable::from_fn("SubscribeClose", move |_| {
close_req.store(VIEWPORT_CLOSE_REQUESTED, Relaxed);
Variant::nil()
}),
);
use classes::window::Flags;
if builder.active.is_some_and(|x| x) {
gd_wnd.grab_focus();
}
if builder.titlebar_shown.is_some_and(|x| !x) {
gd_wnd.set_flag(Flags::BORDERLESS, true);
}
Some(gd_wnd)
};
*surface = Some(SurfaceContext {
painter: gd_painter,
window: gd_wnd,
});
}
let Some(surface) = surface.into_inner() else {
unreachable!()
};
for command in viewport.updates.drain(..) {
use egui::ViewportCommand::*;
let Some(mut window) = surface.window.clone() else {
continue;
};
match command {
Close => {
if id == ViewportId::ROOT {
godot_warn!("Root viewport received close request!");
} else {
}
viewport.close_request.store(VIEWPORT_CLOSE_CLOSE, Relaxed);
}
CancelClose => {
viewport.close_request.store(VIEWPORT_CLOSE_NONE, Relaxed);
}
Title(new_title) => {
window.set_title(&new_title);
}
Transparent(transparent) => {
window.set_transparent_background(transparent);
}
Visible(visible) => {
window.set_visible(visible);
}
StartDrag => {
}
OuterPosition(pos) => window.set_position(pos.to_alternative()),
InnerSize(size) => window.set_size(size.to_alternative()),
MinInnerSize(size) => window.set_min_size(size.to_alternative()),
MaxInnerSize(size) => window.set_max_size(size.to_alternative()),
ResizeIncrements(Some(incr)) => {
let size = window.get_size();
let new_size = size + incr.to_alternative();
window.set_size(new_size);
}
ResizeIncrements(None) => {}
BeginResize(_) => {
}
Resizable(value) => window.set_flag(window::Flags::RESIZE_DISABLED, !value),
EnableButtons { .. } => {}
Minimized(true) => window.set_mode(window::Mode::MINIMIZED),
Minimized(_) => {}
Maximized(true) => window.set_mode(window::Mode::MAXIMIZED),
Maximized(_) => {}
Fullscreen(true) => window.set_mode(window::Mode::FULLSCREEN),
Fullscreen(_) => {}
Decorations(deco) => window.set_flag(window::Flags::BORDERLESS, !deco),
WindowLevel(level) => {
let enabled = match level {
egui::WindowLevel::AlwaysOnBottom | egui::WindowLevel::Normal => false,
egui::WindowLevel::AlwaysOnTop => true,
};
window.set_flag(window::Flags::ALWAYS_ON_TOP, enabled);
}
Icon(_) => {
}
IMERect(rect) => {
window.set_ime_position(rect.to_alternative().position);
}
IMEAllowed(allowed) => {
window.set_ime_active(allowed);
}
IMEPurpose(_why) => {
}
Focus => {
window.grab_focus();
}
RequestUserAttention(_) => {
}
SetTheme(_) => {
}
ContentProtected(_) => {}
CursorPosition(_pos) => {}
CursorGrab(_) => {}
CursorVisible(_) => {
}
MousePassthrough(enabled) => {
window.set_flag(window::Flags::MOUSE_PASSTHROUGH, enabled);
}
Screenshot(_) => {
}
RequestCut => {
}
RequestCopy => {
}
RequestPaste => {
}
}
}
if viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_PENDING {
viewport.close_request.store(VIEWPORT_CLOSE_CLOSE, Relaxed);
}
'wnd: {
let gd_wnd = match surface.window.clone() {
Some(wnd) => wnd,
None => {
if let Some(wnd) = surface.painter.get_window() {
wnd
} else {
break 'wnd;
}
}
};
let info = &mut viewport.info;
let inner_pos = gd_wnd.get_position().cast_float() + surface.painter.get_position();
let inner_size = surface.painter.get_size();
let gd_ds = DisplayServer::singleton();
let id_screen = gd_ds
.window_get_current_screen_ex()
.window_id(gd_wnd.get_window_id())
.done();
let scale = gd_ds.screen_get_scale_ex().screen(id_screen).done();
info.inner_rect = Some(Rect2::new(inner_pos, inner_size).to_counterpart());
info.focused = Some(gd_wnd.has_focus());
info.native_pixels_per_point = Some(scale);
info.fullscreen = Some(gd_wnd.get_mode() == window::Mode::FULLSCREEN);
info.minimized = Some(gd_wnd.get_mode() == window::Mode::MINIMIZED);
info.maximized = Some(gd_wnd.get_mode() == window::Mode::MAXIMIZED);
info.monitor_size = Some(
gd_ds
.screen_get_size_ex()
.screen(id_screen)
.done()
.to_counterpart(),
);
info.outer_rect = Some(egui::Rect::from_min_size(
gd_wnd.get_position().to_alternative(),
gd_wnd.get_size().to_counterpart(),
));
}
let input = viewport.info.clone();
drop(viewport_lock);
self.share
.raw_input_template
.lock()
.viewports
.insert(id, input);
self.surfaces.borrow_mut().pipe(|mut x| {
x.entry(id).or_insert(surface);
});
}
fn viewport_start_frame(&self, id: ViewportId) {
let mut raw_input = self.share.raw_input_template.lock().clone();
{
let mut viewport = self.share.viewports.lock();
let viewport = viewport.get_mut(&id).unwrap();
raw_input.events.extend(viewport.rx_update.try_iter());
raw_input.screen_rect = viewport.info.inner_rect.map(|x| {
egui::Rect::from_min_size(egui::Pos2::ZERO, x.size() / viewport.target_ui_scale)
});
raw_input.focused = viewport.info.focused.unwrap_or_default();
raw_input.viewport_id = id;
viewport.repaint_at = Some(Instant::now() + Duration::from_secs(3600));
if viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_REQUESTED {
viewport
.close_request
.store(VIEWPORT_CLOSE_PENDING, Relaxed);
raw_input
.viewports
.get_mut(&id)
.unwrap()
.events
.push(egui::ViewportEvent::Close);
}
}
self.share.egui.begin_pass(raw_input);
}
fn viewport_end_frame(&self, id: ViewportId) {
let mut output = self.share.egui.end_pass();
let paints = take(&mut output.shapes);
let ppi = output.pixels_per_point;
let primitives = self.share.egui.tessellate(paints, ppi);
self.share
.viewports
.lock()
.get_mut(&id)
.unwrap()
.pipe(|vp| {
vp.paint_this_frame = Some(primitives);
vp.target_ui_scale = ppi;
});
let mut gd_wnd = self
.surfaces
.borrow_mut()
.get(&id)
.and_then(|x| x.painter.get_window())
.expect("A painter should be spawned under any valid window!");
if let Some(ime) = output.platform_output.ime.take() {
gd_wnd.set_ime_active(true);
gd_wnd.set_ime_position(ime.cursor_rect.min.to_alternative());
} else {
gd_wnd.set_ime_active(false);
}
{
let egui::PlatformOutput {
commands,
events,
mutable_text_under_cursor,
cursor_icon,
..
} = take(&mut output.platform_output);
let mut ds = DisplayServer::singleton();
for cmd in commands {
match cmd {
egui::OutputCommand::CopyText(copied_text) => {
ds.clipboard_set(&copied_text);
}
egui::OutputCommand::CopyImage(_color_image) => {
godot_warn!("gdext_egui doesn't support image clipboard copying")
}
egui::OutputCommand::OpenUrl(open_url) => {
open::that(open_url.url).ok();
}
}
}
if mutable_text_under_cursor {
}
for _event in events {
}
let overwrite_cursor = if self.cursor_shape.borrow().is_some() {
!matches!(cursor_icon, CursorIcon::None | CursorIcon::Default)
} else {
cursor_icon != CursorIcon::None
};
if overwrite_cursor {
*self.cursor_shape.borrow_mut() = Some(cursor_icon);
}
}
self.share.full_output.lock().append(output);
for script in self.setup_scripts.borrow_mut().drain(..) {
script(&self.share.egui);
}
}
fn queue_try_start_frame(&self) {
if std::thread::current().id() == self.share.main_thread_id {
self.try_start_frame();
return;
}
if !self.share.try_advance_frame() {
return;
}
self.to_gd()
.call_deferred(symbol_string!(Self, __internal_try_start_frame_inner), &[]);
}
}
impl SharedContext {
fn repaint(&self, info: egui::RequestRepaintInfo) {
if let Some(x) = self.viewports.lock().get_mut(&info.viewport_id) {
x.repaint_at = Some(Instant::now() + info.delay);
self.repaint_queued.store(true, Relaxed);
} else {
godot_warn!("EGUI requested repaint for unregistered viewpot: {info:?}")
};
}
fn try_advance_frame(&self) -> bool {
!self.frame_started.swap(true, Relaxed)
}
fn is_in_frame(&self) -> bool {
self.frame_started.load(Relaxed)
}
fn finish_frame(&self) {
self.frame_started.store(false, Relaxed);
}
}
pub trait FnEguiDraw<R>: FnMut(&egui::Context) -> R + 'static
where
R: Into<WidgetRetain>,
{
}
impl<T, R> FnEguiDraw<R> for T
where
T: FnMut(&egui::Context) -> 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<T: GodotClass> CheckExpired for Gd<T> {
fn expired(&self) -> bool {
self.is_instance_valid()
}
}
impl CheckExpired for std::rc::Rc<std::cell::Cell<bool>> {
fn expired(&self) -> bool {
!self.get()
}
}
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 |ctx: &egui::Context| {
if Instant::now() > expiration {
WidgetRetain::Dispose
} else {
self(ctx).into()
}
}
}
fn bind<C: CheckExpired>(mut self, expired: C) -> impl FnEguiDrawExt<WidgetRetain> {
move |ctx: &egui::Context| {
if expired.expired() {
WidgetRetain::Dispose
} else {
self(ctx).into()
}
}
}
fn once(mut self) -> impl FnEguiDrawExt<WidgetRetain> {
move |ctx: &egui::Context| {
let _ = self(ctx).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(&egui::Context) -> L + 'static,
L: Into<WidgetRetain> + 'static,
{
}