1use std::{
2 cell::{Cell, RefCell},
3 collections::{hash_map, HashSet, VecDeque},
4 mem::take,
5 sync::{
6 atomic::{
7 AtomicBool, AtomicU8,
8 Ordering::{self, Relaxed},
9 },
10 mpsc, Arc,
11 },
12 thread::ThreadId,
13 time::{Duration, Instant},
14};
15
16use educe::Educe;
17use egui::{
18 mutex::Mutex, CursorIcon, DeferredViewportUiCallback, ViewportBuilder, ViewportClass,
19 ViewportId, ViewportIdMap,
20};
21use godot::{
22 classes::{
23 self,
24 control::{LayoutPreset, MouseFilter},
25 window, CanvasLayer, Control, DisplayServer, ICanvasLayer, WeakRef,
26 },
27 prelude::*,
28};
29use tap::prelude::{Pipe, Tap};
30use with_drop::with_drop;
31
32use crate::{
33 default,
34 helpers::{downgrade_gd, try_upgrade_gd, ToCounterpart},
35 surface,
36};
37
38#[derive(GodotClass)]
44#[class(base=CanvasLayer, tool, init, rename=GodotEguiBridge)]
45pub struct EguiBridge {
46 base: Base<CanvasLayer>,
47
48 share: Arc<SharedContext>,
50
51 #[export]
55 #[var(get, set)]
56 #[init(val = 13)]
57 pub max_texture_bits: u8,
58
59 textures: surface::TextureLibrary,
61
62 surfaces: RefCell<ViewportIdMap<SurfaceContext>>,
70
71 setup_scripts: RefCell<Vec<Box<FnDeferredContextAccess>>>,
73
74 cursor_shape: RefCell<Option<egui::CursorIcon>>,
76
77 tx_bg_task: RefCell<Option<mpsc::Sender<DeferredCommand>>>,
79 rx_bg_task: RefCell<Option<mpsc::Receiver<DeferredCommand>>>,
80
81 root_region_sync: Cell<Option<Gd<WeakRef>>>,
83
84 widget_callbacks_first: RefCell<Vec<(i32, Box<FnWidgetCallback>)>>,
86 widget_callbacks_last: RefCell<Vec<(i32, Box<FnWidgetCallback>)>>,
87
88 _non_send_sync: std::marker::PhantomData<*const ()>,
90}
91
92type FnWidgetCallback = dyn FnMut(&egui::Context) -> WidgetRetain + 'static;
93
94enum DeferredCommand {
95 RequestRepaint(ViewportId),
96}
97
98#[derive(Clone)]
99struct SurfaceContext {
100 painter: Gd<surface::EguiViewportBridge>,
102
103 window: Option<Gd<classes::Window>>,
105}
106
107#[derive(Educe)]
108#[educe(Default)]
109struct SharedContext {
110 egui: egui::Context,
111
112 repaint_queued: AtomicBool,
114
115 frame_started: AtomicBool,
117
118 raw_input_template: Mutex<egui::RawInput>,
120
121 full_output: Mutex<egui::FullOutput>,
123
124 spawned_viewports: Mutex<ViewportIdMap<SpawnedViewportContext>>,
126
127 viewports: Mutex<ViewportIdMap<ViewportContext>>,
129
130 #[educe(Default = std::thread::current().id())]
132 main_thread_id: ThreadId,
133}
134
135struct SpawnedViewportContext {
136 repaint: Arc<DeferredViewportUiCallback>,
138
139 dispose: Arc<Mutex<WidgetRetain>>,
141
142 builder: egui::ViewportBuilder,
144}
145
146struct ViewportContext {
147 repaint_at: Option<Instant>,
149
150 rx_update: mpsc::Receiver<egui::Event>,
152
153 builder: egui::ViewportBuilder,
155
156 close_request: Arc<ViewportClose>,
158
159 updates: Vec<egui::ViewportCommand>,
162
163 paint_this_frame: Option<Vec<egui::ClippedPrimitive>>,
165
166 target_ui_scale: f32,
169
170 info: egui::ViewportInfo,
172}
173
174type ViewportClose = AtomicU8;
186
187const VIEWPORT_CLOSE_NONE: u8 = 0;
188const VIEWPORT_CLOSE_REQUESTED: u8 = 1;
189const VIEWPORT_CLOSE_PENDING: u8 = 2;
190const VIEWPORT_CLOSE_CLOSE: u8 = 3;
191
192type FnDeferredContextAccess = dyn FnOnce(&egui::Context) + 'static;
194
195#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
199pub enum WidgetRetain {
200 Retain,
201 Dispose,
202
203 #[default]
206 Unspecified,
207}
208
209impl WidgetRetain {
210 pub fn and(self, other: Self) -> Self {
211 match (self, other) {
212 (Self::Dispose, _) | (_, Self::Dispose) => Self::Dispose,
213 (Self::Retain, _) | (_, Self::Retain) => Self::Retain,
214 _ => Self::Unspecified,
215 }
216 }
217
218 pub fn disposed(&self) -> bool {
219 matches!(self, Self::Dispose)
220 }
221}
222
223impl From<bool> for WidgetRetain {
224 fn from(x: bool) -> Self {
225 if x {
226 Self::Retain
227 } else {
228 Self::Dispose
229 }
230 }
231}
232
233impl From<()> for WidgetRetain {
234 fn from(_: ()) -> Self {
235 Self::Unspecified
236 }
237}
238
239#[godot_api]
242impl ICanvasLayer for EguiBridge {
243 fn process(&mut self, _dt: f64) {
244 self.handle_bg_message();
245
246 if self.share.repaint_queued.swap(false, Relaxed) {
247 self.current_frame();
248 }
249
250 if self.share.is_in_frame() {
251 self.finish_frame();
252 }
253
254 self.handle_bg_message();
255 }
256
257 fn enter_tree(&mut self) {
258 self.try_initiate();
259 }
260
261 fn exit_tree(&mut self) {
262 self.try_dispose();
263 }
264}
265
266#[godot_api]
267impl EguiBridge {
268 #[func]
269 fn __internal_try_start_frame_inner(&self) {
270 self.try_start_frame();
271 }
272}
273
274impl EguiBridge {
280 pub fn setup_context(&self, setter: impl FnOnce(&egui::Context) + 'static + Send) {
284 if self.share.is_in_frame() {
285 self.setup_scripts.borrow_mut().push(Box::new(setter));
286 } else {
287 setter(&self.share.egui);
288 }
289 }
290
291 pub fn sync_root_region(&self, target: Option<Gd<Control>>) {
294 if let Some(target) = target {
295 self.root_region_sync.set(Some(downgrade_gd(target)));
296 } else {
297 self.reset_root_region_sync();
298 }
299 }
300
301 pub fn current_frame(&self) -> &egui::Context {
318 self.try_start_frame();
319
320 &self.share.egui
321 }
322
323 pub fn viewport_immediate<R>(
345 &self,
346 id: ViewportId,
347 builder: ViewportBuilder,
348 show: impl FnMut(&egui::Context, ViewportClass) -> R,
349 ) -> R {
350 self.try_start_frame();
351 let egui = &self.share.egui;
352
353 egui.show_viewport_immediate(id, builder, show)
354 }
355
356 pub fn viewport_spawn<L>(
379 &self,
380 id: ViewportId,
381 builder: ViewportBuilder,
382 show: impl FnMut(&egui::Context) -> L + 'static,
383 ) where
384 L: Into<WidgetRetain>,
385 {
386 self.share.spawned_viewports.lock().pipe(|mut table| {
388 let dispose = Arc::new(Mutex::new(WidgetRetain::default()));
389 let show_fn = FnWrapSendSync(show);
390 let show_fn = Mutex::new(show_fn);
391
392 struct FnWrapSendSync<F>(pub F);
393
394 unsafe impl<F> Send for FnWrapSendSync<F> {}
398
399 table.insert(
400 id,
401 SpawnedViewportContext {
402 dispose: dispose.clone(),
403 repaint: Arc::new(move |ctx| {
404 *dispose.lock() = show_fn.lock().0(ctx).into();
405 }),
406 builder,
407 },
408 )
409 });
410
411 self.queue_try_start_frame();
413 }
414
415 pub fn register_render_callback_first<L>(&self, priority: i32, widget: impl FnEguiDraw<L>)
422 where
423 L: Into<WidgetRetain>,
424 {
425 self.impl_push_panel_item(true, priority, widget);
426 }
427
428 pub fn register_render_callback_last<L>(&self, priority: i32, widget: impl FnEguiDraw<L>)
435 where
436 L: Into<WidgetRetain>,
437 {
438 self.impl_push_panel_item(false, priority, widget);
439 }
440
441 fn impl_push_panel_item<L>(&self, first: bool, priority: i32, mut widget: impl FnEguiDraw<L>)
443 where
444 L: Into<WidgetRetain>,
445 {
446 let show = Box::new(move |ui: &_| widget(ui).into());
447 let mut arr = if first {
448 self.widget_callbacks_first.borrow_mut()
449 } else {
450 self.widget_callbacks_last.borrow_mut()
451 };
452
453 let insert_index = arr
454 .binary_search_by_key(&priority, |(p, ..)| *p)
455 .unwrap_or_else(|x| x);
456
457 arr.insert(insert_index, (priority, show));
458 self.share.repaint_queued.store(true, Relaxed);
459 }
460
461 pub fn viewport_spawn_as_child(
467 &self,
468 _id: ViewportId,
469 _parent: Gd<Control>,
470 _builder: ViewportBuilder,
471 _show: impl FnOnce(&egui::Context) -> WidgetRetain + 'static,
472 ) {
473 }
474
475 pub fn attach_node_to_viewport(&self, _id: ViewportId, node: Gd<Node>) -> Result<(), Gd<Node>> {
479 Err(node)
480 }
481}
482
483impl EguiBridge {
487 fn try_initiate(&self) {
488 if self.tx_bg_task.borrow().is_some() {
489 return;
491 }
492
493 {
495 let (tx_b, rx_b) = std::sync::mpsc::channel::<DeferredCommand>();
496
497 assert!(self.tx_bg_task.replace(Some(tx_b)).is_none());
498 assert!(self.rx_bg_task.replace(Some(rx_b)).is_none());
499 };
500
501 (&self.share.egui).pipe(|ctx| {
503 let w_share = Arc::downgrade(&self.share);
504
505 ctx.set_embed_viewports(false);
506 ctx.set_request_repaint_callback({
507 let w_share = w_share.clone();
509 move |repaint| {
510 let Some(share) = w_share.upgrade() else {
511 godot_print!("Repaint requested for disposed egui bridge: {repaint:?}");
512 return;
513 };
514
515 share.repaint(repaint);
516 }
517 });
518 });
519 }
520
521 fn handle_bg_message(&self) {
522 let Some(rx_b) = self.rx_bg_task.borrow_mut().take() else {
523 return;
524 };
525
526 let ctx = self.share.egui.clone();
527
528 for msg in rx_b.try_iter() {
529 match msg {
530 DeferredCommand::RequestRepaint(viewport_id) => {
531 ctx.request_repaint_of(viewport_id);
532 }
533 }
534 }
535
536 self.rx_bg_task.replace(Some(rx_b));
537 }
538
539 fn try_dispose(&mut self) {
540 let Some(_bg) = self.tx_bg_task.take() else {
542 return;
544 };
545
546 self.rx_bg_task.take();
547
548 }
559
560 fn try_start_frame(&self) {
561 assert!(std::thread::current().id() == self.share.main_thread_id);
562
563 if !self.share.try_advance_frame() {
565 return;
566 }
567
568 self.try_initiate();
570
571 let self_id = self.to_gd().instance_id();
576
577 egui::Context::set_immediate_viewport_renderer(move |ctx, mut viewport| {
578 let Ok(this) = Gd::<Self>::try_from_instance_id(self_id) else {
579 return;
581 };
582
583 let this = this.bind();
584
585 let p_src = this.share.egui.input(|x| x as *const _);
586 let p_new = ctx.input(|x| x as *const _);
587
588 if p_src != p_new {
589 return;
591 }
592
593 this.viewport_validate(
594 viewport.ids.this,
595 Some((viewport.ids.parent, viewport.builder)),
596 );
597 this.viewport_start_frame(viewport.ids.this);
598
599 (viewport.viewport_ui_cb)(ctx);
600 this.viewport_end_frame(viewport.ids.this);
601 });
602
603 let share = self.share.clone();
605 share
606 .viewports
607 .lock()
608 .pipe(|vp| {
609 vp.iter()
610 .map(|(id, value)| (*id, value.info.clone()))
611 .collect::<egui::ViewportIdMap<_>>()
612 })
613 .pipe(|vp| {
614 let mut inp = share.raw_input_template.lock();
615 inp.viewports = vp;
616 inp.time = Some(classes::Time::singleton().get_ticks_usec() as f64 / 1e6);
617
618 inp.max_texture_side = Some(1 << (self.max_texture_bits as usize).clamp(8, 16));
620 inp.modifiers = {
621 use godot::global::Key as GdKey;
622
623 let gd_input = classes::Input::singleton();
624 let is_pressed = |k: GdKey| gd_input.is_key_pressed(k);
625
626 egui::Modifiers {
627 alt: is_pressed(GdKey::ALT),
628 ctrl: is_pressed(GdKey::CTRL),
629 shift: is_pressed(GdKey::SHIFT),
630 command: is_pressed(GdKey::CTRL),
631 mac_cmd: is_pressed(GdKey::META),
632 }
633 };
634 });
635
636 self.share.egui.set_embed_viewports(
638 self.base()
639 .get_viewport()
640 .unwrap()
641 .is_embedding_subwindows(),
642 );
643
644 self.viewport_validate(egui::ViewportId::ROOT, None);
646
647 'sync: {
649 let Some(w_target) = self.root_region_sync.take() else {
650 break 'sync;
651 };
652
653 let Some(target) = try_upgrade_gd::<Control>(w_target.clone()) else {
654 self.reset_root_region_sync();
655 break 'sync;
656 };
657
658 self.root_region_sync.set(Some(w_target));
660
661 let mut surfaces = self.surfaces.borrow_mut();
663 let root = surfaces.get_mut(&ViewportId::ROOT).unwrap();
664
665 let target_rect = target.get_global_rect();
666 let root_rect = root.painter.get_global_rect();
667
668 let err_pos = target_rect.position - root_rect.position;
669 let err_size = target_rect.size - root_rect.size;
670
671 if err_pos.length_squared() < 1e-4 && err_size.length_squared() < 1e-4 {
674 break 'sync;
676 }
677
678 root.painter.set_global_position(target_rect.position);
680 root.painter.set_size(target_rect.size);
681 }
682
683 self.viewport_start_frame(egui::ViewportId::ROOT);
684
685 self.invoke_registered_callbacks(true);
687 }
688
689 fn invoke_registered_callbacks(&self, first: bool) {
690 let get_cb = || {
691 if first {
692 self.widget_callbacks_first.borrow_mut()
693 } else {
694 self.widget_callbacks_last.borrow_mut()
695 }
696 };
697
698 let mut callbacks = { take(&mut *get_cb()) };
699
700 callbacks.retain_mut(|(_, cb)| !cb(&self.share.egui).disposed());
704
705 let mut cbs = get_cb();
706 let should_sort = !cbs.is_empty() && !callbacks.is_empty();
707
708 if should_sort {
709 cbs.extend(callbacks);
710 cbs.sort_by_key(|(p, ..)| *p);
711 } else {
712 *cbs = callbacks;
713 }
714 }
715
716 fn reset_root_region_sync(&self) {
717 self.surfaces
718 .borrow_mut()
719 .get_mut(&egui::ViewportId::ROOT)
720 .unwrap()
721 .pipe(|x| {
722 x.painter
723 .set_anchors_and_offsets_preset(LayoutPreset::FULL_RECT)
724 });
725
726 self.root_region_sync.set(None);
728 }
729
730 fn finish_frame(&mut self) {
731 let share = self.share.clone();
732
733 self.invoke_registered_callbacks(false);
736
737 let viewports = take(&mut *share.spawned_viewports.lock()).tap_mut(|viewports| {
739 viewports.retain(|id, value| {
741 if *value.dispose.lock() == WidgetRetain::Dispose {
742 false
743 } else {
744 let ui_cb = value.repaint.clone();
745 share
746 .egui
747 .show_viewport_deferred(*id, value.builder.clone(), move |ctx, _| {
748 ui_cb(ctx);
749 });
750
751 true
752 }
753 });
754 });
755
756 viewports.pipe(|mut viewports| {
757 let mut lock = share.spawned_viewports.lock();
759
760 viewports.extend(lock.drain());
762 *lock = viewports;
763 });
764
765 self.viewport_end_frame(egui::ViewportId::ROOT);
769
770 let mut remaining_viewports = share
772 .viewports
773 .lock()
774 .keys()
775 .copied()
776 .collect::<HashSet<_>>();
777
778 let mut viewports = VecDeque::new();
779 let now = Instant::now();
780
781 loop {
782 viewports.extend(take(&mut share.full_output.lock().viewport_output));
784
785 let Some((vp_id, vp_out)) = viewports.pop_front() else {
786 break;
787 };
788
789 let scheduled = if let Some(viewport) = share.viewports.lock().get_mut(&vp_id) {
790 if viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_CLOSE {
791 continue;
799 }
800
801 viewport.updates.extend(vp_out.commands);
803
804 if viewport.repaint_at.is_some_and(|x| x < now) {
805 viewport.repaint_at = None; true
807 } else {
808 viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_REQUESTED
810 }
811 } else {
812 false
813 };
814
815 let _ = remaining_viewports.remove(&vp_id);
818
819 self.viewport_validate(vp_id, Some((vp_out.parent, vp_out.builder)));
821
822 if let Some(ui_cb) = vp_out.viewport_ui_cb.filter(|_| scheduled) {
823 self.viewport_start_frame(vp_id);
827 ui_cb(&self.share.egui);
829 self.viewport_end_frame(vp_id);
830 }
831 }
832
833 for id in remaining_viewports {
835 match share.spawned_viewports.lock().entry(id) {
836 hash_map::Entry::Occupied(entry) => {
837 if *entry.get().dispose.lock() == WidgetRetain::Retain {
838 continue;
841 }
842
843 entry.remove();
845 }
846 hash_map::Entry::Vacant(_) => (),
847 }
848
849 Self::free_surface(self.surfaces.borrow_mut().remove(&id));
851
852 assert!(share.viewports.lock().remove(&id).is_some());
856 }
857
858 let egui::FullOutput {
862 platform_output: _,
863 textures_delta:
864 egui::TexturesDelta {
865 set: textures_created,
866 free: textures_freed,
867 },
868 shapes,
869 pixels_per_point: _,
870 viewport_output: _,
871 } = take(&mut *self.share.full_output.lock());
872
873 debug_assert!(shapes.is_empty(), "logic error - shape is viewport-wise");
874
875 if let Some(cursor) = self.cursor_shape.take() {
877 type CS = classes::display_server::CursorShape;
878 let mut ds = DisplayServer::singleton();
879
880 ds.cursor_set_shape(match cursor {
881 egui::CursorIcon::Default => CS::ARROW,
882 egui::CursorIcon::Help => CS::HELP,
885 egui::CursorIcon::PointingHand => CS::POINTING_HAND,
886 egui::CursorIcon::Wait => CS::WAIT,
888 egui::CursorIcon::Crosshair => CS::CROSS,
890 egui::CursorIcon::Text => CS::IBEAM,
891 egui::CursorIcon::VerticalText => CS::IBEAM,
892 egui::CursorIcon::NotAllowed => CS::FORBIDDEN,
897 egui::CursorIcon::AllScroll => CS::MOVE,
900 egui::CursorIcon::ResizeHorizontal => CS::HSIZE,
901 egui::CursorIcon::ResizeNeSw => CS::BDIAGSIZE,
902 egui::CursorIcon::ResizeNwSe => CS::FDIAGSIZE,
903 egui::CursorIcon::ResizeVertical => CS::VSIZE,
904 egui::CursorIcon::ResizeEast => CS::HSIZE,
905 egui::CursorIcon::ResizeSouthEast => CS::FDIAGSIZE,
906 egui::CursorIcon::ResizeSouth => CS::VSIZE,
907 egui::CursorIcon::ResizeSouthWest => CS::BDIAGSIZE,
908 egui::CursorIcon::ResizeWest => CS::HSIZE,
909 egui::CursorIcon::ResizeNorthWest => CS::FDIAGSIZE,
910 egui::CursorIcon::ResizeNorth => CS::VSIZE,
911 egui::CursorIcon::ResizeNorthEast => CS::BDIAGSIZE,
912 egui::CursorIcon::ResizeColumn => CS::HSIZE,
913 egui::CursorIcon::ResizeRow => CS::VSIZE,
914 _cursor => CS::ARROW,
917 });
918 }
919
920 for (id, delta) in textures_created {
924 self.textures.update_texture(id, delta);
925 }
926
927 for (id, mut paint) in self.surfaces.borrow_mut().clone() {
929 let Some((primitives, ui_scale)) = self
930 .share
931 .viewports
932 .lock()
933 .get_mut(&id)
934 .unwrap()
935 .pipe(|vp| vp.paint_this_frame.take().map(|x| (x, vp.target_ui_scale)))
936 else {
937 continue;
939 };
940
941 paint
942 .painter
943 .bind_mut()
944 .draw(&self.textures, primitives, ui_scale);
945 }
946
947 for id in textures_freed {
949 self.textures.free_texture(id);
950 }
951
952 self.share.finish_frame();
956 }
957
958 fn free_surface(x: Option<SurfaceContext>) {
959 if let Some(mut x) = x {
960 x.painter.queue_free();
961
962 if let Some(mut x) = x.window {
963 x.queue_free();
964 }
965 }
966 }
967
968 fn viewport_validate(
969 &self,
970 id: ViewportId,
971 build_with_parent: Option<(ViewportId, ViewportBuilder)>,
972 ) {
973 let mut surface = with_drop(self.surfaces.borrow_mut().remove(&id), Self::free_surface);
975
976 let mut should_rebuild = false;
978 let mut viewport_lock = self.share.viewports.lock();
979 let viewport = match viewport_lock.entry(id) {
980 hash_map::Entry::Occupied(mut entry) => {
981 if let Some((parent, build)) = build_with_parent {
982 let entry = entry.get_mut();
983 let (patch, recreate) = entry.builder.patch(build);
984
985 let _ = recreate;
988
989 if entry.info.parent.is_some_and(|p| p != parent) {
990 should_rebuild = true;
992
993 entry.updates.splice(.., patch);
995 } else {
996 entry.updates.extend(patch);
997 }
998 }
999
1000 entry.into_mut()
1001 }
1002 hash_map::Entry::Vacant(entry) => {
1003 should_rebuild = true;
1004
1005 #[cfg(any())]
1006 godot_print!(
1007 "spawning new EGUI viewport: {id:?} / {}",
1008 build_with_parent
1009 .as_ref()
1010 .map(|x| x.1.title.as_deref().unwrap_or("<unnamed>"))
1011 .unwrap_or("ROOT")
1012 );
1013
1014 let (_tx_update, rx_update) = mpsc::channel();
1016 let mut init = ViewportBuilder::default();
1017
1018 let gd_wnd_parent = build_with_parent
1020 .as_ref()
1021 .map(|x| x.0)
1022 .and_then(|id| {
1023 self.surfaces
1024 .borrow_mut()
1025 .get(&id)
1026 .and_then(|x| x.window.clone())
1027 })
1028 .unwrap_or_else(|| self.base().get_window().expect("not added in tree!"));
1029
1030 let (updates, _) = build_with_parent
1031 .map(|x| {
1032 init.patch(x.1.tap_mut(|init| {
1033 if init.position.is_none() {
1034 let pos = gd_wnd_parent.get_position().to_alternative();
1035 init.position = Some(pos + egui::vec2(25., 25.));
1036 }
1037
1038 if init.inner_size.is_none() {
1039 init.inner_size = Some(egui::vec2(272., 480.));
1040 }
1041 }))
1042 })
1043 .unwrap_or_default();
1044
1045 entry.insert(ViewportContext {
1046 repaint_at: Some(Instant::now()),
1047 rx_update,
1048 close_request: Default::default(),
1049 builder: init,
1050 target_ui_scale: 1.,
1051 updates,
1052 paint_this_frame: None,
1053 info: default(),
1054 })
1055 }
1056 };
1057
1058 if surface.is_none() || should_rebuild {
1059 drop(surface.take());
1060
1061 let (tx_viewport, rx_viewport) = mpsc::channel();
1063 viewport.rx_update = rx_viewport;
1064
1065 let mut gd_painter = surface::EguiViewportBridge::new_alloc();
1067
1068 let ctx = self.share.egui.clone();
1069 gd_painter.bind_mut().initiate(
1070 ctx.clone(),
1071 id,
1072 Box::new(move |ev| {
1073 tx_viewport.send(ev).ok(); }),
1078 );
1079
1080 let tx = self.tx_bg_task.borrow().clone().unwrap();
1081 gd_painter.connect(
1082 "resized",
1083 &Callable::from_fn("Resize", move |_| {
1084 tx.send(DeferredCommand::RequestRepaint(id)).ok();
1089 Variant::nil()
1090 }),
1091 );
1092
1093 let gd_wnd = if id == ViewportId::ROOT {
1094 self.to_gd().add_child(&gd_painter);
1096 gd_painter.set_owner(&self.to_gd());
1097
1098 gd_painter.set_mouse_filter(MouseFilter::IGNORE);
1116
1117 gd_painter.set_process_input(true);
1119
1120 None
1121 } else {
1122 let builder = &viewport.builder;
1123
1124 gd_painter.set_mouse_filter(MouseFilter::PASS);
1127 gd_painter.set_process_input(false);
1128
1129 let mut gd_wnd = classes::Window::new_alloc();
1131
1132 self.to_gd().add_child(&gd_wnd);
1133 gd_wnd.set_owner(&self.to_gd());
1134
1135 gd_wnd.add_child(&gd_painter);
1136 gd_painter.set_owner(&gd_wnd);
1137
1138 let close_req = viewport.close_request.clone();
1140 gd_wnd.connect(
1141 "close_requested",
1142 &Callable::from_fn("SubscribeClose", move |_| {
1143 close_req.store(VIEWPORT_CLOSE_REQUESTED, Relaxed);
1144 Variant::nil()
1145 }),
1146 );
1147
1148 use classes::window::Flags;
1161
1162 if builder.active.is_some_and(|x| x) {
1163 gd_wnd.grab_focus();
1164 }
1165
1166 if builder.titlebar_shown.is_some_and(|x| !x) {
1167 gd_wnd.set_flag(Flags::BORDERLESS, true);
1168 }
1169
1170 Some(gd_wnd)
1171 };
1172
1173 *surface = Some(SurfaceContext {
1174 painter: gd_painter,
1175 window: gd_wnd,
1176 });
1177 }
1178
1179 let Some(surface) = surface.into_inner() else {
1180 unreachable!()
1181 };
1182
1183 for command in viewport.updates.drain(..) {
1184 use egui::ViewportCommand::*;
1185
1186 let Some(mut window) = surface.window.clone() else {
1187 continue;
1189 };
1190
1191 match command {
1192 Close => {
1193 if id == ViewportId::ROOT {
1194 godot_warn!("Root viewport received close request!");
1196 } else {
1197 }
1200
1201 viewport.close_request.store(VIEWPORT_CLOSE_CLOSE, Relaxed);
1202 }
1203 CancelClose => {
1204 viewport.close_request.store(VIEWPORT_CLOSE_NONE, Relaxed);
1205 }
1206 Title(new_title) => {
1207 window.set_title(&new_title);
1208 }
1209 Transparent(transparent) => {
1210 window.set_transparent_background(transparent);
1211 }
1212 Visible(visible) => {
1213 window.set_visible(visible);
1214 }
1215 StartDrag => {
1216 }
1221 OuterPosition(pos) => window.set_position(pos.to_alternative()),
1222
1223 InnerSize(size) => window.set_size(size.to_alternative()),
1225 MinInnerSize(size) => window.set_min_size(size.to_alternative()),
1226 MaxInnerSize(size) => window.set_max_size(size.to_alternative()),
1227 ResizeIncrements(Some(incr)) => {
1228 let size = window.get_size();
1229 let new_size = size + incr.to_alternative();
1230 window.set_size(new_size);
1231 }
1232 ResizeIncrements(None) => {}
1233 BeginResize(_) => {
1234 }
1236 Resizable(value) => window.set_flag(window::Flags::RESIZE_DISABLED, !value),
1237 EnableButtons { .. } => {}
1238 Minimized(true) => window.set_mode(window::Mode::MINIMIZED),
1239 Minimized(_) => {}
1240 Maximized(true) => window.set_mode(window::Mode::MAXIMIZED),
1241 Maximized(_) => {}
1242 Fullscreen(true) => window.set_mode(window::Mode::FULLSCREEN),
1243 Fullscreen(_) => {}
1244 Decorations(deco) => window.set_flag(window::Flags::BORDERLESS, !deco),
1245 WindowLevel(level) => {
1246 let enabled = match level {
1247 egui::WindowLevel::AlwaysOnBottom | egui::WindowLevel::Normal => false,
1248 egui::WindowLevel::AlwaysOnTop => true,
1249 };
1250
1251 window.set_flag(window::Flags::ALWAYS_ON_TOP, enabled);
1252 }
1253 Icon(_) => {
1254 }
1256 IMERect(rect) => {
1257 window.set_ime_position(rect.to_alternative().position);
1258 }
1259 IMEAllowed(allowed) => {
1260 window.set_ime_active(allowed);
1261 }
1262 IMEPurpose(_why) => {
1263 }
1265 Focus => {
1266 window.grab_focus();
1267 }
1268 RequestUserAttention(_) => {
1269 }
1271 SetTheme(_) => {
1272 }
1274 ContentProtected(_) => {}
1275 CursorPosition(_pos) => {}
1276 CursorGrab(_) => {}
1277 CursorVisible(_) => {
1278 }
1281 MousePassthrough(enabled) => {
1282 window.set_flag(window::Flags::MOUSE_PASSTHROUGH, enabled);
1283 }
1284 Screenshot(_) => {
1285 }
1287 RequestCut => {
1288 }
1290 RequestCopy => {
1291 }
1293 RequestPaste => {
1294 }
1296 }
1297 }
1298
1299 if viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_PENDING {
1300 viewport.close_request.store(VIEWPORT_CLOSE_CLOSE, Relaxed);
1302 }
1303
1304 'wnd: {
1306 let gd_wnd = match surface.window.clone() {
1307 Some(wnd) => wnd,
1308 None => {
1309 if let Some(wnd) = surface.painter.get_window() {
1310 wnd
1311 } else {
1312 break 'wnd;
1313 }
1314 }
1315 };
1316
1317 let info = &mut viewport.info;
1318
1319 let inner_pos = gd_wnd.get_position().cast_float() + surface.painter.get_position();
1320 let inner_size = surface.painter.get_size();
1321
1322 let gd_ds = DisplayServer::singleton();
1323 let id_screen = gd_ds
1324 .window_get_current_screen_ex()
1325 .window_id(gd_wnd.get_window_id())
1326 .done();
1327 let scale = gd_ds.screen_get_scale_ex().screen(id_screen).done();
1328
1329 info.inner_rect = Some(Rect2::new(inner_pos, inner_size).to_counterpart());
1330 info.focused = Some(gd_wnd.has_focus());
1331 info.native_pixels_per_point = Some(scale);
1332 info.fullscreen = Some(gd_wnd.get_mode() == window::Mode::FULLSCREEN);
1333 info.minimized = Some(gd_wnd.get_mode() == window::Mode::MINIMIZED);
1334 info.maximized = Some(gd_wnd.get_mode() == window::Mode::MAXIMIZED);
1335 info.monitor_size = Some(
1336 gd_ds
1337 .screen_get_size_ex()
1338 .screen(id_screen)
1339 .done()
1340 .to_counterpart(),
1341 );
1342 info.outer_rect = Some(egui::Rect::from_min_size(
1343 gd_wnd.get_position().to_alternative(),
1344 gd_wnd.get_size().to_counterpart(),
1345 ));
1346 }
1347 let input = viewport.info.clone();
1349
1350 drop(viewport_lock);
1352
1353 self.share
1355 .raw_input_template
1356 .lock()
1357 .viewports
1358 .insert(id, input);
1359
1360 self.surfaces.borrow_mut().pipe(|mut x| {
1362 x.entry(id).or_insert(surface);
1363 });
1364 }
1365
1366 fn viewport_start_frame(&self, id: ViewportId) {
1367 let mut raw_input = self.share.raw_input_template.lock().clone();
1369
1370 {
1371 let mut viewport = self.share.viewports.lock();
1372 let viewport = viewport.get_mut(&id).unwrap();
1373
1374 raw_input.events.extend(viewport.rx_update.try_iter());
1375 raw_input.screen_rect = viewport.info.inner_rect.map(|x| {
1376 egui::Rect::from_min_size(egui::Pos2::ZERO, x.size() / viewport.target_ui_scale)
1377 });
1378
1379 raw_input.focused = viewport.info.focused.unwrap_or_default();
1380 raw_input.viewport_id = id;
1381
1382 viewport.repaint_at = Some(Instant::now() + Duration::from_secs(3600));
1384
1385 if viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_REQUESTED {
1388 viewport
1389 .close_request
1390 .store(VIEWPORT_CLOSE_PENDING, Relaxed);
1391 raw_input
1392 .viewports
1393 .get_mut(&id)
1394 .unwrap()
1395 .events
1396 .push(egui::ViewportEvent::Close);
1397 }
1398 }
1399
1400 self.share.egui.begin_pass(raw_input);
1401 }
1402
1403 fn viewport_end_frame(&self, id: ViewportId) {
1404 let mut output = self.share.egui.end_pass();
1406
1407 let paints = take(&mut output.shapes);
1408 let ppi = output.pixels_per_point;
1409
1410 let primitives = self.share.egui.tessellate(paints, ppi);
1411 self.share
1412 .viewports
1413 .lock()
1414 .get_mut(&id)
1415 .unwrap()
1416 .pipe(|vp| {
1417 vp.paint_this_frame = Some(primitives);
1418 vp.target_ui_scale = ppi;
1419 });
1420
1421 let mut gd_wnd = self
1422 .surfaces
1423 .borrow_mut()
1424 .get(&id)
1425 .and_then(|x| x.painter.get_window())
1426 .expect("A painter should be spawned under any valid window!");
1427
1428 if let Some(ime) = output.platform_output.ime.take() {
1429 gd_wnd.set_ime_active(true);
1431 gd_wnd.set_ime_position(ime.cursor_rect.min.to_alternative());
1432 } else {
1433 gd_wnd.set_ime_active(false);
1434 }
1435
1436 {
1438 let egui::PlatformOutput {
1439 commands,
1440 events,
1441 mutable_text_under_cursor,
1442
1443 cursor_icon,
1445 ..
1446 } = take(&mut output.platform_output);
1447
1448 let mut ds = DisplayServer::singleton();
1449
1450 for cmd in commands {
1451 match cmd {
1452 egui::OutputCommand::CopyText(copied_text) => {
1453 ds.clipboard_set(&copied_text);
1454 }
1455 egui::OutputCommand::CopyImage(_color_image) => {
1456 godot_warn!("gdext_egui doesn't support image clipboard copying")
1457 }
1458 egui::OutputCommand::OpenUrl(open_url) => {
1459 open::that(open_url.url).ok();
1460 }
1461 }
1462 }
1463
1464 if mutable_text_under_cursor {
1465 }
1467
1468 for _event in events {
1469 }
1471
1472 let overwrite_cursor = if self.cursor_shape.borrow().is_some() {
1473 !matches!(cursor_icon, CursorIcon::None | CursorIcon::Default)
1475 } else {
1476 cursor_icon != CursorIcon::None
1478 };
1479
1480 if overwrite_cursor {
1481 *self.cursor_shape.borrow_mut() = Some(cursor_icon);
1482 }
1483 }
1484
1485 self.share.full_output.lock().append(output);
1487
1488 for script in self.setup_scripts.borrow_mut().drain(..) {
1490 script(&self.share.egui);
1491 }
1492 }
1493
1494 fn queue_try_start_frame(&self) {
1496 if std::thread::current().id() == self.share.main_thread_id {
1497 self.try_start_frame();
1498 return;
1499 }
1500
1501 if !self.share.try_advance_frame() {
1502 return;
1503 }
1504
1505 self.to_gd()
1506 .call_deferred(symbol_string!(Self, __internal_try_start_frame_inner), &[]);
1507 }
1508}
1509
1510impl SharedContext {
1511 fn repaint(&self, info: egui::RequestRepaintInfo) {
1512 if let Some(x) = self.viewports.lock().get_mut(&info.viewport_id) {
1513 x.repaint_at = Some(Instant::now() + info.delay);
1514 self.repaint_queued.store(true, Relaxed);
1515 } else {
1516 godot_warn!("EGUI requested repaint for unregistered viewpot: {info:?}")
1517 };
1518 }
1519
1520 fn try_advance_frame(&self) -> bool {
1521 !self.frame_started.swap(true, Relaxed)
1522 }
1523
1524 fn is_in_frame(&self) -> bool {
1525 self.frame_started.load(Relaxed)
1526 }
1527
1528 fn finish_frame(&self) {
1529 self.frame_started.store(false, Relaxed);
1530 }
1531}
1532
1533pub trait FnEguiDraw<R>: FnMut(&egui::Context) -> R + 'static
1541where
1542 R: Into<WidgetRetain>,
1543{
1544}
1545
1546impl<T, R> FnEguiDraw<R> for T
1547where
1548 T: FnMut(&egui::Context) -> R + 'static,
1549 R: Into<WidgetRetain> + 'static,
1550{
1551}
1552
1553pub trait CheckExpired: 'static {
1556 fn expired(&self) -> bool;
1557}
1558
1559impl<T: 'static> CheckExpired for std::rc::Weak<T> {
1560 fn expired(&self) -> bool {
1561 self.strong_count() == 0
1562 }
1563}
1564
1565impl<T: 'static> CheckExpired for std::sync::Weak<T> {
1566 fn expired(&self) -> bool {
1567 self.strong_count() == 0
1568 }
1569}
1570
1571impl CheckExpired for std::sync::Arc<AtomicBool> {
1572 fn expired(&self) -> bool {
1573 !self.load(Ordering::Relaxed)
1574 }
1575}
1576
1577impl<T: GodotClass> CheckExpired for Gd<T> {
1578 fn expired(&self) -> bool {
1579 self.is_instance_valid()
1580 }
1581}
1582
1583impl CheckExpired for std::rc::Rc<std::cell::Cell<bool>> {
1584 fn expired(&self) -> bool {
1585 !self.get()
1586 }
1587}
1588
1589impl CheckExpired for bool {
1590 fn expired(&self) -> bool {
1591 !*self
1592 }
1593}
1594
1595pub trait FnEguiDrawExt<L: Into<WidgetRetain>>: Sized + FnEguiDraw<L> {
1599 fn expires_at(mut self, expiration: Instant) -> impl FnEguiDrawExt<WidgetRetain> {
1602 move |ctx: &egui::Context| {
1603 if Instant::now() > expiration {
1604 WidgetRetain::Dispose
1605 } else {
1606 self(ctx).into()
1607 }
1608 }
1609 }
1610
1611 fn bind<C: CheckExpired>(mut self, expired: C) -> impl FnEguiDrawExt<WidgetRetain> {
1614 move |ctx: &egui::Context| {
1615 if expired.expired() {
1616 WidgetRetain::Dispose
1617 } else {
1618 self(ctx).into()
1619 }
1620 }
1621 }
1622
1623 fn once(mut self) -> impl FnEguiDrawExt<WidgetRetain> {
1625 move |ctx: &egui::Context| {
1626 let _ = self(ctx).into();
1628 WidgetRetain::Dispose
1629 }
1630 }
1631
1632 fn lifespan(self, duration: Duration) -> impl FnEguiDrawExt<WidgetRetain> {
1640 self.expires_at(Instant::now() + duration)
1641 }
1642}
1643
1644impl<T, L> FnEguiDrawExt<L> for T
1645where
1646 T: FnMut(&egui::Context) -> L + 'static,
1647 L: Into<WidgetRetain> + 'static,
1648{
1649}