Skip to content

Commit 69dda56

Browse files
timon-schellingKeavon
authored andcommitted
custom cursors with caching
1 parent 2e44818 commit 69dda56

File tree

6 files changed

+68
-16
lines changed

6 files changed

+68
-16
lines changed

desktop/src/app.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,8 @@ impl App {
361361
}
362362
}
363363
AppEvent::CursorChange(cursor) => {
364-
if let Some(window) = &self.window {
365-
window.set_cursor(cursor);
364+
if let Some(window) = &mut self.window {
365+
window.set_cursor(event_loop, cursor);
366366
}
367367
}
368368
AppEvent::CloseWindow => {

desktop/src/cef.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,19 @@
1212
//!
1313
//! The system gracefully falls back to CPU textures when hardware acceleration is unavailable.
1414
15-
use crate::event::{AppEvent, AppEventScheduler};
16-
use crate::render::FrameBufferRef;
17-
use crate::wrapper::{WgpuContext, deserialize_editor_message};
1815
use std::fs::File;
19-
use std::io::{Cursor, Read};
16+
use std::io;
17+
use std::io::Read;
2018
use std::path::PathBuf;
2119
use std::sync::mpsc::Receiver;
2220
use std::sync::{Arc, Mutex};
2321
use std::time::Instant;
2422

23+
use crate::event::{AppEvent, AppEventScheduler};
24+
use crate::render::FrameBufferRef;
25+
use crate::window::Cursor;
26+
use crate::wrapper::{WgpuContext, deserialize_editor_message};
27+
2528
mod consts;
2629
mod context;
2730
mod dirs;
@@ -42,7 +45,7 @@ pub(crate) trait CefEventHandler: Send + Sync + 'static {
4245
#[cfg(feature = "accelerated_paint")]
4346
fn draw_gpu(&self, shared_texture: SharedTextureHandle);
4447
fn load_resource(&self, path: PathBuf) -> Option<Resource>;
45-
fn cursor_change(&self, cursor: winit::cursor::Cursor);
48+
fn cursor_change(&self, cursor: Cursor);
4649
/// Schedule the main event loop to run the CEF event loop after the timeout.
4750
/// See [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
4851
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
@@ -105,7 +108,7 @@ pub(crate) struct Resource {
105108
#[expect(dead_code)]
106109
#[derive(Clone)]
107110
pub(crate) enum ResourceReader {
108-
Embedded(Cursor<&'static [u8]>),
111+
Embedded(io::Cursor<&'static [u8]>),
109112
File(Arc<File>),
110113
}
111114
impl Read for ResourceReader {
@@ -227,7 +230,7 @@ impl CefEventHandler for CefHandler {
227230
&& let Some(file) = resources.get_file(&path)
228231
{
229232
return Some(Resource {
230-
reader: ResourceReader::Embedded(Cursor::new(file.contents())),
233+
reader: ResourceReader::Embedded(io::Cursor::new(file.contents())),
231234
mimetype,
232235
});
233236
}
@@ -252,7 +255,7 @@ impl CefEventHandler for CefHandler {
252255
None
253256
}
254257

255-
fn cursor_change(&self, cursor: winit::cursor::Cursor) {
258+
fn cursor_change(&self, cursor: Cursor) {
256259
self.app_event_scheduler.schedule(AppEvent::CursorChange(cursor));
257260
}
258261

desktop/src/cef/internal/display_handler.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use cef::rc::{Rc, RcImpl};
22
use cef::sys::{_cef_display_handler_t, cef_base_ref_counted_t, cef_cursor_type_t::*, cef_log_severity_t::*};
3-
use cef::{CefString, ImplDisplayHandler, WrapDisplayHandler};
3+
use cef::{CefString, ImplDisplayHandler, Point, Size, WrapDisplayHandler};
44
use winit::cursor::CursorIcon;
55

66
use crate::cef::CefEventHandler;
@@ -25,7 +25,21 @@ type CefCursorHandle = cef::CursorHandle;
2525
type CefCursorHandle = *mut u8;
2626

2727
impl<H: CefEventHandler> ImplDisplayHandler for DisplayHandlerImpl<H> {
28-
fn on_cursor_change(&self, _browser: Option<&mut cef::Browser>, _cursor: CefCursorHandle, cursor_type: cef::CursorType, _custom_cursor_info: Option<&cef::CursorInfo>) -> std::ffi::c_int {
28+
fn on_cursor_change(&self, _browser: Option<&mut cef::Browser>, _cursor: CefCursorHandle, cursor_type: cef::CursorType, custom_cursor_info: Option<&cef::CursorInfo>) -> std::ffi::c_int {
29+
if let Some(custom_cursor_info) = custom_cursor_info {
30+
let Size { width, height } = custom_cursor_info.size;
31+
let Point { x: hotspot_x, y: hotspot_y } = custom_cursor_info.hotspot;
32+
let buffer_size = (width * height * 4) as usize;
33+
let buffer_ptr = custom_cursor_info.buffer as *const u8;
34+
35+
if !buffer_ptr.is_null() && buffer_ptr.align_offset(std::mem::align_of::<u8>()) == 0 {
36+
let buffer = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) }.to_vec();
37+
let cursor = winit::cursor::CustomCursorSource::from_rgba(buffer, width as u16, height as u16, hotspot_x as u16, hotspot_y as u16).unwrap();
38+
self.event_handler.cursor_change(cursor.into());
39+
return 1; // We handled the cursor change.
40+
}
41+
}
42+
2943
let cursor = match cursor_type.into() {
3044
CT_POINTER => CursorIcon::Default,
3145
CT_CROSS => CursorIcon::Crosshair,
@@ -72,7 +86,6 @@ impl<H: CefEventHandler> ImplDisplayHandler for DisplayHandlerImpl<H> {
7286
CT_GRABBING => CursorIcon::Grabbing,
7387
CT_MIDDLE_PANNING_VERTICAL => CursorIcon::AllScroll,
7488
CT_MIDDLE_PANNING_HORIZONTAL => CursorIcon::AllScroll,
75-
CT_CUSTOM => CursorIcon::Default,
7689
CT_DND_NONE => CursorIcon::Default,
7790
CT_DND_MOVE => CursorIcon::Move,
7891
CT_DND_COPY => CursorIcon::Copy,

desktop/src/event.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::wrapper::messages::DesktopWrapperMessage;
33

44
pub(crate) enum AppEvent {
55
UiUpdate(wgpu::Texture),
6-
CursorChange(winit::cursor::Cursor),
6+
CursorChange(crate::window::Cursor),
77
ScheduleBrowserWork(std::time::Instant),
88
WebCommunicationInitialized,
99
DesktopWrapperMessage(DesktopWrapperMessage),

desktop/src/window.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use std::collections::HashMap;
12
use std::sync::Arc;
3+
use winit::cursor::{CursorIcon, CustomCursor, CustomCursorSource};
24
use winit::event_loop::ActiveEventLoop;
35
use winit::window::{Window as WinitWindow, WindowAttributes};
46

@@ -35,6 +37,7 @@ pub(crate) struct Window {
3537
winit_window: Arc<dyn winit::window::Window>,
3638
#[allow(dead_code)]
3739
native_handle: native::NativeWindowImpl,
40+
custom_cursors: HashMap<CustomCursorSource, CustomCursor>,
3841
}
3942

4043
impl Window {
@@ -57,6 +60,7 @@ impl Window {
5760
Self {
5861
winit_window: winit_window.into(),
5962
native_handle,
63+
custom_cursors: HashMap::new(),
6064
}
6165
}
6266

@@ -108,11 +112,43 @@ impl Window {
108112
self.native_handle.show_all();
109113
}
110114

111-
pub(crate) fn set_cursor(&self, cursor: winit::cursor::Cursor) {
115+
pub(crate) fn set_cursor(&mut self, event_loop: &dyn ActiveEventLoop, cursor: Cursor) {
116+
let cursor = match cursor {
117+
Cursor::Icon(cursor_icon) => cursor_icon.into(),
118+
Cursor::Custom(custom_cursor_source) => {
119+
let custom_cursor = match self.custom_cursors.get(&custom_cursor_source).cloned() {
120+
Some(cursor) => cursor,
121+
None => {
122+
let Ok(custom_cursor) = event_loop.create_custom_cursor(custom_cursor_source.clone()) else {
123+
tracing::error!("Failed to create custom cursor");
124+
return;
125+
};
126+
self.custom_cursors.insert(custom_cursor_source, custom_cursor.clone());
127+
custom_cursor
128+
}
129+
};
130+
custom_cursor.into()
131+
}
132+
};
112133
self.winit_window.set_cursor(cursor);
113134
}
114135

115136
pub(crate) fn update_menu(&self, entries: Vec<MenuItem>) {
116137
self.native_handle.update_menu(entries);
117138
}
118139
}
140+
141+
pub(crate) enum Cursor {
142+
Icon(CursorIcon),
143+
Custom(CustomCursorSource),
144+
}
145+
impl From<CursorIcon> for Cursor {
146+
fn from(icon: CursorIcon) -> Self {
147+
Cursor::Icon(icon)
148+
}
149+
}
150+
impl From<CustomCursorSource> for Cursor {
151+
fn from(custom: CustomCursorSource) -> Self {
152+
Cursor::Custom(custom)
153+
}
154+
}

frontend/package-installer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ if (isInstallNeeded()) {
3232
console.log("Finished installing npm packages.");
3333
} catch (_) {
3434
// eslint-disable-next-line no-console
35-
console.error("Failed to install npm packages. Please run `npm install` from the `/frontend` directory.");
35+
console.error("Failed to install npm packages. Please delete the `node_modules` folder and run `npm install` from the `/frontend` directory.");
3636
process.exit(1);
3737
}
3838
} else {

0 commit comments

Comments
 (0)