feat: unify webview source into WebviewSource enum with dynamic reload (#22)
Unify CefWebviewUri and InlineHtml into a single WebviewSource enum component, eliminating the dual-component inconsistency where both existed on the same entity
Add ResolvedWebviewUri internal component as a resolution layer between user-facing source and CEF
Support dynamic reload: mutating WebviewSource at runtime automatically navigates the existing browser via browsers.navigate() without recreation
Inline HTML is served via cef://localhost/__inline__/{id} with automatic cleanup on entity despawn
This commit is contained in:
@@ -64,6 +64,9 @@ bevy_cef = { workspace = true, features = ["debug"] }
|
|||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
objc = { version = "0.2" }
|
objc = { version = "0.2" }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("cargo-clippy"))'] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
serialize = ["bevy/serialize"]
|
serialize = ["bevy/serialize"]
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ fn spawn_webview(
|
|||||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefWebviewUri::local("brp.html"),
|
WebviewSource::local("brp.html"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial {
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial {
|
||||||
base: StandardMaterial {
|
base: StandardMaterial {
|
||||||
@@ -72,7 +72,7 @@ fn ime(mut windows: Query<&mut bevy::prelude::Window>) {
|
|||||||
|
|
||||||
fn show_devtool(
|
fn show_devtool(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
webviews: Query<Entity, With<CefWebviewUri>>,
|
webviews: Query<Entity, With<WebviewSource>>,
|
||||||
mut initialized: Local<bool>,
|
mut initialized: Local<bool>,
|
||||||
) {
|
) {
|
||||||
if *initialized {
|
if *initialized {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ fn spawn_webview(
|
|||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefWebviewUri::new("https://github.com/not-elm/bevy_cef"),
|
WebviewSource::new("https://github.com/not-elm/bevy_cef"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendedMaterial {
|
MeshMaterial3d(materials.add(WebviewExtendedMaterial {
|
||||||
extension: CustomExtension {
|
extension: CustomExtension {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ fn spawn_webview(
|
|||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
DebugWebview,
|
DebugWebview,
|
||||||
CefWebviewUri::new("https://github.com/not-elm/bevy_cef"),
|
WebviewSource::new("https://github.com/not-elm/bevy_cef"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial {
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial {
|
||||||
base: StandardMaterial {
|
base: StandardMaterial {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ fn spawn_webview(
|
|||||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefWebviewUri::local("extensions.html"),
|
WebviewSource::local("extensions.html"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ fn spawn_webview(
|
|||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
DebugWebview,
|
DebugWebview,
|
||||||
CefWebviewUri::local("host_emit.html"),
|
WebviewSource::local("host_emit.html"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
));
|
));
|
||||||
|
|||||||
67
examples/inline_html.rs
Normal file
67
examples/inline_html.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//! Demonstrates rendering inline HTML content directly without an external URL or asset file.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_cef::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins((DefaultPlugins, CefPlugin::default()))
|
||||||
|
.add_systems(
|
||||||
|
Startup,
|
||||||
|
(spawn_camera, spawn_directional_light, spawn_webview),
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_camera(mut commands: Commands) {
|
||||||
|
commands.spawn((
|
||||||
|
Camera3d::default(),
|
||||||
|
Transform::from_translation(Vec3::new(0., 0., 3.)).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_directional_light(mut commands: Commands) {
|
||||||
|
commands.spawn((
|
||||||
|
DirectionalLight::default(),
|
||||||
|
Transform::from_translation(Vec3::new(1., 1., 1.)).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_webview(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
|
) {
|
||||||
|
commands.spawn((
|
||||||
|
WebviewSource::inline(
|
||||||
|
r#"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.container { text-align: center; }
|
||||||
|
h1 { font-size: 3rem; margin-bottom: 0.5rem; }
|
||||||
|
p { font-size: 1.2rem; opacity: 0.8; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Inline HTML</h1>
|
||||||
|
<p>This content is rendered from a Rust string.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>"#,
|
||||||
|
),
|
||||||
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ fn spawn_webview(
|
|||||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefWebviewUri::local("js_emit.html"),
|
WebviewSource::local("js_emit.html"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ fn spawn_webview(
|
|||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
DebugWebview,
|
DebugWebview,
|
||||||
CefWebviewUri::new("https://github.com/not-elm/bevy_cef"),
|
WebviewSource::new("https://github.com/not-elm/bevy_cef"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ fn spawn_webview(
|
|||||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefWebviewUri::new("https://github.com/not-elm/bevy_cef"),
|
WebviewSource::new("https://github.com/not-elm/bevy_cef"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
// Here, we add a simple script to show an alert.
|
// Here, we add a simple script to show an alert.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ fn spawn_webview(
|
|||||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefWebviewUri::new("https://github.com/not-elm/bevy_cef"),
|
WebviewSource::new("https://github.com/not-elm/bevy_cef"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ fn spawn_camera_2d(mut commands: Commands) {
|
|||||||
|
|
||||||
fn spawn_sprite_webview(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
|
fn spawn_sprite_webview(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefWebviewUri::new("https://github.com/not-elm/bevy_cef"),
|
WebviewSource::new("https://github.com/not-elm/bevy_cef"),
|
||||||
Pickable::default(),
|
Pickable::default(),
|
||||||
Sprite {
|
Sprite {
|
||||||
image: images.add(Image::default()),
|
image: images.add(Image::default()),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ fn spawn_webview(
|
|||||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefWebviewUri("https://bevy.org/".to_string()),
|
WebviewSource::new("https://bevy.org/"),
|
||||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
mod components;
|
mod components;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
mod localhost;
|
pub(crate) mod localhost;
|
||||||
mod message_loop;
|
mod message_loop;
|
||||||
|
|
||||||
pub use components::*;
|
pub use components::*;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pub(crate) struct WebviewCoreComponentsPlugin;
|
|||||||
impl Plugin for WebviewCoreComponentsPlugin {
|
impl Plugin for WebviewCoreComponentsPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<WebviewSize>()
|
app.register_type::<WebviewSize>()
|
||||||
.register_type::<CefWebviewUri>()
|
.register_type::<WebviewSource>()
|
||||||
.register_type::<HostWindow>()
|
.register_type::<HostWindow>()
|
||||||
.register_type::<ZoomLevel>()
|
.register_type::<ZoomLevel>()
|
||||||
.register_type::<AudioMuted>()
|
.register_type::<AudioMuted>()
|
||||||
@@ -15,35 +15,54 @@ impl Plugin for WebviewCoreComponentsPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component that specifies the URI of the webview.
|
/// A component that specifies the content source of a webview.
|
||||||
///
|
///
|
||||||
/// When opening a remote web page, specify the URI with the http(s) schema.
|
/// Use [`WebviewSource::new`] for remote URLs, [`WebviewSource::local`] for local files
|
||||||
|
/// served via `cef://localhost/`, or [`WebviewSource::inline`] for raw HTML content.
|
||||||
///
|
///
|
||||||
/// When opening a local file, use the custom schema `cef://localhost/` to specify the file path.
|
/// When the value of this component is changed at runtime, the existing browser
|
||||||
/// Alternatively, you can also use [`CefWebviewUri::local`].
|
/// automatically navigates to the new source without being recreated.
|
||||||
#[derive(Component, Debug, Clone, PartialEq, Eq, Hash, Reflect)]
|
#[derive(Component, Debug, Clone, Reflect)]
|
||||||
#[reflect(Component, Debug)]
|
#[reflect(Component, Debug)]
|
||||||
#[require(WebviewSize, ZoomLevel, AudioMuted, PreloadScripts)]
|
#[require(WebviewSize, ZoomLevel, AudioMuted, PreloadScripts)]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
pub enum WebviewSource {
|
||||||
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
|
/// A remote or local URL (e.g. `"https://..."` or `"cef://localhost/file.html"`).
|
||||||
pub struct CefWebviewUri(pub String);
|
Url(String),
|
||||||
|
/// Raw HTML content served via an internal `cef://localhost/__inline__/{id}` scheme.
|
||||||
|
InlineHtml(String),
|
||||||
|
}
|
||||||
|
|
||||||
impl CefWebviewUri {
|
impl WebviewSource {
|
||||||
/// Creates a new `CefWebviewUri` with the given URI.
|
/// Creates a URL source.
|
||||||
///
|
///
|
||||||
/// If you want to specify a local file path, use [`CefWebviewUri::local`] instead.
|
/// To specify a local file path, use [`WebviewSource::local`] instead.
|
||||||
pub fn new(uri: impl Into<String>) -> Self {
|
pub fn new(url: impl Into<String>) -> Self {
|
||||||
Self(uri.into())
|
Self::Url(url.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `CefWebviewUri` with the given file path.
|
/// Creates a local file source.
|
||||||
///
|
///
|
||||||
/// It interprets the given path as a file path in the format `cef://localhost/<file_path>`.
|
/// The given path is interpreted as `cef://localhost/<path>`.
|
||||||
pub fn local(uri: impl Into<String>) -> Self {
|
pub fn local(path: impl Into<String>) -> Self {
|
||||||
Self(format!("{SCHEME_CEF}://{HOST_CEF}/{}", uri.into()))
|
Self::Url(format!("{SCHEME_CEF}://{HOST_CEF}/{}", path.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an inline HTML source.
|
||||||
|
///
|
||||||
|
/// The HTML content is served through the internal `cef://localhost/__inline__/{id}` scheme,
|
||||||
|
/// so IPC (`window.cef.emit/listen/brp`) and [`PreloadScripts`] work as expected.
|
||||||
|
pub fn inline(html: impl Into<String>) -> Self {
|
||||||
|
Self::InlineHtml(html.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal component holding the resolved URL string passed to CEF.
|
||||||
|
///
|
||||||
|
/// This is automatically managed by the resolver system and should not be
|
||||||
|
/// inserted manually.
|
||||||
|
#[derive(Component, Debug, Clone)]
|
||||||
|
pub(crate) struct ResolvedWebviewUri(pub(crate) String);
|
||||||
|
|
||||||
/// Specifies the view size of the webview.
|
/// Specifies the view size of the webview.
|
||||||
///
|
///
|
||||||
/// This does not affect the actual object size.
|
/// This does not affect the actual object size.
|
||||||
|
|||||||
@@ -1,7 +1,30 @@
|
|||||||
use crate::common::localhost::asset_loader::CefResponseHandle;
|
use crate::common::localhost::asset_loader::CefResponseHandle;
|
||||||
use bevy::platform::collections::HashSet;
|
use crate::common::{ResolvedWebviewUri, WebviewSource};
|
||||||
|
use bevy::platform::collections::{HashMap, HashSet};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_cef_core::prelude::*;
|
use bevy_cef_core::prelude::*;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
static INLINE_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// Prefix for inline HTML URIs within the `cef://localhost/` scheme.
|
||||||
|
const INLINE_PREFIX: &str = "__inline__/";
|
||||||
|
|
||||||
|
/// Cleanup marker that stays on the entity. Removed on despawn to clean up the store.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub(crate) struct InlineHtmlId(pub(crate) String);
|
||||||
|
|
||||||
|
/// In-memory store for inline HTML content.
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub(crate) struct InlineHtmlStore {
|
||||||
|
by_id: HashMap<String, Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlineHtmlStore {
|
||||||
|
pub(crate) fn remove(&mut self, id: &str) {
|
||||||
|
self.by_id.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ResponserPlugin;
|
pub struct ResponserPlugin;
|
||||||
|
|
||||||
@@ -10,6 +33,8 @@ impl Plugin for ResponserPlugin {
|
|||||||
let (tx, rx) = async_channel::unbounded();
|
let (tx, rx) = async_channel::unbounded();
|
||||||
app.insert_resource(Requester(tx))
|
app.insert_resource(Requester(tx))
|
||||||
.insert_resource(RequesterReceiver(rx))
|
.insert_resource(RequesterReceiver(rx))
|
||||||
|
.init_resource::<InlineHtmlStore>()
|
||||||
|
.add_systems(PreUpdate, resolve_webview_source)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
@@ -26,17 +51,76 @@ fn any_changed_assets(mut er: MessageReader<AssetEvent<CefResponse>>) -> bool {
|
|||||||
.any(|event| matches!(event, AssetEvent::Modified { .. }))
|
.any(|event| matches!(event, AssetEvent::Modified { .. }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_webview_source(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut store: ResMut<InlineHtmlStore>,
|
||||||
|
query: Query<(Entity, &WebviewSource, Option<&InlineHtmlId>), Changed<WebviewSource>>,
|
||||||
|
) {
|
||||||
|
for (entity, source, existing_id) in query.iter() {
|
||||||
|
// Clean up old inline entry if switching away or updating
|
||||||
|
if let Some(old_id) = existing_id {
|
||||||
|
store.by_id.remove(&old_id.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
match source {
|
||||||
|
WebviewSource::Url(url) => {
|
||||||
|
let mut entity_commands = commands.entity(entity);
|
||||||
|
entity_commands.insert(ResolvedWebviewUri(url.clone()));
|
||||||
|
if existing_id.is_some() {
|
||||||
|
entity_commands.remove::<InlineHtmlId>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WebviewSource::InlineHtml(html) => {
|
||||||
|
let id = INLINE_ID_COUNTER
|
||||||
|
.fetch_add(1, Ordering::Relaxed)
|
||||||
|
.to_string();
|
||||||
|
store.by_id.insert(id.clone(), html.as_bytes().to_vec());
|
||||||
|
|
||||||
|
let url = format!("{SCHEME_CEF}://{HOST_CEF}/{INLINE_PREFIX}{id}");
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert((ResolvedWebviewUri(url), InlineHtmlId(id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn coming_request(
|
fn coming_request(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
requester_receiver: Res<RequesterReceiver>,
|
requester_receiver: Res<RequesterReceiver>,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
|
store: Res<InlineHtmlStore>,
|
||||||
) {
|
) {
|
||||||
while let Ok(request) = requester_receiver.0.try_recv() {
|
while let Ok(request) = requester_receiver.0.try_recv() {
|
||||||
|
if let Some(id) = extract_inline_id(&request.uri) {
|
||||||
|
let response = match store.by_id.get(id) {
|
||||||
|
Some(data) => CefResponse {
|
||||||
|
mime_type: "text/html".to_string(),
|
||||||
|
status_code: 200,
|
||||||
|
data: data.clone(),
|
||||||
|
},
|
||||||
|
None => CefResponse {
|
||||||
|
mime_type: "text/plain".to_string(),
|
||||||
|
status_code: 404,
|
||||||
|
data: b"Not Found".to_vec(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let _ = request.responser.0.send_blocking(response);
|
||||||
|
} else {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
CefResponseHandle(asset_server.load(request.uri)),
|
CefResponseHandle(asset_server.load(request.uri)),
|
||||||
request.responser,
|
request.responser,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the inline ID from a URI like `__inline__/123` or `__inline__/123?query#fragment`.
|
||||||
|
fn extract_inline_id(uri: &str) -> Option<&str> {
|
||||||
|
let rest = uri.strip_prefix(INLINE_PREFIX)?;
|
||||||
|
// Strip query string and fragment
|
||||||
|
let id = rest.split(['?', '#']).next().unwrap_or(rest);
|
||||||
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn responser(
|
fn responser(
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ mod macos {
|
|||||||
pub fn install_cef_app_protocol() {
|
pub fn install_cef_app_protocol() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let cls = Class::get("NSApplication").expect("NSApplication クラスが見つかりません");
|
let cls = Class::get("NSApplication").expect("NSApplication クラスが見つかりません");
|
||||||
#[allow(unexpected_cfgs)]
|
|
||||||
let sel_name = sel!(isHandlingSendEvent);
|
let sel_name = sel!(isHandlingSendEvent);
|
||||||
let success = class_addMethod(
|
let success = class_addMethod(
|
||||||
cls as *const _,
|
cls as *const _,
|
||||||
@@ -141,7 +140,6 @@ mod macos {
|
|||||||
);
|
);
|
||||||
assert!(success, "メソッド追加に失敗しました");
|
assert!(success, "メソッド追加に失敗しました");
|
||||||
|
|
||||||
#[allow(unexpected_cfgs)]
|
|
||||||
let sel_set = sel!(setHandlingSendEvent:);
|
let sel_set = sel!(setHandlingSendEvent:);
|
||||||
let success2 = class_addMethod(
|
let success2 = class_addMethod(
|
||||||
cls as *const _,
|
cls as *const _,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::common::CefWebviewUri;
|
use crate::common::WebviewSource;
|
||||||
use bevy::input::keyboard::KeyboardInput;
|
use bevy::input::keyboard::KeyboardInput;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_cef_core::prelude::{Browsers, create_cef_key_event, keyboard_modifiers};
|
use bevy_cef_core::prelude::{Browsers, create_cef_key_event, keyboard_modifiers};
|
||||||
@@ -70,7 +70,7 @@ fn send_key_event(
|
|||||||
mut is_ime_commiting: ResMut<IsImeCommiting>,
|
mut is_ime_commiting: ResMut<IsImeCommiting>,
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
browsers: NonSend<Browsers>,
|
browsers: NonSend<Browsers>,
|
||||||
webviews: Query<Entity, With<CefWebviewUri>>,
|
webviews: Query<Entity, With<WebviewSource>>,
|
||||||
) {
|
) {
|
||||||
let modifiers = keyboard_modifiers(&input);
|
let modifiers = keyboard_modifiers(&input);
|
||||||
for event in er.read() {
|
for event in er.read() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::prelude::{CefWebviewUri, WebviewSize};
|
use crate::prelude::{WebviewSize, WebviewSource};
|
||||||
use crate::system_param::mesh_aabb::MeshAabb;
|
use crate::system_param::mesh_aabb::MeshAabb;
|
||||||
use bevy::ecs::system::SystemParam;
|
use bevy::ecs::system::SystemParam;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -12,9 +12,9 @@ pub struct WebviewPointer<'w, 's, C: Component = Camera3d> {
|
|||||||
'w,
|
'w,
|
||||||
's,
|
's,
|
||||||
(&'static GlobalTransform, &'static WebviewSize),
|
(&'static GlobalTransform, &'static WebviewSize),
|
||||||
(With<CefWebviewUri>, Without<Camera>),
|
(With<WebviewSource>, Without<Camera>),
|
||||||
>,
|
>,
|
||||||
parents: Query<'w, 's, (Option<&'static ChildOf>, Has<CefWebviewUri>)>,
|
parents: Query<'w, 's, (Option<&'static ChildOf>, Has<WebviewSource>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Component> WebviewPointer<'_, '_, C> {
|
impl<C: Component> WebviewPointer<'_, '_, C> {
|
||||||
@@ -46,7 +46,7 @@ impl<C: Component> WebviewPointer<'_, '_, C> {
|
|||||||
|
|
||||||
fn find_webview_entity(
|
fn find_webview_entity(
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
parents: &Query<(Option<&ChildOf>, Has<CefWebviewUri>)>,
|
parents: &Query<(Option<&ChildOf>, Has<WebviewSource>)>,
|
||||||
) -> Option<Entity> {
|
) -> Option<Entity> {
|
||||||
let (child_of, has_webview) = parents.get(entity).ok()?;
|
let (child_of, has_webview) = parents.get(entity).ok()?;
|
||||||
if has_webview {
|
if has_webview {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use crate::common::{CefWebviewUri, HostWindow, IpcEventRawSender, WebviewSize};
|
use crate::common::localhost::responser::{InlineHtmlId, InlineHtmlStore};
|
||||||
|
use crate::common::{
|
||||||
|
HostWindow, IpcEventRawSender, ResolvedWebviewUri, WebviewSize, WebviewSource,
|
||||||
|
};
|
||||||
use crate::cursor_icon::SystemCursorIconSender;
|
use crate::cursor_icon::SystemCursorIconSender;
|
||||||
use crate::prelude::PreloadScripts;
|
use crate::prelude::PreloadScripts;
|
||||||
use crate::webview::mesh::MeshWebviewPlugin;
|
use crate::webview::mesh::MeshWebviewPlugin;
|
||||||
@@ -79,16 +82,24 @@ impl Plugin for WebviewPlugin {
|
|||||||
(
|
(
|
||||||
resize.run_if(any_resized),
|
resize.run_if(any_resized),
|
||||||
create_webview.run_if(added_webview),
|
create_webview.run_if(added_webview),
|
||||||
|
navigate_on_source_change,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.add_observer(apply_request_show_devtool)
|
.add_observer(apply_request_show_devtool)
|
||||||
.add_observer(apply_request_close_devtool);
|
.add_observer(apply_request_close_devtool);
|
||||||
|
|
||||||
app.world_mut()
|
app.world_mut()
|
||||||
.register_component_hooks::<CefWebviewUri>()
|
.register_component_hooks::<WebviewSource>()
|
||||||
.on_despawn(|mut world: DeferredWorld, ctx: HookContext| {
|
.on_despawn(|mut world: DeferredWorld, ctx: HookContext| {
|
||||||
world.non_send_resource_mut::<Browsers>().close(&ctx.entity);
|
world.non_send_resource_mut::<Browsers>().close(&ctx.entity);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.world_mut()
|
||||||
|
.register_component_hooks::<InlineHtmlId>()
|
||||||
|
.on_remove(|mut world: DeferredWorld, ctx: HookContext| {
|
||||||
|
let id = world.get::<InlineHtmlId>(ctx.entity).unwrap().0.clone();
|
||||||
|
world.resource_mut::<InlineHtmlStore>().remove(&id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +107,7 @@ fn any_resized(webviews: Query<Entity, Changed<WebviewSize>>) -> bool {
|
|||||||
!webviews.is_empty()
|
!webviews.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn added_webview(webviews: Query<Entity, Added<CefWebviewUri>>) -> bool {
|
fn added_webview(webviews: Query<Entity, Added<ResolvedWebviewUri>>) -> bool {
|
||||||
!webviews.is_empty()
|
!webviews.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +125,12 @@ fn create_webview(
|
|||||||
webviews: Query<
|
webviews: Query<
|
||||||
(
|
(
|
||||||
Entity,
|
Entity,
|
||||||
&CefWebviewUri,
|
&ResolvedWebviewUri,
|
||||||
&WebviewSize,
|
&WebviewSize,
|
||||||
&PreloadScripts,
|
&PreloadScripts,
|
||||||
Option<&HostWindow>,
|
Option<&HostWindow>,
|
||||||
),
|
),
|
||||||
Added<CefWebviewUri>,
|
Added<ResolvedWebviewUri>,
|
||||||
>,
|
>,
|
||||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
) {
|
) {
|
||||||
@@ -148,6 +159,19 @@ fn create_webview(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn navigate_on_source_change(
|
||||||
|
browsers: NonSend<Browsers>,
|
||||||
|
webviews: Query<(Entity, &ResolvedWebviewUri), Changed<ResolvedWebviewUri>>,
|
||||||
|
added: Query<Entity, Added<ResolvedWebviewUri>>,
|
||||||
|
) {
|
||||||
|
for (entity, uri) in webviews.iter() {
|
||||||
|
if added.contains(entity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
browsers.navigate(&entity, &uri.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resize(
|
fn resize(
|
||||||
browsers: NonSend<Browsers>,
|
browsers: NonSend<Browsers>,
|
||||||
webviews: Query<(Entity, &WebviewSize), Changed<WebviewSize>>,
|
webviews: Query<(Entity, &WebviewSize), Changed<WebviewSize>>,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ impl Plugin for MeshWebviewPlugin {
|
|||||||
|
|
||||||
fn setup_observers(
|
fn setup_observers(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
webviews: Query<Entity, (Added<CefWebviewUri>, Or<(With<Mesh3d>, With<Mesh2d>)>)>,
|
webviews: Query<Entity, (Added<WebviewSource>, Or<(With<Mesh3d>, With<Mesh2d>)>)>,
|
||||||
) {
|
) {
|
||||||
for entity in webviews.iter() {
|
for entity in webviews.iter() {
|
||||||
commands
|
commands
|
||||||
@@ -88,7 +88,7 @@ fn on_mouse_wheel(
|
|||||||
browsers: NonSend<Browsers>,
|
browsers: NonSend<Browsers>,
|
||||||
pointer: WebviewPointer,
|
pointer: WebviewPointer,
|
||||||
windows: Query<&Window>,
|
windows: Query<&Window>,
|
||||||
webviews: Query<Entity, With<CefWebviewUri>>,
|
webviews: Query<Entity, With<WebviewSource>>,
|
||||||
) {
|
) {
|
||||||
let Some(cursor_pos) = windows.iter().find_map(|window| window.cursor_position()) else {
|
let Some(cursor_pos) = windows.iter().find_map(|window| window.cursor_position()) else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::common::{CefWebviewUri, WebviewSize};
|
use crate::common::{WebviewSize, WebviewSource};
|
||||||
use crate::prelude::update_webview_image;
|
use crate::prelude::update_webview_image;
|
||||||
use bevy::input::mouse::MouseWheel;
|
use bevy::input::mouse::MouseWheel;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -30,7 +30,7 @@ impl Plugin for WebviewSpritePlugin {
|
|||||||
fn render(
|
fn render(
|
||||||
mut er: MessageReader<RenderTextureMessage>,
|
mut er: MessageReader<RenderTextureMessage>,
|
||||||
mut images: ResMut<Assets<bevy::prelude::Image>>,
|
mut images: ResMut<Assets<bevy::prelude::Image>>,
|
||||||
webviews: Query<&Sprite, With<CefWebviewUri>>,
|
webviews: Query<&Sprite, With<WebviewSource>>,
|
||||||
) {
|
) {
|
||||||
for texture in er.read() {
|
for texture in er.read() {
|
||||||
if let Ok(sprite) = webviews.get(texture.webview)
|
if let Ok(sprite) = webviews.get(texture.webview)
|
||||||
@@ -43,7 +43,7 @@ fn render(
|
|||||||
|
|
||||||
fn setup_observers(
|
fn setup_observers(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
webviews: Query<Entity, (Added<CefWebviewUri>, With<Sprite>)>,
|
webviews: Query<Entity, (Added<WebviewSource>, With<Sprite>)>,
|
||||||
) {
|
) {
|
||||||
for entity in webviews.iter() {
|
for entity in webviews.iter() {
|
||||||
commands
|
commands
|
||||||
|
|||||||
Reference in New Issue
Block a user