Skip to content
42 changes: 37 additions & 5 deletions core/src/graphics/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@
//! to convert SVGs to PNGs at construction time.

use crate::utils::geometry::Position;
use crate::utils::svg_renderer::{render_user_badge_to_png, SvgRenderError};
use crate::utils::svg_renderer::{render_user_badge_to_png, SvgRenderError, UserBadgeKind};
use iced::widget::canvas::Frame;
use iced::Rectangle;
use std::time::{Duration, Instant};

const PENCIL_BADGE_LOGICAL_HEIGHT: f32 = 75.0;
const PENCIL_TIP_LOGICAL: (f32, f32) = (1.2, 13.5);

/// Cursor display mode
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorMode {
/// Normal arrow cursor
Normal,
/// Pointer/hand cursor
Pointer,
/// Pencil cursor for drawing mode
Pencil,
}

/// An image-based cursor for rendering on iced canvas frames.
Expand All @@ -28,6 +33,10 @@ pub struct Cursor {
normal_cursor: (iced_core::image::Handle, (f32, f32)),
/// (handle, (width, height)) for pointer/hand cursor
pointer_cursor: (iced_core::image::Handle, (f32, f32)),
/// (handle, (width, height)) for pencil cursor
pencil_cursor: (iced_core::image::Handle, (f32, f32)),
/// Offset from the pencil badge top-left to the visible pencil tip
pencil_hotspot: (f32, f32),
/// Current position of the cursor
position: Option<Position>,
/// Current cursor display mode
Expand All @@ -39,15 +48,19 @@ pub struct Cursor {
impl Cursor {
/// Creates a new `Cursor` with the given color and name.
pub fn new(color: &str, name: &str) -> Result<Self, SvgRenderError> {
let normal_png = render_user_badge_to_png(color, name, false)?;
let pointer_png = render_user_badge_to_png(color, name, true)?;
let normal_png = render_user_badge_to_png(color, name, UserBadgeKind::Normal)?;
let pointer_png = render_user_badge_to_png(color, name, UserBadgeKind::Pointer)?;
let pencil_png = render_user_badge_to_png(color, name, UserBadgeKind::Pencil)?;

let normal_dims = image::load_from_memory(&normal_png).map_err(|e| {
SvgRenderError::PngSaveError(format!("Failed to read PNG dimensions: {e}"))
})?;
let pointer_dims = image::load_from_memory(&pointer_png).map_err(|e| {
SvgRenderError::PngSaveError(format!("Failed to read PNG dimensions: {e}"))
})?;
let pencil_dims = image::load_from_memory(&pencil_png).map_err(|e| {
SvgRenderError::PngSaveError(format!("Failed to read PNG dimensions: {e}"))
})?;

let normal_cursor = (
iced_core::image::Handle::from_bytes(normal_png),
Expand All @@ -63,11 +76,25 @@ impl Cursor {
pointer_dims.height() as f32 / 2.5,
),
);
let pencil_cursor = (
iced_core::image::Handle::from_bytes(pencil_png),
(
pencil_dims.width() as f32 / 2.5,
pencil_dims.height() as f32 / 2.5,
),
);
let pencil_scale = pencil_cursor.1 .1 / PENCIL_BADGE_LOGICAL_HEIGHT;
let pencil_hotspot = (
PENCIL_TIP_LOGICAL.0 * pencil_scale,
PENCIL_TIP_LOGICAL.1 * pencil_scale,
);

Ok(Self {
visible_name: name.to_string(),
normal_cursor,
pointer_cursor,
pencil_cursor,
pencil_hotspot,
position: None,
mode: CursorMode::Normal,
last_update: None,
Expand Down Expand Up @@ -112,15 +139,20 @@ impl Cursor {

let (handle, (width, height)) = match self.mode {
CursorMode::Pointer => &self.pointer_cursor,
CursorMode::Pencil => &self.pencil_cursor,
CursorMode::Normal => &self.normal_cursor,
};
let (hotspot_x, hotspot_y) = match self.mode {
CursorMode::Pencil => self.pencil_hotspot,
_ => (0.0, 0.0),
};

let image = iced_core::image::Image::new(handle.clone());
let position = translate(self.position.unwrap());
frame.draw_image(
Rectangle {
x: position.x as f32,
y: position.y as f32,
x: position.x as f32 - hotspot_x,
y: position.y as f32 - hotspot_y,
width: *width,
height: *height,
},
Expand Down
71 changes: 35 additions & 36 deletions core/src/input/mouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ impl CursorState {
}

struct ControllerCursor {
/// Cursor state
cursor_state: CursorState,
/*
* This is used to record when the controller
Expand All @@ -240,18 +239,20 @@ struct ControllerCursor {
*/
clicked: bool,
mode: CursorMode,
pointer_mode: bool,
/// Caches mode before controllers are globally disabled.
/// `Some` = controllers disabled, `None` = controllers enabled.
cached_mode: Option<CursorMode>,
has_control: bool,
identity: String,
}

impl ControllerCursor {
fn new(cursor_state: CursorState, identity: String, mode: CursorMode) -> Self {
fn new(cursor_state: CursorState, identity: String) -> Self {
Self {
cursor_state,
clicked: false,
mode,
pointer_mode: mode == CursorMode::Pointer,
mode: CursorMode::Normal,
cached_mode: None,
has_control: false,
identity,
}
Expand Down Expand Up @@ -284,20 +285,24 @@ impl ControllerCursor {
}

fn set_mode(&mut self, mode: CursorMode) {
self.mode = mode;
if self.cached_mode.is_some() {
self.cached_mode = Some(mode);
} else {
self.mode = mode;
}
}

fn set_pointer_mode(&mut self, enabled: bool, remote_control_enabled: bool) {
if !enabled && remote_control_enabled {
self.mode = CursorMode::Normal;
} else {
self.mode = CursorMode::Pointer;
fn disable(&mut self) {
if self.cached_mode.is_none() {
self.cached_mode = Some(self.mode);
}
self.pointer_mode = enabled;
self.mode = CursorMode::Pointer;
}

fn pointer_mode(&self) -> bool {
self.pointer_mode
fn enable(&mut self) {
if let Some(cached) = self.cached_mode.take() {
self.mode = cached;
}
}

fn clicked(&mut self) -> bool {
Expand Down Expand Up @@ -664,16 +669,14 @@ impl CursorController {
}
}

let mode = if self.controllers_cursors_enabled {
CursorMode::Normal
} else {
CursorMode::Pointer
};
controllers_cursors.push(ControllerCursor::new(
let mut controller = ControllerCursor::new(
CursorState::new(self.redraw_thread_sender.clone(), self.clock.clone()),
identity,
mode,
));
);
if !self.controllers_cursors_enabled {
controller.disable();
}
controllers_cursors.push(controller);
}

/// Removes a remote controller from the cursor management system.
Expand Down Expand Up @@ -957,13 +960,9 @@ impl CursorController {
let mut any_had_control = false;
for controller in controllers_cursors.iter_mut() {
if enabled {
if controller.pointer_mode() {
controller.set_mode(CursorMode::Pointer);
} else {
controller.set_mode(CursorMode::Normal);
}
controller.enable();
} else {
controller.set_mode(CursorMode::Pointer);
controller.disable();
}

if controller.has_control() {
Expand All @@ -986,13 +985,11 @@ impl CursorController {
}
}

/// Switch pointer mode made by the controller.
/// # Parameters
/// Sets the cursor mode for a specific controller.
///
/// * `identity` - Session ID identifying which controller to modify
/// * `enabled` - Whether to enable (true) or disable (false) pointer mode for the specified controller
pub fn set_controller_pointer(&mut self, enabled: bool, identity: &str) {
log::info!("set_controller_pointer: {identity} {enabled}");
/// If the controller currently has control, it is given back to the sharer.
pub fn set_controller_mode(&mut self, identity: &str, mode: CursorMode) {
log::info!("set_controller_mode: {identity} {mode:?}");

let had_control = {
let mut controllers_cursors = self.controllers_cursors.lock().unwrap();
Expand All @@ -1003,12 +1000,14 @@ impl CursorController {
}

if controller.has_control() {
log::info!("set_controller_pointer: controller {identity} has control, give control back to sharer.");
log::info!(
"set_controller_mode: controller {identity} has control, giving back to sharer."
);
controller.show();
had_control = true;
}

controller.set_pointer_mode(enabled, self.controllers_cursors_enabled);
controller.set_mode(mode);
break;
}
had_control
Expand Down
19 changes: 11 additions & 8 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub(crate) mod windows;

use camera::capturer::{poll_camera_stream, CameraCapturer};
use capture::capturer::{poll_stream, Capturer};
use graphics::graphics_context::participant::CursorMode;
use graphics::graphics_context::GraphicsContext;
use graphics::graphics_window_context::ContextManager;
use input::clipboard::ClipboardController;
Expand Down Expand Up @@ -1258,17 +1259,19 @@ impl<'a> ApplicationHandler<UserEvent> for Application<'a> {
if let (Some(window_manager), Some(remote_control)) =
(self.window_manager.as_mut(), self.remote_control.as_mut())
{
let cursor_controller = &mut remote_control.cursor_controller;
match &drawing_mode {
DrawingMode::Disabled => {
remote_control
.cursor_controller
.set_controller_pointer(false, sid.as_str());
DrawingMode::Draw(_) => {
cursor_controller.set_controller_mode(sid.as_str(), CursorMode::Pencil);
}
DrawingMode::ClickAnimation => {
cursor_controller
.set_controller_mode(sid.as_str(), CursorMode::Pointer);
}
_ => {
remote_control
.cursor_controller
.set_controller_pointer(true, sid.as_str());
DrawingMode::Disabled => {
cursor_controller.set_controller_mode(sid.as_str(), CursorMode::Normal);
}
DrawingMode::Any => {}
}
if let Some(gfx) = window_manager.active_gfx_mut() {
gfx.set_drawing_mode(sid.as_str(), drawing_mode.clone());
Expand Down
Loading
Loading