Compare commits
18 Commits
22210c664b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c6efe1498b | |||
| e43a22931c | |||
| 530fac29dc | |||
| 0bdc962e2c | |||
| 0e629b98af | |||
| ac07af581e | |||
| a1152ca759 | |||
| d39602eb0e | |||
|
|
d422e14cf8 | ||
|
|
19f7628a53 | ||
|
|
255dc27e1a | ||
|
|
c35ca1b920 | ||
|
|
1b1da6665c | ||
|
|
a6fec33006 | ||
|
|
a26b8e9648 | ||
|
|
9da992fdd6 | ||
|
|
40a3455f55 | ||
|
|
2afc7bb0de |
@@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
allowed-tools: Bash(cargo test:*),
|
|
||||||
description: Create new tests
|
|
||||||
---
|
|
||||||
|
|
||||||
1. Reading $ARGUMENTS and understanding the implementation details.
|
|
||||||
2. Creating new tests for $ARGUMENTS in `tests` module in the same file.
|
|
||||||
3. Run `cargo test --workspace --all-features` to ensure all tests pass.
|
|
||||||
4. If any tests fail, fix the issues and re-run the tests.
|
|
||||||
|
|
||||||
## Contracts
|
|
||||||
|
|
||||||
- You have to write the rust-doc that describes the test case for each test function.
|
|
||||||
37
AGENTS.md
37
AGENTS.md
@@ -1,37 +0,0 @@
|
|||||||
# Repository Guidelines
|
|
||||||
|
|
||||||
## Project Structure & Module Organization
|
|
||||||
- `src/`: Public `bevy_cef` crate API surface.
|
|
||||||
- `crates/bevy_cef_core`: Core CEF integration and IPC.
|
|
||||||
- `crates/bevy_cef_debug_render_process`: Debug render-process tool.
|
|
||||||
- `examples/`: Runnable samples (e.g., `simple.rs`, `js_emit.rs`, `brp.rs`).
|
|
||||||
- `assets/`: Local HTML/CSS/JS served via `cef://localhost/`.
|
|
||||||
- `docs/`: Supporting documentation.
|
|
||||||
|
|
||||||
## Build, Test, and Development Commands
|
|
||||||
- `cargo build --features debug`: Build with macOS debug tooling enabled.
|
|
||||||
- `cargo run --example simple --features debug`: Run a basic webview example.
|
|
||||||
- `cargo test --workspace --all-features`: Run tests (currently no automated tests; validates compilation).
|
|
||||||
- `make fix`: Run `cargo clippy --fix` and `cargo fmt --all`.
|
|
||||||
- `make install`: Install and copy the debug render process into the CEF framework (macOS).
|
|
||||||
|
|
||||||
## Coding Style & Naming Conventions
|
|
||||||
- Rust 2024 edition; format with `cargo fmt` before committing.
|
|
||||||
- Lint with `cargo clippy` (see `make fix`).
|
|
||||||
- Naming: `snake_case` for modules/functions/files, `CamelCase` for types/traits, `SCREAMING_SNAKE_CASE` for constants.
|
|
||||||
- Prefer small, focused modules; keep public API in `src/` and implementation in `crates/`.
|
|
||||||
|
|
||||||
## Testing Guidelines
|
|
||||||
- No dedicated test suite yet; rely on examples for manual verification.
|
|
||||||
- Use `examples/` to validate features (IPC, navigation, zoom, devtools).
|
|
||||||
- If adding tests, keep names descriptive (e.g., `test_ipc_roundtrip`) and run with `cargo test --workspace --all-features`.
|
|
||||||
|
|
||||||
## Commit & Pull Request Guidelines
|
|
||||||
- Commit subjects are short, imperative; common prefixes include `add:`, `fix:`, `update:`, `remove:`.
|
|
||||||
- Include PR or issue references when relevant (e.g., `Support Bevy 0.17 (#11)`).
|
|
||||||
- PRs should describe changes, testing performed, and target platform (macOS/Windows/Linux).
|
|
||||||
- For webview or UI changes, include screenshots or short clips when possible.
|
|
||||||
|
|
||||||
## Platform & Configuration Notes
|
|
||||||
- Primary development target is macOS; CEF framework should exist at `$HOME/.local/share/cef`.
|
|
||||||
- Local assets are served as `cef://localhost/<file>`; prefer `CefWebviewUri::local("file.html")`.
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
- Support Bevy 0.18
|
- Support Bevy 0.18
|
||||||
- Update CEF version to 144.2.0+144.0.11
|
- Update CEF version to 144.4.0
|
||||||
- Improve message loop handling
|
- Improve message loop handling
|
||||||
- We can now specify command-line switches when creating the `CefPlugin`.
|
- We can now specify command-line switches when creating the `CefPlugin`.
|
||||||
- As a result, `CefPlugin` is no longer a unit struct.
|
- As a result, `CefPlugin` is no longer a unit struct.
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
- Fixed so that the webview can detect pointers correctly even if it is not the root entity.
|
- Fixed so that the webview can detect pointers correctly even if it is not the root entity.
|
||||||
- Avoid a crash when updating the cursor icon
|
- Avoid a crash when updating the cursor icon
|
||||||
|
- Fixed IME input not working due to `bevy_winit` not calling `set_ime_allowed()` on initial window creation
|
||||||
|
|
||||||
## v0.1.0
|
## v0.1.0
|
||||||
|
|
||||||
|
|||||||
96
CLAUDE.md
96
CLAUDE.md
@@ -1,96 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
This is `bevy_cef`, a Bevy plugin that integrates the Chromium Embedded Framework (CEF) into Bevy applications, allowing webviews to be rendered as 3D objects in the game world or as UI overlays.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Multi-Process Design
|
|
||||||
- **Browser Process**: Main application process running Bevy (`bevy_cef_core::browser_process`)
|
|
||||||
- **Render Process**: Separate CEF render process (`bevy_cef_core::render_process`)
|
|
||||||
- Communication through IPC channels and Bevy Remote Protocol (BRP)
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
- `CefWebviewUri`: URL specification (`CefWebviewUri::new("url")` or `CefWebviewUri::local("file.html")`)
|
|
||||||
- `WebviewSize`: Rendering dimensions (default 800x800), controls texture resolution not 3D size
|
|
||||||
- `WebviewExtendStandardMaterial`: Primary material for 3D mesh rendering
|
|
||||||
- `WebviewSpriteMaterial`: Material for 2D sprite rendering
|
|
||||||
- `HostWindow`: Optional parent window (defaults to PrimaryWindow)
|
|
||||||
- `ZoomLevel`: f64 zoom control (0.0 = default)
|
|
||||||
- `AudioMuted`: bool for audio control
|
|
||||||
- `PreloadScripts`: Vec<String> of scripts to execute before page scripts
|
|
||||||
- `CefExtensions`: Custom JS extensions via `register_extension` (global to all webviews)
|
|
||||||
|
|
||||||
### Plugin Architecture
|
|
||||||
The main `CefPlugin` accepts `CommandLineConfig` for CEF command-line switches and `CefExtensions` for custom JavaScript APIs. Sub-plugins:
|
|
||||||
- `LocalHostPlugin`: Serves local assets via `cef://localhost/` scheme
|
|
||||||
- `MessageLoopPlugin`: CEF message loop integration (macOS uses `CefDoMessageLoopWork()`)
|
|
||||||
- `WebviewCoreComponentsPlugin`: Core component registration
|
|
||||||
- `WebviewPlugin`: Webview lifecycle and DevTools
|
|
||||||
- `IpcPlugin`: IPC containing `IpcRawEventPlugin` and `HostEmitPlugin`
|
|
||||||
- `KeyboardPlugin`, `SystemCursorIconPlugin`, `NavigationPlugin`, `ZoomPlugin`, `AudioMutePlugin`
|
|
||||||
- `RemotePlugin`: Auto-added for BRP support if not present
|
|
||||||
|
|
||||||
### IPC System
|
|
||||||
Three communication patterns:
|
|
||||||
1. **JS Emit**: Webview → Bevy via `JsEmitEventPlugin<E>` where E: `DeserializeOwned + Send + Sync + 'static`
|
|
||||||
- Events wrapped in `Receive<E>` EntityEvent
|
|
||||||
- JavaScript: `window.cef.emit('event_name', data)`
|
|
||||||
2. **Host Emit**: Bevy → Webview via `HostEmitEvent` (EntityEvent)
|
|
||||||
- JavaScript: `window.cef.listen('event_name', callback)`
|
|
||||||
3. **BRP**: Bidirectional RPC via `bevy_remote`
|
|
||||||
- JavaScript: `await window.cef.brp({ method: 'method_name', params: {...} })`
|
|
||||||
|
|
||||||
### EntityEvent Pattern
|
|
||||||
Navigation and DevTools events are `EntityEvent` types requiring explicit `webview: Entity` field:
|
|
||||||
- `HostEmitEvent`, `RequestGoBack`, `RequestGoForward`, `RequestShowDevTool`, `RequestCloseDevtool`
|
|
||||||
|
|
||||||
## Development Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Fix and format code
|
|
||||||
make fix
|
|
||||||
|
|
||||||
# Run examples (macOS requires debug feature)
|
|
||||||
cargo run --example simple --features debug
|
|
||||||
|
|
||||||
# Install debug render process tool
|
|
||||||
make install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debug Tools Setup (macOS)
|
|
||||||
Manual installation required before running with `debug` feature:
|
|
||||||
```bash
|
|
||||||
cargo install export-cef-dir
|
|
||||||
export-cef-dir --force $HOME/.local/share
|
|
||||||
cargo install bevy_cef_debug_render_process
|
|
||||||
mv $HOME/.cargo/bin/bevy_cef_debug_render_process "$HOME/.local/share/Chromium Embedded Framework.framework/Libraries/bevy_cef_debug_render_process"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Local Asset Loading
|
|
||||||
|
|
||||||
Local HTML/assets served via `cef://localhost/` scheme:
|
|
||||||
- Place assets in `assets/` directory
|
|
||||||
- Reference as `CefWebviewUri::local("filename.html")`
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
No automated tests. Testing done through examples:
|
|
||||||
- `cargo test --workspace --all-features` (for any future tests)
|
|
||||||
- Examples: simple, js_emit, host_emit, brp, navigation, zoom_level, sprite, devtool, custom_material, preload_scripts, extensions
|
|
||||||
|
|
||||||
## Platform Notes
|
|
||||||
|
|
||||||
- Primary platform: macOS (uses `objc` crate for window handling)
|
|
||||||
- CEF framework location: `$HOME/.local/share/Chromium Embedded Framework.framework`
|
|
||||||
- Windows/Linux: Infrastructure ready but needs testing
|
|
||||||
- Key resources (`Browsers`, library loaders) are `NonSend` - CEF is not thread-safe
|
|
||||||
|
|
||||||
## Workspace Structure
|
|
||||||
|
|
||||||
- Root crate: `bevy_cef` (public API)
|
|
||||||
- `crates/bevy_cef_core`: Core CEF integration logic
|
|
||||||
- `crates/bevy_cef_debug_render_process`: Debug render process executable
|
|
||||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -1930,9 +1930,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cef"
|
name = "cef"
|
||||||
version = "144.2.0+144.0.11"
|
version = "145.3.0+145.0.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8743b7a17569f5a0b27a1ef72a2275dc096ae32263bf35fc5541f46e73084ec5"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_metadata",
|
"cargo_metadata",
|
||||||
@@ -1950,9 +1948,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cef-dll-sys"
|
name = "cef-dll-sys"
|
||||||
version = "144.2.0+144.0.11"
|
version = "145.3.0+145.0.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cf6882e1e10fe7ff0c9b1815e8a2943c0975be5bb48f15ca3b4cbb9dc4329d0e"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cmake",
|
"cmake",
|
||||||
@@ -2469,8 +2465,6 @@ checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "download-cef"
|
name = "download-cef"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6002a6b95f9f3bbe5693ac02a03221a8e3af6c9f5f4539f5734a8d6cde1a3616"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
19
Cargo.toml
19
Cargo.toml
@@ -13,9 +13,7 @@ exclude = ["assets/"]
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = ["crates/*"]
|
||||||
"crates/*",
|
|
||||||
]
|
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.0-dev"
|
version = "0.2.0-dev"
|
||||||
@@ -38,9 +36,9 @@ bevy = { version = "0.18", default-features = false, features = [
|
|||||||
"picking",
|
"picking",
|
||||||
] }
|
] }
|
||||||
bevy_remote = "0.18"
|
bevy_remote = "0.18"
|
||||||
cef = { version = "144.2.0" }
|
cef = { path = "../../cef-rs/cef" }
|
||||||
cef-dll-sys = { version = "144.2.0", features = ["sandbox"] }
|
cef-dll-sys = { path = "../../cef-rs/sys" }
|
||||||
bevy_cef = { path = "." , version = "0.2.0-dev" }
|
bevy_cef = { path = ".", version = "0.2.0-dev" }
|
||||||
bevy_cef_core = { path = "crates/bevy_cef_core", version = "0.2.0-dev" }
|
bevy_cef_core = { path = "crates/bevy_cef_core", version = "0.2.0-dev" }
|
||||||
async-channel = { version = "2.5" }
|
async-channel = { version = "2.5" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
@@ -58,12 +56,19 @@ serde_json = { workspace = true }
|
|||||||
raw-window-handle = "0.6"
|
raw-window-handle = "0.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bevy = { workspace = true, default-features = true, features = ["file_watcher"]}
|
bevy = { workspace = true, default-features = true, features = [
|
||||||
|
"file_watcher",
|
||||||
|
] }
|
||||||
bevy_cef = { workspace = true, features = ["debug"] }
|
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"]
|
||||||
|
|||||||
19
Makefile
19
Makefile
@@ -1,19 +0,0 @@
|
|||||||
.PHONY: fix install
|
|
||||||
|
|
||||||
BIN := bevy_cef_debug_render_process
|
|
||||||
CEF_LIB := $(HOME)/.local/share/Chromium Embedded Framework.framework/Libraries
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: fix install
|
|
||||||
|
|
||||||
BIN := bevy_cef_debug_render_process
|
|
||||||
CEF_LIB := $(HOME)/.local/share/Chromium Embedded Framework.framework/Libraries
|
|
||||||
CARGO_BIN := $(HOME)/.cargo/bin
|
|
||||||
|
|
||||||
fix:
|
|
||||||
cargo clippy --fix --allow-dirty --allow-staged --workspace --all --all-features
|
|
||||||
cargo fmt --all
|
|
||||||
|
|
||||||
install:
|
|
||||||
cargo install --path ./crates/bevy_cef_debug_render_process --force
|
|
||||||
mv "$(CARGO_BIN)/$(BIN)" "$(CEF_LIB)/$(BIN)"
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# Not my own repo, but since its the only thing out there that does what i need, i'll have to deal with it, had to fix it's shit first tho cuz ofc....
|
||||||
|
|
||||||
# bevy_cef
|
# bevy_cef
|
||||||
|
|
||||||
A powerful Bevy plugin for embedding web content using the Chromium Embedded Framework (CEF).
|
A powerful Bevy plugin for embedding web content using the Chromium Embedded Framework (CEF).
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ impl ImplBrowserProcessHandler for BrowserProcessHandlerBuilder {
|
|||||||
command_line.append_switch(Some(&"ignore-certificate-errors".into()));
|
command_line.append_switch(Some(&"ignore-certificate-errors".into()));
|
||||||
command_line.append_switch(Some(&"ignore-ssl-errors".into()));
|
command_line.append_switch(Some(&"ignore-ssl-errors".into()));
|
||||||
command_line.append_switch(Some(&"enable-logging=stderr".into()));
|
command_line.append_switch(Some(&"enable-logging=stderr".into()));
|
||||||
|
command_line.append_switch(Some(&"disable-web-security".into()));
|
||||||
// Pass extensions to render process via command line
|
// Pass extensions to render process via command line
|
||||||
if !self.extensions.is_empty()
|
if !self.extensions.is_empty()
|
||||||
&& let Ok(json) = serde_json::to_string(&self.extensions.0)
|
&& let Ok(json) = serde_json::to_string(&self.extensions.0)
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ pub struct WebviewBrowser {
|
|||||||
pub client: Browser,
|
pub client: Browser,
|
||||||
pub host: BrowserHost,
|
pub host: BrowserHost,
|
||||||
pub size: SharedViewSize,
|
pub size: SharedViewSize,
|
||||||
pub ime_caret: SharedImeCaret,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Browsers {
|
pub struct Browsers {
|
||||||
@@ -67,11 +66,10 @@ impl Browsers {
|
|||||||
) {
|
) {
|
||||||
let mut context = Self::request_context(requester);
|
let mut context = Self::request_context(requester);
|
||||||
let size = Rc::new(Cell::new(webview_size));
|
let size = Rc::new(Cell::new(webview_size));
|
||||||
let ime_caret = Rc::new(Cell::new(0));
|
|
||||||
let browser = browser_host_create_browser_sync(
|
let browser = browser_host_create_browser_sync(
|
||||||
Some(&WindowInfo {
|
Some(&WindowInfo {
|
||||||
windowless_rendering_enabled: true as _,
|
windowless_rendering_enabled: true as _,
|
||||||
external_begin_frame_enabled: false as _,
|
external_begin_frame_enabled: true as _,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
parent_view: match _window_handle {
|
parent_view: match _window_handle {
|
||||||
Some(RawWindowHandle::AppKit(handle)) => handle.ns_view.as_ptr(),
|
Some(RawWindowHandle::AppKit(handle)) => handle.ns_view.as_ptr(),
|
||||||
@@ -80,13 +78,12 @@ impl Browsers {
|
|||||||
Some(RawWindowHandle::Wayland(handle)) => handle.surface.as_ptr(),
|
Some(RawWindowHandle::Wayland(handle)) => handle.surface.as_ptr(),
|
||||||
_ => std::ptr::null_mut(),
|
_ => std::ptr::null_mut(),
|
||||||
},
|
},
|
||||||
// shared_texture_enabled: true as _,
|
shared_texture_enabled: false as _,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
Some(&mut self.client_handler(
|
Some(&mut self.client_handler(
|
||||||
webview,
|
webview,
|
||||||
size.clone(),
|
size.clone(),
|
||||||
ime_caret.clone(),
|
|
||||||
ipc_event_sender,
|
ipc_event_sender,
|
||||||
brp_sender,
|
brp_sender,
|
||||||
system_cursor_icon_sender,
|
system_cursor_icon_sender,
|
||||||
@@ -105,7 +102,6 @@ impl Browsers {
|
|||||||
host,
|
host,
|
||||||
client: browser,
|
client: browser,
|
||||||
size,
|
size,
|
||||||
ime_caret,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.browsers.insert(webview, webview_browser);
|
self.browsers.insert(webview, webview_browser);
|
||||||
@@ -273,6 +269,25 @@ impl Browsers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Navigate a specific webview to a new URL.
|
||||||
|
pub fn navigate(&self, webview: &Entity, url: &str) {
|
||||||
|
if let Some(browser) = self.browsers.get(webview)
|
||||||
|
&& let Some(frame) = browser.client.main_frame()
|
||||||
|
{
|
||||||
|
frame.load_url(Some(&url.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reload a specific webview's current page.
|
||||||
|
pub fn reload_webview(&self, webview: &Entity) {
|
||||||
|
if let Some(browser) = self.browsers.get(webview)
|
||||||
|
&& let Some(frame) = browser.client.main_frame()
|
||||||
|
{
|
||||||
|
let url = frame.url().into_string();
|
||||||
|
frame.load_url(Some(&url.as_str().into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the current zoom level for the specified webview.
|
/// Returns the current zoom level for the specified webview.
|
||||||
///
|
///
|
||||||
/// ## Reference
|
/// ## Reference
|
||||||
@@ -332,7 +347,7 @@ impl Browsers {
|
|||||||
.values()
|
.values()
|
||||||
.filter(|b| b.client.focused_frame().is_some())
|
.filter(|b| b.client.focused_frame().is_some())
|
||||||
{
|
{
|
||||||
let replacement_range = Self::ime_caret_range_for(browser);
|
let replacement_range = Self::ime_caret_range_for();
|
||||||
browser.host.ime_set_composition(
|
browser.host.ime_set_composition(
|
||||||
Some(&text.into()),
|
Some(&text.into()),
|
||||||
Some(&underlines),
|
Some(&underlines),
|
||||||
@@ -342,6 +357,19 @@ impl Browsers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ## Reference
|
||||||
|
///
|
||||||
|
/// [`ImeCancelComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#ac12a8076859d0c1e58e55080f698e7a9)
|
||||||
|
pub fn ime_cancel_composition(&self) {
|
||||||
|
for browser in self
|
||||||
|
.browsers
|
||||||
|
.values()
|
||||||
|
.filter(|b| b.client.focused_frame().is_some())
|
||||||
|
{
|
||||||
|
browser.host.ime_cancel_composition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ## Reference
|
/// ## Reference
|
||||||
///
|
///
|
||||||
/// [`ImeSetComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a567b41fb2d3917843ece3b57adc21ebe)
|
/// [`ImeSetComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a567b41fb2d3917843ece3b57adc21ebe)
|
||||||
@@ -361,10 +389,10 @@ impl Browsers {
|
|||||||
.values()
|
.values()
|
||||||
.filter(|b| b.client.focused_frame().is_some())
|
.filter(|b| b.client.focused_frame().is_some())
|
||||||
{
|
{
|
||||||
let replacement_range = Self::ime_caret_range_for(browser);
|
let replacement_range = Self::ime_caret_range_for();
|
||||||
browser
|
browser
|
||||||
.host
|
.host
|
||||||
.ime_commit_text(Some(&text.into()), Some(&replacement_range), 0)
|
.ime_commit_text(Some(&text.into()), Some(&replacement_range), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +415,6 @@ impl Browsers {
|
|||||||
&self,
|
&self,
|
||||||
webview: Entity,
|
webview: Entity,
|
||||||
size: SharedViewSize,
|
size: SharedViewSize,
|
||||||
ime_caret: SharedImeCaret,
|
|
||||||
ipc_event_sender: Sender<IpcEventRaw>,
|
ipc_event_sender: Sender<IpcEventRaw>,
|
||||||
brp_sender: Sender<BrpMessage>,
|
brp_sender: Sender<BrpMessage>,
|
||||||
system_cursor_icon_sender: SystemCursorIconSenderInner,
|
system_cursor_icon_sender: SystemCursorIconSenderInner,
|
||||||
@@ -396,7 +423,6 @@ impl Browsers {
|
|||||||
webview,
|
webview,
|
||||||
self.sender.clone(),
|
self.sender.clone(),
|
||||||
size.clone(),
|
size.clone(),
|
||||||
ime_caret,
|
|
||||||
))
|
))
|
||||||
.with_display_handler(DisplayHandlerBuilder::build(system_cursor_icon_sender))
|
.with_display_handler(DisplayHandlerBuilder::build(system_cursor_icon_sender))
|
||||||
.with_message_handler(JsEmitEventHandler::new(webview, ipc_event_sender))
|
.with_message_handler(JsEmitEventHandler::new(webview, ipc_event_sender))
|
||||||
@@ -405,11 +431,11 @@ impl Browsers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn ime_caret_range_for(browser: &WebviewBrowser) -> Range {
|
fn ime_caret_range_for() -> Range {
|
||||||
let caret = browser.ime_caret.get();
|
// Use sentinel replacement range to indicate caret position
|
||||||
Range {
|
Range {
|
||||||
from: caret,
|
from: u32::MAX,
|
||||||
to: caret,
|
to: u32::MAX,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub fn create_cef_key_event(
|
|||||||
// ButtonState::Pressed if input.just_pressed(key_event.key_code) => {
|
// ButtonState::Pressed if input.just_pressed(key_event.key_code) => {
|
||||||
// cef_key_event_type_t::KEYEVENT_RAWKEYDOWN
|
// cef_key_event_type_t::KEYEVENT_RAWKEYDOWN
|
||||||
// }
|
// }
|
||||||
ButtonState::Pressed => cef_key_event_type_t::KEYEVENT_CHAR,
|
ButtonState::Pressed => cef_key_event_type_t::KEYEVENT_RAWKEYDOWN,
|
||||||
ButtonState::Released => cef_key_event_type_t::KEYEVENT_KEYUP,
|
ButtonState::Released => cef_key_event_type_t::KEYEVENT_KEYUP,
|
||||||
};
|
};
|
||||||
let windows_key_code = keycode_to_windows_vk(key_event.key_code);
|
let windows_key_code = keycode_to_windows_vk(key_event.key_code);
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ pub enum RenderPaintElementType {
|
|||||||
Popup,
|
Popup,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SharedViewSize = std::rc::Rc<Cell<bevy::prelude::Vec2>>;
|
pub type SharedViewSize = std::rc::Rc<Cell<Vec2>>;
|
||||||
pub type SharedImeCaret = std::rc::Rc<Cell<u32>>;
|
|
||||||
|
|
||||||
/// ## Reference
|
/// ## Reference
|
||||||
///
|
///
|
||||||
@@ -44,7 +43,6 @@ pub struct RenderHandlerBuilder {
|
|||||||
webview: Entity,
|
webview: Entity,
|
||||||
texture_sender: TextureSender,
|
texture_sender: TextureSender,
|
||||||
size: SharedViewSize,
|
size: SharedViewSize,
|
||||||
ime_caret: SharedImeCaret,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderHandlerBuilder {
|
impl RenderHandlerBuilder {
|
||||||
@@ -52,14 +50,12 @@ impl RenderHandlerBuilder {
|
|||||||
webview: Entity,
|
webview: Entity,
|
||||||
texture_sender: TextureSender,
|
texture_sender: TextureSender,
|
||||||
size: SharedViewSize,
|
size: SharedViewSize,
|
||||||
ime_caret: SharedImeCaret,
|
|
||||||
) -> RenderHandler {
|
) -> RenderHandler {
|
||||||
RenderHandler::new(Self {
|
RenderHandler::new(Self {
|
||||||
object: std::ptr::null_mut(),
|
object: std::ptr::null_mut(),
|
||||||
webview,
|
webview,
|
||||||
texture_sender,
|
texture_sender,
|
||||||
size,
|
size,
|
||||||
ime_caret,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +87,6 @@ impl Clone for RenderHandlerBuilder {
|
|||||||
webview: self.webview,
|
webview: self.webview,
|
||||||
texture_sender: self.texture_sender.clone(),
|
texture_sender: self.texture_sender.clone(),
|
||||||
size: self.size.clone(),
|
size: self.size.clone(),
|
||||||
ime_caret: self.ime_caret.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,17 +126,6 @@ impl ImplRenderHandler for RenderHandlerBuilder {
|
|||||||
let _ = self.texture_sender.send_blocking(texture);
|
let _ = self.texture_sender.send_blocking(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_text_selection_changed(
|
|
||||||
&self,
|
|
||||||
_browser: Option<&mut Browser>,
|
|
||||||
_: Option<&CefString>,
|
|
||||||
selected_range: Option<&Range>,
|
|
||||||
) {
|
|
||||||
if let Some(selected_range) = selected_range {
|
|
||||||
self.ime_caret.set(selected_range.to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_raw(&self) -> *mut sys::_cef_render_handler_t {
|
fn get_raw(&self) -> *mut sys::_cef_render_handler_t {
|
||||||
self.object.cast()
|
self.object.cast()
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ pub fn debug_chromium_embedded_framework_dir_path() -> PathBuf {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.join(".local")
|
.join(".local")
|
||||||
.join("share")
|
.join("share")
|
||||||
.join("Chromium Embedded Framework.framework")
|
.join("avii")
|
||||||
|
.join("cef")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_render_process_path() -> PathBuf {
|
pub fn debug_render_process_path() -> PathBuf {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ fn main() {
|
|||||||
App::new()
|
App::new()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
DefaultPlugins,
|
DefaultPlugins,
|
||||||
CefPlugin::default(),
|
CefPlugin {
|
||||||
|
command_line_config: CommandLineConfig {
|
||||||
|
switches: ["--in-process-gpu", "--no-sandbox"].to_vec(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
WebviewExtendMaterialPlugin::<CustomExtension>::default(),
|
WebviewExtendMaterialPlugin::<CustomExtension>::default(),
|
||||||
))
|
))
|
||||||
.add_systems(Startup, (spawn_camera, spawn_webview))
|
.add_systems(Startup, (spawn_camera, spawn_webview))
|
||||||
@@ -33,7 +39,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 {
|
||||||
|
|||||||
@@ -10,7 +10,16 @@ use bevy_cef::prelude::*;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((DefaultPlugins, CefPlugin::default()))
|
.add_plugins((
|
||||||
|
DefaultPlugins,
|
||||||
|
CefPlugin {
|
||||||
|
command_line_config: CommandLineConfig {
|
||||||
|
switches: ["--in-process-gpu"].to_vec(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
))
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Startup,
|
Startup,
|
||||||
(spawn_camera, spawn_directional_light, spawn_webview),
|
(spawn_camera, spawn_directional_light, spawn_webview),
|
||||||
@@ -49,7 +58,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.
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ use bevy_cef::prelude::*;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((DefaultPlugins, CefPlugin::default()))
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugins(CefPlugin {
|
||||||
|
command_line_config: CommandLineConfig {
|
||||||
|
switches: ["--no-zygote", "--no-sandbox"].to_vec(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Startup,
|
Startup,
|
||||||
(spawn_camera, spawn_directional_light, spawn_webview),
|
(spawn_camera, spawn_directional_light, spawn_webview),
|
||||||
@@ -33,7 +40,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.
|
||||||
@@ -55,7 +74,7 @@ pub struct WebviewSize(pub Vec2);
|
|||||||
|
|
||||||
impl Default for WebviewSize {
|
impl Default for WebviewSize {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(Vec2::splat(800.0))
|
Self(Vec2::new(768.0, 1024.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,3 +116,10 @@ where
|
|||||||
Self(scripts.into_iter().map(Into::into).collect())
|
Self(scripts.into_iter().map(Into::into).collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Holds the webview surface texture handle for alpha hit-testing.
|
||||||
|
///
|
||||||
|
/// This component is automatically inserted and updated by the render systems.
|
||||||
|
/// It provides material-type-agnostic access to the webview texture.
|
||||||
|
#[derive(Component, Debug, Clone)]
|
||||||
|
pub(crate) struct WebviewSurface(pub(crate) Handle<Image>);
|
||||||
|
|||||||
@@ -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,19 +51,78 @@ 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() {
|
||||||
commands.spawn((
|
if let Some(id) = extract_inline_id(&request.uri) {
|
||||||
CefResponseHandle(asset_server.load(request.uri)),
|
let response = match store.by_id.get(id) {
|
||||||
request.responser,
|
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((
|
||||||
|
CefResponseHandle(asset_server.load(request.uri)),
|
||||||
|
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(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut handle_stores: Local<HashSet<Handle<CefResponse>>>,
|
mut handle_stores: Local<HashSet<Handle<CefResponse>>>,
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ impl Plugin for MessageLoopPlugin {
|
|||||||
|
|
||||||
let mut cef_app =
|
let mut cef_app =
|
||||||
BrowserProcessAppBuilder::build(tx, self.config.clone(), self.extensions.clone());
|
BrowserProcessAppBuilder::build(tx, self.config.clone(), self.extensions.clone());
|
||||||
let ret = execute_process(
|
let _ = execute_process(
|
||||||
Some(args.as_main_args()),
|
Some(args.as_main_args()),
|
||||||
Some(&mut cef_app),
|
Some(&mut cef_app),
|
||||||
std::ptr::null_mut(),
|
std::ptr::null_mut(),
|
||||||
);
|
);
|
||||||
assert_eq!(ret, -1, "cannot execute browser process");
|
// assert_eq!(ret, -1, "cannot execute browser process");
|
||||||
|
|
||||||
cef_initialize(&args, &mut cef_app);
|
cef_initialize(&args, &mut cef_app);
|
||||||
|
|
||||||
@@ -54,20 +54,10 @@ fn load_cef_library(app: &mut App) {
|
|||||||
|
|
||||||
fn cef_initialize(args: &Args, cef_app: &mut cef::App) {
|
fn cef_initialize(args: &Args, cef_app: &mut cef::App) {
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
root_cache_path: debug_chromium_embedded_framework_dir_path()
|
||||||
framework_dir_path: debug_chromium_embedded_framework_dir_path()
|
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into(),
|
.into(),
|
||||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
|
||||||
browser_subprocess_path: debug_render_process_path().to_str().unwrap().into(),
|
|
||||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
|
||||||
no_sandbox: true as _,
|
|
||||||
windowless_rendering_enabled: true as _,
|
|
||||||
// #[cfg(any(target_os = "windows", target_os = "linux"))]
|
|
||||||
// multi_threaded_message_loop: true as _,
|
|
||||||
multi_threaded_message_loop: false as _,
|
|
||||||
external_message_pump: true as _,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -86,6 +76,7 @@ fn cef_do_message_loop_work(
|
|||||||
mut timer: Local<Option<MessageLoopTimer>>,
|
mut timer: Local<Option<MessageLoopTimer>>,
|
||||||
mut max_delay_timer: Local<MessageLoopWorkingMaxDelayTimer>,
|
mut max_delay_timer: Local<MessageLoopWorkingMaxDelayTimer>,
|
||||||
) {
|
) {
|
||||||
|
// cef::do_message_loop_work();
|
||||||
while let Ok(t) = receiver.try_recv() {
|
while let Ok(t) = receiver.try_recv() {
|
||||||
timer.replace(t);
|
timer.replace(t);
|
||||||
}
|
}
|
||||||
@@ -99,60 +90,3 @@ fn cef_do_message_loop_work(
|
|||||||
fn cef_shutdown(_: NonSend<RunOnMainThread>) {
|
fn cef_shutdown(_: NonSend<RunOnMainThread>) {
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
mod macos {
|
|
||||||
use core::sync::atomic::AtomicBool;
|
|
||||||
use objc::runtime::{Class, Object, Sel};
|
|
||||||
use objc::{sel, sel_impl};
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
|
|
||||||
unsafe extern "C" {
|
|
||||||
fn class_addMethod(
|
|
||||||
cls: *const Class,
|
|
||||||
name: Sel,
|
|
||||||
imp: *const c_void,
|
|
||||||
types: *const c_char,
|
|
||||||
) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
static IS_HANDLING_SEND_EVENT: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
extern "C" fn is_handling_send_event(_: &Object, _: Sel) -> bool {
|
|
||||||
IS_HANDLING_SEND_EVENT.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn set_handling_send_event(_: &Object, _: Sel, flag: bool) {
|
|
||||||
IS_HANDLING_SEND_EVENT.swap(flag, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn install_cef_app_protocol() {
|
|
||||||
unsafe {
|
|
||||||
let cls = Class::get("NSApplication").expect("NSApplication クラスが見つかりません");
|
|
||||||
#[allow(unexpected_cfgs)]
|
|
||||||
let sel_name = sel!(isHandlingSendEvent);
|
|
||||||
let success = class_addMethod(
|
|
||||||
cls as *const _,
|
|
||||||
sel_name,
|
|
||||||
is_handling_send_event as *const c_void,
|
|
||||||
c"c@:".as_ptr() as *const c_char,
|
|
||||||
);
|
|
||||||
assert!(success, "メソッド追加に失敗しました");
|
|
||||||
|
|
||||||
#[allow(unexpected_cfgs)]
|
|
||||||
let sel_set = sel!(setHandlingSendEvent:);
|
|
||||||
let success2 = class_addMethod(
|
|
||||||
cls as *const _,
|
|
||||||
sel_set,
|
|
||||||
set_handling_send_event as *const c_void,
|
|
||||||
c"v@:c".as_ptr() as *const c_char,
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
success2,
|
|
||||||
"Failed to add setHandlingSendEvent: to NSApplication"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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};
|
||||||
@@ -14,6 +14,9 @@ impl Plugin for KeyboardPlugin {
|
|||||||
app.init_resource::<IsImeCommiting>().add_systems(
|
app.init_resource::<IsImeCommiting>().add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
|
// Workaround for bevy_winit not calling `set_ime_allowed` on initial window
|
||||||
|
// creation when `Window::ime_enabled` is `true` from the start.
|
||||||
|
activate_ime,
|
||||||
ime_event.run_if(on_message::<Ime>),
|
ime_event.run_if(on_message::<Ime>),
|
||||||
send_key_event.run_if(on_message::<KeyboardInput>),
|
send_key_event.run_if(on_message::<KeyboardInput>),
|
||||||
)
|
)
|
||||||
@@ -22,6 +25,42 @@ impl Plugin for KeyboardPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Workaround: bevy_winit does not call `winit::Window::set_ime_allowed()` during initial window
|
||||||
|
/// creation when `Window::ime_enabled` is `true`. This means `Ime` events are never generated.
|
||||||
|
///
|
||||||
|
/// To trigger bevy_winit's own `changed_windows` system, we temporarily toggle `ime_enabled` off
|
||||||
|
/// then back on over two frames, which causes the change detection to fire and call
|
||||||
|
/// `set_ime_allowed(true)` internally.
|
||||||
|
fn activate_ime(mut windows: Query<&mut Window>, mut state: Local<ImeActivationState>) {
|
||||||
|
match *state {
|
||||||
|
ImeActivationState::Pending => {
|
||||||
|
for mut window in windows.iter_mut() {
|
||||||
|
if window.ime_enabled {
|
||||||
|
window.ime_enabled = false;
|
||||||
|
*state = ImeActivationState::Toggled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImeActivationState::Toggled => {
|
||||||
|
for mut window in windows.iter_mut() {
|
||||||
|
if !window.ime_enabled {
|
||||||
|
window.ime_enabled = true;
|
||||||
|
*state = ImeActivationState::Done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImeActivationState::Done => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
enum ImeActivationState {
|
||||||
|
#[default]
|
||||||
|
Pending,
|
||||||
|
Toggled,
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Resource, Default, Serialize, Deserialize, Reflect)]
|
#[derive(Resource, Default, Serialize, Deserialize, Reflect)]
|
||||||
#[reflect(Default, Serialize, Deserialize)]
|
#[reflect(Default, Serialize, Deserialize)]
|
||||||
struct IsImeCommiting(bool);
|
struct IsImeCommiting(bool);
|
||||||
@@ -31,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() {
|
||||||
@@ -65,7 +104,7 @@ fn ime_event(
|
|||||||
is_ime_commiting.0 = true;
|
is_ime_commiting.0 = true;
|
||||||
}
|
}
|
||||||
Ime::Disabled { .. } => {
|
Ime::Disabled { .. } => {
|
||||||
browsers.ime_finish_composition(false);
|
browsers.ime_cancel_composition();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,13 @@ use bevy_cef_core::prelude::{CefExtensions, CommandLineConfig};
|
|||||||
use bevy_remote::RemotePlugin;
|
use bevy_remote::RemotePlugin;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::{CefPlugin, RunOnMainThread, common::*, navigation::*, webview::prelude::*};
|
pub use crate::{
|
||||||
pub use bevy_cef_core::prelude::{CefExtensions, CommandLineConfig};
|
CefPlugin, RunOnMainThread, common::*, navigation::*, system_param::mesh_aabb::*,
|
||||||
|
webview::prelude::*,
|
||||||
|
};
|
||||||
|
pub use bevy_cef_core::prelude::{
|
||||||
|
Browsers, CefExtensions, CommandLineConfig, create_cef_key_event,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RunOnMainThread;
|
pub struct RunOnMainThread;
|
||||||
|
|||||||
@@ -8,8 +8,12 @@ impl Plugin for NavigationPlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<RequestGoBack>()
|
app.register_type::<RequestGoBack>()
|
||||||
.register_type::<RequestGoForward>()
|
.register_type::<RequestGoForward>()
|
||||||
|
.register_type::<RequestNavigate>()
|
||||||
|
.register_type::<RequestReload>()
|
||||||
.add_observer(apply_request_go_back)
|
.add_observer(apply_request_go_back)
|
||||||
.add_observer(apply_request_go_forward);
|
.add_observer(apply_request_go_forward)
|
||||||
|
.add_observer(apply_request_navigate)
|
||||||
|
.add_observer(apply_request_reload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +31,21 @@ pub struct RequestGoForward {
|
|||||||
pub webview: Entity,
|
pub webview: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trigger event to navigate to a new URL.
|
||||||
|
#[derive(Debug, EntityEvent, Clone, Reflect, Serialize, Deserialize)]
|
||||||
|
pub struct RequestNavigate {
|
||||||
|
#[event_target]
|
||||||
|
pub webview: Entity,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trigger event to reload the current page.
|
||||||
|
#[derive(Debug, EntityEvent, Copy, Clone, Reflect, Serialize, Deserialize)]
|
||||||
|
pub struct RequestReload {
|
||||||
|
#[event_target]
|
||||||
|
pub webview: Entity,
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_request_go_back(trigger: On<RequestGoBack>, browsers: NonSend<Browsers>) {
|
fn apply_request_go_back(trigger: On<RequestGoBack>, browsers: NonSend<Browsers>) {
|
||||||
browsers.go_back(&trigger.webview);
|
browsers.go_back(&trigger.webview);
|
||||||
}
|
}
|
||||||
@@ -34,3 +53,11 @@ fn apply_request_go_back(trigger: On<RequestGoBack>, browsers: NonSend<Browsers>
|
|||||||
fn apply_request_go_forward(trigger: On<RequestGoForward>, browsers: NonSend<Browsers>) {
|
fn apply_request_go_forward(trigger: On<RequestGoForward>, browsers: NonSend<Browsers>) {
|
||||||
browsers.go_forward(&trigger.webview);
|
browsers.go_forward(&trigger.webview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_request_navigate(trigger: On<RequestNavigate>, browsers: NonSend<Browsers>) {
|
||||||
|
browsers.navigate(&trigger.webview, &trigger.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_request_reload(trigger: On<RequestReload>, browsers: NonSend<Browsers>) {
|
||||||
|
browsers.reload_webview(&trigger.webview);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::prelude::{CefWebviewUri, WebviewSize};
|
use crate::common::WebviewSurface;
|
||||||
|
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 +13,11 @@ 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>)>,
|
||||||
|
surfaces: Query<'w, 's, &'static WebviewSurface>,
|
||||||
|
images: Res<'w, Assets<Image>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Component> WebviewPointer<'_, '_, C> {
|
impl<C: Component> WebviewPointer<'_, '_, C> {
|
||||||
@@ -31,7 +34,7 @@ impl<C: Component> WebviewPointer<'_, '_, C> {
|
|||||||
let (min, max) = self.aabb.calculate_local(webview);
|
let (min, max) = self.aabb.calculate_local(webview);
|
||||||
let aabb_size = Vec2::new(max.x - min.x, max.y - min.y);
|
let aabb_size = Vec2::new(max.x - min.x, max.y - min.y);
|
||||||
let (webview_gtf, webview_size) = self.webviews.get(webview).ok()?;
|
let (webview_gtf, webview_size) = self.webviews.get(webview).ok()?;
|
||||||
self.cameras.iter().find_map(|(camera, camera_gtf)| {
|
let pos = self.cameras.iter().find_map(|(camera, camera_gtf)| {
|
||||||
pointer_to_webview_uv(
|
pointer_to_webview_uv(
|
||||||
viewport_pos,
|
viewport_pos,
|
||||||
camera,
|
camera,
|
||||||
@@ -40,13 +43,38 @@ impl<C: Component> WebviewPointer<'_, '_, C> {
|
|||||||
aabb_size,
|
aabb_size,
|
||||||
webview_size.0,
|
webview_size.0,
|
||||||
)
|
)
|
||||||
})
|
})?;
|
||||||
|
if self.is_transparent_at(webview, pos) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_transparent_at(&self, webview: Entity, pos: Vec2) -> bool {
|
||||||
|
let Ok(surface) = self.surfaces.get(webview) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(image) = self.images.get(surface.0.id()) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let width = image.width();
|
||||||
|
let height = image.height();
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let x = (pos.x.floor() as u32).min(width - 1);
|
||||||
|
let y = (pos.y.floor() as u32).min(height - 1);
|
||||||
|
let offset = ((y * width + x) * 4 + 3) as usize;
|
||||||
|
let Some(data) = image.data.as_ref() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
data.len() > offset && data[offset] == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -92,7 +120,7 @@ fn pointer_to_webview_uv(
|
|||||||
// outside plane bounds
|
// outside plane bounds
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let px = u * tex_size.x;
|
let px = (1.0 - u) * tex_size.x;
|
||||||
let py = (1.0 - v) * tex_size.y;
|
let py = (1.0 - v) * tex_size.y;
|
||||||
Some(Vec2::new(px, py))
|
Some(Vec2::new(px, py))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>, Or<(With<Mesh3d>, With<Mesh2d>)>)>,
|
||||||
) {
|
) {
|
||||||
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;
|
||||||
@@ -98,7 +98,7 @@ fn on_mouse_wheel(
|
|||||||
let Some(pos) = pointer.pointer_pos(webview, cursor_pos) else {
|
let Some(pos) = pointer.pointer_pos(webview, cursor_pos) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
browsers.send_mouse_wheel(&webview, pos, Vec2::new(event.x, event.y));
|
browsers.send_mouse_wheel(&webview, pos, Vec2::new(event.x, event.y) * 40.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::prelude::{WebviewMaterial, update_webview_image};
|
use crate::prelude::{WebviewMaterial, WebviewSurface, update_webview_image};
|
||||||
use bevy::app::Plugin;
|
use bevy::app::Plugin;
|
||||||
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
|
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -35,6 +35,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render<E: MaterialExtension>(
|
fn render<E: MaterialExtension>(
|
||||||
|
mut commands: Commands,
|
||||||
mut er: MessageReader<RenderTextureMessage>,
|
mut er: MessageReader<RenderTextureMessage>,
|
||||||
mut images: ResMut<Assets<Image>>,
|
mut images: ResMut<Assets<Image>>,
|
||||||
mut materials: ResMut<Assets<WebviewExtendedMaterial<E>>>,
|
mut materials: ResMut<Assets<WebviewExtendedMaterial<E>>>,
|
||||||
@@ -48,6 +49,9 @@ fn render<E: MaterialExtension>(
|
|||||||
.base
|
.base
|
||||||
.surface
|
.surface
|
||||||
.get_or_insert_with(|| images.add(Image::default()));
|
.get_or_insert_with(|| images.add(Image::default()));
|
||||||
|
commands
|
||||||
|
.entity(texture.webview)
|
||||||
|
.insert(WebviewSurface(handle.clone()));
|
||||||
images.get_mut(handle.id())
|
images.get_mut(handle.id())
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::prelude::{WebviewMaterial, update_webview_image};
|
use crate::prelude::{WebviewMaterial, WebviewSurface, update_webview_image};
|
||||||
use bevy::asset::*;
|
use bevy::asset::*;
|
||||||
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
|
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -31,6 +31,7 @@ impl MaterialExtension for WebviewMaterial {
|
|||||||
pub type WebviewExtendStandardMaterial = ExtendedMaterial<StandardMaterial, WebviewMaterial>;
|
pub type WebviewExtendStandardMaterial = ExtendedMaterial<StandardMaterial, WebviewMaterial>;
|
||||||
|
|
||||||
fn render_standard_materials(
|
fn render_standard_materials(
|
||||||
|
mut commands: Commands,
|
||||||
mut er: MessageReader<RenderTextureMessage>,
|
mut er: MessageReader<RenderTextureMessage>,
|
||||||
mut images: ResMut<Assets<Image>>,
|
mut images: ResMut<Assets<Image>>,
|
||||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
@@ -44,6 +45,9 @@ fn render_standard_materials(
|
|||||||
.extension
|
.extension
|
||||||
.surface
|
.surface
|
||||||
.get_or_insert_with(|| images.add(Image::default()));
|
.get_or_insert_with(|| images.add(Image::default()));
|
||||||
|
commands
|
||||||
|
.entity(texture.webview)
|
||||||
|
.insert(WebviewSurface(handle.clone()));
|
||||||
images.get_mut(handle.id())
|
images.get_mut(handle.id())
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
}
|
}
|
||||||
#import webview::util::{
|
#import webview::util::{
|
||||||
surface_color,
|
surface_color,
|
||||||
|
overlay_color,
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
@@ -18,15 +19,13 @@ fn fragment(
|
|||||||
in: VertexOutput,
|
in: VertexOutput,
|
||||||
@builtin(front_facing) is_front: bool,
|
@builtin(front_facing) is_front: bool,
|
||||||
) -> FragmentOutput {
|
) -> FragmentOutput {
|
||||||
var pbr_input = pbr_input_from_standard_material(in, is_front);
|
|
||||||
pbr_input.material.base_color *= surface_color(in.uv);
|
|
||||||
pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
|
|
||||||
var out: FragmentOutput;
|
var out: FragmentOutput;
|
||||||
if (material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u {
|
|
||||||
out.color = apply_pbr_lighting(pbr_input);
|
var surface = surface_color(in.uv);
|
||||||
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
|
var overlay = overlay_color(in.uv);
|
||||||
}else{
|
var result_rgb = mix(surface.rgb, overlay.rgb, overlay.a);
|
||||||
out.color = pbr_input.material.base_color;
|
|
||||||
}
|
out.color = vec4(result_rgb, 1.0);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ impl Plugin for WebviewMaterialPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone, PartialEq, Eq, Hash, Default)]
|
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone, PartialEq, Default)]
|
||||||
pub struct WebviewMaterial {
|
pub struct WebviewMaterial {
|
||||||
/// Holds the texture handle for the webview.
|
/// Holds the texture handle for the webview.
|
||||||
///
|
///
|
||||||
@@ -30,6 +30,9 @@ pub struct WebviewMaterial {
|
|||||||
#[texture(101)]
|
#[texture(101)]
|
||||||
#[sampler(102)]
|
#[sampler(102)]
|
||||||
pub surface: Option<Handle<Image>>,
|
pub surface: Option<Handle<Image>>,
|
||||||
|
#[texture(103)]
|
||||||
|
#[sampler(104)]
|
||||||
|
pub overlay: Option<Handle<Image>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Material for WebviewMaterial {}
|
impl Material for WebviewMaterial {}
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
@group(#{MATERIAL_BIND_GROUP}) @binding(101) var surface_texture: texture_2d<f32>;
|
@group(#{MATERIAL_BIND_GROUP}) @binding(101) var surface_texture: texture_2d<f32>;
|
||||||
@group(#{MATERIAL_BIND_GROUP}) @binding(102) var surface_sampler: sampler;
|
@group(#{MATERIAL_BIND_GROUP}) @binding(102) var surface_sampler: sampler;
|
||||||
|
|
||||||
|
@group(#{MATERIAL_BIND_GROUP}) @binding(103) var overlay_texture: texture_2d<f32>;
|
||||||
|
@group(#{MATERIAL_BIND_GROUP}) @binding(104) var overlay_sampler: sampler;
|
||||||
|
|
||||||
fn surface_color(uv: vec2<f32>) -> vec4<f32> {
|
fn surface_color(uv: vec2<f32>) -> vec4<f32> {
|
||||||
return textureSampleBias(surface_texture, surface_sampler, uv, view.mip_bias);
|
return textureSampleBias(surface_texture, surface_sampler, uv, view.mip_bias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn overlay_color(uv: vec2<f32>) -> vec4<f32> {
|
||||||
|
return textureSampleBias(overlay_texture, overlay_sampler, uv, view.mip_bias);
|
||||||
|
}
|
||||||
@@ -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