cranpose 0.9.4

Cranpose runtime and UI facade
Documentation
use std::rc::Rc;

use cranpose_ui::Rect;
use raw_window_handle::HasWindowHandle;

use crate::{
    native_view::NativeViewHost,
    webview_host::{BrowserView, WebViews},
};

pub(crate) fn wake_callback(proxy: &winit::event_loop::EventLoopProxy) -> Rc<dyn Fn()> {
    let proxy = proxy.clone();
    Rc::new(move || proxy.wake_up())
}

impl BrowserView for wry::WebView {
    fn navigate(&mut self, url: &str) -> Result<(), String> {
        self.load_url(url).map_err(|error| error.to_string())
    }

    fn place(&mut self, bounds: Rect) -> Result<(), String> {
        #[cfg(not(target_os = "ios"))]
        self.set_bounds(wry_bounds(bounds))
            .map_err(|error| error.to_string())?;
        #[cfg(target_os = "ios")]
        {
            use objc2_core_foundation::{CGPoint, CGRect, CGSize};
            use wry::WebViewExtIOS;
            self.webview().setFrame(CGRect::new(
                CGPoint::new(f64::from(bounds.x), f64::from(bounds.y)),
                CGSize::new(
                    f64::from(bounds.width.max(0.0)),
                    f64::from(bounds.height.max(0.0)),
                ),
            ));
        }
        self.set_visible(bounds.width > 0.0 && bounds.height > 0.0)
            .map_err(|error| error.to_string())
    }
}

fn wry_bounds(bounds: Rect) -> wry::Rect {
    wry::Rect {
        position: wry::dpi::LogicalPosition::new(bounds.x, bounds.y).into(),
        size: wry::dpi::LogicalSize::new(bounds.width.max(0.0), bounds.height.max(0.0)).into(),
    }
}

pub(crate) fn sync<R: cranpose_render_common::Renderer>(
    views: &mut WebViews<wry::WebView>,
    host: &NativeViewHost,
    shell: Option<&mut cranpose_app_shell::AppShell<R>>,
    window: Option<&impl HasWindowHandle>,
    wake: &Rc<dyn Fn()>,
) where
    R::Error: std::fmt::Debug,
{
    if host.is_empty() && views.is_empty() {
        return;
    }
    let (Some(shell), Some(window)) = (shell, window) else {
        return;
    };
    views.sync(host, shell.layout_tree(), |id, url, bounds, events| {
        #[cfg(target_os = "linux")]
        gtk::init().map_err(|error| error.to_string())?;
        let events = events.clone();
        let wake = Rc::clone(wake);
        let builder = wry::WebViewBuilder::new()
            .with_bounds(wry_bounds(bounds))
            .with_url(url)
            .with_on_page_load_handler(move |event, url| {
                if matches!(event, wry::PageLoadEvent::Finished) {
                    let _ = events.send((id, format!("loaded:{url}")));
                    wake();
                }
            });
        #[cfg(not(target_os = "ios"))]
        let mut view = builder
            .build_as_child(window)
            .map_err(|error| error.to_string())?;
        #[cfg(target_os = "ios")]
        let mut view = builder.build(window).map_err(|error| error.to_string())?;
        view.place(bounds)?;
        Ok(view)
    });
}

#[cfg(target_os = "linux")]
pub(crate) fn pump(views: &WebViews<wry::WebView>) -> Option<web_time::Instant> {
    views.views().next()?;
    if gtk::is_initialized() {
        for _ in 0..32 {
            if !gtk::events_pending() {
                break;
            }
            gtk::main_iteration_do(false);
        }
    }
    Some(web_time::Instant::now() + std::time::Duration::from_millis(16))
}