bevy_cef 0.14.0

Bevy CEF integration for web rendering
use crate::prelude::WebviewMaterial;
#[cfg(not(target_os = "macos"))]
use crate::prelude::{WebviewSurface, update_webview_image};
#[cfg(not(target_os = "macos"))]
use bevy::asset::AssetEventSystems;
use bevy::asset::{AssetPath, embedded_asset, embedded_path};
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
use bevy::prelude::*;
use bevy::shader::ShaderRef;
// `RenderTextureMessage` from the core prelude is only used by the CPU `OnPaint`
// consumer (Linux/Windows). macOS injects the texture via the GPU path.
#[cfg(not(target_os = "macos"))]
use bevy_cef_core::prelude::*;

pub(super) struct WebviewExtendStandardMaterialPlugin;

impl Plugin for WebviewExtendStandardMaterialPlugin {
    fn build(&self, app: &mut App) {
        // NOTE: `MaterialPlugin::build` starts loading `fragment_shader()` immediately, so
        // the shader must be embedded before the plugin is added.
        embedded_asset!(app, "webview_extend_standard_material.wesl");
        app.add_plugins(MaterialPlugin::<WebviewExtendStandardMaterial>::default());

        // CPU `OnPaint` consumer: Linux/Windows only. macOS injects the live
        // webview texture into `RenderAssets<GpuImage>` via the GPU path.
        #[cfg(not(target_os = "macos"))]
        app.add_systems(
            PostUpdate,
            render_standard_materials.before(AssetEventSystems),
        );
    }
}

impl MaterialExtension for WebviewMaterial {
    fn fragment_shader() -> ShaderRef {
        ShaderRef::Path(
            AssetPath::from_path_buf(embedded_path!("webview_extend_standard_material.wesl"))
                .with_source("embedded"),
        )
    }
}

pub type WebviewExtendStandardMaterial = ExtendedMaterial<StandardMaterial, WebviewMaterial>;

#[cfg(not(target_os = "macos"))]
fn render_standard_materials(
    mut commands: Commands,
    mut er: MessageReader<RenderTextureMessage>,
    mut images: ResMut<Assets<Image>>,
    mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
    webviews: Query<&MeshMaterial3d<WebviewExtendStandardMaterial>>,
) {
    for texture in er.read() {
        if let Ok(handle) = webviews.get(texture.webview)
            && let Some(mut material) = materials.get_mut(handle.id())
            && let Some(image) = {
                let handle = material
                    .extension
                    .surface
                    .get_or_insert_with(|| images.add(Image::default()));
                commands
                    .entity(texture.webview)
                    .insert(WebviewSurface(handle.clone()));
                images.get_mut(handle.id())
            }
        {
            update_webview_image(texture, image);
        }
    }
}

#[cfg(target_os = "macos")]
impl crate::webview::gpu_surface::WebviewSurfaceSlot for WebviewExtendStandardMaterial {
    fn webview_surface_slot(&self) -> &Option<Handle<Image>> {
        &self.extension.surface
    }

    fn webview_surface_slot_mut(&mut self) -> &mut Option<Handle<Image>> {
        &mut self.extension.surface
    }
}

#[cfg(all(test, target_os = "macos"))]
mod tests {
    use super::*;
    use crate::prelude::WebviewMaterial;
    use crate::webview::gpu_surface::WebviewSurfaceSlot;

    #[test]
    fn slot_points_at_extension_surface() {
        let mut m = WebviewExtendStandardMaterial {
            base: StandardMaterial::default(),
            extension: WebviewMaterial::default(),
        };
        // Default has no surface yet.
        assert!(m.webview_surface_slot().is_none());
        // Writing through the slot must land in `extension.surface`.
        *m.webview_surface_slot_mut() = Some(Handle::<Image>::default());
        assert!(m.extension.surface.is_some());
    }
}