Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added core/resources/click_texture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
716 changes: 716 additions & 0 deletions core/src/graphics/click_animation.rs

Large diffs are not rendered by default.

118 changes: 5 additions & 113 deletions core/src/graphics/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
use crate::utils::geometry::Extent;
use wgpu::util::DeviceExt;

use super::{create_texture, GraphicsContext, OverlayError, Texture, Vertex};
use super::{
create_texture,
point::{Point, TransformMatrix},
GraphicsContext, OverlayError, Texture, Vertex,
};

/// Maximum number of cursors that can be rendered simultaneously
const MAX_CURSORS: u32 = 100;
Expand All @@ -17,118 +21,6 @@ const BASE_OFFSET_X: f32 = 0.001;
/// Base vertical offset for cursor positioning (as a fraction of screen space)
const BASE_OFFSET_Y: f32 = 0.002;

/// A 4x4 transformation matrix for GPU vertex transformations.
///
/// This matrix is used to transform cursor vertices in the shader,
/// primarily for positioning cursors at specific screen coordinates.
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct TransformMatrix {
pub matrix: [[f32; 4]; 4],
}

/// Uniform buffer data structure containing a transformation matrix.
///
/// This struct is uploaded to the GPU as a uniform buffer to provide
/// transformation data to the vertex shader for cursor positioning.
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct TranslationUniform {
transform: TransformMatrix,
}

impl TranslationUniform {
/// Creates a new translation uniform with an identity transformation matrix.
///
/// The identity matrix means no transformation is applied initially.
fn new() -> Self {
Self {
transform: TransformMatrix {
matrix: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
},
}
}

/// Sets the translation component of the transformation matrix.
///
/// # Arguments
/// * `x` - Horizontal translation in normalized device coordinates (-1.0 to 1.0)
/// * `y` - Vertical translation in normalized device coordinates (-1.0 to 1.0)
///
/// # Note
/// The coordinates are multiplied by 2.0 because the input is expected to be
/// in the range 0.0-1.0, but NDC space ranges from -1.0 to 1.0.
/// Y is negated to match screen coordinate conventions.
fn set_translation(&mut self, x: f32, y: f32) {
// We need to multiply by 2.0 because the cursor position is in the range of -1.0 to 1.0
self.transform.matrix[3][0] = x * 2.0;
self.transform.matrix[3][1] = -y * 2.0;
}
}

/// Represents a point in 2D space with position and offset information.
///
/// This struct manages cursor positioning with both absolute coordinates
/// and rendering offsets. The transform matrix is automatically updated
/// when the position changes.
#[derive(Debug)]
struct Point {
/// Absolute X coordinate
x: f32,
/// Absolute Y coordinate
y: f32,
/// Horizontal rendering offset
offset_x: f32,
/// Vertical rendering offset
offset_y: f32,
/// GPU transformation matrix for this point
transform_matrix: TranslationUniform,
}

impl Point {
/// Creates a new point with the specified position and offsets.
///
/// # Arguments
/// * `x` - Initial X coordinate
/// * `y` - Initial Y coordinate
/// * `offset_x` - Horizontal rendering offset
/// * `offset_y` - Vertical rendering offset
fn new(x: f32, y: f32, offset_x: f32, offset_y: f32) -> Self {
Self {
x,
y,
offset_x,
offset_y,
transform_matrix: TranslationUniform::new(),
}
}

/// Returns the current transformation matrix for GPU upload.
fn get_transform_matrix(&self) -> TransformMatrix {
self.transform_matrix.transform
}

/// Updates the point's position and recalculates the transformation matrix.
///
/// # Arguments
/// * `x` - New X coordinate
/// * `y` - New Y coordinate
///
/// The transformation matrix is updated to position the cursor at the
/// specified coordinates, accounting for the configured offsets.
fn set_position(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
self.transform_matrix
.set_translation(x - self.offset_x, y - self.offset_y);
}
}

/// Represents a single cursor with its texture, geometry, and position data.
///
/// Each cursor maintains its own vertex and index buffers for geometry,
Expand Down
39 changes: 37 additions & 2 deletions core/src/graphics/graphics_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
//! such as cursors and markers on top of shared screen content. It uses wgpu for
//! hardware-accelerated rendering with proper alpha blending and transparent window support.

use crate::input::mouse::CursorController;
use crate::utils::geometry::Extent;
use crate::{input::mouse::CursorController, utils::geometry::Position};
use image::GenericImageView;
use log::error;
use std::sync::Arc;
Expand All @@ -23,6 +23,13 @@ use marker::MarkerRenderer;
pub mod cursor;
use cursor::{Cursor, CursorsRenderer};

#[path = "click_animation.rs"]
pub mod click_animation;
use click_animation::ClickAnimationRenderer;

#[path = "point.rs"]
pub mod point;

/// Errors that can occur during overlay graphics operations.
#[derive(Error, Debug)]
pub enum OverlayError {
Expand Down Expand Up @@ -128,6 +135,9 @@ pub struct GraphicsContext<'a> {

/// Renderer for corner markers indicating overlay boundaries
marker_renderer: MarkerRenderer,

/// Renderer for click animations
click_animation_renderer: ClickAnimationRenderer,
}

impl<'a> GraphicsContext<'a> {
Expand Down Expand Up @@ -275,6 +285,18 @@ impl<'a> GraphicsContext<'a> {
scale,
)?;

let click_animation_renderer = ClickAnimationRenderer::create(
&device,
&queue,
surface_config.format,
&texture_path,
Extent {
width: size.width as f64,
height: size.height as f64,
},
scale,
)?;

Ok(Self {
surface,
device,
Expand All @@ -284,6 +306,7 @@ impl<'a> GraphicsContext<'a> {
#[cfg(target_os = "windows")]
_direct_composition: direct_composition,
marker_renderer,
click_animation_renderer,
})
}

Expand Down Expand Up @@ -345,7 +368,7 @@ impl<'a> GraphicsContext<'a> {
/// If frame acquisition fails (e.g., surface lost), the method logs the error
/// and returns early without crashing. This provides resilience against
/// temporary graphics driver issues or window state changes.
pub fn draw(&self, cursor_controller: &CursorController) {
pub fn draw(&mut self, cursor_controller: &CursorController) {
let output = match self.surface.get_current_texture() {
Ok(output) => output,
Err(e) => {
Expand Down Expand Up @@ -385,6 +408,8 @@ impl<'a> GraphicsContext<'a> {
cursor_controller.draw(&mut render_pass, self);

self.marker_renderer.draw(&mut render_pass);
self.click_animation_renderer
.draw(&mut render_pass, &self.queue);

drop(render_pass);

Expand All @@ -403,6 +428,16 @@ impl<'a> GraphicsContext<'a> {
pub fn window(&self) -> &Window {
&self.window
}

/// Requests to enable a click animation at the specified position.
///
/// # Arguments
/// * `position` - Screen position where the animation should appear
pub fn enable_click_animation(&mut self, position: Position) {
log::debug!("GraphicsContext::enable_click_animation: {position:?}");
self.click_animation_renderer
.enable_click_animation(position);
}
}

/// Creates a GPU texture from an image file for overlay rendering.
Expand Down
111 changes: 111 additions & 0 deletions core/src/graphics/point.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/// A 4x4 transformation matrix for GPU vertex transformations.
///
/// This matrix is used to transform cursor vertices in the shader,
/// primarily for positioning cursors at specific screen coordinates.
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct TransformMatrix {
pub matrix: [[f32; 4]; 4],
}

/// Uniform buffer data structure containing a transformation matrix.
///
/// This struct is uploaded to the GPU as a uniform buffer to provide
/// transformation data to the vertex shader for cursor positioning.
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct TranslationUniform {
transform: TransformMatrix,
}

impl TranslationUniform {
/// Creates a new translation uniform with an identity transformation matrix.
///
/// The identity matrix means no transformation is applied initially.
fn new() -> Self {
Self {
transform: TransformMatrix {
matrix: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
},
}
}

/// Sets the translation component of the transformation matrix.
///
/// # Arguments
/// * `x` - Horizontal translation in normalized device coordinates (-1.0 to 1.0)
/// * `y` - Vertical translation in normalized device coordinates (-1.0 to 1.0)
///
/// # Note
/// The coordinates are multiplied by 2.0 because the input is expected to be
/// in the range 0.0-1.0, but NDC space ranges from -1.0 to 1.0.
/// Y is negated to match screen coordinate conventions.
fn set_translation(&mut self, x: f32, y: f32) {
// We need to multiply by 2.0 because the cursor position is in the range of -1.0 to 1.0
self.transform.matrix[3][0] = x * 2.0;
self.transform.matrix[3][1] = -y * 2.0;
}
}

/// Represents a point in 2D space with position and offset information.
///
/// This struct manages cursor positioning with both absolute coordinates
/// and rendering offsets. The transform matrix is automatically updated
/// when the position changes.
#[derive(Debug)]
pub struct Point {
/// Absolute X coordinate
x: f32,
/// Absolute Y coordinate
y: f32,
/// Horizontal rendering offset
offset_x: f32,
/// Vertical rendering offset
offset_y: f32,
/// GPU transformation matrix for this point
transform_matrix: TranslationUniform,
}

impl Point {
/// Creates a new point with the specified position and offsets.
///
/// # Arguments
/// * `x` - Initial X coordinate
/// * `y` - Initial Y coordinate
/// * `offset_x` - Horizontal rendering offset
/// * `offset_y` - Vertical rendering offset
pub fn new(x: f32, y: f32, offset_x: f32, offset_y: f32) -> Self {
Self {
x,
y,
offset_x,
offset_y,
transform_matrix: TranslationUniform::new(),
}
}

/// Returns the current transformation matrix for GPU upload.
pub fn get_transform_matrix(&self) -> TransformMatrix {
self.transform_matrix.transform
}

/// Updates the point's position and recalculates the transformation matrix.
///
/// # Arguments
/// * `x` - New X coordinate
/// * `y` - New Y coordinate
///
/// The transformation matrix is updated to position the cursor at the
/// specified coordinates, accounting for the configured offsets.
pub fn set_position(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
self.transform_matrix
.set_translation(x - self.offset_x, y - self.offset_y);
}
}
33 changes: 33 additions & 0 deletions core/src/graphics/shader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,37 @@ fn vs_lines_main(
out.texture_coords = model.texture_coords;
out.clip_position = vec4<f32>(model.position, 0.0, 1.0);
return out;
}

@vertex
fn vs_click_animation_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.texture_coords = model.texture_coords;
out.clip_position = coords.transform * vec4<f32>(model.position, 0.0, 1.0);
return out;
}

@group(2) @binding(0)
var<uniform> radius: f32;

@fragment
fn fs_click_animation_main(in: VertexOutput) -> @location(0) vec4<f32> {
let color = textureSample(t_diffuse, s_diffuse, in.texture_coords);
let centered_coords = in.texture_coords - vec2<f32>(0.5, 0.5);
let dist = length(centered_coords);

let radius_start = 0.1;
if radius == radius_start {
let alpha = 1.0 - smoothstep(radius, radius + 0.1, dist);
return vec4<f32>(color.rgb, color.a * alpha);
} else {
let ring_width = 0.01;
let edge = 0.02;
let outer = smoothstep(radius - ring_width - edge, radius - ring_width + edge, dist);
let inner = 1.0 - smoothstep(radius + ring_width - edge, radius + ring_width + edge, dist);
let alpha = inner * outer;
return vec4<f32>(color.rgb, color.a * alpha);
}
}
Loading
Loading