nightshade-editor 0.13.4

An interactive editor for the Nightshade game engine
use crate::widgets::{EditorWidget, WebWidget};
use nightshade::prelude::{egui, window};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender, channel};
use wry::dpi::{LogicalPosition, LogicalSize};
use wry::{NewWindowResponse, Rect, WebView, WebViewBuilder};

pub struct WebviewState {
    webviews: HashMap<String, WebView>,
    webview_bounds: HashMap<String, (f64, f64, f64, f64)>,
    window: Option<Arc<window::Window>>,
    new_window_sender: Sender<String>,
    new_window_receiver: Receiver<String>,
}

impl Default for WebviewState {
    fn default() -> Self {
        let (sender, receiver) = channel();
        Self {
            webviews: HashMap::new(),
            webview_bounds: HashMap::new(),
            window: None,
            new_window_sender: sender,
            new_window_receiver: receiver,
        }
    }
}

impl WebviewState {
    pub fn set_window(&mut self, window: Arc<window::Window>) {
        self.window = Some(window);
    }

    pub fn create_webview(&mut self, id: String, url: &str, rect: egui::Rect) {
        if self.webviews.contains_key(&id) {
            return;
        }

        let Some(window) = &self.window else {
            return;
        };

        let bounds = Rect {
            position: LogicalPosition::new(rect.min.x as f64, rect.min.y as f64).into(),
            size: LogicalSize::new(rect.width() as f64, rect.height() as f64).into(),
        };

        let sender = self.new_window_sender.clone();
        let webview_result = WebViewBuilder::new()
            .with_url(url)
            .with_bounds(bounds)
            .with_navigation_handler(|_| true)
            .with_new_window_req_handler(move |url, _| {
                let _ = sender.send(url);
                NewWindowResponse::Deny
            })
            .build_as_child(window.as_ref());

        if let Ok(webview) = webview_result {
            let _ = webview.set_visible(true);
            self.webview_bounds.insert(
                id.clone(),
                (
                    rect.min.x as f64,
                    rect.min.y as f64,
                    rect.width() as f64,
                    rect.height() as f64,
                ),
            );
            self.webviews.insert(id, webview);
        }
    }

    pub fn update_position(&mut self, id: &str, rect: egui::Rect) {
        let Some(webview) = self.webviews.get(id) else {
            return;
        };

        let new_bounds = (
            rect.min.x as f64,
            rect.min.y as f64,
            rect.width() as f64,
            rect.height() as f64,
        );

        if let Some(old_bounds) = self.webview_bounds.get(id)
            && *old_bounds == new_bounds
        {
            return;
        }

        let bounds = Rect {
            position: LogicalPosition::new(new_bounds.0, new_bounds.1).into(),
            size: LogicalSize::new(new_bounds.2, new_bounds.3).into(),
        };

        let _ = webview.set_bounds(bounds);
        self.webview_bounds.insert(id.to_string(), new_bounds);
    }

    pub fn ensure_all_visible(&self) {
        for webview in self.webviews.values() {
            let _ = webview.set_visible(true);
        }
    }

    pub fn pending_new_windows(&self) -> Vec<String> {
        self.new_window_receiver.try_iter().collect()
    }

    pub fn has_webview(&self, id: &str) -> bool {
        self.webviews.contains_key(id)
    }

    pub fn retain_only(&mut self, active_ids: &[(String, String, egui::Rect)]) {
        self.webviews
            .retain(|id, _| active_ids.iter().any(|(active_id, _, _)| active_id == id));
        self.webview_bounds
            .retain(|id, _| active_ids.iter().any(|(active_id, _, _)| active_id == id));
    }
}

impl crate::Editor {
    pub fn update_webviews(&mut self, world: &mut nightshade::prelude::World) {
        if let Some(window_handle) = &world.resources.window.handle {
            self.webview_state.set_window(window_handle.clone());
        }

        self.webview_state
            .retain_only(&self.context.ui.web_widget_rects);

        for (id, url, rect) in self.context.ui.web_widget_rects.drain(..) {
            if !self.webview_state.has_webview(&id) {
                self.webview_state.create_webview(id.clone(), &url, rect);
            }
            self.webview_state.update_position(&id, rect);
        }

        self.webview_state.ensure_all_visible();

        for url in self.webview_state.pending_new_windows() {
            let web_widget = WebWidget {
                url,
                id: nightshade::prelude::uuid::Uuid::new_v4().to_string(),
            };
            self.mosaic.insert_pane(EditorWidget::Web(web_widget));
        }
    }
}