use bevy::{
prelude::*,
ui::{widget::ImageMode, UiSystem},
};
pub mod prelude {
pub use crate::{
Webview, WebviewApp, WebviewBundle, WebviewCommand, WebviewEventReader, WebviewEventWriter,
WebviewPlugin, WebviewSize, WebviewUIBundle,
};
pub use headless_webview::engines;
#[cfg(feature = "engine")]
pub use headless_webview_engine as webview_engine;
}
pub use serde;
mod events;
mod systems;
mod types;
mod webview;
use events::{
BuiltinWebviewEvent, InputEvent, InputEventMapping, OutputEventMapping, WebviewEvent,
};
pub use events::{WebviewApp, WebviewEventReader, WebviewEventWriter};
use headless_webview::HeadlessWindow;
use headless_webview::WindowBuilder;
use serde::Serialize;
pub(crate) use systems::WebviewInteraction;
use webview::webview_thread;
pub(crate) const BUILTIN_RPC_INPUT_METHOD: &str = "_webview";
pub struct WebviewPlugin<ENGINE: HeadlessWindow> {
pub(crate) engine: Option<fn() -> WindowBuilder<ENGINE>>,
}
impl<ENGINE: HeadlessWindow> WebviewPlugin<ENGINE> {
pub fn new() -> Self {
Self { engine: None }
}
}
impl<ENGINE: HeadlessWindow> WebviewPlugin<ENGINE> {
pub fn with_engine(engine: fn() -> WindowBuilder<ENGINE>) -> Self {
Self {
engine: Some(engine),
}
}
pub fn register_engine(mut self, engine: fn() -> WindowBuilder<ENGINE>) -> Self {
self.engine = Some(engine);
self
}
}
impl<ENGINE> Plugin for WebviewPlugin<ENGINE>
where
ENGINE: HeadlessWindow + 'static,
{
fn build(&self, app: &mut App) {
let event_transport = webview_thread(WebviewEngine(
self.engine
.expect("Webview is missing an engine. Please use `.register_engine(engine)`"),
));
app.insert_resource(InputEventMapping::default())
.insert_resource(OutputEventMapping::default())
.insert_resource(event_transport)
.add_event::<InputEvent>()
.add_event::<WebviewEvent<WebviewCommand>>()
.add_webview_input_event::<BuiltinWebviewEvent>(BUILTIN_RPC_INPUT_METHOD)
.add_system_to_stage(
CoreStage::PreUpdate,
systems::inject_rpc_requests_system.label(PreUpdateLabel::Pre),
)
.add_system(systems::rpc_builtin_event_handler)
.add_system(systems::webview_ui_focus_system)
.add_system(systems::ui_event)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::ui_size
.label(PostUpdateLabel::PrePre)
.after(UiSystem::Flex),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::create_webview_system
.label(PostUpdateLabel::Pre)
.after(UiSystem::Flex),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::keyboard_event_system.label(PostUpdateLabel::Pre),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::webview_changed_system.label(PostUpdateLabel::Pre),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::rpc_command_system.label(PostUpdateLabel::Pre),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::removed_webviews_system.label(PostUpdateLabel::Pre),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::rpc_fallthrough_event_logger
.label(PostUpdateLabel::Update)
.after(PostUpdateLabel::Pre),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::tick_webviews_system
.label(PostUpdateLabel::Update)
.after(PostUpdateLabel::Pre),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::update_webview_textures
.label(PostUpdateLabel::Post)
.after(PostUpdateLabel::Update),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::app_exit
.label(PostUpdateLabel::Post)
.after(PostUpdateLabel::Update),
);
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub(crate) enum PreUpdateLabel {
Pre,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub(crate) enum PostUpdateLabel {
PrePre,
Pre,
Update,
Post,
}
#[derive(Clone)]
pub(crate) struct WebviewEngine<T: HeadlessWindow>(fn() -> WindowBuilder<T>);
#[derive(Bundle, Clone, Default, Debug)]
pub struct WebviewBundle {
pub webview: Webview,
pub size: WebviewSize,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub webview_state: WebviewState,
}
#[derive(Component, Debug, Clone)]
pub struct WebviewSize {
pub x: f32,
pub y: f32,
pub ppu: f32,
}
impl Default for WebviewSize {
fn default() -> Self {
Self {
x: 1.,
y: 1.,
ppu: 400.,
}
}
}
impl WebviewSize {
pub fn pixels_x(&self) -> u32 {
(self.x * self.ppu).round() as u32
}
pub fn pixels_y(&self) -> u32 {
(self.y * self.ppu).round() as u32
}
pub fn texture_byte_size_rgba_mb(&self) -> f32 {
self.pixels_x() as f32 * self.pixels_y() as f32 * 4. / 1024. / 1024.
}
}
#[derive(Bundle, Clone, Default, Debug)]
pub struct WebviewUIBundle {
pub webview: Webview,
pub node: Node,
pub style: Style,
pub image_mode: ImageMode,
pub calculated_size: CalculatedSize,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub visibility: Visibility,
pub interaction: WebviewInteraction,
pub webview_state: WebviewState,
}
#[derive(Component, Clone, Debug)]
pub struct Webview {
pub uri: Option<String>,
pub html: Option<String>,
pub color: Color,
pub initialization_script: Option<String>,
}
impl Default for Webview {
fn default() -> Self {
Self {
uri: None,
html: None,
color: Default::default(),
initialization_script: None,
}
}
}
#[derive(Component, Serialize, Debug, Clone)]
pub enum WebviewCommand {
LoadUri(String),
LoadHtml(String),
Reload,
RunJavascript(String),
}
#[derive(Debug, Clone, Default, Component)]
pub struct WebviewState {
pub(crate) texture_added: bool,
}