Skip to main content

gdext_egui/
context.rs

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/* ---------------------------------------------------------------------------------------------- */
39/*                                             BRIDGE                                             */
40/* ---------------------------------------------------------------------------------------------- */
41
42/// Primary Egui Interface.
43#[derive(GodotClass)]
44#[class(base=CanvasLayer, tool, init, rename=GodotEguiBridge)]
45pub struct EguiBridge {
46    base: Base<CanvasLayer>,
47
48    /// Requires [`Arc`] for egui callback requirements.
49    share: Arc<SharedContext>,
50
51    /// Number of bits allowed for texture size.
52    ///
53    /// The texture will be 2^max_texture_size
54    #[export]
55    #[var(get, set)]
56    #[init(val = 13)]
57    pub max_texture_bits: u8,
58
59    /// Texture storage
60    textures: surface::TextureLibrary,
61
62    /// Pending intra-frame access methods
63
64    /// Actual Godot Nodes for realization of viewports.
65    ///
66    /// # NOTE
67    ///
68    /// Lock order MUST be `share.viewports` -> `painters.`
69    surfaces: RefCell<ViewportIdMap<SurfaceContext>>,
70
71    /// Setup scripts that was deferred until next frame end.
72    setup_scripts: RefCell<Vec<Box<FnDeferredContextAccess>>>,
73
74    /// Determines the cursor shape of this frame.
75    cursor_shape: RefCell<Option<egui::CursorIcon>>,
76
77    /// Handle to tesselation worker tx channel
78    tx_bg_task: RefCell<Option<mpsc::Sender<DeferredCommand>>>,
79    rx_bg_task: RefCell<Option<mpsc::Receiver<DeferredCommand>>>,
80
81    /// A object that root region is being synced.
82    root_region_sync: Cell<Option<Gd<WeakRef>>>,
83
84    /// callbacks for widgets rendering.
85    widget_callbacks_first: RefCell<Vec<(i32, Box<FnWidgetCallback>)>>,
86    widget_callbacks_last: RefCell<Vec<(i32, Box<FnWidgetCallback>)>>,
87
88    /// non-send + non-sync even when threading is implemented for godot objects ...
89    _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    /// Actual painter window.
101    painter: Gd<surface::EguiViewportBridge>,
102
103    /// Container window if exist.
104    window: Option<Gd<classes::Window>>,
105}
106
107#[derive(Educe)]
108#[educe(Default)]
109struct SharedContext {
110    egui: egui::Context,
111
112    /// Repaint was queued.
113    repaint_queued: AtomicBool,
114
115    /// Detects whether to start new frame.
116    frame_started: AtomicBool,
117
118    /// Template input for each viewport rendering.
119    raw_input_template: Mutex<egui::RawInput>,
120
121    /// Accumulated output for entire single frame.
122    full_output: Mutex<egui::FullOutput>,
123
124    /// List of viewports that is tracked by this context.
125    spawned_viewports: Mutex<ViewportIdMap<SpawnedViewportContext>>,
126
127    /// List of viewports that
128    viewports: Mutex<ViewportIdMap<ViewportContext>>,
129
130    /// The thread ID that instance was initiated.
131    #[educe(Default = std::thread::current().id())]
132    main_thread_id: ThreadId,
133}
134
135struct SpawnedViewportContext {
136    /// Captures `dispose`, then set it to false when viewport closed.
137    repaint: Arc<DeferredViewportUiCallback>,
138
139    /// Should spawned viewport be closed?
140    dispose: Arc<Mutex<WidgetRetain>>,
141
142    /// Sets at the very first frame.
143    builder: egui::ViewportBuilder,
144}
145
146struct ViewportContext {
147    /// Repainted when time point reaches here.
148    repaint_at: Option<Instant>,
149
150    /// Any input captures from viewport.
151    rx_update: mpsc::Receiver<egui::Event>,
152
153    /// Viewport initialization
154    builder: egui::ViewportBuilder,
155
156    /// Close request status
157    close_request: Arc<ViewportClose>,
158
159    /// Viewport commands pending apply. When should be recreated, the second parameter
160    /// set to [`Some`].
161    updates: Vec<egui::ViewportCommand>,
162
163    /// Paint commands that is being applied,
164    paint_this_frame: Option<Vec<egui::ClippedPrimitive>>,
165
166    /// Logical zoom rate requested by user. The painter will accept and rescale event
167    /// position and paintings to fit the zoom rate.
168    target_ui_scale: f32,
169
170    /// Cached viewport information, that we're currently updating on.
171    info: egui::ViewportInfo,
172}
173
174/// Closing steps
175///
176/// 1. Requested: Godot Window sends close signal => `ViewportContext::close_request`
177///    (=flag) is set to `VIEWPORT_CLOSE_REQUESTED`
178/// 2. Next start of frame: `VIEWPORT_CLOSE_REQUESTED` is detected, then it sets to
179///    `PENDING`, delivering `egui::ViewportEvent::Close` to make user detect if it's
180///    closing
181/// 3. If User don't want the viewport to be closed, user can send
182///    `ViewportCommand::CancelClose` to cancel the close request.
183/// 4. If not canceled, then the same frame, `PENDING` transitions to `CLOSE`, which will
184///    be disposed on next frame's `finish_frame` call.
185type 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
192/// Callback for deferred context access, for non-rendering purposes.
193type FnDeferredContextAccess = dyn FnOnce(&egui::Context) + 'static;
194
195/* --------------------------------- Widget Lifetime Control -------------------------------- */
196
197/// Every spawned widgets are retained as long as the callback returns true.
198#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
199pub enum WidgetRetain {
200    Retain,
201    Dispose,
202
203    /// For widgets, it is treated as `Retain` permanently. For viewports, it'll be
204    /// disposed at the end of frame.
205    #[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 ----------------------------------------- */
240
241#[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
274/* -------------------------------------------- APIs -------------------------------------------- */
275
276/// APIs for spawning viewports.
277///
278/// Key for every APIs are that any access to [`egui::Context`] triggers
279impl EguiBridge {
280    /// Access to egui context at intra-frame. This will be called immediately if we're
281    /// already out of frame boundary(e.g. start..end), otherwise, queue it to be called
282    /// later.
283    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    /// Synchronize root viewport's region with given control. If [`None`] is given, it
292    /// unregisters synchronization.
293    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    /// Start a new frame (if required), and return context which you can draw with.
302    ///
303    /// This is very default way of using EGUI, and anything you draw upon this will be
304    /// shown below the spawned root canvas; [`EguiBridge`]
305    ///
306    /// Use this when you want to draw widget every frame within `process()` function.
307    ///
308    /// # Caveats
309    ///
310    /// - Cloning `egui::Context` and access it directly out of provided lifecycle is not
311    ///   recommneded. Please guarantee that you only access this context within main
312    ///   thread, right after calling `current_frame`.
313    ///
314    /// # Panics
315    ///
316    /// - Called from non main gameplay thread.
317    pub fn current_frame(&self) -> &egui::Context {
318        self.try_start_frame();
319
320        &self.share.egui
321    }
322
323    /// Render viewport for current frame.
324    ///
325    /// This is shortcut to following code.
326    ///
327    /// ```no_run
328    /// # use godot::prelude::*;
329    /// # use gdext_egui::*;
330    /// # let bridge = EguiBridge::new_alloc();
331    /// let id = ViewportId::from_hash("123");
332    /// let builder = ViewportBuilder::default();
333    ///
334    /// bridge.current_frame().show_viewport_immediate(
335    ///     id, builder, |ctx, viewport_class| {
336    ///         // do something ...    
337    ///     }
338    /// );
339    /// ```
340    ///
341    /// # Panics
342    ///
343    ///
344    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    /// Spawn new viewport, which renders provided callback at the start of next
357    /// frame. This is thread-safe, however, you should call exactly once per gameplay
358    /// frame to ensure viewport is persisted correctly.
359    ///
360    /// This is inherently a shortcut to following code.
361    ///
362    /// ```no_run
363    /// # use godot::prelude::*;
364    /// # use gdext_egui::*;
365    /// # let bridge = EguiBridge::new_alloc();
366    /// let id = ViewportId::from_hash("123");
367    /// let builder = ViewportBuilder::default();
368    ///
369    /// bridge.egui_start().show_viewport_deferred(
370    ///     id, builder, move |ctx, viewport_class| {
371    ///         // do something ...    
372    ///
373    ///         // Viewport will be retained as long as you
374    ///         true
375    ///     }
376    /// );
377    /// ```
378    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        // Spawn a viewport which is retained as long as show returns true.
387        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            // SAFETY: EguiBridge can't escape main thread
395            // + All viewport methods are invoked from main thread, and never touches
396            //   other thread.
397            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        // Ensure the ui frame gets
412        self.queue_try_start_frame();
413    }
414
415    /// Registers callback for widget rendering at frame start.
416    ///
417    /// See also [`FnEguiDrawExt`] decorator for every method with signature
418    /// `impl FnMut(&egui::Context) -> impl Into<WidgetRetain> + 'static`
419    ///
420    /// Callbacks registered with lower priority will be called earlier.
421    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    /// Registers callback for widget rendering at frame end.
429    ///
430    /// See also [`FnEguiDrawExt`] decorator for every method with signature
431    /// `impl FnMut(&egui::Context) -> impl Into<WidgetRetain> + 'static`
432    ///
433    /// Callbacks registered with lower priority will be called earlier.
434    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    /// Registers callback for widget rendering, at very first of the frame start.
442    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    /// Spawn new viewport as child of existing node. If specified parent node is behind
462    /// other node, the input may work naturally as the egui surface always intercepts any
463    /// GUI input. It is advised to use this method for any node that lays over any other
464    /// GUI nodes, which makes all egui rendering appear always top of the other GUI
465    /// nodes.
466    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    /// Attach given node to given viewport's window.
476    ///
477    /// TODO: Implement this!
478    pub fn attach_node_to_viewport(&self, _id: ViewportId, node: Gd<Node>) -> Result<(), Gd<Node>> {
479        Err(node)
480    }
481}
482
483/* ------------------------------------------ Privates ------------------------------------------ */
484
485/// Private API implementations
486impl EguiBridge {
487    fn try_initiate(&self) {
488        if self.tx_bg_task.borrow().is_some() {
489            // It's already initiated.
490            return;
491        }
492
493        // Spawn background worker thread
494        {
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        // Setup egui context & repaint callback.
502        (&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                // Prevent cyclic reference; `share` is already holding the reference!
508                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        // Join background worker thread. To do this, channel should be closed first.
541        let Some(_bg) = self.tx_bg_task.take() else {
542            // Service is just not initialized.
543            return;
544        };
545
546        self.rx_bg_task.take();
547
548        // XXX: Seems all these manual cleanup redundant; as they're all in the tree?
549
550        // self.textures.clear();
551
552        // self.share.viewports.lock().clear();
553        // self.share.spawned_viewports.lock().clear();
554
555        // for (_, surface) in self.surfaces.borrow_mut().drain() {
556        //     Self::free_surface(Some(surface));
557        // }
558    }
559
560    fn try_start_frame(&self) {
561        assert!(std::thread::current().id() == self.share.main_thread_id);
562
563        // Only perform frame start when necessary.
564        if !self.share.try_advance_frame() {
565            return;
566        }
567
568        // Just lazily initiate the system.
569        self.try_initiate();
570
571        // Register immediate renderer for this frame.
572        // NOTE: Capture only the InstanceId (a Copy integer) instead of a Variant/WeakRef,
573        // because this closure is stored in egui's thread-local and may outlive the Godot
574        // engine binding — dropping a Variant after engine shutdown causes a panic.
575        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                // Object has been freed.
580                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                // Another EGUI runtime?
590                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        // Gather global input information
604        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                // XXX: 256~ 65536 texture size limitation => is this practical?
619                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        // Before starting a frame, check if we can spawn separate windows for viewport.
637        self.share.egui.set_embed_viewports(
638            self.base()
639                .get_viewport()
640                .unwrap()
641                .is_embedding_subwindows(),
642        );
643
644        // Start root frame as normal.
645        self.viewport_validate(egui::ViewportId::ROOT, None);
646
647        // After root region is initialized, try sync it with root region.
648        '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            // Target is still valid; return it back to the list.
659            self.root_region_sync.set(Some(w_target));
660
661            // Check if size mismatches
662            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            // We use error approximation here as the sync size is result of calculation
672            // => which may have floating point errors.
673            if err_pos.length_squared() < 1e-4 && err_size.length_squared() < 1e-4 {
674                // No need to sync
675                break 'sync;
676            }
677
678            // Sync root region
679            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        // Call registered callbacks for start of the frames.
686        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        // We release borrow here to make callbacks safely invoke
701        // `register_render_callback_*` methods.
702
703        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        // just to ensure.
727        self.root_region_sync.set(None);
728    }
729
730    fn finish_frame(&mut self) {
731        let share = self.share.clone();
732
733        /* ------------------------- Spawned Widget / Viewport Handling ------------------------- */
734        // Deal with registered callbacks for frame end.
735        self.invoke_registered_callbacks(false);
736
737        // Deal with spawned viewports.
738        let viewports = take(&mut *share.spawned_viewports.lock()).tap_mut(|viewports| {
739            // Check if any of the spawned viewports should be disposed.
740            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            // Check-in viewports list.
758            let mut lock = share.spawned_viewports.lock();
759
760            // Overwrite previous viewports with newly spawned ones, if exist.
761            viewports.extend(lock.drain());
762            *lock = viewports;
763        });
764
765        /* ------------------------------ Viewport Deltas Handling ------------------------------ */
766
767        // End main frame loop.
768        self.viewport_end_frame(egui::ViewportId::ROOT);
769
770        // Handle viewport changes from output, visit each viewports
771        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            // Not any lock should be held here.
783            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                    // If this is `PENDING`, it means the user side renderer has already
792                    // seen viewport close request, however, didn't deal with it, which
793                    // means accepted disposal of viewport close.
794
795                    // Simply by not invoking subsequent rendering logic, (more precisely,
796                    // not removing viewport ID from `remaining_viewports`), we can safely
797                    // dispose this viewport.
798                    continue;
799                }
800
801                // Commands are only meaningful when viewport already present.
802                viewport.updates.extend(vp_out.commands);
803
804                if viewport.repaint_at.is_some_and(|x| x < now) {
805                    viewport.repaint_at = None; // Clear repaint timer until next request
806                    true
807                } else {
808                    // If viewport is being closed now, force repainting it.
809                    viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_REQUESTED
810                }
811            } else {
812                false
813            };
814
815            // Don't need to check if remove succeeded; as it can be a viewport created
816            // inside rendering loop; which is perfectly valid egui API call.
817            let _ = remaining_viewports.remove(&vp_id);
818
819            // Validate viewport.
820            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                // Check if we should repaint this deferred viewport. For root and
824                // immediate viewports, these methods are already invoked!
825
826                self.viewport_start_frame(vp_id);
827                // Populate renderings
828                ui_cb(&self.share.egui);
829                self.viewport_end_frame(vp_id);
830            }
831        }
832
833        // Deal with removed viewports
834        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                        // The widget didn't agree to close, so we put it back to the list.
839                        // Other than `Retain` treated as `Dispose`.
840                        continue;
841                    }
842
843                    // Spawned viewport also agreed to close.
844                    entry.remove();
845                }
846                hash_map::Entry::Vacant(_) => (),
847            }
848
849            // Painter should be freed first, then viewport.
850            Self::free_surface(self.surfaces.borrow_mut().remove(&id));
851
852            // Remove viewport from context. Assertion here since we've retrieved
853            // remaining_viewports from viewport list itself, any 'subtractive'
854            // modification on viewports list is internal logic error!
855            assert!(share.viewports.lock().remove(&id).is_some());
856        }
857
858        /* -------------------------------- Frame Output Handling ------------------------------- */
859
860        // Cleanup full_output for next frame.
861        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        // Handle cursor shape
876        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::None =>
883                // egui::CursorIcon::ContextMenu => CursorShape::meu,
884                egui::CursorIcon::Help => CS::HELP,
885                egui::CursorIcon::PointingHand => CS::POINTING_HAND,
886                // egui::CursorIcon::Progress =>
887                egui::CursorIcon::Wait => CS::WAIT,
888                // egui::CursorIcon::Cell =>
889                egui::CursorIcon::Crosshair => CS::CROSS,
890                egui::CursorIcon::Text => CS::IBEAM,
891                egui::CursorIcon::VerticalText => CS::IBEAM,
892                // egui::CursorIcon::Alias => CS::,
893                // egui::CursorIcon::Copy =>
894                // egui::CursorIcon::Move =>
895                // egui::CursorIcon::NoDrop =>
896                egui::CursorIcon::NotAllowed => CS::FORBIDDEN,
897                // egui::CursorIcon::Grab => ,
898                // egui::CursorIcon::Grabbing =>
899                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                // egui::CursorIcon::ZoomIn =>
915                // egui::CursorIcon::ZoomOut =>
916                _cursor => CS::ARROW,
917            });
918        }
919
920        /* -------------------------------------- Painting -------------------------------------- */
921
922        // Handle new textures from output.
923        for (id, delta) in textures_created {
924            self.textures.update_texture(id, delta);
925        }
926
927        // Paint all viewports
928        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                // This viewport is not re-rendered this frame.
938                continue;
939            };
940
941            paint
942                .painter
943                .bind_mut()
944                .draw(&self.textures, primitives, ui_scale);
945        }
946
947        // Handle disposed textures from output.
948        for id in textures_freed {
949            self.textures.free_texture(id);
950        }
951
952        /* ---------------------------------------- Done. --------------------------------------- */
953
954        // Finish this frame.
955        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        // Checkout painter
974        let mut surface = with_drop(self.surfaces.borrow_mut().remove(&id), Self::free_surface);
975
976        // Spawn context if viewport id not exist
977        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                    // We don't need to trigger recreation from this flag ... Everything
986                    // is configurable through commands.
987                    let _ = recreate;
988
989                    if entry.info.parent.is_some_and(|p| p != parent) {
990                        // Parent is changed, so we need to recreate this viewport.
991                        should_rebuild = true;
992
993                        // In this case, previous updates will be discarded.
994                        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                // Just throw away this ... it'll be replaced by new one.
1015                let (_tx_update, rx_update) = mpsc::channel();
1016                let mut init = ViewportBuilder::default();
1017
1018                // Derive some defaults from parent window
1019                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            // Create channel between new viewport and painter.
1062            let (tx_viewport, rx_viewport) = mpsc::channel();
1063            viewport.rx_update = rx_viewport;
1064
1065            // Rebuild UI.
1066            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                    // NOTE: cloning egui context into this closure doesn't make cyclic reference
1074                    // - Both are field of `Self`, which does not refer to each other.
1075
1076                    tx_viewport.send(ev).ok(); // Failing this is just okay.
1077                }),
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                    // Send repaint request to background worker. Here we don't directly
1085                    // call `Context::request_repaint` method on context object to prevent
1086                    // deadlock, as we're not sure when this bound method is called. (it
1087                    // actually deadlocks on widget initialization)
1088                    tx.send(DeferredCommand::RequestRepaint(id)).ok();
1089                    Variant::nil()
1090                }),
1091            );
1092
1093            let gd_wnd = if id == ViewportId::ROOT {
1094                // Attach directly to this component.
1095                self.to_gd().add_child(&gd_painter);
1096                gd_painter.set_owner(&self.to_gd());
1097
1098                // NOTE: For root viewport...
1099                //
1100                // TODO: Merge `IGNORE` behavior between non-root and root.
1101                // - This is required to implement `add node as child of viewport`
1102                //   feature.
1103                //
1104                // Godot's default `gui_input` handling method, does not propagate inputs
1105                // into its siblings if they are obscured by this node. Since we're
1106                // creating a control which covers entire drawable space, and intercepting
1107                // all inputs, if mouse filter is applied anything other than `IGNORE`
1108                // would effectively prevent all other non-parent node to receive any
1109                // input.
1110                //
1111                // Therefore, we rather intercept any inputs in `_input()` method, and if
1112                // we need to consume the input inside egui, we rather make call to
1113                // `Viewport::set_input_as_handled()` which consumes input even before
1114                // reaching out to `gui_input()` callbacks of any.
1115                gd_painter.set_mouse_filter(MouseFilter::IGNORE);
1116
1117                // To do the tricks
1118                gd_painter.set_process_input(true);
1119
1120                None
1121            } else {
1122                let builder = &viewport.builder;
1123
1124                // NOTE: For other viewports, they exclusively use the window, therefore
1125                // don't need an `input` trick to work correctly.
1126                gd_painter.set_mouse_filter(MouseFilter::PASS);
1127                gd_painter.set_process_input(false);
1128
1129                // Spawn additional window to hold painter.
1130                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                // Bind window close request.
1139                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                // NOTE: List of recreation-only flags
1149                // - active
1150                // - app_id
1151                // - close_button
1152                // - minimwze_button
1153                // - maximize_button
1154                // - title_shown
1155                // - titlebar_buttons_shown
1156                // - titlebar_shown
1157                // - fullsize_content_view
1158                // - drag_and_drop
1159
1160                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                // Root viewport won't receive any viewport commands.
1188                continue;
1189            };
1190
1191            match command {
1192                Close => {
1193                    if id == ViewportId::ROOT {
1194                        // Ignore close signal to root ... It's simply not allowed!
1195                        godot_warn!("Root viewport received close request!");
1196                    } else {
1197                        // In any other cases; close signal is ignored. User can easily
1198                        // dispose the viewport by not calling `show_viewport_deferred`
1199                    }
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                    // TODO: Implement this
1217                    //
1218                    // Set viewport.dragging = true; then until it finishes dragging, get
1219                    // mouse delta then move the window.
1220                }
1221                OuterPosition(pos) => window.set_position(pos.to_alternative()),
1222
1223                // FIXME: Change painter size; not the containing window size.
1224                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                    // TODO: Implement this
1235                }
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                    // TODO: Find way to handle this.
1255                }
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                    // TODO: How?
1264                }
1265                Focus => {
1266                    window.grab_focus();
1267                }
1268                RequestUserAttention(_) => {
1269                    // No way?
1270                }
1271                SetTheme(_) => {
1272                    // How?
1273                }
1274                ContentProtected(_) => {}
1275                CursorPosition(_pos) => {}
1276                CursorGrab(_) => {}
1277                CursorVisible(_) => {
1278                    // TODO: How can we achieve this in safe manner?
1279                    // - e.g. If user simply disposed EGUI after hiding cursor...
1280                }
1281                MousePassthrough(enabled) => {
1282                    window.set_flag(window::Flags::MOUSE_PASSTHROUGH, enabled);
1283                }
1284                Screenshot(_) => {
1285                    // TODO: How?
1286                }
1287                RequestCut => {
1288                    // TODO
1289                }
1290                RequestCopy => {
1291                    // TODO
1292                }
1293                RequestPaste => {
1294                    // TODO
1295                }
1296            }
1297        }
1298
1299        if viewport.close_request.load(Relaxed) == VIEWPORT_CLOSE_PENDING {
1300            // Close request is accepted, so we should dispose this viewport.
1301            viewport.close_request.store(VIEWPORT_CLOSE_CLOSE, Relaxed);
1302        }
1303
1304        // Update viewport input from surface output.
1305        '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        // Just validate viewport information on input
1348        let input = viewport.info.clone();
1349
1350        // After copying required information, drop the lock.
1351        drop(viewport_lock);
1352
1353        // Reset viewport info.
1354        self.share
1355            .raw_input_template
1356            .lock()
1357            .viewports
1358            .insert(id, input);
1359
1360        // Checkin surface again.
1361        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        // NOTE: Seems recursive call to begin_frame is handled by stack internally.
1368        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            // Just set repaint schedule to far future.
1383            viewport.repaint_at = Some(Instant::now() + Duration::from_secs(3600));
1384
1385            // If close request is delivered from platform, forward the event to EGUI that
1386            // allow user logic to handle this. (e.g. cancel the close request)
1387            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        // Retrieve viewport-wise output.
1405        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            // XXX: Is calling this every frame safe?
1430            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        // Handle platform outputs accumulated from all viewports.
1437        {
1438            let egui::PlatformOutput {
1439                commands,
1440                events,
1441                mutable_text_under_cursor,
1442
1443                // Handled by each viewport.
1444                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                // XXX: Do we need virtual board ...?
1466            }
1467
1468            for _event in events {
1469                // We're not interested in widget outputs
1470            }
1471
1472            let overwrite_cursor = if self.cursor_shape.borrow().is_some() {
1473                // Do not overwrite meaningful cursor with `None` or `Default`
1474                !matches!(cursor_icon, CursorIcon::None | CursorIcon::Default)
1475            } else {
1476                // Prevent `None` cursor disturbing the engine's cursor control
1477                cursor_icon != CursorIcon::None
1478            };
1479
1480            if overwrite_cursor {
1481                *self.cursor_shape.borrow_mut() = Some(cursor_icon);
1482            }
1483        }
1484
1485        // Accumulate outputs to primary output.
1486        self.share.full_output.lock().append(output);
1487
1488        // Call setup scripts that was queued during frame.
1489        for script in self.setup_scripts.borrow_mut().drain(..) {
1490            script(&self.share.egui);
1491        }
1492    }
1493
1494    /// Start frame in thread-safe manner.
1495    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
1533/* ---------------------------------------------------------------------------------------------- */
1534/*                                         WIDGET SUPPORT                                         */
1535/* ---------------------------------------------------------------------------------------------- */
1536
1537/* ----------------------------------------- Decorators ----------------------------------------- */
1538
1539/// Base trait for all widget callbacks.
1540pub 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
1553/* ------------------------------------- Expiration Sentinel ------------------------------------ */
1554
1555pub 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
1595/* ------------------------------------------ Extension ----------------------------------------- */
1596
1597/// Various utilities to extend the widget callback.
1598pub trait FnEguiDrawExt<L: Into<WidgetRetain>>: Sized + FnEguiDraw<L> {
1599    /// Set the expiration time of the widget. If the widget is not disposed after the given
1600    /// system time, it'll be disposed automatically.
1601    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    /// Set the expiration time of the widget. If the widget is not disposed after the given
1612    /// time, it'll be disposed automatically.
1613    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    /// Trigger the widget only once. After the first call, the widget will be disposed.
1624    fn once(mut self) -> impl FnEguiDrawExt<WidgetRetain> {
1625        move |ctx: &egui::Context| {
1626            // Only the first call will be executed.
1627            let _ = self(ctx).into();
1628            WidgetRetain::Dispose
1629        }
1630    }
1631
1632    /// Set the lifespan of the widget. If the widget is not disposed after the given
1633    /// time, it'll be disposed automatically.
1634    ///
1635    /// # Warning
1636    ///
1637    /// The time is not game delta time, but the system time: Which means, even if you
1638    /// stopped the game, the widget will be disposed after the given 'real' time.
1639    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}