mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Implement basic cef based desktop app
This commit is contained in:
parent
03751dfd7c
commit
809a00979a
21 changed files with 2647 additions and 2636 deletions
|
|
@ -42,9 +42,39 @@
|
|||
extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ];
|
||||
};
|
||||
|
||||
libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: {
|
||||
version = "138.0.26";
|
||||
gitRevision = "84f2d27";
|
||||
chromiumVersion = "138.0.7204.158";
|
||||
srcHash = "sha256-d9jQJX7rgdoHfROD3zmOdMSesRdKE3slB5ZV+U2wlbQ=";
|
||||
|
||||
__intentionallyOverridingVersion = true;
|
||||
|
||||
postInstall = ''
|
||||
strip $out/lib/*
|
||||
'';
|
||||
});
|
||||
|
||||
libcefPath = pkgs.runCommand "libcef-path" {} ''
|
||||
mkdir -p $out
|
||||
|
||||
ln -s ${libcef}/include $out/include
|
||||
find ${libcef}/lib -type f -name "*" -exec ln -s {} $out/ \;
|
||||
find ${libcef}/libexec -type f -name "*" -exec ln -s {} $out/ \;
|
||||
cp -r ${libcef}/share/cef/* $out/
|
||||
|
||||
echo '${builtins.toJSON {
|
||||
type = "minimal";
|
||||
name = builtins.baseNameOf libcef.src.url;
|
||||
sha1 = "";
|
||||
}}' > $out/archive.json
|
||||
'';
|
||||
|
||||
# Shared build inputs - system libraries that need to be in LD_LIBRARY_PATH
|
||||
buildInputs = with pkgs; [
|
||||
# System libraries
|
||||
wayland
|
||||
wayland.dev
|
||||
openssl
|
||||
vulkan-loader
|
||||
mesa
|
||||
|
|
@ -85,8 +115,8 @@
|
|||
devShells.default = pkgs.mkShell {
|
||||
packages = buildInputs ++ buildTools ++ devTools;
|
||||
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
|
||||
GIO_MODULE_DIR="${pkgs.glib-networking}/lib/gio/modules/";
|
||||
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}:/${libcefPath}";
|
||||
CEF_PATH = libcefPath;
|
||||
XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS";
|
||||
|
||||
shellHook = ''
|
||||
|
|
|
|||
3152
Cargo.lock
generated
3152
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"editor",
|
||||
"desktop",
|
||||
"proc-macros",
|
||||
"frontend/wasm",
|
||||
"node-graph/gapplication-io",
|
||||
|
|
@ -110,7 +111,7 @@ web-sys = { version = "=0.3.77", features = [
|
|||
"HtmlImageElement",
|
||||
"ImageBitmapRenderingContext",
|
||||
] }
|
||||
winit = "0.29"
|
||||
winit = { version = "0.30", features = ["wayland", "rwh_06"] }
|
||||
url = "2.5"
|
||||
tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] }
|
||||
vello = { git = "https://github.com/linebender/vello.git", rev = "3275ec8" } # TODO switch back to stable when a release is made
|
||||
|
|
|
|||
3
desktop/.gitignore
vendored
Normal file
3
desktop/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
32
desktop/Cargo.toml
Normal file
32
desktop/Cargo.toml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "graphite-desktop"
|
||||
version = "0.1.0"
|
||||
description = "Graphite Desktop"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
license = "Apache-2.0"
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
rust-version = "1.79"
|
||||
|
||||
[features]
|
||||
default = ["gpu"]
|
||||
gpu = ["graphite-editor/gpu"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
graphite-editor = { path = "../editor", features = [
|
||||
"gpu",
|
||||
"ron",
|
||||
"vello",
|
||||
"decouple-execution",
|
||||
] }
|
||||
wgpu = { workspace = true }
|
||||
winit = { workspace = true, features = ["serde"] }
|
||||
base64.workspace = true
|
||||
thiserror.workspace = true
|
||||
pollster = "0.3"
|
||||
cef = "138.5.0"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
tracing = "0.1.41"
|
||||
bytemuck = { version = "1.23.1", features = ["derive"] }
|
||||
include_dir = "0.7.4"
|
||||
10
desktop/build.rs
Normal file
10
desktop/build.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
use std::fs::metadata;
|
||||
|
||||
fn main() {
|
||||
let frontend_dir = format!("{}/../frontend/dist", env!("CARGO_MANIFEST_DIR"));
|
||||
metadata(&frontend_dir).expect("Failed to find frontend directory. Please build the frontend first.");
|
||||
metadata(format!("{}/index.html", &frontend_dir)).expect("Failed to find index.html in frontend directory.");
|
||||
|
||||
println!("cargo:rerun-if-changed=.");
|
||||
println!("cargo:rerun-if-changed=../frontend");
|
||||
}
|
||||
124
desktop/src/cef/context.rs
Normal file
124
desktop/src/cef/context.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
use cef::sys::CEF_API_VERSION_LAST;
|
||||
use cef::{api_hash, args::Args, execute_process, Browser, CefString, Settings};
|
||||
use cef::{browser_host_create_browser_sync, initialize, BrowserSettings, DictionaryValue, ImplCommandLine, RequestContext, WindowInfo};
|
||||
use thiserror::Error;
|
||||
use winit::event::WindowEvent;
|
||||
|
||||
use super::input::{handle_window_event, InputState};
|
||||
use super::EventHandler;
|
||||
|
||||
use super::internal::{AppImpl, ClientImpl, NonBrowserAppImpl, RenderHandlerImpl};
|
||||
|
||||
pub(crate) struct Setup {}
|
||||
pub(crate) struct Initialized {}
|
||||
pub(crate) trait ContextState {}
|
||||
impl ContextState for Setup {}
|
||||
impl ContextState for Initialized {}
|
||||
|
||||
pub(crate) struct Context<S: ContextState> {
|
||||
args: Args,
|
||||
pub(crate) browser: Option<Browser>,
|
||||
pub(crate) input_state: InputState,
|
||||
marker: std::marker::PhantomData<S>,
|
||||
}
|
||||
|
||||
impl Context<Setup> {
|
||||
pub(crate) fn new() -> Result<Context<Setup>, SetupError> {
|
||||
#[cfg(target_os = "macos")]
|
||||
let _loader = {
|
||||
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
|
||||
assert!(loader.load());
|
||||
loader
|
||||
};
|
||||
let _ = api_hash(CEF_API_VERSION_LAST, 0);
|
||||
let args = Args::new();
|
||||
let cmd = args.as_cmd_line().unwrap();
|
||||
let switch = CefString::from("type");
|
||||
let is_browser_process = cmd.has_switch(Some(&switch)) != 1;
|
||||
if !is_browser_process {
|
||||
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
|
||||
let mut app = NonBrowserAppImpl::new();
|
||||
let ret = execute_process(Some(args.as_main_args()), Some(&mut app), std::ptr::null_mut());
|
||||
if ret >= 0 {
|
||||
return Err(SetupError::SubprocessFailed(process_type.to_string()));
|
||||
} else {
|
||||
return Err(SetupError::Subprocess);
|
||||
}
|
||||
}
|
||||
Ok(Context {
|
||||
args,
|
||||
browser: None,
|
||||
input_state: InputState::default(),
|
||||
marker: std::marker::PhantomData::<Setup>,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn init(self, event_handler: impl EventHandler) -> Result<Context<Initialized>, InitError> {
|
||||
let mut settings = Settings::default();
|
||||
settings.windowless_rendering_enabled = 1;
|
||||
settings.multi_threaded_message_loop = 0;
|
||||
|
||||
let mut cef_app = AppImpl::new(event_handler.clone());
|
||||
|
||||
let res = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
|
||||
if res != 1 {
|
||||
return Err(InitError::InitializationFailed);
|
||||
}
|
||||
|
||||
let render_handler = RenderHandlerImpl::new(event_handler.clone());
|
||||
let mut client = ClientImpl::new(render_handler);
|
||||
|
||||
let url = CefString::from("graphite://frontend/");
|
||||
|
||||
let mut window_info = WindowInfo::default();
|
||||
window_info.windowless_rendering_enabled = 1;
|
||||
|
||||
let mut settings = BrowserSettings::default();
|
||||
settings.windowless_frame_rate = 60;
|
||||
settings.background_color = 0x0;
|
||||
|
||||
let browser = browser_host_create_browser_sync(
|
||||
Some(&window_info),
|
||||
Some(&mut client),
|
||||
Some(&url),
|
||||
Some(&settings),
|
||||
Option::<&mut DictionaryValue>::None,
|
||||
Option::<&mut RequestContext>::None,
|
||||
);
|
||||
|
||||
Ok(Context {
|
||||
args: self.args,
|
||||
browser,
|
||||
input_state: self.input_state,
|
||||
marker: std::marker::PhantomData::<Initialized>,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Context<Initialized> {
|
||||
pub(crate) fn work(&mut self) {
|
||||
cef::do_message_loop_work();
|
||||
}
|
||||
|
||||
pub(crate) fn handle_window_event(&mut self, event: &WindowEvent) {
|
||||
handle_window_event(self, event);
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(self) {
|
||||
cef::shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum SetupError {
|
||||
#[error("this is the sub process should exit immediately")]
|
||||
Subprocess,
|
||||
#[error("subprocess returned non zero exit code")]
|
||||
SubprocessFailed(String),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum InitError {
|
||||
#[error("initialization failed")]
|
||||
InitializationFailed,
|
||||
}
|
||||
384
desktop/src/cef/input/keymap.rs
Normal file
384
desktop/src/cef/input/keymap.rs
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
macro_rules! map_enum {
|
||||
($target:expr, $enum:ident, $( ($code:expr, $variant:ident), )+ ) => {
|
||||
match $target {
|
||||
$(
|
||||
$enum::$variant => $code,
|
||||
)+
|
||||
_ => 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! map {
|
||||
($target:expr, $( ($code:expr, $variant:literal), )+ ) => {
|
||||
match $target {
|
||||
$(
|
||||
$variant => $code,
|
||||
)+
|
||||
_ => 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) trait ToVKBits {
|
||||
fn to_vk_bits(&self) -> i32;
|
||||
}
|
||||
|
||||
impl ToVKBits for winit::keyboard::NamedKey {
|
||||
fn to_vk_bits(&self) -> i32 {
|
||||
use winit::keyboard::NamedKey;
|
||||
map_enum!(
|
||||
self,
|
||||
NamedKey,
|
||||
(0x12, Alt),
|
||||
(0xA5, AltGraph),
|
||||
(0x14, CapsLock),
|
||||
(0x11, Control),
|
||||
(0x90, NumLock),
|
||||
(0x91, ScrollLock),
|
||||
(0x10, Shift),
|
||||
(0x5B, Meta),
|
||||
(0x5C, Super),
|
||||
(0x0D, Enter),
|
||||
(0x09, Tab),
|
||||
(0x20, Space),
|
||||
(0x28, ArrowDown),
|
||||
(0x25, ArrowLeft),
|
||||
(0x27, ArrowRight),
|
||||
(0x26, ArrowUp),
|
||||
(0x23, End),
|
||||
(0x24, Home),
|
||||
(0x22, PageDown),
|
||||
(0x21, PageUp),
|
||||
(0x08, Backspace),
|
||||
(0x0C, Clear),
|
||||
(0xF7, CrSel),
|
||||
(0x2E, Delete),
|
||||
(0xF9, EraseEof),
|
||||
(0xF8, ExSel),
|
||||
(0x2D, Insert),
|
||||
(0x1E, Accept),
|
||||
(0xF6, Attn),
|
||||
(0x03, Cancel),
|
||||
(0x5D, ContextMenu),
|
||||
(0x1B, Escape),
|
||||
(0x2B, Execute),
|
||||
(0x2F, Help),
|
||||
(0x13, Pause),
|
||||
(0xFA, Play),
|
||||
(0x5D, Props),
|
||||
(0x29, Select),
|
||||
(0xFB, ZoomIn),
|
||||
(0xFB, ZoomOut),
|
||||
(0x2C, PrintScreen),
|
||||
(0x5F, Standby),
|
||||
(0x1C, Convert),
|
||||
(0x18, FinalMode),
|
||||
(0x1F, ModeChange),
|
||||
(0x1D, NonConvert),
|
||||
(0xE5, Process),
|
||||
(0x15, HangulMode),
|
||||
(0x19, HanjaMode),
|
||||
(0x17, JunjaMode),
|
||||
(0x15, KanaMode),
|
||||
(0x19, KanjiMode),
|
||||
(0xB0, MediaFastForward),
|
||||
(0xB3, MediaPause),
|
||||
(0xB3, MediaPlay),
|
||||
(0xB3, MediaPlayPause),
|
||||
(0xB1, MediaRewind),
|
||||
(0xB2, MediaStop),
|
||||
(0xB0, MediaTrackNext),
|
||||
(0xB1, MediaTrackPrevious),
|
||||
(0x2A, Print),
|
||||
(0xAE, AudioVolumeDown),
|
||||
(0xAF, AudioVolumeUp),
|
||||
(0xAD, AudioVolumeMute),
|
||||
(0xB6, LaunchApplication1),
|
||||
(0xB7, LaunchApplication2),
|
||||
(0xB4, LaunchMail),
|
||||
(0xB5, LaunchMediaPlayer),
|
||||
(0xB5, LaunchMusicPlayer),
|
||||
(0xA6, BrowserBack),
|
||||
(0xAB, BrowserFavorites),
|
||||
(0xA7, BrowserForward),
|
||||
(0xAC, BrowserHome),
|
||||
(0xA8, BrowserRefresh),
|
||||
(0xAA, BrowserSearch),
|
||||
(0xA9, BrowserStop),
|
||||
(0xFB, ZoomToggle),
|
||||
(0x70, F1),
|
||||
(0x71, F2),
|
||||
(0x72, F3),
|
||||
(0x73, F4),
|
||||
(0x74, F5),
|
||||
(0x75, F6),
|
||||
(0x76, F7),
|
||||
(0x77, F8),
|
||||
(0x78, F9),
|
||||
(0x79, F10),
|
||||
(0x7A, F11),
|
||||
(0x7B, F12),
|
||||
(0x7C, F13),
|
||||
(0x7D, F14),
|
||||
(0x7E, F15),
|
||||
(0x7F, F16),
|
||||
(0x80, F17),
|
||||
(0x81, F18),
|
||||
(0x82, F19),
|
||||
(0x83, F20),
|
||||
(0x84, F21),
|
||||
(0x85, F22),
|
||||
(0x86, F23),
|
||||
(0x87, F24),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToVKBits for char {
|
||||
fn to_vk_bits(&self) -> i32 {
|
||||
map!(
|
||||
self,
|
||||
(0x0041, 'a'),
|
||||
(0x0042, 'b'),
|
||||
(0x0043, 'c'),
|
||||
(0x0044, 'd'),
|
||||
(0x0045, 'e'),
|
||||
(0x0046, 'f'),
|
||||
(0x0047, 'g'),
|
||||
(0x0048, 'h'),
|
||||
(0x0049, 'i'),
|
||||
(0x004a, 'j'),
|
||||
(0x004b, 'k'),
|
||||
(0x004c, 'l'),
|
||||
(0x004d, 'm'),
|
||||
(0x004e, 'n'),
|
||||
(0x004f, 'o'),
|
||||
(0x0050, 'p'),
|
||||
(0x0051, 'q'),
|
||||
(0x0052, 'r'),
|
||||
(0x0053, 's'),
|
||||
(0x0054, 't'),
|
||||
(0x0055, 'u'),
|
||||
(0x0056, 'v'),
|
||||
(0x0057, 'w'),
|
||||
(0x0058, 'x'),
|
||||
(0x0059, 'y'),
|
||||
(0x005a, 'z'),
|
||||
(0x0041, 'A'),
|
||||
(0x0042, 'B'),
|
||||
(0x0043, 'C'),
|
||||
(0x0044, 'D'),
|
||||
(0x0045, 'E'),
|
||||
(0x0046, 'F'),
|
||||
(0x0047, 'G'),
|
||||
(0x0048, 'H'),
|
||||
(0x0049, 'I'),
|
||||
(0x004a, 'J'),
|
||||
(0x004b, 'K'),
|
||||
(0x004c, 'L'),
|
||||
(0x004d, 'M'),
|
||||
(0x004e, 'N'),
|
||||
(0x004f, 'O'),
|
||||
(0x0050, 'P'),
|
||||
(0x0051, 'Q'),
|
||||
(0x0052, 'R'),
|
||||
(0x0053, 'S'),
|
||||
(0x0054, 'T'),
|
||||
(0x0055, 'U'),
|
||||
(0x0056, 'V'),
|
||||
(0x0057, 'W'),
|
||||
(0x0058, 'X'),
|
||||
(0x0059, 'Y'),
|
||||
(0x005a, 'Z'),
|
||||
(0x0031, '1'),
|
||||
(0x0032, '2'),
|
||||
(0x0032, '3'),
|
||||
(0x0033, '4'),
|
||||
(0x0034, '5'),
|
||||
(0x0035, '6'),
|
||||
(0x0036, '7'),
|
||||
(0x0037, '8'),
|
||||
(0x0039, '9'),
|
||||
(0x0030, '0'),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ToDomBits {
|
||||
fn to_dom_bits(&self) -> i32;
|
||||
}
|
||||
|
||||
impl ToDomBits for winit::keyboard::NamedKey {
|
||||
fn to_dom_bits(&self) -> i32 {
|
||||
use winit::keyboard::NamedKey;
|
||||
map_enum!(
|
||||
self,
|
||||
NamedKey,
|
||||
(0x0000, Hyper),
|
||||
(0x0085, Super),
|
||||
(0x0025, Control),
|
||||
(0x0032, Shift),
|
||||
(0x0040, Alt),
|
||||
(0x0000, Fn),
|
||||
(0x0000, FnLock),
|
||||
(0x0024, Enter),
|
||||
(0x0009, Escape),
|
||||
(0x0016, Backspace),
|
||||
(0x0017, Tab),
|
||||
(0x0041, Space),
|
||||
(0x0042, CapsLock),
|
||||
(0x0043, F1),
|
||||
(0x0044, F2),
|
||||
(0x0045, F3),
|
||||
(0x0046, F4),
|
||||
(0x0047, F5),
|
||||
(0x0048, F6),
|
||||
(0x0049, F7),
|
||||
(0x004a, F8),
|
||||
(0x004b, F9),
|
||||
(0x004c, F10),
|
||||
(0x005f, F11),
|
||||
(0x0060, F12),
|
||||
(0x006b, PrintScreen),
|
||||
(0x004e, ScrollLock),
|
||||
(0x007f, Pause),
|
||||
(0x0076, Insert),
|
||||
(0x006e, Home),
|
||||
(0x0070, PageUp),
|
||||
(0x0077, Delete),
|
||||
(0x0073, End),
|
||||
(0x0075, PageDown),
|
||||
(0x0072, ArrowRight),
|
||||
(0x0071, ArrowLeft),
|
||||
(0x0074, ArrowDown),
|
||||
(0x006f, ArrowUp),
|
||||
(0x004d, NumLock),
|
||||
(0x0087, ContextMenu),
|
||||
(0x007c, Power),
|
||||
(0x00bf, F13),
|
||||
(0x00c0, F14),
|
||||
(0x00c1, F15),
|
||||
(0x00c2, F16),
|
||||
(0x00c3, F17),
|
||||
(0x00c4, F18),
|
||||
(0x00c5, F19),
|
||||
(0x00c6, F20),
|
||||
(0x00c7, F21),
|
||||
(0x00c8, F22),
|
||||
(0x00c9, F23),
|
||||
(0x00ca, F24),
|
||||
(0x008e, Open),
|
||||
(0x0092, Help),
|
||||
(0x008c, Select),
|
||||
(0x0089, Again),
|
||||
(0x008b, Undo),
|
||||
(0x0091, Cut),
|
||||
(0x008d, Copy),
|
||||
(0x008f, Paste),
|
||||
(0x0090, Find),
|
||||
(0x0079, AudioVolumeMute),
|
||||
(0x007b, AudioVolumeUp),
|
||||
(0x007a, AudioVolumeDown),
|
||||
(0x0065, KanaMode),
|
||||
(0x0064, Convert),
|
||||
(0x0066, NonConvert),
|
||||
(0x0000, Props),
|
||||
(0x00e9, BrightnessUp),
|
||||
(0x00e8, BrightnessDown),
|
||||
(0x00d7, MediaPlay),
|
||||
(0x00d1, MediaPause),
|
||||
(0x00af, MediaRecord),
|
||||
(0x00d8, MediaFastForward),
|
||||
(0x00b0, MediaRewind),
|
||||
(0x00ab, MediaTrackNext),
|
||||
(0x00ad, MediaTrackPrevious),
|
||||
(0x00ae, MediaStop),
|
||||
(0x00a9, Eject),
|
||||
(0x00ac, MediaPlayPause),
|
||||
(0x00a3, LaunchMail),
|
||||
(0x024d, LaunchScreenSaver),
|
||||
(0x00e1, BrowserSearch),
|
||||
(0x00b4, BrowserHome),
|
||||
(0x00a6, BrowserBack),
|
||||
(0x00a7, BrowserForward),
|
||||
(0x0088, BrowserStop),
|
||||
(0x00b5, BrowserRefresh),
|
||||
(0x00a4, BrowserFavorites),
|
||||
(0x017c, ZoomToggle),
|
||||
(0x00f0, MailReply),
|
||||
(0x00f1, MailForward),
|
||||
(0x00ef, MailSend),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDomBits for char {
|
||||
fn to_dom_bits(&self) -> i32 {
|
||||
map!(
|
||||
self,
|
||||
(0x0026, 'a'),
|
||||
(0x0038, 'b'),
|
||||
(0x0036, 'c'),
|
||||
(0x0028, 'd'),
|
||||
(0x001a, 'e'),
|
||||
(0x0029, 'f'),
|
||||
(0x002a, 'g'),
|
||||
(0x002b, 'h'),
|
||||
(0x001f, 'i'),
|
||||
(0x002c, 'j'),
|
||||
(0x002d, 'k'),
|
||||
(0x002e, 'l'),
|
||||
(0x003a, 'm'),
|
||||
(0x0039, 'n'),
|
||||
(0x0020, 'o'),
|
||||
(0x0021, 'p'),
|
||||
(0x0018, 'q'),
|
||||
(0x001b, 'r'),
|
||||
(0x0027, 's'),
|
||||
(0x001c, 't'),
|
||||
(0x001e, 'u'),
|
||||
(0x0037, 'v'),
|
||||
(0x0019, 'w'),
|
||||
(0x0035, 'x'),
|
||||
(0x001d, 'y'),
|
||||
(0x0034, 'z'),
|
||||
(0x0026, 'A'),
|
||||
(0x0038, 'B'),
|
||||
(0x0036, 'C'),
|
||||
(0x0028, 'D'),
|
||||
(0x001a, 'E'),
|
||||
(0x0029, 'F'),
|
||||
(0x002a, 'G'),
|
||||
(0x002b, 'H'),
|
||||
(0x001f, 'I'),
|
||||
(0x002c, 'J'),
|
||||
(0x002d, 'K'),
|
||||
(0x002e, 'L'),
|
||||
(0x003a, 'M'),
|
||||
(0x0039, 'N'),
|
||||
(0x0020, 'O'),
|
||||
(0x0021, 'P'),
|
||||
(0x0018, 'Q'),
|
||||
(0x001b, 'R'),
|
||||
(0x0027, 'S'),
|
||||
(0x001c, 'T'),
|
||||
(0x001e, 'U'),
|
||||
(0x0037, 'V'),
|
||||
(0x0019, 'W'),
|
||||
(0x0035, 'X'),
|
||||
(0x001d, 'Y'),
|
||||
(0x0034, 'Z'),
|
||||
(0x000a, '1'),
|
||||
(0x000b, '2'),
|
||||
(0x000c, '3'),
|
||||
(0x000d, '4'),
|
||||
(0x000e, '5'),
|
||||
(0x000f, '6'),
|
||||
(0x0010, '7'),
|
||||
(0x0011, '8'),
|
||||
(0x0012, '9'),
|
||||
(0x0013, '0'),
|
||||
)
|
||||
}
|
||||
}
|
||||
273
desktop/src/cef/input/mod.rs
Normal file
273
desktop/src/cef/input/mod.rs
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t};
|
||||
use cef::{ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent};
|
||||
use winit::dpi::PhysicalPosition;
|
||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent};
|
||||
|
||||
use super::context::{Context, Initialized};
|
||||
|
||||
mod keymap;
|
||||
use keymap::{ToDomBits, ToVKBits};
|
||||
|
||||
pub(crate) fn handle_window_event(context: &mut Context<Initialized>, event: &WindowEvent) {
|
||||
match event {
|
||||
WindowEvent::Resized(_) => {
|
||||
if let Some(browser) = &context.browser {
|
||||
browser.host().unwrap().was_resized();
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
if let Some(browser) = &context.browser {
|
||||
if let Some(host) = browser.host() {
|
||||
host.set_focus(1);
|
||||
}
|
||||
|
||||
context.input_state.update_mouse_position(position);
|
||||
let mouse_event: MouseEvent = (&context.input_state).into();
|
||||
browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0);
|
||||
}
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
if let Some(browser) = &context.browser {
|
||||
if let Some(host) = browser.host() {
|
||||
host.set_focus(1);
|
||||
|
||||
let mouse_up = match state {
|
||||
ElementState::Pressed => 0,
|
||||
ElementState::Released => 1,
|
||||
};
|
||||
|
||||
let cef_button = match button {
|
||||
MouseButton::Left => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT)),
|
||||
MouseButton::Right => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT)),
|
||||
MouseButton::Middle => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE)),
|
||||
MouseButton::Forward => None, //TODO: Handle Forward button
|
||||
MouseButton::Back => None, //TODO: Handle Back button
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut mouse_state = context.input_state.mouse_state.clone();
|
||||
match button {
|
||||
MouseButton::Left => {
|
||||
mouse_state.left = match state {
|
||||
ElementState::Pressed => true,
|
||||
ElementState::Released => false,
|
||||
}
|
||||
}
|
||||
MouseButton::Right => {
|
||||
mouse_state.right = match state {
|
||||
ElementState::Pressed => true,
|
||||
ElementState::Released => false,
|
||||
}
|
||||
}
|
||||
MouseButton::Middle => {
|
||||
mouse_state.middle = match state {
|
||||
ElementState::Pressed => true,
|
||||
ElementState::Released => false,
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
context.input_state.update_mouse_state(mouse_state);
|
||||
|
||||
let mouse_event: MouseEvent = (&context.input_state).into();
|
||||
|
||||
if let Some(button) = cef_button {
|
||||
host.send_mouse_click_event(
|
||||
Some(&mouse_event),
|
||||
button,
|
||||
mouse_up,
|
||||
1, // click count
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => {
|
||||
if let Some(browser) = &context.browser {
|
||||
if let Some(host) = browser.host() {
|
||||
let mouse_event = (&context.input_state).into();
|
||||
let line_width = 40; //feels about right, TODO: replace with correct value
|
||||
let line_height = 30; //feels about right, TODO: replace with correct value
|
||||
let (delta_x, delta_y) = match delta {
|
||||
MouseScrollDelta::LineDelta(x, y) => (*x * line_width as f32, *y * line_height as f32),
|
||||
MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32),
|
||||
};
|
||||
host.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::ModifiersChanged(modifiers) => {
|
||||
context.input_state.update_modifiers(&modifiers.state());
|
||||
}
|
||||
WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => {
|
||||
if let Some(browser) = &context.browser {
|
||||
if let Some(host) = browser.host() {
|
||||
host.set_focus(1);
|
||||
|
||||
let (named_key, character) = match &event.logical_key {
|
||||
winit::keyboard::Key::Named(named_key) => (Some(named_key), None),
|
||||
winit::keyboard::Key::Character(str) => {
|
||||
let char = str.chars().next().unwrap_or('\0');
|
||||
(None, Some(char))
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let mut key_event = KeyEvent::default();
|
||||
key_event.size = std::mem::size_of::<KeyEvent>();
|
||||
key_event.focus_on_editable_field = 1;
|
||||
key_event.modifiers = context.input_state.cef_modifiers(&event.location, event.repeat).raw();
|
||||
key_event.is_system_key = 0;
|
||||
|
||||
if let Some(named_key) = named_key {
|
||||
key_event.native_key_code = named_key.to_dom_bits();
|
||||
key_event.windows_key_code = named_key.to_vk_bits();
|
||||
}
|
||||
if let Some(char) = character {
|
||||
key_event.native_key_code = char.to_dom_bits();
|
||||
key_event.windows_key_code = char.to_vk_bits();
|
||||
}
|
||||
|
||||
match event.state {
|
||||
ElementState::Pressed => {
|
||||
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN);
|
||||
host.send_key_event(Some(&key_event));
|
||||
|
||||
if let Some(char) = character {
|
||||
let mut buf = [0; 2];
|
||||
char.encode_utf16(&mut buf);
|
||||
key_event.character = buf[0] as u16;
|
||||
let mut buf = [0; 2];
|
||||
char.to_lowercase().next().unwrap().encode_utf16(&mut buf);
|
||||
key_event.unmodified_character = buf[0] as u16;
|
||||
|
||||
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR);
|
||||
host.send_key_event(Some(&key_event));
|
||||
}
|
||||
}
|
||||
ElementState::Released => {
|
||||
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP);
|
||||
host.send_key_event(Some(&key_event));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct MouseState {
|
||||
left: bool,
|
||||
right: bool,
|
||||
middle: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub(crate) struct MousePosition {
|
||||
x: usize,
|
||||
y: usize,
|
||||
}
|
||||
|
||||
impl From<&PhysicalPosition<f64>> for MousePosition {
|
||||
fn from(position: &PhysicalPosition<f64>) -> Self {
|
||||
Self {
|
||||
x: position.x as usize,
|
||||
y: position.y as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct InputState {
|
||||
modifiers: winit::keyboard::ModifiersState,
|
||||
mouse_position: MousePosition,
|
||||
mouse_state: MouseState,
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
fn update_modifiers(&mut self, modifiers: &winit::keyboard::ModifiersState) {
|
||||
self.modifiers = modifiers.clone();
|
||||
}
|
||||
|
||||
fn update_mouse_position(&mut self, position: &PhysicalPosition<f64>) {
|
||||
self.mouse_position = position.into();
|
||||
}
|
||||
|
||||
fn update_mouse_state(&mut self, state: MouseState) {
|
||||
self.mouse_state = state;
|
||||
}
|
||||
|
||||
fn cef_modifiers(&self, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> CefModifiers {
|
||||
CefModifiers::new(&self, location, is_repeat)
|
||||
}
|
||||
|
||||
fn cef_modifiers_mouse_event(&self) -> CefModifiers {
|
||||
self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<CefModifiers> for InputState {
|
||||
fn into(self) -> CefModifiers {
|
||||
CefModifiers::new(&self, &winit::keyboard::KeyLocation::Standard, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<MouseEvent> for &InputState {
|
||||
fn into(self) -> MouseEvent {
|
||||
MouseEvent {
|
||||
x: self.mouse_position.x as i32,
|
||||
y: self.mouse_position.y as i32,
|
||||
modifiers: self.cef_modifiers_mouse_event().raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CefModifiers(u32);
|
||||
|
||||
impl CefModifiers {
|
||||
fn new(input_state: &InputState, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> Self {
|
||||
let mut inner = 0;
|
||||
|
||||
if input_state.modifiers.shift_key() {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32;
|
||||
}
|
||||
if input_state.modifiers.control_key() {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32;
|
||||
}
|
||||
if input_state.modifiers.alt_key() {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32;
|
||||
}
|
||||
if input_state.modifiers.super_key() {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32;
|
||||
}
|
||||
|
||||
if input_state.mouse_state.left {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32;
|
||||
}
|
||||
if input_state.mouse_state.right {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32;
|
||||
}
|
||||
if input_state.mouse_state.middle {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32;
|
||||
}
|
||||
|
||||
if is_repeat {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT as u32;
|
||||
}
|
||||
|
||||
inner |= match location {
|
||||
winit::keyboard::KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT as u32,
|
||||
winit::keyboard::KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT as u32,
|
||||
winit::keyboard::KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD as u32,
|
||||
winit::keyboard::KeyLocation::Standard => 0,
|
||||
};
|
||||
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
fn raw(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
61
desktop/src/cef/internal/app.rs
Normal file
61
desktop/src/cef/internal/app.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||
use cef::{App, BrowserProcessHandler, ImplApp, SchemeRegistrar, WrapApp};
|
||||
|
||||
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||
use crate::cef::EventHandler;
|
||||
|
||||
use super::browser_process_handler::BrowserProcessHandlerImpl;
|
||||
|
||||
pub(crate) struct AppImpl<H: EventHandler> {
|
||||
object: *mut RcImpl<_cef_app_t, Self>,
|
||||
event_handler: H,
|
||||
}
|
||||
impl<H: EventHandler> AppImpl<H> {
|
||||
pub(crate) fn new(event_handler: H) -> App {
|
||||
App::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
event_handler,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> ImplApp for AppImpl<H> {
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(BrowserProcessHandlerImpl::new(self.event_handler.clone()))
|
||||
}
|
||||
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
GraphiteSchemeHandlerFactory::register_schemes(registrar);
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_app_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> Clone for AppImpl<H> {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
}
|
||||
Self {
|
||||
object: self.object,
|
||||
event_handler: self.event_handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> Rc for AppImpl<H> {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> WrapApp for AppImpl<H> {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
55
desktop/src/cef/internal/browser_process_handler.rs
Normal file
55
desktop/src/cef/internal/browser_process_handler.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t};
|
||||
use cef::{BrowserProcessHandler, CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler};
|
||||
|
||||
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||
use crate::cef::EventHandler;
|
||||
|
||||
pub(crate) struct BrowserProcessHandlerImpl<H: EventHandler> {
|
||||
object: *mut RcImpl<cef_browser_process_handler_t, Self>,
|
||||
event_handler: H,
|
||||
}
|
||||
impl<H: EventHandler> BrowserProcessHandlerImpl<H> {
|
||||
pub(crate) fn new(event_handler: H) -> BrowserProcessHandler {
|
||||
BrowserProcessHandler::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
event_handler,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
|
||||
fn on_context_initialized(&self) {
|
||||
cef::register_scheme_handler_factory(Some(&CefString::from("graphite")), None, Some(&mut SchemeHandlerFactory::new(GraphiteSchemeHandlerFactory::new())));
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_browser_process_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> Clone for BrowserProcessHandlerImpl<H> {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
}
|
||||
Self {
|
||||
object: self.object,
|
||||
event_handler: self.event_handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> Rc for BrowserProcessHandlerImpl<H> {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> WrapBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
52
desktop/src/cef/internal/client.rs
Normal file
52
desktop/src/cef/internal/client.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_client_t, cef_base_ref_counted_t};
|
||||
use cef::{Client, ImplClient, RenderHandler, WrapClient};
|
||||
|
||||
pub(crate) struct ClientImpl {
|
||||
object: *mut RcImpl<_cef_client_t, Self>,
|
||||
render_handler: RenderHandler,
|
||||
}
|
||||
impl ClientImpl {
|
||||
pub(crate) fn new(render_handler: RenderHandler) -> Client {
|
||||
Client::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
render_handler,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplClient for ClientImpl {
|
||||
fn render_handler(&self) -> Option<RenderHandler> {
|
||||
Some(self.render_handler.clone())
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_client_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ClientImpl {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
}
|
||||
Self {
|
||||
object: self.object,
|
||||
render_handler: self.render_handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Rc for ClientImpl {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl WrapClient for ClientImpl {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
10
desktop/src/cef/internal/mod.rs
Normal file
10
desktop/src/cef/internal/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
mod app;
|
||||
mod browser_process_handler;
|
||||
mod client;
|
||||
mod non_browser_app;
|
||||
mod render_handler;
|
||||
|
||||
pub(crate) use app::AppImpl;
|
||||
pub(crate) use client::ClientImpl;
|
||||
pub(crate) use non_browser_app::NonBrowserAppImpl;
|
||||
pub(crate) use render_handler::RenderHandlerImpl;
|
||||
47
desktop/src/cef/internal/non_browser_app.rs
Normal file
47
desktop/src/cef/internal/non_browser_app.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||
use cef::{App, ImplApp, SchemeRegistrar, WrapApp};
|
||||
|
||||
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||
|
||||
pub(crate) struct NonBrowserAppImpl {
|
||||
object: *mut RcImpl<_cef_app_t, Self>,
|
||||
}
|
||||
impl NonBrowserAppImpl {
|
||||
pub(crate) fn new() -> App {
|
||||
App::new(Self { object: std::ptr::null_mut() })
|
||||
}
|
||||
}
|
||||
|
||||
impl ImplApp for NonBrowserAppImpl {
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
GraphiteSchemeHandlerFactory::register_schemes(registrar);
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_app_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NonBrowserAppImpl {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
}
|
||||
Self { object: self.object }
|
||||
}
|
||||
}
|
||||
impl Rc for NonBrowserAppImpl {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl WrapApp for NonBrowserAppImpl {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
81
desktop/src/cef/internal/render_handler.rs
Normal file
81
desktop/src/cef/internal/render_handler.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t};
|
||||
use cef::{Browser, ImplBrowser, ImplBrowserHost, ImplRenderHandler, PaintElementType, Rect, RenderHandler, WrapRenderHandler};
|
||||
|
||||
use crate::cef::EventHandler;
|
||||
|
||||
pub(crate) struct RenderHandlerImpl<H: EventHandler> {
|
||||
object: *mut RcImpl<_cef_render_handler_t, Self>,
|
||||
event_handler: H,
|
||||
}
|
||||
impl<H: EventHandler> RenderHandlerImpl<H> {
|
||||
pub(crate) fn new(event_handler: H) -> RenderHandler {
|
||||
RenderHandler::new(Self {
|
||||
object: std::ptr::null_mut(),
|
||||
event_handler,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
||||
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
|
||||
if let Some(rect) = rect {
|
||||
let view = self.event_handler.view();
|
||||
*rect = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: view.width as i32,
|
||||
height: view.height as i32,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn on_paint(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
_type_: PaintElementType,
|
||||
_dirty_rect_count: usize,
|
||||
_dirty_rects: Option<&Rect>,
|
||||
buffer: *const u8,
|
||||
width: ::std::os::raw::c_int,
|
||||
height: ::std::os::raw::c_int,
|
||||
) {
|
||||
let buffer_size = (width * height * 4) as usize;
|
||||
let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };
|
||||
let draw_successful = self.event_handler.draw(buffer_slice.to_vec(), width as usize, height as usize);
|
||||
if !draw_successful {
|
||||
if let Some(browser) = browser {
|
||||
browser.host().unwrap().was_resized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_render_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: EventHandler> Clone for RenderHandlerImpl<H> {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
}
|
||||
Self {
|
||||
object: self.object,
|
||||
event_handler: self.event_handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> Rc for RenderHandlerImpl<H> {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<H: EventHandler> WrapRenderHandler for RenderHandlerImpl<H> {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
26
desktop/src/cef/mod.rs
Normal file
26
desktop/src/cef/mod.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
mod input;
|
||||
|
||||
mod scheme_handler;
|
||||
|
||||
mod context;
|
||||
|
||||
mod internal;
|
||||
|
||||
pub(crate) trait EventHandler: Clone {
|
||||
fn view(&self) -> View;
|
||||
fn draw(&self, buffer: Vec<u8>, width: usize, height: usize) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct View {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) height: usize,
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub(crate) fn new(width: usize, height: usize) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
|
||||
222
desktop/src/cef/scheme_handler.rs
Normal file
222
desktop/src/cef/scheme_handler.rs
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
use std::cell::RefCell;
|
||||
use std::ffi::c_int;
|
||||
use std::slice::Iter;
|
||||
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::sys::{_cef_resource_handler_t, _cef_scheme_handler_factory_t, cef_base_ref_counted_t, cef_scheme_options_t};
|
||||
use cef::{
|
||||
Browser, Callback, CefString, Frame, ImplRequest, ImplResourceHandler, ImplResponse, ImplSchemeHandlerFactory, ImplSchemeRegistrar, Request, ResourceHandler, ResourceReadCallback, Response,
|
||||
SchemeRegistrar, WrapResourceHandler, WrapSchemeHandlerFactory,
|
||||
};
|
||||
use include_dir::{include_dir, Dir};
|
||||
|
||||
pub(crate) struct GraphiteSchemeHandlerFactory {
|
||||
object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>,
|
||||
}
|
||||
impl GraphiteSchemeHandlerFactory {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { object: std::ptr::null_mut() }
|
||||
}
|
||||
|
||||
pub(crate) fn register_schemes(registrar: Option<&mut SchemeRegistrar>) {
|
||||
if let Some(registrar) = registrar {
|
||||
let mut scheme_options = 0;
|
||||
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_STANDARD as i32;
|
||||
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_FETCH_ENABLED as i32;
|
||||
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_SECURE as i32;
|
||||
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_CORS_ENABLED as i32;
|
||||
registrar.add_custom_scheme(Some(&CefString::from("graphite")), scheme_options);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ImplSchemeHandlerFactory for GraphiteSchemeHandlerFactory {
|
||||
fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option<ResourceHandler> {
|
||||
if let Some(scheme_name) = scheme_name {
|
||||
if scheme_name.to_string() != "graphite" {
|
||||
return None;
|
||||
}
|
||||
if let Some(request) = request {
|
||||
let url = CefString::from(&request.url()).to_string();
|
||||
let path = url.strip_prefix("graphite://").unwrap();
|
||||
let domain = path.split('/').next().unwrap_or("");
|
||||
let path = path.strip_prefix(domain).unwrap_or("");
|
||||
let path = path.trim_start_matches('/');
|
||||
return match domain {
|
||||
"frontend" => {
|
||||
if path.is_empty() {
|
||||
Some(ResourceHandler::new(GraphiteFrontendResourceHandler::new("index.html")))
|
||||
} else {
|
||||
Some(ResourceHandler::new(GraphiteFrontendResourceHandler::new(path)))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
return None;
|
||||
}
|
||||
None
|
||||
}
|
||||
fn get_raw(&self) -> *mut _cef_scheme_handler_factory_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
static FRONTEND: Dir = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist");
|
||||
|
||||
struct GraphiteFrontendResourceHandler<'a> {
|
||||
object: *mut RcImpl<_cef_resource_handler_t, Self>,
|
||||
data: Option<RefCell<Iter<'a, u8>>>,
|
||||
mimetype: Option<String>,
|
||||
}
|
||||
impl<'a> GraphiteFrontendResourceHandler<'a> {
|
||||
pub fn new(path: &str) -> Self {
|
||||
let file = FRONTEND.get_file(path);
|
||||
let data = if let Some(file) = file {
|
||||
Some(RefCell::new(file.contents().iter()))
|
||||
} else {
|
||||
println!("Failed to find asset at path: {}", path);
|
||||
None
|
||||
};
|
||||
let mimetype = if let Some(file) = file {
|
||||
let ext = file.path().extension().and_then(|s| s.to_str()).unwrap_or("");
|
||||
|
||||
// We know what file types will be in the assets this should be fine
|
||||
match ext {
|
||||
"html" => Some("text/html".to_string()),
|
||||
"css" => Some("text/css".to_string()),
|
||||
"wasm" => Some("application/wasm".to_string()),
|
||||
"js" => Some("application/javascript".to_string()),
|
||||
"png" => Some("image/png".to_string()),
|
||||
"jpg" | "jpeg" => Some("image/jpeg".to_string()),
|
||||
"svg" => Some("image/svg+xml".to_string()),
|
||||
"xml" => Some("application/xml".to_string()),
|
||||
"json" => Some("application/json".to_string()),
|
||||
"ico" => Some("image/x-icon".to_string()),
|
||||
"woff" => Some("font/woff".to_string()),
|
||||
"woff2" => Some("font/woff2".to_string()),
|
||||
"ttf" => Some("font/ttf".to_string()),
|
||||
"otf" => Some("font/otf".to_string()),
|
||||
"webmanifest" => Some("application/manifest+json".to_string()),
|
||||
"graphite" => Some("application/graphite+json".to_string()),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self {
|
||||
object: std::ptr::null_mut(),
|
||||
data,
|
||||
mimetype,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> ImplResourceHandler for GraphiteFrontendResourceHandler<'a> {
|
||||
fn open(&self, _request: Option<&mut Request>, handle_request: Option<&mut c_int>, _callback: Option<&mut Callback>) -> c_int {
|
||||
if let Some(handle_request) = handle_request {
|
||||
*handle_request = 1;
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn response_headers(&self, response: Option<&mut Response>, response_length: Option<&mut i64>, _redirect_url: Option<&mut CefString>) {
|
||||
if let Some(response_length) = response_length {
|
||||
*response_length = -1; // Indicating that the length is unknown
|
||||
}
|
||||
if let Some(response) = response {
|
||||
if let Some(_) = &self.data {
|
||||
if let Some(mimetype) = &self.mimetype {
|
||||
let cef_mime = CefString::from(mimetype.as_str());
|
||||
response.set_mime_type(Some(&cef_mime));
|
||||
} else {
|
||||
response.set_mime_type(None);
|
||||
}
|
||||
response.set_status(200);
|
||||
} else {
|
||||
response.set_status(404);
|
||||
response.set_mime_type(Some(&CefString::from("text/plain")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&self, data_out: *mut u8, bytes_to_read: c_int, bytes_read: Option<&mut c_int>, _callback: Option<&mut ResourceReadCallback>) -> c_int {
|
||||
let mut read = 0;
|
||||
|
||||
let out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read as usize) };
|
||||
if let Some(data) = &self.data {
|
||||
let mut data = data.borrow_mut();
|
||||
for i in 0..bytes_to_read as usize {
|
||||
if let Some(&byte) = data.next() {
|
||||
out[i] = byte;
|
||||
read += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bytes_read) = bytes_read {
|
||||
*bytes_read = read;
|
||||
}
|
||||
|
||||
if read > 0 {
|
||||
1 // Indicating that data was read
|
||||
} else {
|
||||
0 // Indicating no data was read
|
||||
}
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_resource_handler_t {
|
||||
self.object.cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapSchemeHandlerFactory for GraphiteSchemeHandlerFactory {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
impl<'a> WrapResourceHandler for GraphiteFrontendResourceHandler<'a> {
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_handler_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for GraphiteSchemeHandlerFactory {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
}
|
||||
Self { object: self.object }
|
||||
}
|
||||
}
|
||||
impl<'a> Clone for GraphiteFrontendResourceHandler<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let rc_impl = &mut *self.object;
|
||||
rc_impl.interface.add_ref();
|
||||
}
|
||||
Self {
|
||||
object: self.object,
|
||||
data: self.data.clone(),
|
||||
mimetype: self.mimetype.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rc for GraphiteSchemeHandlerFactory {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> Rc for GraphiteFrontendResourceHandler<'a> {
|
||||
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||
unsafe {
|
||||
let base = &*self.object;
|
||||
std::mem::transmute(&base.cef_object)
|
||||
}
|
||||
}
|
||||
}
|
||||
314
desktop/src/main.rs
Normal file
314
desktop/src/main.rs
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
use std::fmt::Debug;
|
||||
use std::process::exit;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::*;
|
||||
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy};
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
mod cef;
|
||||
use cef::Setup;
|
||||
|
||||
mod render;
|
||||
use render::{FrameBuffer, GraphicsState};
|
||||
|
||||
pub(crate) enum CustomEvent {
|
||||
UiUpdate,
|
||||
Resized,
|
||||
DoBrowserWork,
|
||||
}
|
||||
|
||||
pub(crate) struct WindowState {
|
||||
width: Option<usize>,
|
||||
height: Option<usize>,
|
||||
ui_fb: Option<FrameBuffer>,
|
||||
preview_fb: Option<FrameBuffer>,
|
||||
graphics_state: Option<GraphicsState>,
|
||||
event_loop_proxy: Option<EventLoopProxy<CustomEvent>>,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
width: None,
|
||||
height: None,
|
||||
ui_fb: None,
|
||||
preview_fb: None,
|
||||
graphics_state: None,
|
||||
event_loop_proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(self) -> WindowStateHandle {
|
||||
WindowStateHandle { inner: Arc::new(Mutex::new(self)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for WindowState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("WindowState")
|
||||
.field("width", &self.width.is_some())
|
||||
.field("height", &self.height.is_some())
|
||||
.field("ui_fb", &self.ui_fb.is_some())
|
||||
.field("preview_fb", &self.preview_fb.is_some())
|
||||
.field("graphics_state", &self.graphics_state.is_some())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WindowStateHandle {
|
||||
inner: Arc<Mutex<WindowState>>,
|
||||
}
|
||||
|
||||
impl WindowStateHandle {
|
||||
fn with<'a, P>(&self, p: P) -> Result<(), PoisonError<MutexGuard<'a, WindowState>>>
|
||||
where
|
||||
P: FnOnce(&mut WindowState),
|
||||
{
|
||||
match self.inner.lock() {
|
||||
Ok(mut guard) => Ok(p(&mut guard)),
|
||||
Err(_) => todo!("not error handling yet"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for WindowStateHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self { inner: self.inner.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CefEventHandler {
|
||||
window_state: WindowStateHandle,
|
||||
}
|
||||
|
||||
impl CefEventHandler {
|
||||
fn new(window_state: WindowStateHandle) -> Self {
|
||||
Self { window_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl cef::EventHandler for CefEventHandler {
|
||||
fn view(&self) -> cef::View {
|
||||
let mut w = 1;
|
||||
let mut h = 1;
|
||||
|
||||
self.window_state
|
||||
.with(|s| match s {
|
||||
WindowState {
|
||||
width: Some(width),
|
||||
height: Some(height),
|
||||
..
|
||||
} => {
|
||||
w = *width;
|
||||
h = *height;
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cef::View::new(w, h)
|
||||
}
|
||||
|
||||
fn draw(&self, buffer: Vec<u8>, width: usize, height: usize) -> bool {
|
||||
let fb = FrameBuffer::new(buffer, width, height)
|
||||
.map_err(|e| {
|
||||
panic!("Failed to create FrameBuffer: {}", e);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut correct_size = true;
|
||||
self.window_state
|
||||
.with(|s| {
|
||||
if let Some(event_loop_proxy) = &s.event_loop_proxy {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::UiUpdate);
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::DoBrowserWork);
|
||||
}
|
||||
if width != s.width.unwrap_or(1) || height != s.height.unwrap_or(1) {
|
||||
correct_size = false;
|
||||
} else {
|
||||
s.ui_fb = Some(fb);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
correct_size
|
||||
}
|
||||
}
|
||||
|
||||
struct WinitApp {
|
||||
window_state: WindowStateHandle,
|
||||
cef_context: cef::Context<cef::Initialized>,
|
||||
window: Option<Arc<Window>>,
|
||||
}
|
||||
|
||||
impl WinitApp {
|
||||
fn new(window_state: WindowStateHandle, cef_context: cef::Context<cef::Initialized>) -> Self {
|
||||
Self {
|
||||
window_state,
|
||||
cef_context,
|
||||
window: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
self.window_state
|
||||
.with(|s| match s {
|
||||
WindowState { width: Some(w), height: Some(h), .. } => {
|
||||
let window = Arc::new(
|
||||
event_loop
|
||||
.create_window(
|
||||
Window::default_attributes()
|
||||
.with_title("CEF Offscreen Rendering")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(*w as u32, *h as u32)),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let graphics_state = pollster::block_on(GraphicsState::new(window.clone()));
|
||||
|
||||
self.window = Some(window.clone());
|
||||
s.graphics_state = Some(graphics_state);
|
||||
|
||||
let _ = thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
window.request_redraw();
|
||||
});
|
||||
|
||||
println!("Winit window created and ready");
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
|
||||
match event {
|
||||
CustomEvent::DoBrowserWork => {
|
||||
self.cef_context.work();
|
||||
}
|
||||
CustomEvent::UiUpdate | CustomEvent::Resized => {
|
||||
if let Some(window) = &self.window {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
|
||||
self.cef_context.handle_window_event(&event);
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("The close button was pressed; stopping");
|
||||
event_loop.exit();
|
||||
}
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
self.window_state
|
||||
.with(|s| {
|
||||
s.width = Some(physical_size.width as usize);
|
||||
s.height = Some(physical_size.height as usize);
|
||||
if let Some(elp) = &s.event_loop_proxy {
|
||||
let _ = elp.send_event(CustomEvent::Resized);
|
||||
}
|
||||
if let Some(event_loop_proxy) = &s.event_loop_proxy {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::DoBrowserWork);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
WindowEvent::RedrawRequested => {
|
||||
self.cef_context.work();
|
||||
|
||||
self.window_state
|
||||
.with(|s| match s {
|
||||
WindowState {
|
||||
width: Some(width),
|
||||
height: Some(height),
|
||||
graphics_state: Some(graphics_state),
|
||||
ui_fb,
|
||||
..
|
||||
} => {
|
||||
if let Some(fb) = &*ui_fb {
|
||||
graphics_state.update_texture(fb);
|
||||
if fb.width() != *width && fb.height() != *height {
|
||||
graphics_state.resize(*width, *height);
|
||||
}
|
||||
} else {
|
||||
graphics_state.resize(*width, *height);
|
||||
}
|
||||
|
||||
match graphics_state.render() {
|
||||
Ok(_) => {}
|
||||
Err(wgpu::SurfaceError::Lost) => {
|
||||
graphics_state.resize(*width, *height);
|
||||
}
|
||||
Err(wgpu::SurfaceError::OutOfMemory) => {
|
||||
event_loop.exit();
|
||||
}
|
||||
Err(e) => eprintln!("{:?}", e),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.window_state
|
||||
.with(|s| {
|
||||
if let Some(event_loop_proxy) = &s.event_loop_proxy {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::DoBrowserWork);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cef_context = match cef::Context::<Setup>::new() {
|
||||
Ok(c) => c,
|
||||
Err(cef::SetupError::Subprocess) => exit(0),
|
||||
Err(cef::SetupError::SubprocessFailed(t)) => {
|
||||
println!("Subprocess of type {t} failed");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let window_state = WindowState::new().handle();
|
||||
|
||||
window_state
|
||||
.with(|s| {
|
||||
s.width = Some(1200);
|
||||
s.height = Some(800);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
|
||||
event_loop.set_control_flow(ControlFlow::Wait);
|
||||
|
||||
window_state.with(|s| s.event_loop_proxy = Some(event_loop.create_proxy())).unwrap();
|
||||
|
||||
let cef_context = match cef_context.init(CefEventHandler::new(window_state.clone())) {
|
||||
Ok(c) => c,
|
||||
Err(cef::InitError::InitializationFailed) => {
|
||||
println!("Cef initialization failed");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
println!("Cef initialized successfully");
|
||||
|
||||
let mut winit_app = WinitApp::new(window_state, cef_context);
|
||||
|
||||
event_loop.run_app(&mut winit_app).unwrap();
|
||||
|
||||
winit_app.cef_context.shutdown();
|
||||
}
|
||||
36
desktop/src/render/fullscreen_texture.wgsl
Normal file
36
desktop/src/render/fullscreen_texture.wgsl
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let pos = array(
|
||||
// 1st triangle
|
||||
vec2f( -1.0, -1.0), // center
|
||||
vec2f( 1.0, -1.0), // right, center
|
||||
vec2f( -1.0, 1.0), // center, top
|
||||
|
||||
// 2nd triangle
|
||||
vec2f( -1.0, 1.0), // center, top
|
||||
vec2f( 1.0, -1.0), // right, center
|
||||
vec2f( 1.0, 1.0), // right, top
|
||||
);
|
||||
let xy = pos[vertex_index];
|
||||
out.clip_position = vec4f(xy , 0.0, 1.0);
|
||||
let coords = (xy / 2. + 0.5);
|
||||
out.tex_coords = vec2f(coords.x, 1. - coords.y);
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
}
|
||||
328
desktop/src/render/mod.rs
Normal file
328
desktop/src/render/mod.rs
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use thiserror::Error;
|
||||
use winit::window::Window;
|
||||
|
||||
pub(crate) struct FrameBuffer {
|
||||
buffer: Vec<u8>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum FrameBufferError {
|
||||
#[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")]
|
||||
InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },
|
||||
}
|
||||
|
||||
impl FrameBuffer {
|
||||
pub(crate) fn new(buffer: Vec<u8>, width: usize, height: usize) -> Result<Self, FrameBufferError> {
|
||||
let fb = Self { buffer, width, height };
|
||||
fb.validate_size()?;
|
||||
Ok(fb)
|
||||
}
|
||||
|
||||
pub(crate) fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
pub(crate) fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub(crate) fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn validate_size(&self) -> Result<(), FrameBufferError> {
|
||||
if self.buffer.len() != self.width * self.height * 4 {
|
||||
Err(FrameBufferError::InvalidSize {
|
||||
buffer_size: self.buffer.len(),
|
||||
expected_size: self.width * self.height * 4,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct GraphicsState {
|
||||
surface: wgpu::Surface<'static>,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
texture: Option<wgpu::Texture>,
|
||||
bind_group: Option<wgpu::BindGroup>,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
impl GraphicsState {
|
||||
pub(crate) async fn new(window: Arc<Window>) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::PRIMARY,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let surface = instance.create_surface(window).unwrap();
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
label: None,
|
||||
memory_hints: Default::default(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let surface_caps = surface.get_capabilities(&adapter);
|
||||
let surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]);
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: surface_caps.present_modes[0],
|
||||
alpha_mode: surface_caps.alpha_modes[0],
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
};
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
// Create shader module
|
||||
let shader = device.create_shader_module(wgpu::include_wgsl!("fullscreen_texture.wgsl"));
|
||||
|
||||
// Create sampler
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group_layout"),
|
||||
});
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &[&texture_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Render Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let mut graphics_state = Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
config,
|
||||
texture: None,
|
||||
bind_group: None,
|
||||
render_pipeline,
|
||||
sampler,
|
||||
};
|
||||
|
||||
// Initialize with a test pattern so we always have something to render
|
||||
let width = 800;
|
||||
let height = 600;
|
||||
let initial_data = vec![34u8; width * height * 4]; // Gray texture #222222FF
|
||||
|
||||
let fb = FrameBuffer::new(initial_data, width, height)
|
||||
.map_err(|e| {
|
||||
panic!("Failed to create initial FrameBuffer: {}", e);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
graphics_state.update_texture(&fb);
|
||||
|
||||
graphics_state
|
||||
}
|
||||
|
||||
pub(crate) fn resize(&mut self, width: usize, height: usize) {
|
||||
if width > 0 && height > 0 && (self.config.width != width as u32 || self.config.height != height as u32) {
|
||||
self.config.width = width as u32;
|
||||
self.config.height = height as u32;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_texture(&mut self, frame_buffer: &FrameBuffer) {
|
||||
let data = frame_buffer.buffer();
|
||||
let width = frame_buffer.width() as u32;
|
||||
let height = frame_buffer.height() as u32;
|
||||
|
||||
if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) {
|
||||
self.config.width = width;
|
||||
self.config.height = height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
|
||||
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("CEF Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
self.queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
data,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * width),
|
||||
rows_per_image: Some(height),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
|
||||
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &self.render_pipeline.get_bind_group_layout(0),
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
||||
},
|
||||
],
|
||||
label: Some("texture_bind_group"),
|
||||
});
|
||||
|
||||
self.texture = Some(texture);
|
||||
self.bind_group = Some(bind_group);
|
||||
}
|
||||
|
||||
pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") });
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1.0 }),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
if let Some(bind_group) = &self.bind_group {
|
||||
render_pass.set_bind_group(0, bind_group, &[]);
|
||||
render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle
|
||||
} else {
|
||||
println!("No bind group available - showing clear color only");
|
||||
}
|
||||
}
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -217,26 +217,30 @@ impl ApplicationIo for WasmApplicationIo {
|
|||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn create_window(&self) -> SurfaceHandle<Self::Surface> {
|
||||
log::trace!("Spawning window");
|
||||
todo!("winit api changed, calling create_window on EventLoop is deprecated");
|
||||
|
||||
#[cfg(all(not(test), target_os = "linux", feature = "wayland"))]
|
||||
use winit::platform::wayland::EventLoopBuilderExtWayland;
|
||||
// log::trace!("Spawning window");
|
||||
|
||||
#[cfg(all(not(test), target_os = "linux", feature = "wayland"))]
|
||||
let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap();
|
||||
#[cfg(not(all(not(test), target_os = "linux", feature = "wayland")))]
|
||||
let event_loop = winit::event_loop::EventLoop::new().unwrap();
|
||||
// #[cfg(all(not(test), target_os = "linux", feature = "wayland"))]
|
||||
// use winit::platform::wayland::EventLoopBuilderExtWayland;
|
||||
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title("Graphite")
|
||||
.with_inner_size(winit::dpi::PhysicalSize::new(800, 600))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
// #[cfg(all(not(test), target_os = "linux", feature = "wayland"))]
|
||||
// let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap();
|
||||
// #[cfg(not(all(not(test), target_os = "linux", feature = "wayland")))]
|
||||
// let event_loop = winit::event_loop::EventLoop::new().unwrap();
|
||||
|
||||
SurfaceHandle {
|
||||
window_id: SurfaceId(window.id().into()),
|
||||
surface: Arc::new(window),
|
||||
}
|
||||
// let window = event_loop
|
||||
// .create_window(
|
||||
// winit::window::WindowAttributes::default()
|
||||
// .with_title("Graphite")
|
||||
// .with_inner_size(winit::dpi::PhysicalSize::new(800, 600)),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// SurfaceHandle {
|
||||
// window_id: SurfaceId(window.id().into()),
|
||||
// surface: Arc::new(window),
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue