style: cargo fmt with the workspace tab policy (hard_tabs)
Whitespace only; the fork has no own rustfmt.toml so the Oak root policy applies.
This commit is contained in:
+268
-268
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
PlatformDispatcher, Priority, PriorityQueueReceiver, PriorityQueueSender, RunnableVariant,
|
||||
ThreadTaskTimings,
|
||||
PlatformDispatcher, Priority, PriorityQueueReceiver, PriorityQueueSender, RunnableVariant,
|
||||
ThreadTaskTimings,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
@@ -12,131 +12,131 @@ use web_time::Instant;
|
||||
const MIN_BACKGROUND_THREADS: usize = 2;
|
||||
|
||||
fn shared_memory_supported() -> bool {
|
||||
#[cfg(feature = "multithreaded")]
|
||||
{
|
||||
let global = js_sys::global();
|
||||
let has_shared_array_buffer =
|
||||
js_sys::Reflect::has(&global, &JsValue::from_str("SharedArrayBuffer")).unwrap_or(false);
|
||||
let has_atomics =
|
||||
js_sys::Reflect::has(&global, &JsValue::from_str("Atomics")).unwrap_or(false);
|
||||
let memory = js_sys::WebAssembly::Memory::from(wasm_bindgen::memory());
|
||||
let buffer = memory.buffer();
|
||||
let is_shared_buffer = buffer.is_instance_of::<js_sys::SharedArrayBuffer>();
|
||||
has_shared_array_buffer && has_atomics && is_shared_buffer
|
||||
}
|
||||
#[cfg(not(feature = "multithreaded"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
#[cfg(feature = "multithreaded")]
|
||||
{
|
||||
let global = js_sys::global();
|
||||
let has_shared_array_buffer =
|
||||
js_sys::Reflect::has(&global, &JsValue::from_str("SharedArrayBuffer")).unwrap_or(false);
|
||||
let has_atomics =
|
||||
js_sys::Reflect::has(&global, &JsValue::from_str("Atomics")).unwrap_or(false);
|
||||
let memory = js_sys::WebAssembly::Memory::from(wasm_bindgen::memory());
|
||||
let buffer = memory.buffer();
|
||||
let is_shared_buffer = buffer.is_instance_of::<js_sys::SharedArrayBuffer>();
|
||||
has_shared_array_buffer && has_atomics && is_shared_buffer
|
||||
}
|
||||
#[cfg(not(feature = "multithreaded"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
enum MainThreadItem {
|
||||
Runnable(RunnableVariant),
|
||||
Delayed {
|
||||
runnable: RunnableVariant,
|
||||
millis: i32,
|
||||
},
|
||||
// TODO-Wasm: Shouldn't these run on their own dedicated thread?
|
||||
RealtimeFunction(Box<dyn FnOnce() + Send>),
|
||||
Runnable(RunnableVariant),
|
||||
Delayed {
|
||||
runnable: RunnableVariant,
|
||||
millis: i32,
|
||||
},
|
||||
// TODO-Wasm: Shouldn't these run on their own dedicated thread?
|
||||
RealtimeFunction(Box<dyn FnOnce() + Send>),
|
||||
}
|
||||
|
||||
struct MainThreadMailbox {
|
||||
sender: PriorityQueueSender<MainThreadItem>,
|
||||
receiver: parking_lot::Mutex<PriorityQueueReceiver<MainThreadItem>>,
|
||||
signal: AtomicI32,
|
||||
sender: PriorityQueueSender<MainThreadItem>,
|
||||
receiver: parking_lot::Mutex<PriorityQueueReceiver<MainThreadItem>>,
|
||||
signal: AtomicI32,
|
||||
}
|
||||
|
||||
impl MainThreadMailbox {
|
||||
fn new() -> Self {
|
||||
let (sender, receiver) = PriorityQueueReceiver::new();
|
||||
Self {
|
||||
sender,
|
||||
receiver: parking_lot::Mutex::new(receiver),
|
||||
signal: AtomicI32::new(0),
|
||||
}
|
||||
}
|
||||
fn new() -> Self {
|
||||
let (sender, receiver) = PriorityQueueReceiver::new();
|
||||
Self {
|
||||
sender,
|
||||
receiver: parking_lot::Mutex::new(receiver),
|
||||
signal: AtomicI32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn post(&self, priority: Priority, item: MainThreadItem) {
|
||||
if self.sender.spin_send(priority, item).is_err() {
|
||||
log::error!("MainThreadMailbox::send failed: receiver disconnected");
|
||||
}
|
||||
fn post(&self, priority: Priority, item: MainThreadItem) {
|
||||
if self.sender.spin_send(priority, item).is_err() {
|
||||
log::error!("MainThreadMailbox::send failed: receiver disconnected");
|
||||
}
|
||||
|
||||
// TODO-Wasm: Verify this lock-free protocol
|
||||
let view = self.signal_view();
|
||||
js_sys::Atomics::store(&view, 0, 1).ok();
|
||||
js_sys::Atomics::notify(&view, 0).ok();
|
||||
}
|
||||
// TODO-Wasm: Verify this lock-free protocol
|
||||
let view = self.signal_view();
|
||||
js_sys::Atomics::store(&view, 0, 1).ok();
|
||||
js_sys::Atomics::notify(&view, 0).ok();
|
||||
}
|
||||
|
||||
fn drain(&self, window: &web_sys::Window) {
|
||||
let mut receiver = self.receiver.lock();
|
||||
loop {
|
||||
// We need these `spin` variants because we can't acquire a lock on the main thread.
|
||||
// TODO-WASM: Should we do something different?
|
||||
match receiver.spin_try_pop() {
|
||||
Ok(Some(item)) => execute_on_main_thread(window, item),
|
||||
Ok(None) => break,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn drain(&self, window: &web_sys::Window) {
|
||||
let mut receiver = self.receiver.lock();
|
||||
loop {
|
||||
// We need these `spin` variants because we can't acquire a lock on the main thread.
|
||||
// TODO-WASM: Should we do something different?
|
||||
match receiver.spin_try_pop() {
|
||||
Ok(Some(item)) => execute_on_main_thread(window, item),
|
||||
Ok(None) => break,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn signal_view(&self) -> js_sys::Int32Array {
|
||||
let byte_offset = self.signal.as_ptr() as u32;
|
||||
let memory = js_sys::WebAssembly::Memory::from(wasm_bindgen::memory());
|
||||
js_sys::Int32Array::new_with_byte_offset_and_length(&memory.buffer(), byte_offset, 1)
|
||||
}
|
||||
fn signal_view(&self) -> js_sys::Int32Array {
|
||||
let byte_offset = self.signal.as_ptr() as u32;
|
||||
let memory = js_sys::WebAssembly::Memory::from(wasm_bindgen::memory());
|
||||
js_sys::Int32Array::new_with_byte_offset_and_length(&memory.buffer(), byte_offset, 1)
|
||||
}
|
||||
|
||||
fn run_waker_loop(self: &Arc<Self>, window: web_sys::Window) {
|
||||
if !shared_memory_supported() {
|
||||
log::warn!("SharedArrayBuffer not available; main thread mailbox waker loop disabled");
|
||||
return;
|
||||
}
|
||||
fn run_waker_loop(self: &Arc<Self>, window: web_sys::Window) {
|
||||
if !shared_memory_supported() {
|
||||
log::warn!("SharedArrayBuffer not available; main thread mailbox waker loop disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
let mailbox = Arc::clone(self);
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let view = mailbox.signal_view();
|
||||
loop {
|
||||
js_sys::Atomics::store(&view, 0, 0).expect("Atomics.store failed");
|
||||
let mailbox = Arc::clone(self);
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let view = mailbox.signal_view();
|
||||
loop {
|
||||
js_sys::Atomics::store(&view, 0, 0).expect("Atomics.store failed");
|
||||
|
||||
let result = match js_sys::Atomics::wait_async(&view, 0, 0) {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
log::error!("Atomics.waitAsync failed: {error:?}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
let result = match js_sys::Atomics::wait_async(&view, 0, 0) {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
log::error!("Atomics.waitAsync failed: {error:?}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let is_async = js_sys::Reflect::get(&result, &JsValue::from_str("async"))
|
||||
.ok()
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
let is_async = js_sys::Reflect::get(&result, &JsValue::from_str("async"))
|
||||
.ok()
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_async {
|
||||
log::error!("Atomics.waitAsync returned synchronously; waker loop exiting");
|
||||
break;
|
||||
}
|
||||
if !is_async {
|
||||
log::error!("Atomics.waitAsync returned synchronously; waker loop exiting");
|
||||
break;
|
||||
}
|
||||
|
||||
let promise: js_sys::Promise =
|
||||
js_sys::Reflect::get(&result, &JsValue::from_str("value"))
|
||||
.expect("waitAsync result missing 'value'")
|
||||
.unchecked_into();
|
||||
let promise: js_sys::Promise =
|
||||
js_sys::Reflect::get(&result, &JsValue::from_str("value"))
|
||||
.expect("waitAsync result missing 'value'")
|
||||
.unchecked_into();
|
||||
|
||||
let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
|
||||
let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
|
||||
|
||||
mailbox.drain(&window);
|
||||
}
|
||||
});
|
||||
}
|
||||
mailbox.drain(&window);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebDispatcher {
|
||||
main_thread_id: std::thread::ThreadId,
|
||||
browser_window: web_sys::Window,
|
||||
background_sender: PriorityQueueSender<RunnableVariant>,
|
||||
main_thread_mailbox: Arc<MainThreadMailbox>,
|
||||
supports_threads: bool,
|
||||
#[cfg(feature = "multithreaded")]
|
||||
_background_threads: Vec<wasm_thread::JoinHandle<()>>,
|
||||
main_thread_id: std::thread::ThreadId,
|
||||
browser_window: web_sys::Window,
|
||||
background_sender: PriorityQueueSender<RunnableVariant>,
|
||||
main_thread_mailbox: Arc<MainThreadMailbox>,
|
||||
supports_threads: bool,
|
||||
#[cfg(feature = "multithreaded")]
|
||||
_background_threads: Vec<wasm_thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
// Safety: `web_sys::Window` is only accessed from the main thread
|
||||
@@ -145,196 +145,196 @@ unsafe impl Send for WebDispatcher {}
|
||||
unsafe impl Sync for WebDispatcher {}
|
||||
|
||||
impl WebDispatcher {
|
||||
pub fn new(browser_window: web_sys::Window, _allow_threads: bool) -> Self {
|
||||
#[cfg(feature = "multithreaded")]
|
||||
let (background_sender, background_receiver) = PriorityQueueReceiver::new();
|
||||
#[cfg(not(feature = "multithreaded"))]
|
||||
let (background_sender, _) = PriorityQueueReceiver::new();
|
||||
pub fn new(browser_window: web_sys::Window, _allow_threads: bool) -> Self {
|
||||
#[cfg(feature = "multithreaded")]
|
||||
let (background_sender, background_receiver) = PriorityQueueReceiver::new();
|
||||
#[cfg(not(feature = "multithreaded"))]
|
||||
let (background_sender, _) = PriorityQueueReceiver::new();
|
||||
|
||||
let main_thread_mailbox = Arc::new(MainThreadMailbox::new());
|
||||
let main_thread_mailbox = Arc::new(MainThreadMailbox::new());
|
||||
|
||||
#[cfg(feature = "multithreaded")]
|
||||
let supports_threads = _allow_threads && shared_memory_supported();
|
||||
#[cfg(not(feature = "multithreaded"))]
|
||||
let supports_threads = false;
|
||||
#[cfg(feature = "multithreaded")]
|
||||
let supports_threads = _allow_threads && shared_memory_supported();
|
||||
#[cfg(not(feature = "multithreaded"))]
|
||||
let supports_threads = false;
|
||||
|
||||
if supports_threads {
|
||||
main_thread_mailbox.run_waker_loop(browser_window.clone());
|
||||
} else {
|
||||
log::warn!(
|
||||
"SharedArrayBuffer not available; falling back to single-threaded dispatcher"
|
||||
);
|
||||
}
|
||||
if supports_threads {
|
||||
main_thread_mailbox.run_waker_loop(browser_window.clone());
|
||||
} else {
|
||||
log::warn!(
|
||||
"SharedArrayBuffer not available; falling back to single-threaded dispatcher"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "multithreaded")]
|
||||
let background_threads = if supports_threads {
|
||||
let thread_count = browser_window
|
||||
.navigator()
|
||||
.hardware_concurrency()
|
||||
.max(MIN_BACKGROUND_THREADS as f64) as usize;
|
||||
#[cfg(feature = "multithreaded")]
|
||||
let background_threads = if supports_threads {
|
||||
let thread_count = browser_window
|
||||
.navigator()
|
||||
.hardware_concurrency()
|
||||
.max(MIN_BACKGROUND_THREADS as f64) as usize;
|
||||
|
||||
// TODO-Wasm: Is it bad to have web workers blocking for a long time like this?
|
||||
(0..thread_count)
|
||||
.map(|i| {
|
||||
let mut receiver = background_receiver.clone();
|
||||
wasm_thread::Builder::new()
|
||||
.name(format!("background-worker-{i}"))
|
||||
.spawn(move || {
|
||||
loop {
|
||||
let runnable: RunnableVariant = match receiver.pop() {
|
||||
Ok(runnable) => runnable,
|
||||
Err(_) => {
|
||||
log::info!(
|
||||
"background-worker-{i}: channel disconnected, exiting"
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
// TODO-Wasm: Is it bad to have web workers blocking for a long time like this?
|
||||
(0..thread_count)
|
||||
.map(|i| {
|
||||
let mut receiver = background_receiver.clone();
|
||||
wasm_thread::Builder::new()
|
||||
.name(format!("background-worker-{i}"))
|
||||
.spawn(move || {
|
||||
loop {
|
||||
let runnable: RunnableVariant = match receiver.pop() {
|
||||
Ok(runnable) => runnable,
|
||||
Err(_) => {
|
||||
log::info!(
|
||||
"background-worker-{i}: channel disconnected, exiting"
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
runnable.run();
|
||||
}
|
||||
})
|
||||
.expect("failed to spawn background worker thread")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
runnable.run();
|
||||
}
|
||||
})
|
||||
.expect("failed to spawn background worker thread")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
Self {
|
||||
main_thread_id: std::thread::current().id(),
|
||||
browser_window,
|
||||
background_sender,
|
||||
main_thread_mailbox,
|
||||
supports_threads,
|
||||
#[cfg(feature = "multithreaded")]
|
||||
_background_threads: background_threads,
|
||||
}
|
||||
}
|
||||
Self {
|
||||
main_thread_id: std::thread::current().id(),
|
||||
browser_window,
|
||||
background_sender,
|
||||
main_thread_mailbox,
|
||||
supports_threads,
|
||||
#[cfg(feature = "multithreaded")]
|
||||
_background_threads: background_threads,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_main_thread(&self) -> bool {
|
||||
std::thread::current().id() == self.main_thread_id
|
||||
}
|
||||
fn on_main_thread(&self) -> bool {
|
||||
std::thread::current().id() == self.main_thread_id
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDispatcher for WebDispatcher {
|
||||
fn get_all_timings(&self) -> Vec<ThreadTaskTimings> {
|
||||
// TODO-Wasm: should we panic here?
|
||||
Vec::new()
|
||||
}
|
||||
fn get_all_timings(&self) -> Vec<ThreadTaskTimings> {
|
||||
// TODO-Wasm: should we panic here?
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn get_current_thread_timings(&self) -> ThreadTaskTimings {
|
||||
ThreadTaskTimings {
|
||||
thread_name: None,
|
||||
thread_id: std::thread::current().id(),
|
||||
timings: Vec::new(),
|
||||
total_pushed: 0,
|
||||
}
|
||||
}
|
||||
fn get_current_thread_timings(&self) -> ThreadTaskTimings {
|
||||
ThreadTaskTimings {
|
||||
thread_name: None,
|
||||
thread_id: std::thread::current().id(),
|
||||
timings: Vec::new(),
|
||||
total_pushed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_main_thread(&self) -> bool {
|
||||
self.on_main_thread()
|
||||
}
|
||||
fn is_main_thread(&self) -> bool {
|
||||
self.on_main_thread()
|
||||
}
|
||||
|
||||
fn dispatch(&self, runnable: RunnableVariant, priority: Priority) {
|
||||
if !self.supports_threads {
|
||||
self.dispatch_on_main_thread(runnable, priority);
|
||||
return;
|
||||
}
|
||||
fn dispatch(&self, runnable: RunnableVariant, priority: Priority) {
|
||||
if !self.supports_threads {
|
||||
self.dispatch_on_main_thread(runnable, priority);
|
||||
return;
|
||||
}
|
||||
|
||||
let result = if self.on_main_thread() {
|
||||
self.background_sender.spin_send(priority, runnable)
|
||||
} else {
|
||||
self.background_sender.send(priority, runnable)
|
||||
};
|
||||
let result = if self.on_main_thread() {
|
||||
self.background_sender.spin_send(priority, runnable)
|
||||
} else {
|
||||
self.background_sender.send(priority, runnable)
|
||||
};
|
||||
|
||||
if let Err(error) = result {
|
||||
log::error!("dispatch: failed to send to background queue: {error:?}");
|
||||
}
|
||||
}
|
||||
if let Err(error) = result {
|
||||
log::error!("dispatch: failed to send to background queue: {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority) {
|
||||
if self.on_main_thread() {
|
||||
schedule_runnable(&self.browser_window, runnable, priority);
|
||||
} else {
|
||||
self.main_thread_mailbox
|
||||
.post(priority, MainThreadItem::Runnable(runnable));
|
||||
}
|
||||
}
|
||||
fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority) {
|
||||
if self.on_main_thread() {
|
||||
schedule_runnable(&self.browser_window, runnable, priority);
|
||||
} else {
|
||||
self.main_thread_mailbox
|
||||
.post(priority, MainThreadItem::Runnable(runnable));
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
|
||||
let millis = duration.as_millis().min(i32::MAX as u128) as i32;
|
||||
if self.on_main_thread() {
|
||||
let callback = Closure::once_into_js(move || {
|
||||
runnable.run();
|
||||
});
|
||||
self.browser_window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.unchecked_ref(),
|
||||
millis,
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
self.main_thread_mailbox
|
||||
.post(Priority::High, MainThreadItem::Delayed { runnable, millis });
|
||||
}
|
||||
}
|
||||
fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
|
||||
let millis = duration.as_millis().min(i32::MAX as u128) as i32;
|
||||
if self.on_main_thread() {
|
||||
let callback = Closure::once_into_js(move || {
|
||||
runnable.run();
|
||||
});
|
||||
self.browser_window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.unchecked_ref(),
|
||||
millis,
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
self.main_thread_mailbox
|
||||
.post(Priority::High, MainThreadItem::Delayed { runnable, millis });
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_realtime(&self, function: Box<dyn FnOnce() + Send>) {
|
||||
if self.on_main_thread() {
|
||||
let callback = Closure::once_into_js(move || {
|
||||
function();
|
||||
});
|
||||
self.browser_window
|
||||
.queue_microtask(callback.unchecked_ref());
|
||||
} else {
|
||||
self.main_thread_mailbox
|
||||
.post(Priority::High, MainThreadItem::RealtimeFunction(function));
|
||||
}
|
||||
}
|
||||
fn spawn_realtime(&self, function: Box<dyn FnOnce() + Send>) {
|
||||
if self.on_main_thread() {
|
||||
let callback = Closure::once_into_js(move || {
|
||||
function();
|
||||
});
|
||||
self.browser_window
|
||||
.queue_microtask(callback.unchecked_ref());
|
||||
} else {
|
||||
self.main_thread_mailbox
|
||||
.post(Priority::High, MainThreadItem::RealtimeFunction(function));
|
||||
}
|
||||
}
|
||||
|
||||
fn now(&self) -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
fn now(&self) -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_on_main_thread(window: &web_sys::Window, item: MainThreadItem) {
|
||||
match item {
|
||||
MainThreadItem::Runnable(runnable) => {
|
||||
runnable.run();
|
||||
}
|
||||
MainThreadItem::Delayed { runnable, millis } => {
|
||||
let callback = Closure::once_into_js(move || {
|
||||
runnable.run();
|
||||
});
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.unchecked_ref(),
|
||||
millis,
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
MainThreadItem::RealtimeFunction(function) => {
|
||||
function();
|
||||
}
|
||||
}
|
||||
match item {
|
||||
MainThreadItem::Runnable(runnable) => {
|
||||
runnable.run();
|
||||
}
|
||||
MainThreadItem::Delayed { runnable, millis } => {
|
||||
let callback = Closure::once_into_js(move || {
|
||||
runnable.run();
|
||||
});
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
callback.unchecked_ref(),
|
||||
millis,
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
MainThreadItem::RealtimeFunction(function) => {
|
||||
function();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn schedule_runnable(window: &web_sys::Window, runnable: RunnableVariant, priority: Priority) {
|
||||
let callback = Closure::once_into_js(move || {
|
||||
runnable.run();
|
||||
});
|
||||
let callback: &js_sys::Function = callback.unchecked_ref();
|
||||
let callback = Closure::once_into_js(move || {
|
||||
runnable.run();
|
||||
});
|
||||
let callback: &js_sys::Function = callback.unchecked_ref();
|
||||
|
||||
match priority {
|
||||
Priority::RealtimeAudio => {
|
||||
window.queue_microtask(callback);
|
||||
}
|
||||
_ => {
|
||||
// TODO-Wasm: this ought to enqueue so we can dequeue with proper priority
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(callback, 0)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
match priority {
|
||||
Priority::RealtimeAudio => {
|
||||
window.queue_microtask(callback);
|
||||
}
|
||||
_ => {
|
||||
// TODO-Wasm: this ought to enqueue so we can dequeue with proper priority
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(callback, 0)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ use gpui::{Bounds, DisplayId, Pixels, PlatformDisplay, Point, Size, px};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebDisplay {
|
||||
id: DisplayId,
|
||||
uuid: uuid::Uuid,
|
||||
browser_window: web_sys::Window,
|
||||
id: DisplayId,
|
||||
uuid: uuid::Uuid,
|
||||
browser_window: web_sys::Window,
|
||||
}
|
||||
|
||||
// Safety: WASM is single-threaded — there is no concurrent access to `web_sys::Window`.
|
||||
@@ -13,86 +13,86 @@ unsafe impl Send for WebDisplay {}
|
||||
unsafe impl Sync for WebDisplay {}
|
||||
|
||||
impl WebDisplay {
|
||||
pub fn new(browser_window: web_sys::Window) -> Self {
|
||||
WebDisplay {
|
||||
id: DisplayId::new(1),
|
||||
uuid: uuid::Uuid::new_v4(),
|
||||
browser_window,
|
||||
}
|
||||
}
|
||||
pub fn new(browser_window: web_sys::Window) -> Self {
|
||||
WebDisplay {
|
||||
id: DisplayId::new(1),
|
||||
uuid: uuid::Uuid::new_v4(),
|
||||
browser_window,
|
||||
}
|
||||
}
|
||||
|
||||
fn screen_size(&self) -> Size<Pixels> {
|
||||
let Some(screen) = self.browser_window.screen().ok() else {
|
||||
return Size {
|
||||
width: px(1920.),
|
||||
height: px(1080.),
|
||||
};
|
||||
};
|
||||
fn screen_size(&self) -> Size<Pixels> {
|
||||
let Some(screen) = self.browser_window.screen().ok() else {
|
||||
return Size {
|
||||
width: px(1920.),
|
||||
height: px(1080.),
|
||||
};
|
||||
};
|
||||
|
||||
let width = screen.width().unwrap_or(1920) as f32;
|
||||
let height = screen.height().unwrap_or(1080) as f32;
|
||||
let width = screen.width().unwrap_or(1920) as f32;
|
||||
let height = screen.height().unwrap_or(1080) as f32;
|
||||
|
||||
Size {
|
||||
width: px(width),
|
||||
height: px(height),
|
||||
}
|
||||
}
|
||||
Size {
|
||||
width: px(width),
|
||||
height: px(height),
|
||||
}
|
||||
}
|
||||
|
||||
fn viewport_size(&self) -> Size<Pixels> {
|
||||
let width = self
|
||||
.browser_window
|
||||
.inner_width()
|
||||
.ok()
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(1920.0) as f32;
|
||||
let height = self
|
||||
.browser_window
|
||||
.inner_height()
|
||||
.ok()
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(1080.0) as f32;
|
||||
fn viewport_size(&self) -> Size<Pixels> {
|
||||
let width = self
|
||||
.browser_window
|
||||
.inner_width()
|
||||
.ok()
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(1920.0) as f32;
|
||||
let height = self
|
||||
.browser_window
|
||||
.inner_height()
|
||||
.ok()
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(1080.0) as f32;
|
||||
|
||||
Size {
|
||||
width: px(width),
|
||||
height: px(height),
|
||||
}
|
||||
}
|
||||
Size {
|
||||
width: px(width),
|
||||
height: px(height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDisplay for WebDisplay {
|
||||
fn id(&self) -> DisplayId {
|
||||
self.id
|
||||
}
|
||||
fn id(&self) -> DisplayId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn uuid(&self) -> Result<uuid::Uuid> {
|
||||
Ok(self.uuid)
|
||||
}
|
||||
fn uuid(&self) -> Result<uuid::Uuid> {
|
||||
Ok(self.uuid)
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
let size = self.screen_size();
|
||||
Bounds {
|
||||
origin: Point::default(),
|
||||
size,
|
||||
}
|
||||
}
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
let size = self.screen_size();
|
||||
Bounds {
|
||||
origin: Point::default(),
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
fn visible_bounds(&self) -> Bounds<Pixels> {
|
||||
let size = self.viewport_size();
|
||||
Bounds {
|
||||
origin: Point::default(),
|
||||
size,
|
||||
}
|
||||
}
|
||||
fn visible_bounds(&self) -> Bounds<Pixels> {
|
||||
let size = self.viewport_size();
|
||||
Bounds {
|
||||
origin: Point::default(),
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
fn default_bounds(&self) -> Bounds<Pixels> {
|
||||
let visible = self.visible_bounds();
|
||||
let width = visible.size.width * 0.75;
|
||||
let height = visible.size.height * 0.75;
|
||||
let origin_x = (visible.size.width - width) / 2.0;
|
||||
let origin_y = (visible.size.height - height) / 2.0;
|
||||
Bounds {
|
||||
origin: Point::new(origin_x, origin_y),
|
||||
size: Size { width, height },
|
||||
}
|
||||
}
|
||||
fn default_bounds(&self) -> Bounds<Pixels> {
|
||||
let visible = self.visible_bounds();
|
||||
let width = visible.size.width * 0.75;
|
||||
let height = visible.size.height * 0.75;
|
||||
let origin_x = (visible.size.width - width) / 2.0;
|
||||
let origin_y = (visible.size.height - height) / 2.0;
|
||||
Bounds {
|
||||
origin: Point::new(origin_x, origin_y),
|
||||
size: Size { width, height },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+611
-611
File diff suppressed because it is too large
Load Diff
@@ -8,30 +8,30 @@ use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch, js_name = "fetch")]
|
||||
fn global_fetch(input: &web_sys::Request) -> Result<js_sys::Promise, JsValue>;
|
||||
#[wasm_bindgen(catch, js_name = "fetch")]
|
||||
fn global_fetch(input: &web_sys::Request) -> Result<js_sys::Promise, JsValue>;
|
||||
}
|
||||
|
||||
pub struct FetchHttpClient;
|
||||
|
||||
impl Default for FetchHttpClient {
|
||||
fn default() -> Self {
|
||||
Self
|
||||
}
|
||||
fn default() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "multithreaded")]
|
||||
impl FetchHttpClient {
|
||||
pub unsafe fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
pub unsafe fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "multithreaded"))]
|
||||
impl FetchHttpClient {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a `!Send` future to satisfy the `Send` bound on `BoxFuture`.
|
||||
@@ -40,60 +40,60 @@ struct AssertSend<F>(F);
|
||||
unsafe impl<F> Send for AssertSend<F> {}
|
||||
|
||||
impl<F: Future> Future for AssertSend<F> {
|
||||
type Output = F::Output;
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
let inner = unsafe { self.map_unchecked_mut(|this| &mut this.0) };
|
||||
inner.poll(cx)
|
||||
}
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
let inner = unsafe { self.map_unchecked_mut(|this| &mut this.0) };
|
||||
inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for FetchHttpClient {
|
||||
fn get(
|
||||
&self,
|
||||
url: &str,
|
||||
follow_redirects: bool,
|
||||
) -> futures::future::BoxFuture<'static, anyhow::Result<HttpResponse>> {
|
||||
let url = url.to_string();
|
||||
Box::pin(AssertSend(async move {
|
||||
let init = web_sys::RequestInit::new();
|
||||
init.set_method("GET");
|
||||
fn get(
|
||||
&self,
|
||||
url: &str,
|
||||
follow_redirects: bool,
|
||||
) -> futures::future::BoxFuture<'static, anyhow::Result<HttpResponse>> {
|
||||
let url = url.to_string();
|
||||
Box::pin(AssertSend(async move {
|
||||
let init = web_sys::RequestInit::new();
|
||||
init.set_method("GET");
|
||||
|
||||
if !follow_redirects {
|
||||
init.set_redirect(web_sys::RequestRedirect::Manual);
|
||||
}
|
||||
if !follow_redirects {
|
||||
init.set_redirect(web_sys::RequestRedirect::Manual);
|
||||
}
|
||||
|
||||
let request = web_sys::Request::new_with_str_and_init(&url, &init)
|
||||
.map_err(|error| anyhow!("failed to create fetch Request: {error:?}"))?;
|
||||
let request = web_sys::Request::new_with_str_and_init(&url, &init)
|
||||
.map_err(|error| anyhow!("failed to create fetch Request: {error:?}"))?;
|
||||
|
||||
let promise = global_fetch(&request)
|
||||
.map_err(|error| anyhow!("fetch threw an error: {error:?}"))?;
|
||||
let response_value = wasm_bindgen_futures::JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|error| anyhow!("fetch failed: {error:?}"))?;
|
||||
let promise = global_fetch(&request)
|
||||
.map_err(|error| anyhow!("fetch threw an error: {error:?}"))?;
|
||||
let response_value = wasm_bindgen_futures::JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|error| anyhow!("fetch failed: {error:?}"))?;
|
||||
|
||||
let web_response: web_sys::Response = response_value
|
||||
.dyn_into()
|
||||
.map_err(|error| anyhow!("fetch result is not a Response: {error:?}"))?;
|
||||
let web_response: web_sys::Response = response_value
|
||||
.dyn_into()
|
||||
.map_err(|error| anyhow!("fetch result is not a Response: {error:?}"))?;
|
||||
|
||||
let status_code = http::StatusCode::from_u16(web_response.status())
|
||||
.map_err(|_| anyhow!("invalid status code"))?;
|
||||
let status_code = http::StatusCode::from_u16(web_response.status())
|
||||
.map_err(|_| anyhow!("invalid status code"))?;
|
||||
|
||||
let body_promise = web_response
|
||||
.array_buffer()
|
||||
.map_err(|error| anyhow!("failed to initiate response body read: {error:?}"))?;
|
||||
let body_value = wasm_bindgen_futures::JsFuture::from(body_promise)
|
||||
.await
|
||||
.map_err(|error| anyhow!("failed to read response body: {error:?}"))?;
|
||||
let array_buffer: js_sys::ArrayBuffer = body_value
|
||||
.dyn_into()
|
||||
.map_err(|error| anyhow!("response body is not an ArrayBuffer: {error:?}"))?;
|
||||
let body = js_sys::Uint8Array::new(&array_buffer).to_vec();
|
||||
let body_promise = web_response
|
||||
.array_buffer()
|
||||
.map_err(|error| anyhow!("failed to initiate response body read: {error:?}"))?;
|
||||
let body_value = wasm_bindgen_futures::JsFuture::from(body_promise)
|
||||
.await
|
||||
.map_err(|error| anyhow!("failed to read response body: {error:?}"))?;
|
||||
let array_buffer: js_sys::ArrayBuffer = body_value
|
||||
.dyn_into()
|
||||
.map_err(|error| anyhow!("response body is not an ArrayBuffer: {error:?}"))?;
|
||||
let body = js_sys::Uint8Array::new(&array_buffer).to_vec();
|
||||
|
||||
Ok(HttpResponse {
|
||||
status: status_code,
|
||||
body,
|
||||
})
|
||||
}))
|
||||
}
|
||||
Ok(HttpResponse {
|
||||
status: status_code,
|
||||
body,
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ use gpui::PlatformKeyboardLayout;
|
||||
pub struct WebKeyboardLayout;
|
||||
|
||||
impl WebKeyboardLayout {
|
||||
pub fn new() -> Self {
|
||||
WebKeyboardLayout
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
WebKeyboardLayout
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformKeyboardLayout for WebKeyboardLayout {
|
||||
fn id(&self) -> &str {
|
||||
"us"
|
||||
}
|
||||
fn id(&self) -> &str {
|
||||
"us"
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"US"
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
"US"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,35 +3,35 @@ use log::{Level, Log, Metadata, Record};
|
||||
struct ConsoleLogger;
|
||||
|
||||
impl Log for ConsoleLogger {
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if !self.enabled(record.metadata()) {
|
||||
return;
|
||||
}
|
||||
fn log(&self, record: &Record) {
|
||||
if !self.enabled(record.metadata()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = format!(
|
||||
"[{}] {}: {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.args()
|
||||
);
|
||||
let js_string = wasm_bindgen::JsValue::from_str(&message);
|
||||
let message = format!(
|
||||
"[{}] {}: {}",
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.args()
|
||||
);
|
||||
let js_string = wasm_bindgen::JsValue::from_str(&message);
|
||||
|
||||
match record.level() {
|
||||
Level::Error => web_sys::console::error_1(&js_string),
|
||||
Level::Warn => web_sys::console::warn_1(&js_string),
|
||||
Level::Info => web_sys::console::info_1(&js_string),
|
||||
Level::Debug | Level::Trace => web_sys::console::log_1(&js_string),
|
||||
}
|
||||
}
|
||||
match record.level() {
|
||||
Level::Error => web_sys::console::error_1(&js_string),
|
||||
Level::Warn => web_sys::console::warn_1(&js_string),
|
||||
Level::Info => web_sys::console::info_1(&js_string),
|
||||
Level::Debug | Level::Trace => web_sys::console::log_1(&js_string),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
pub fn init_logging() {
|
||||
log::set_logger(&ConsoleLogger).ok();
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
log::set_logger(&ConsoleLogger).ok();
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
+339
-339
@@ -5,431 +5,431 @@ use crate::window::WebWindow;
|
||||
use anyhow::Result;
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DummyKeyboardMapper,
|
||||
ForegroundExecutor, Keymap, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
|
||||
PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PlatformWindow, Task,
|
||||
ThermalState, WindowAppearance, WindowParams,
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DummyKeyboardMapper,
|
||||
ForegroundExecutor, Keymap, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
|
||||
PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PlatformWindow, Task,
|
||||
ThermalState, WindowAppearance, WindowParams,
|
||||
};
|
||||
use gpui_wgpu::WgpuContext;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
static BUNDLED_FONTS: &[&[u8]] = &[
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf"),
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Italic.ttf"),
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-SemiBold.ttf"),
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-SemiBoldItalic.ttf"),
|
||||
include_bytes!("../../../assets/fonts/lilex/Lilex-Regular.ttf"),
|
||||
include_bytes!("../../../assets/fonts/lilex/Lilex-Bold.ttf"),
|
||||
include_bytes!("../../../assets/fonts/lilex/Lilex-Italic.ttf"),
|
||||
include_bytes!("../../../assets/fonts/lilex/Lilex-BoldItalic.ttf"),
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf"),
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Italic.ttf"),
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-SemiBold.ttf"),
|
||||
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-SemiBoldItalic.ttf"),
|
||||
include_bytes!("../../../assets/fonts/lilex/Lilex-Regular.ttf"),
|
||||
include_bytes!("../../../assets/fonts/lilex/Lilex-Bold.ttf"),
|
||||
include_bytes!("../../../assets/fonts/lilex/Lilex-Italic.ttf"),
|
||||
include_bytes!("../../../assets/fonts/lilex/Lilex-BoldItalic.ttf"),
|
||||
];
|
||||
|
||||
pub struct WebPlatform {
|
||||
browser_window: web_sys::Window,
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<dyn PlatformTextSystem>,
|
||||
active_window: RefCell<Option<AnyWindowHandle>>,
|
||||
active_display: Rc<dyn PlatformDisplay>,
|
||||
callbacks: RefCell<WebPlatformCallbacks>,
|
||||
wgpu_context: Rc<RefCell<Option<WgpuContext>>>,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
last_cursor_css: Rc<Cell<&'static str>>,
|
||||
_cursor_restore_listeners: Vec<EventListenerHandle>,
|
||||
browser_window: web_sys::Window,
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<dyn PlatformTextSystem>,
|
||||
active_window: RefCell<Option<AnyWindowHandle>>,
|
||||
active_display: Rc<dyn PlatformDisplay>,
|
||||
callbacks: RefCell<WebPlatformCallbacks>,
|
||||
wgpu_context: Rc<RefCell<Option<WgpuContext>>>,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
last_cursor_css: Rc<Cell<&'static str>>,
|
||||
_cursor_restore_listeners: Vec<EventListenerHandle>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct WebPlatformCallbacks {
|
||||
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
||||
quit: Option<Box<dyn FnMut()>>,
|
||||
reopen: Option<Box<dyn FnMut()>>,
|
||||
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||
will_open_app_menu: Option<Box<dyn FnMut()>>,
|
||||
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||
keyboard_layout_change: Option<Box<dyn FnMut()>>,
|
||||
thermal_state_change: Option<Box<dyn FnMut()>>,
|
||||
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
||||
quit: Option<Box<dyn FnMut()>>,
|
||||
reopen: Option<Box<dyn FnMut()>>,
|
||||
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||
will_open_app_menu: Option<Box<dyn FnMut()>>,
|
||||
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||
keyboard_layout_change: Option<Box<dyn FnMut()>>,
|
||||
thermal_state_change: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
impl WebPlatform {
|
||||
pub fn new(allow_multi_threading: bool) -> Self {
|
||||
let browser_window =
|
||||
web_sys::window().expect("must be running in a browser window context");
|
||||
let dispatcher = Arc::new(WebDispatcher::new(
|
||||
browser_window.clone(),
|
||||
allow_multi_threading,
|
||||
));
|
||||
let background_executor = BackgroundExecutor::new(dispatcher.clone());
|
||||
let foreground_executor = ForegroundExecutor::new(dispatcher);
|
||||
let text_system = Arc::new(gpui_wgpu::CosmicTextSystem::new_without_system_fonts(
|
||||
"IBM Plex Sans",
|
||||
));
|
||||
let fonts = BUNDLED_FONTS
|
||||
.iter()
|
||||
.map(|bytes| Cow::Borrowed(*bytes))
|
||||
.collect();
|
||||
if let Err(error) = text_system.add_fonts(fonts) {
|
||||
log::error!("failed to load bundled fonts: {error:#}");
|
||||
}
|
||||
let text_system: Arc<dyn PlatformTextSystem> = text_system;
|
||||
let active_display: Rc<dyn PlatformDisplay> =
|
||||
Rc::new(WebDisplay::new(browser_window.clone()));
|
||||
pub fn new(allow_multi_threading: bool) -> Self {
|
||||
let browser_window =
|
||||
web_sys::window().expect("must be running in a browser window context");
|
||||
let dispatcher = Arc::new(WebDispatcher::new(
|
||||
browser_window.clone(),
|
||||
allow_multi_threading,
|
||||
));
|
||||
let background_executor = BackgroundExecutor::new(dispatcher.clone());
|
||||
let foreground_executor = ForegroundExecutor::new(dispatcher);
|
||||
let text_system = Arc::new(gpui_wgpu::CosmicTextSystem::new_without_system_fonts(
|
||||
"IBM Plex Sans",
|
||||
));
|
||||
let fonts = BUNDLED_FONTS
|
||||
.iter()
|
||||
.map(|bytes| Cow::Borrowed(*bytes))
|
||||
.collect();
|
||||
if let Err(error) = text_system.add_fonts(fonts) {
|
||||
log::error!("failed to load bundled fonts: {error:#}");
|
||||
}
|
||||
let text_system: Arc<dyn PlatformTextSystem> = text_system;
|
||||
let active_display: Rc<dyn PlatformDisplay> =
|
||||
Rc::new(WebDisplay::new(browser_window.clone()));
|
||||
|
||||
let cursor_visible = Rc::new(Cell::new(true));
|
||||
let last_cursor_css = Rc::new(Cell::new("default"));
|
||||
let cursor_restore_listeners = cursor_restore_listeners(
|
||||
&browser_window,
|
||||
cursor_visible.clone(),
|
||||
last_cursor_css.clone(),
|
||||
);
|
||||
let cursor_visible = Rc::new(Cell::new(true));
|
||||
let last_cursor_css = Rc::new(Cell::new("default"));
|
||||
let cursor_restore_listeners = cursor_restore_listeners(
|
||||
&browser_window,
|
||||
cursor_visible.clone(),
|
||||
last_cursor_css.clone(),
|
||||
);
|
||||
|
||||
Self {
|
||||
browser_window,
|
||||
background_executor,
|
||||
foreground_executor,
|
||||
text_system,
|
||||
active_window: RefCell::new(None),
|
||||
active_display,
|
||||
callbacks: RefCell::new(WebPlatformCallbacks::default()),
|
||||
wgpu_context: Rc::new(RefCell::new(None)),
|
||||
cursor_visible,
|
||||
last_cursor_css,
|
||||
_cursor_restore_listeners: cursor_restore_listeners,
|
||||
}
|
||||
}
|
||||
Self {
|
||||
browser_window,
|
||||
background_executor,
|
||||
foreground_executor,
|
||||
text_system,
|
||||
active_window: RefCell::new(None),
|
||||
active_display,
|
||||
callbacks: RefCell::new(WebPlatformCallbacks::default()),
|
||||
wgpu_context: Rc::new(RefCell::new(None)),
|
||||
cursor_visible,
|
||||
last_cursor_css,
|
||||
_cursor_restore_listeners: cursor_restore_listeners,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Platform for WebPlatform {
|
||||
fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.background_executor.clone()
|
||||
}
|
||||
fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.background_executor.clone()
|
||||
}
|
||||
|
||||
fn foreground_executor(&self) -> ForegroundExecutor {
|
||||
self.foreground_executor.clone()
|
||||
}
|
||||
fn foreground_executor(&self) -> ForegroundExecutor {
|
||||
self.foreground_executor.clone()
|
||||
}
|
||||
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
|
||||
self.text_system.clone()
|
||||
}
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
|
||||
self.text_system.clone()
|
||||
}
|
||||
|
||||
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
|
||||
let wgpu_context = self.wgpu_context.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match WgpuContext::new_web().await {
|
||||
Ok(context) => {
|
||||
log::info!("WebGPU context initialized successfully");
|
||||
*wgpu_context.borrow_mut() = Some(context);
|
||||
on_finish_launching();
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to initialize WebGPU context: {err:#}");
|
||||
on_finish_launching();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
|
||||
let wgpu_context = self.wgpu_context.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match WgpuContext::new_web().await {
|
||||
Ok(context) => {
|
||||
log::info!("WebGPU context initialized successfully");
|
||||
*wgpu_context.borrow_mut() = Some(context);
|
||||
on_finish_launching();
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to initialize WebGPU context: {err:#}");
|
||||
on_finish_launching();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn quit(&self) {
|
||||
log::warn!("WebPlatform::quit called, but quitting is not supported in the browser .");
|
||||
}
|
||||
fn quit(&self) {
|
||||
log::warn!("WebPlatform::quit called, but quitting is not supported in the browser .");
|
||||
}
|
||||
|
||||
fn restart(&self, _binary_path: Option<PathBuf>) {}
|
||||
fn restart(&self, _binary_path: Option<PathBuf>) {}
|
||||
|
||||
fn activate(&self, _ignoring_other_apps: bool) {}
|
||||
fn activate(&self, _ignoring_other_apps: bool) {}
|
||||
|
||||
fn hide(&self) {}
|
||||
fn hide(&self) {}
|
||||
|
||||
fn hide_other_apps(&self) {}
|
||||
fn hide_other_apps(&self) {}
|
||||
|
||||
fn unhide_other_apps(&self) {}
|
||||
fn unhide_other_apps(&self) {}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
vec![self.active_display.clone()]
|
||||
}
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
vec![self.active_display.clone()]
|
||||
}
|
||||
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
Some(self.active_display.clone())
|
||||
}
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
Some(self.active_display.clone())
|
||||
}
|
||||
|
||||
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||
*self.active_window.borrow()
|
||||
}
|
||||
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||
*self.active_window.borrow()
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
params: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>> {
|
||||
let context_ref = self.wgpu_context.borrow();
|
||||
let context = context_ref.as_ref().ok_or_else(|| {
|
||||
anyhow::anyhow!("WebGPU context not initialized. Was Platform::run() called?")
|
||||
})?;
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
params: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>> {
|
||||
let context_ref = self.wgpu_context.borrow();
|
||||
let context = context_ref.as_ref().ok_or_else(|| {
|
||||
anyhow::anyhow!("WebGPU context not initialized. Was Platform::run() called?")
|
||||
})?;
|
||||
|
||||
let window = WebWindow::new(handle, params, context, self.browser_window.clone())?;
|
||||
*self.active_window.borrow_mut() = Some(handle);
|
||||
Ok(Box::new(window))
|
||||
}
|
||||
let window = WebWindow::new(handle, params, context, self.browser_window.clone())?;
|
||||
*self.active_window.borrow_mut() = Some(handle);
|
||||
Ok(Box::new(window))
|
||||
}
|
||||
|
||||
fn window_appearance(&self) -> WindowAppearance {
|
||||
let Ok(Some(media_query)) = self
|
||||
.browser_window
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
else {
|
||||
return WindowAppearance::Light;
|
||||
};
|
||||
if media_query.matches() {
|
||||
WindowAppearance::Dark
|
||||
} else {
|
||||
WindowAppearance::Light
|
||||
}
|
||||
}
|
||||
fn window_appearance(&self) -> WindowAppearance {
|
||||
let Ok(Some(media_query)) = self
|
||||
.browser_window
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
else {
|
||||
return WindowAppearance::Light;
|
||||
};
|
||||
if media_query.matches() {
|
||||
WindowAppearance::Dark
|
||||
} else {
|
||||
WindowAppearance::Light
|
||||
}
|
||||
}
|
||||
|
||||
fn open_url(&self, url: &str) {
|
||||
if let Err(error) = self.browser_window.open_with_url(url) {
|
||||
log::warn!("Failed to open URL '{url}': {error:?}");
|
||||
}
|
||||
}
|
||||
fn open_url(&self, url: &str) {
|
||||
if let Err(error) = self.browser_window.open_with_url(url) {
|
||||
log::warn!("Failed to open URL '{url}': {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
|
||||
self.callbacks.borrow_mut().open_urls = Some(callback);
|
||||
}
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
|
||||
self.callbacks.borrow_mut().open_urls = Some(callback);
|
||||
}
|
||||
|
||||
fn register_url_scheme(&self, _url: &str) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
fn register_url_scheme(&self, _url: &str) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn prompt_for_paths(
|
||||
&self,
|
||||
_options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
tx.send(Err(anyhow::anyhow!(
|
||||
"prompt_for_paths is not supported on the web"
|
||||
)))
|
||||
.ok();
|
||||
rx
|
||||
}
|
||||
fn prompt_for_paths(
|
||||
&self,
|
||||
_options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
tx.send(Err(anyhow::anyhow!(
|
||||
"prompt_for_paths is not supported on the web"
|
||||
)))
|
||||
.ok();
|
||||
rx
|
||||
}
|
||||
|
||||
fn prompt_for_new_path(
|
||||
&self,
|
||||
_directory: &Path,
|
||||
_suggested_name: Option<&str>,
|
||||
) -> oneshot::Receiver<Result<Option<PathBuf>>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
sender
|
||||
.send(Err(anyhow::anyhow!(
|
||||
"prompt_for_new_path is not supported on the web"
|
||||
)))
|
||||
.ok();
|
||||
receiver
|
||||
}
|
||||
fn prompt_for_new_path(
|
||||
&self,
|
||||
_directory: &Path,
|
||||
_suggested_name: Option<&str>,
|
||||
) -> oneshot::Receiver<Result<Option<PathBuf>>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
sender
|
||||
.send(Err(anyhow::anyhow!(
|
||||
"prompt_for_new_path is not supported on the web"
|
||||
)))
|
||||
.ok();
|
||||
receiver
|
||||
}
|
||||
|
||||
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn reveal_path(&self, _path: &Path) {}
|
||||
fn reveal_path(&self, _path: &Path) {}
|
||||
|
||||
fn open_with_system(&self, _path: &Path) {}
|
||||
fn open_with_system(&self, _path: &Path) {}
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().quit = Some(callback);
|
||||
}
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().quit = Some(callback);
|
||||
}
|
||||
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().reopen = Some(callback);
|
||||
}
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().reopen = Some(callback);
|
||||
}
|
||||
|
||||
fn set_menus(&self, _menus: Vec<Menu>, _keymap: &Keymap) {}
|
||||
fn set_menus(&self, _menus: Vec<Menu>, _keymap: &Keymap) {}
|
||||
|
||||
fn set_dock_menu(&self, _menu: Vec<MenuItem>, _keymap: &Keymap) {}
|
||||
fn set_dock_menu(&self, _menu: Vec<MenuItem>, _keymap: &Keymap) {}
|
||||
|
||||
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
|
||||
self.callbacks.borrow_mut().app_menu_action = Some(callback);
|
||||
}
|
||||
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
|
||||
self.callbacks.borrow_mut().app_menu_action = Some(callback);
|
||||
}
|
||||
|
||||
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().will_open_app_menu = Some(callback);
|
||||
}
|
||||
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().will_open_app_menu = Some(callback);
|
||||
}
|
||||
|
||||
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
|
||||
self.callbacks.borrow_mut().validate_app_menu_command = Some(callback);
|
||||
}
|
||||
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
|
||||
self.callbacks.borrow_mut().validate_app_menu_command = Some(callback);
|
||||
}
|
||||
|
||||
fn thermal_state(&self) -> ThermalState {
|
||||
ThermalState::Nominal
|
||||
}
|
||||
fn thermal_state(&self) -> ThermalState {
|
||||
ThermalState::Nominal
|
||||
}
|
||||
|
||||
fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().thermal_state_change = Some(callback);
|
||||
}
|
||||
fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().thermal_state_change = Some(callback);
|
||||
}
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
"Web"
|
||||
}
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
"Web"
|
||||
}
|
||||
|
||||
fn app_path(&self) -> Result<PathBuf> {
|
||||
Err(anyhow::anyhow!("app_path is not available on the web"))
|
||||
}
|
||||
fn app_path(&self) -> Result<PathBuf> {
|
||||
Err(anyhow::anyhow!("app_path is not available on the web"))
|
||||
}
|
||||
|
||||
fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
|
||||
Err(anyhow::anyhow!(
|
||||
"path_for_auxiliary_executable is not available on the web"
|
||||
))
|
||||
}
|
||||
fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
|
||||
Err(anyhow::anyhow!(
|
||||
"path_for_auxiliary_executable is not available on the web"
|
||||
))
|
||||
}
|
||||
|
||||
fn set_cursor_style(&self, style: CursorStyle) {
|
||||
let css_cursor = match style {
|
||||
CursorStyle::Arrow => "default",
|
||||
CursorStyle::IBeam => "text",
|
||||
CursorStyle::Crosshair => "crosshair",
|
||||
CursorStyle::ClosedHand => "grabbing",
|
||||
CursorStyle::OpenHand => "grab",
|
||||
CursorStyle::PointingHand => "pointer",
|
||||
CursorStyle::ResizeLeft | CursorStyle::ResizeRight | CursorStyle::ResizeLeftRight => {
|
||||
"ew-resize"
|
||||
}
|
||||
CursorStyle::ResizeUp | CursorStyle::ResizeDown | CursorStyle::ResizeUpDown => {
|
||||
"ns-resize"
|
||||
}
|
||||
CursorStyle::ResizeUpLeftDownRight => "nesw-resize",
|
||||
CursorStyle::ResizeUpRightDownLeft => "nwse-resize",
|
||||
CursorStyle::ResizeColumn => "col-resize",
|
||||
CursorStyle::ResizeRow => "row-resize",
|
||||
CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",
|
||||
CursorStyle::OperationNotAllowed => "not-allowed",
|
||||
CursorStyle::DragLink => "alias",
|
||||
CursorStyle::DragCopy => "copy",
|
||||
CursorStyle::ContextualMenu => "context-menu",
|
||||
};
|
||||
fn set_cursor_style(&self, style: CursorStyle) {
|
||||
let css_cursor = match style {
|
||||
CursorStyle::Arrow => "default",
|
||||
CursorStyle::IBeam => "text",
|
||||
CursorStyle::Crosshair => "crosshair",
|
||||
CursorStyle::ClosedHand => "grabbing",
|
||||
CursorStyle::OpenHand => "grab",
|
||||
CursorStyle::PointingHand => "pointer",
|
||||
CursorStyle::ResizeLeft | CursorStyle::ResizeRight | CursorStyle::ResizeLeftRight => {
|
||||
"ew-resize"
|
||||
}
|
||||
CursorStyle::ResizeUp | CursorStyle::ResizeDown | CursorStyle::ResizeUpDown => {
|
||||
"ns-resize"
|
||||
}
|
||||
CursorStyle::ResizeUpLeftDownRight => "nesw-resize",
|
||||
CursorStyle::ResizeUpRightDownLeft => "nwse-resize",
|
||||
CursorStyle::ResizeColumn => "col-resize",
|
||||
CursorStyle::ResizeRow => "row-resize",
|
||||
CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",
|
||||
CursorStyle::OperationNotAllowed => "not-allowed",
|
||||
CursorStyle::DragLink => "alias",
|
||||
CursorStyle::DragCopy => "copy",
|
||||
CursorStyle::ContextualMenu => "context-menu",
|
||||
};
|
||||
|
||||
self.last_cursor_css.set(css_cursor);
|
||||
if self.cursor_visible.get() {
|
||||
set_body_cursor(&self.browser_window, css_cursor);
|
||||
}
|
||||
}
|
||||
self.last_cursor_css.set(css_cursor);
|
||||
if self.cursor_visible.get() {
|
||||
set_body_cursor(&self.browser_window, css_cursor);
|
||||
}
|
||||
}
|
||||
|
||||
fn hide_cursor_until_mouse_moves(&self) {
|
||||
if !self.cursor_visible.replace(false) {
|
||||
return;
|
||||
}
|
||||
set_body_cursor(&self.browser_window, "none");
|
||||
}
|
||||
fn hide_cursor_until_mouse_moves(&self) {
|
||||
if !self.cursor_visible.replace(false) {
|
||||
return;
|
||||
}
|
||||
set_body_cursor(&self.browser_window, "none");
|
||||
}
|
||||
|
||||
fn is_cursor_visible(&self) -> bool {
|
||||
self.cursor_visible.get()
|
||||
}
|
||||
fn is_cursor_visible(&self) -> bool {
|
||||
self.cursor_visible.get()
|
||||
}
|
||||
|
||||
fn should_auto_hide_scrollbars(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn should_auto_hide_scrollbars(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
None
|
||||
}
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
None
|
||||
}
|
||||
|
||||
fn write_to_clipboard(&self, _item: ClipboardItem) {}
|
||||
fn write_to_clipboard(&self, _item: ClipboardItem) {}
|
||||
|
||||
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
|
||||
Task::ready(Err(anyhow::anyhow!(
|
||||
"credential storage is not available on the web"
|
||||
)))
|
||||
}
|
||||
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
|
||||
Task::ready(Err(anyhow::anyhow!(
|
||||
"credential storage is not available on the web"
|
||||
)))
|
||||
}
|
||||
|
||||
fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
|
||||
Task::ready(Err(anyhow::anyhow!(
|
||||
"credential storage is not available on the web"
|
||||
)))
|
||||
}
|
||||
fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
|
||||
Task::ready(Err(anyhow::anyhow!(
|
||||
"credential storage is not available on the web"
|
||||
)))
|
||||
}
|
||||
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
|
||||
Box::new(WebKeyboardLayout)
|
||||
}
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
|
||||
Box::new(WebKeyboardLayout)
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
|
||||
Rc::new(DummyKeyboardMapper)
|
||||
}
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
|
||||
Rc::new(DummyKeyboardMapper)
|
||||
}
|
||||
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().keyboard_layout_change = Some(callback);
|
||||
}
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
|
||||
self.callbacks.borrow_mut().keyboard_layout_change = Some(callback);
|
||||
}
|
||||
}
|
||||
|
||||
struct EventListenerHandle {
|
||||
target: web_sys::EventTarget,
|
||||
event_name: &'static str,
|
||||
closure: Closure<dyn FnMut(JsValue)>,
|
||||
target: web_sys::EventTarget,
|
||||
event_name: &'static str,
|
||||
closure: Closure<dyn FnMut(JsValue)>,
|
||||
}
|
||||
|
||||
impl Drop for EventListenerHandle {
|
||||
fn drop(&mut self) {
|
||||
self.target
|
||||
.remove_event_listener_with_callback(
|
||||
self.event_name,
|
||||
self.closure.as_ref().unchecked_ref(),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
fn drop(&mut self) {
|
||||
self.target
|
||||
.remove_event_listener_with_callback(
|
||||
self.event_name,
|
||||
self.closure.as_ref().unchecked_ref(),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor_restore_listeners(
|
||||
browser_window: &web_sys::Window,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
last_cursor_css: Rc<Cell<&'static str>>,
|
||||
browser_window: &web_sys::Window,
|
||||
cursor_visible: Rc<Cell<bool>>,
|
||||
last_cursor_css: Rc<Cell<&'static str>>,
|
||||
) -> Vec<EventListenerHandle> {
|
||||
let mut handles = Vec::new();
|
||||
let Some(document) = browser_window.document() else {
|
||||
return handles;
|
||||
};
|
||||
let mut handles = Vec::new();
|
||||
let Some(document) = browser_window.document() else {
|
||||
return handles;
|
||||
};
|
||||
|
||||
let make_restore_handler = |browser_window: web_sys::Window| {
|
||||
let cursor_visible = cursor_visible.clone();
|
||||
let last_cursor_css = last_cursor_css.clone();
|
||||
Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
|
||||
if !cursor_visible.replace(true) {
|
||||
set_body_cursor(&browser_window, last_cursor_css.get());
|
||||
}
|
||||
})
|
||||
};
|
||||
let make_restore_handler = |browser_window: web_sys::Window| {
|
||||
let cursor_visible = cursor_visible.clone();
|
||||
let last_cursor_css = last_cursor_css.clone();
|
||||
Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
|
||||
if !cursor_visible.replace(true) {
|
||||
set_body_cursor(&browser_window, last_cursor_css.get());
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let mut add_listener = |target: &web_sys::EventTarget, event_name: &'static str| {
|
||||
let closure = make_restore_handler(browser_window.clone());
|
||||
target
|
||||
.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())
|
||||
.ok();
|
||||
handles.push(EventListenerHandle {
|
||||
target: target.clone(),
|
||||
event_name,
|
||||
closure,
|
||||
});
|
||||
};
|
||||
let mut add_listener = |target: &web_sys::EventTarget, event_name: &'static str| {
|
||||
let closure = make_restore_handler(browser_window.clone());
|
||||
target
|
||||
.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())
|
||||
.ok();
|
||||
handles.push(EventListenerHandle {
|
||||
target: target.clone(),
|
||||
event_name,
|
||||
closure,
|
||||
});
|
||||
};
|
||||
|
||||
let document_target: &web_sys::EventTarget = document.as_ref();
|
||||
let window_target: &web_sys::EventTarget = browser_window.as_ref();
|
||||
let document_target: &web_sys::EventTarget = document.as_ref();
|
||||
let window_target: &web_sys::EventTarget = browser_window.as_ref();
|
||||
|
||||
add_listener(document_target, "mousemove");
|
||||
add_listener(document_target, "mouseenter");
|
||||
add_listener(window_target, "blur");
|
||||
add_listener(document_target, "visibilitychange");
|
||||
add_listener(document_target, "mousemove");
|
||||
add_listener(document_target, "mouseenter");
|
||||
add_listener(window_target, "blur");
|
||||
add_listener(document_target, "visibilitychange");
|
||||
|
||||
handles
|
||||
handles
|
||||
}
|
||||
|
||||
fn set_body_cursor(browser_window: &web_sys::Window, css_cursor: &str) {
|
||||
if let Some(document) = browser_window.document()
|
||||
&& let Some(body) = document.body()
|
||||
&& let Err(error) = body.style().set_property("cursor", css_cursor)
|
||||
{
|
||||
log::warn!("Failed to set cursor style: {error:?}");
|
||||
}
|
||||
if let Some(document) = browser_window.document()
|
||||
&& let Some(body) = document.body()
|
||||
&& let Err(error) = body.style().set_property("cursor", css_cursor)
|
||||
{
|
||||
log::warn!("Failed to set cursor style: {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
+579
-579
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user