Support Bevy 0.17 (#11)

* update: modify for 0.17

* update: enhance webview functionality and improve plugin implementation

* update: refactor webview system and improve binding group usage

* update: refactor command triggering for webview events and enhance Receive struct

* update: refactor command triggering for webview dev tools

* update: refactor render process handler and improve webview handling

* update: refactor webview browser handling and improve IME caret management

* clippy

* fmt

* update: improve README formatting and clarify version compatibility

* update: support Bevy 0.17 and enhance permissions in settings

* update: enhance CI configuration by adding Wayland and XKB dependencies

* delete: settings.json

* update: refactor shader imports and improve binding group definitions

* update: refactor devtool command triggers for improved clarity

* update: modify LibraryLoader initialization for improved path handling on macOS

* fmt

---------

Co-authored-by: not-elm <elmgameinfo@gmail.com>
This commit is contained in:
elm
2025-10-26 16:55:03 +09:00
committed by GitHub
parent 0bb9b58fae
commit edf9e064b9
35 changed files with 1753 additions and 1189 deletions

View File

@@ -5,17 +5,20 @@ use serde::{Deserialize, Serialize};
/// A trigger event to emit an event from the host to the webview.
///
/// You need to subscribe to this event on the webview side by calling `window.cef.listen("event-id", (e) => {})` beforehand.
#[derive(Default, Reflect, Debug, Clone, Serialize, Deserialize, Event)]
#[reflect(Default, Serialize, Deserialize)]
#[derive(Reflect, Debug, Clone, Serialize, Deserialize, EntityEvent)]
#[reflect(Serialize, Deserialize)]
pub struct HostEmitEvent {
#[event_target]
pub webview: Entity,
pub id: String,
pub payload: String,
}
impl HostEmitEvent {
/// Creates a new `HostEmitEvent` with the given id and payload.
pub fn new(id: impl Into<String>, payload: &impl Serialize) -> Self {
pub fn new(webview: Entity, id: impl Into<String>, payload: &impl Serialize) -> Self {
Self {
webview,
id: id.into(),
payload: serde_json::to_string(payload).unwrap_or_default(),
}
@@ -30,8 +33,8 @@ impl Plugin for HostEmitPlugin {
}
}
fn host_emit(trigger: Trigger<HostEmitEvent>, browsers: NonSend<Browsers>) {
fn host_emit(trigger: On<HostEmitEvent>, browsers: NonSend<Browsers>) {
if let Ok(v) = serde_json::to_value(&trigger.payload) {
browsers.emit_event(&trigger.target(), trigger.id.clone(), &v);
browsers.emit_event(&trigger.webview, trigger.id.clone(), &v);
}
}

View File

@@ -3,28 +3,59 @@ use bevy::prelude::*;
use bevy_cef_core::prelude::*;
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
pub struct JsEmitEventPlugin<E: Event + DeserializeOwned>(PhantomData<E>);
#[derive(Debug, EntityEvent)]
pub struct Receive<M: Sync + Send + 'static> {
#[event_target]
pub webview: Entity,
pub payload: M,
}
impl<E: Event + DeserializeOwned> Plugin for JsEmitEventPlugin<E> {
impl<M> Deref for Receive<M>
where
M: Sync + Send + 'static,
{
type Target = M;
fn deref(&self) -> &Self::Target {
&self.payload
}
}
impl<M> DerefMut for Receive<M>
where
M: Sync + Send + 'static,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.payload
}
}
pub struct JsEmitEventPlugin<E: DeserializeOwned>(PhantomData<E>);
impl<E: DeserializeOwned + Send + Sync + 'static> Plugin for JsEmitEventPlugin<E> {
fn build(&self, app: &mut App) {
app.add_systems(Update, receive_events::<E>);
}
}
impl<E: Event + DeserializeOwned> Default for JsEmitEventPlugin<E> {
impl<E: DeserializeOwned> Default for JsEmitEventPlugin<E> {
fn default() -> Self {
Self(PhantomData)
}
}
fn receive_events<E: Event + DeserializeOwned>(
fn receive_events<E: DeserializeOwned + Send + Sync + 'static>(
mut commands: Commands,
receiver: ResMut<IpcEventRawReceiver>,
) {
while let Ok(event) = receiver.0.try_recv() {
if let Ok(payload) = serde_json::from_str::<E>(&event.payload) {
commands.entity(event.webview).trigger(payload);
commands.trigger(Receive {
webview: event.webview,
payload,
});
}
}
}

View File

@@ -21,7 +21,7 @@ impl Plugin for ResponserPlugin {
}
}
fn any_changed_assets(mut er: EventReader<AssetEvent<CefResponse>>) -> bool {
fn any_changed_assets(mut er: MessageReader<AssetEvent<CefResponse>>) -> bool {
er.read()
.any(|event| matches!(event, AssetEvent::Modified { .. }))
}

View File

@@ -19,7 +19,7 @@ pub struct MessageLoopPlugin {
impl Plugin for MessageLoopPlugin {
fn build(&self, app: &mut App) {
app.insert_non_send_resource(RunOnMainThread)
.add_systems(Update, cef_shutdown.run_if(on_event::<AppExit>));
.add_systems(Update, cef_shutdown.run_if(on_message::<AppExit>));
#[cfg(target_os = "macos")]
app.add_systems(Main, cef_do_message_loop_work);

View File

@@ -1,7 +1,6 @@
use async_channel::{Receiver, Sender};
use bevy::prelude::*;
use bevy::window::SystemCursorIcon;
use bevy::winit::cursor::CursorIcon;
use bevy::window::{CursorIcon, SystemCursorIcon};
/// This plugin manages the system cursor icon by receiving updates from CEF and applying them to the application window's cursor icon.
pub(super) struct SystemCursorIconPlugin;

View File

@@ -14,8 +14,8 @@ impl Plugin for KeyboardPlugin {
app.init_resource::<IsImeCommiting>().add_systems(
Update,
(
ime_event.run_if(on_event::<Ime>),
send_key_event.run_if(on_event::<KeyboardInput>),
ime_event.run_if(on_message::<Ime>),
send_key_event.run_if(on_message::<KeyboardInput>),
)
.chain(),
);
@@ -27,7 +27,7 @@ impl Plugin for KeyboardPlugin {
struct IsImeCommiting(bool);
fn send_key_event(
mut er: EventReader<KeyboardInput>,
mut er: MessageReader<KeyboardInput>,
mut is_ime_commiting: ResMut<IsImeCommiting>,
input: Res<ButtonInput<KeyCode>>,
browsers: NonSend<Browsers>,
@@ -51,7 +51,7 @@ fn send_key_event(
}
fn ime_event(
mut er: EventReader<Ime>,
mut er: MessageReader<Ime>,
mut is_ime_commiting: ResMut<IsImeCommiting>,
browsers: NonSend<Browsers>,
) {

View File

@@ -14,17 +14,23 @@ impl Plugin for NavigationPlugin {
}
/// A trigger event to navigate backwards.
#[derive(Default, Debug, Event, Copy, Clone, Reflect, Serialize, Deserialize)]
pub struct RequestGoBack;
#[derive(Debug, EntityEvent, Copy, Clone, Reflect, Serialize, Deserialize)]
pub struct RequestGoBack {
#[event_target]
pub webview: Entity,
}
/// A trigger event to navigate forwards.
#[derive(Default, Debug, Event, Copy, Clone, Reflect, Serialize, Deserialize)]
pub struct RequestGoForward;
fn apply_request_go_back(trigger: Trigger<RequestGoBack>, browsers: NonSend<Browsers>) {
browsers.go_back(&trigger.target());
#[derive(Debug, EntityEvent, Copy, Clone, Reflect, Serialize, Deserialize)]
pub struct RequestGoForward {
#[event_target]
pub webview: Entity,
}
fn apply_request_go_forward(trigger: Trigger<RequestGoForward>, browsers: NonSend<Browsers>) {
browsers.go_forward(&trigger.target());
fn apply_request_go_back(trigger: On<RequestGoBack>, browsers: NonSend<Browsers>) {
browsers.go_back(&trigger.webview);
}
fn apply_request_go_forward(trigger: On<RequestGoForward>, browsers: NonSend<Browsers>) {
browsers.go_forward(&trigger.webview);
}

View File

@@ -1,7 +1,7 @@
use bevy::camera::primitives::Aabb;
use bevy::ecs::system::SystemParam;
use bevy::math::Vec3;
use bevy::prelude::{Children, Entity, GlobalTransform, Query};
use bevy::render::primitives::Aabb;
use bevy::prelude::*;
#[derive(SystemParam)]
pub struct MeshAabb<'w, 's> {

View File

@@ -18,11 +18,11 @@ pub struct WebviewPointer<'w, 's, C: Component = Camera3d> {
}
impl<C: Component> WebviewPointer<'_, '_, C> {
pub fn pos_from_trigger<P>(&self, trigger: &Trigger<Pointer<P>>) -> Option<(Entity, Vec2)>
pub fn pos_from_trigger<P>(&self, trigger: &On<Pointer<P>>) -> Option<(Entity, Vec2)>
where
P: Clone + Reflect + Debug,
{
let webview = find_webview_entity(trigger.target, &self.parents)?;
let webview = find_webview_entity(trigger.entity, &self.parents)?;
let pos = self.pointer_pos(webview, trigger.pointer_location.position)?;
Some((webview, pos))
}

View File

@@ -2,11 +2,11 @@ use crate::common::{CefWebviewUri, HostWindow, IpcEventRawSender, WebviewSize};
use crate::cursor_icon::SystemCursorIconSender;
use crate::prelude::PreloadScripts;
use crate::webview::mesh::MeshWebviewPlugin;
use bevy::ecs::component::HookContext;
use bevy::ecs::lifecycle::HookContext;
use bevy::ecs::world::DeferredWorld;
use bevy::prelude::*;
use bevy::window::PrimaryWindow;
use bevy::winit::WinitWindows;
use bevy::winit::WINIT_WINDOWS;
use bevy_cef_core::prelude::*;
use bevy_remote::BrpSender;
#[allow(deprecated)]
@@ -32,12 +32,16 @@ pub mod prelude {
/// struct DebugWebview;
///
/// fn show_devtool_system(mut commands: Commands, webviews: Query<Entity, With<DebugWebview>>) {
/// commands.entity(webviews.single().unwrap()).trigger(RequestShowDevTool);
/// let entity = webviews.single().unwrap();
/// commands.entity(entity).trigger(|webview| RequestShowDevTool { webview });
/// }
/// ```
#[derive(Reflect, Debug, Default, Copy, Clone, Serialize, Deserialize, Event)]
#[reflect(Default, Serialize, Deserialize)]
pub struct RequestShowDevTool;
#[derive(Reflect, Debug, Copy, Clone, Serialize, Deserialize, EntityEvent)]
#[reflect(Serialize, Deserialize)]
pub struct RequestShowDevTool {
#[event_target]
pub webview: Entity,
}
/// A Trigger event to request closing the developer tools in a webview.
///
@@ -51,12 +55,16 @@ pub struct RequestShowDevTool;
/// struct DebugWebview;
///
/// fn close_devtool_system(mut commands: Commands, webviews: Query<Entity, With<DebugWebview>>) {
/// commands.entity(webviews.single().unwrap()).trigger(RequestCloseDevtool);
/// let entity = webviews.single().unwrap();
/// commands.entity(entity).trigger(|webview| RequestCloseDevtool { webview });
/// }
/// ```
#[derive(Reflect, Debug, Default, Copy, Clone, Serialize, Deserialize, Event)]
#[reflect(Default, Serialize, Deserialize)]
pub struct RequestCloseDevtool;
#[derive(Reflect, Debug, Copy, Clone, Serialize, Deserialize, EntityEvent)]
#[reflect(Serialize, Deserialize)]
pub struct RequestCloseDevtool {
#[event_target]
pub webview: Entity,
}
pub struct WebviewPlugin;
@@ -103,7 +111,6 @@ fn create_webview(
ipc_event_sender: Res<IpcEventRawSender>,
brp_sender: Res<BrpSender>,
cursor_icon_sender: Res<SystemCursorIconSender>,
winit_windows: NonSend<WinitWindows>,
webviews: Query<
(
Entity,
@@ -116,26 +123,29 @@ fn create_webview(
>,
primary_window: Query<Entity, With<PrimaryWindow>>,
) {
for (entity, uri, size, initialize_scripts, host_window) in webviews.iter() {
let host_window = host_window
.and_then(|w| winit_windows.get_window(w.0))
.or_else(|| winit_windows.get_window(primary_window.single().ok()?))
.and_then(|w| {
#[allow(deprecated)]
w.raw_window_handle().ok()
});
browsers.create_browser(
entity,
&uri.0,
size.0,
requester.clone(),
ipc_event_sender.0.clone(),
brp_sender.clone(),
cursor_icon_sender.clone(),
&initialize_scripts.0,
host_window,
);
}
WINIT_WINDOWS.with(|winit_windows| {
let winit_windows = winit_windows.borrow();
for (entity, uri, size, initialize_scripts, host_window) in webviews.iter() {
let host_window = host_window
.and_then(|w| winit_windows.get_window(w.0))
.or_else(|| winit_windows.get_window(primary_window.single().ok()?))
.and_then(|w| {
#[allow(deprecated)]
w.raw_window_handle().ok()
});
browsers.create_browser(
entity,
&uri.0,
size.0,
requester.clone(),
ipc_event_sender.0.clone(),
brp_sender.clone(),
cursor_icon_sender.clone(),
&initialize_scripts.0,
host_window,
);
}
});
}
fn resize(
@@ -147,10 +157,10 @@ fn resize(
}
}
fn apply_request_show_devtool(trigger: Trigger<RequestShowDevTool>, browsers: NonSend<Browsers>) {
browsers.show_devtool(&trigger.target());
fn apply_request_show_devtool(trigger: On<RequestShowDevTool>, browsers: NonSend<Browsers>) {
browsers.show_devtool(&trigger.webview);
}
fn apply_request_close_devtool(trigger: Trigger<RequestCloseDevtool>, browsers: NonSend<Browsers>) {
browsers.close_devtools(&trigger.target());
fn apply_request_close_devtool(trigger: On<RequestCloseDevtool>, browsers: NonSend<Browsers>) {
browsers.close_devtools(&trigger.webview);
}

View File

@@ -29,7 +29,7 @@ impl Plugin for MeshWebviewPlugin {
Update,
(
setup_observers,
on_mouse_wheel.run_if(on_event::<MouseWheel>),
on_mouse_wheel.run_if(on_message::<MouseWheel>),
),
);
}
@@ -49,7 +49,7 @@ fn setup_observers(
}
fn on_pointer_move(
trigger: Trigger<Pointer<Move>>,
trigger: On<Pointer<Move>>,
input: Res<ButtonInput<MouseButton>>,
pointer: WebviewPointer,
browsers: NonSend<Browsers>,
@@ -62,7 +62,7 @@ fn on_pointer_move(
}
fn on_pointer_pressed(
trigger: Trigger<Pointer<Pressed>>,
trigger: On<Pointer<Press>>,
browsers: NonSend<Browsers>,
pointer: WebviewPointer,
) {
@@ -73,7 +73,7 @@ fn on_pointer_pressed(
}
fn on_pointer_released(
trigger: Trigger<Pointer<Released>>,
trigger: On<Pointer<Release>>,
browsers: NonSend<Browsers>,
pointer: WebviewPointer,
) {
@@ -84,7 +84,7 @@ fn on_pointer_released(
}
fn on_mouse_wheel(
mut er: EventReader<MouseWheel>,
mut er: MessageReader<MouseWheel>,
browsers: NonSend<Browsers>,
pointer: WebviewPointer,
windows: Query<&Window>,

View File

@@ -1,4 +1,5 @@
use crate::prelude::{WebviewMaterial, update_webview_image};
use bevy::app::Plugin;
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
use bevy::prelude::*;
use bevy::render::render_resource::AsBindGroup;
@@ -16,7 +17,7 @@ pub struct WebviewExtendMaterialPlugin<E>(PhantomData<E>);
impl<E> Default for WebviewExtendMaterialPlugin<E>
where
E: MaterialExtension + Default,
<E as AsBindGroup>::Data: PartialEq + Eq + Hash + Clone,
<E as AsBindGroup>::Data: PartialEq + Eq + Hash + Clone + Copy,
{
fn default() -> Self {
Self(PhantomData)
@@ -25,8 +26,7 @@ where
impl<E> Plugin for WebviewExtendMaterialPlugin<E>
where
E: MaterialExtension + Default,
<E as AsBindGroup>::Data: PartialEq + Eq + Hash + Clone,
E: MaterialExtension + AsBindGroup<Data: PartialEq + Eq + Hash + Clone + Copy> + Default,
{
fn build(&self, app: &mut App) {
app.add_plugins(MaterialPlugin::<WebviewExtendedMaterial<E>>::default())
@@ -35,7 +35,7 @@ where
}
fn render<E: MaterialExtension>(
mut er: EventReader<RenderTexture>,
mut er: MessageReader<RenderTextureMessage>,
mut images: ResMut<Assets<Image>>,
mut materials: ResMut<Assets<WebviewExtendedMaterial<E>>>,
webviews: Query<&MeshMaterial3d<WebviewExtendedMaterial<E>>>,

View File

@@ -1,11 +1,11 @@
use crate::prelude::{WebviewMaterial, update_webview_image};
use bevy::asset::{load_internal_asset, weak_handle};
use bevy::asset::*;
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
use bevy::prelude::*;
use bevy::render::render_resource::ShaderRef;
use bevy::shader::ShaderRef;
use bevy_cef_core::prelude::*;
const FRAGMENT_SHADER_HANDLE: Handle<Shader> = weak_handle!("b231681f-9c17-4df6-89c9-9dc353e85a08");
const FRAGMENT_SHADER_HANDLE: Handle<Shader> = uuid_handle!("b231681f-9c17-4df6-89c9-9dc353e85a08");
pub(super) struct WebviewExtendStandardMaterialPlugin;
@@ -31,7 +31,7 @@ impl MaterialExtension for WebviewMaterial {
pub type WebviewExtendStandardMaterial = ExtendedMaterial<StandardMaterial, WebviewMaterial>;
fn render_standard_materials(
mut er: EventReader<RenderTexture>,
mut er: MessageReader<RenderTextureMessage>,
mut images: ResMut<Assets<Image>>,
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
webviews: Query<&MeshMaterial3d<WebviewExtendStandardMaterial>>,

View File

@@ -1,17 +1,17 @@
use bevy::asset::{RenderAssetUsages, load_internal_asset, weak_handle};
use bevy::asset::*;
use bevy::prelude::*;
use bevy::render::render_resource::{AsBindGroup, Extent3d, TextureDimension, TextureFormat};
use bevy_cef_core::prelude::*;
const WEBVIEW_UTIL_SHADER_HANDLE: Handle<Shader> =
weak_handle!("6c7cb871-4208-4407-9c25-306c6f069e2b");
uuid_handle!("6c7cb871-4208-4407-9c25-306c6f069e2b");
pub(super) struct WebviewMaterialPlugin;
impl Plugin for WebviewMaterialPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(MaterialPlugin::<WebviewMaterial>::default())
.add_event::<RenderTexture>()
.add_message::<RenderTextureMessage>()
.add_systems(Update, send_render_textures);
load_internal_asset!(
app,
@@ -22,7 +22,7 @@ impl Plugin for WebviewMaterialPlugin {
}
}
#[derive(Asset, Reflect, Default, Debug, Clone, AsBindGroup)]
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct WebviewMaterial {
/// Holds the texture handle for the webview.
///
@@ -34,13 +34,13 @@ pub struct WebviewMaterial {
impl Material for WebviewMaterial {}
fn send_render_textures(mut ew: EventWriter<RenderTexture>, browsers: NonSend<Browsers>) {
fn send_render_textures(mut ew: MessageWriter<RenderTextureMessage>, browsers: NonSend<Browsers>) {
while let Ok(texture) = browsers.try_receive_texture() {
ew.write(texture);
}
}
pub(crate) fn update_webview_image(texture: RenderTexture, image: &mut Image) {
pub(crate) fn update_webview_image(texture: RenderTextureMessage, image: &mut Image) {
*image = Image::new(
Extent3d {
width: texture.width,

View File

@@ -5,8 +5,8 @@
mesh_view_bindings::view,
}
@group(2) @binding(101) var surface_texture: texture_2d<f32>;
@group(2) @binding(102) var surface_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(101) var surface_texture: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(102) var surface_sampler: sampler;
fn surface_color(uv: vec2<f32>) -> vec4<f32> {
return textureSampleBias(surface_texture, surface_sampler, uv, view.mip_bias);

View File

@@ -2,7 +2,7 @@ use crate::common::{CefWebviewUri, WebviewSize};
use crate::prelude::update_webview_image;
use bevy::input::mouse::MouseWheel;
use bevy::prelude::*;
use bevy_cef_core::prelude::{Browsers, RenderTexture};
use bevy_cef_core::prelude::{Browsers, RenderTextureMessage};
use std::fmt::Debug;
pub(in crate::webview) struct WebviewSpritePlugin;
@@ -17,15 +17,18 @@ impl Plugin for WebviewSpritePlugin {
Update,
(
setup_observers,
on_mouse_wheel.run_if(on_event::<MouseWheel>),
on_mouse_wheel.run_if(on_message::<MouseWheel>),
),
)
.add_systems(PostUpdate, render.run_if(on_event::<RenderTexture>));
.add_systems(
PostUpdate,
render.run_if(on_message::<RenderTextureMessage>),
);
}
}
fn render(
mut er: EventReader<RenderTexture>,
mut er: MessageReader<RenderTextureMessage>,
mut images: ResMut<Assets<bevy::prelude::Image>>,
webviews: Query<&Sprite, With<CefWebviewUri>>,
) {
@@ -52,7 +55,7 @@ fn setup_observers(
}
fn apply_on_pointer_move(
trigger: Trigger<Pointer<Move>>,
trigger: On<Pointer<Move>>,
input: Res<ButtonInput<MouseButton>>,
browsers: NonSend<Browsers>,
cameras: Query<(&Camera, &GlobalTransform)>,
@@ -61,11 +64,11 @@ fn apply_on_pointer_move(
let Some(pos) = obtain_relative_pos_from_trigger(&trigger, &webviews, &cameras) else {
return;
};
browsers.send_mouse_move(&trigger.target, input.get_pressed(), pos, false);
browsers.send_mouse_move(&trigger.entity, input.get_pressed(), pos, false);
}
fn apply_on_pointer_pressed(
trigger: Trigger<Pointer<Pressed>>,
trigger: On<Pointer<Press>>,
browsers: NonSend<Browsers>,
cameras: Query<(&Camera, &GlobalTransform)>,
webviews: Query<(&Sprite, &WebviewSize, &GlobalTransform)>,
@@ -73,11 +76,11 @@ fn apply_on_pointer_pressed(
let Some(pos) = obtain_relative_pos_from_trigger(&trigger, &webviews, &cameras) else {
return;
};
browsers.send_mouse_click(&trigger.target, pos, trigger.button, false);
browsers.send_mouse_click(&trigger.entity, pos, trigger.button, false);
}
fn apply_on_pointer_released(
trigger: Trigger<Pointer<Released>>,
trigger: On<Pointer<Release>>,
browsers: NonSend<Browsers>,
cameras: Query<(&Camera, &GlobalTransform)>,
webviews: Query<(&Sprite, &WebviewSize, &GlobalTransform)>,
@@ -85,11 +88,11 @@ fn apply_on_pointer_released(
let Some(pos) = obtain_relative_pos_from_trigger(&trigger, &webviews, &cameras) else {
return;
};
browsers.send_mouse_click(&trigger.target, pos, trigger.button, true);
browsers.send_mouse_click(&trigger.entity, pos, trigger.button, true);
}
fn on_mouse_wheel(
mut er: EventReader<MouseWheel>,
mut er: MessageReader<MouseWheel>,
browsers: NonSend<Browsers>,
webviews: Query<(Entity, &Sprite, &WebviewSize, &GlobalTransform)>,
cameras: Query<(&Camera, &GlobalTransform)>,
@@ -111,11 +114,11 @@ fn on_mouse_wheel(
}
fn obtain_relative_pos_from_trigger<E: Debug + Clone + Reflect>(
trigger: &Trigger<Pointer<E>>,
trigger: &On<Pointer<E>>,
webviews: &Query<(&Sprite, &WebviewSize, &GlobalTransform)>,
cameras: &Query<(&Camera, &GlobalTransform)>,
) -> Option<Vec2> {
let (sprite, webview_size, gtf) = webviews.get(trigger.target()).ok()?;
let (sprite, webview_size, gtf) = webviews.get(trigger.entity).ok()?;
obtain_relative_pos(
sprite,
webview_size,