perf: eliminate crosshair input lag by using immediate cursor position and forced view redraws
This commit is contained in:
@@ -691,6 +691,11 @@ pub enum Message {
|
||||
x: u32,
|
||||
y: u32,
|
||||
},
|
||||
/// Lightweight no-op message emitted by the canvas overlay on every
|
||||
/// `CursorMoved` event. Its sole purpose is to trigger a `view()`
|
||||
/// rebuild so the crosshair overlay redraws at the latest cursor
|
||||
/// position during idle (non-drawing) pointer movement.
|
||||
CursorOverlayRedraw,
|
||||
ModifiersChanged(iced::keyboard::Modifiers),
|
||||
SpacePanChanged(bool),
|
||||
|
||||
@@ -3554,6 +3559,11 @@ impl HcieIcedApp {
|
||||
self.canvas_cursor_pos = Some((x, y));
|
||||
}
|
||||
|
||||
// No-op — this message exists solely to trigger a view() rebuild
|
||||
// so the crosshair overlay redraws at the latest cursor position.
|
||||
// See Strategy 1 in the cursor lag fix.
|
||||
Message::CursorOverlayRedraw => {}
|
||||
|
||||
Message::CanvasZoomSet(z) => {
|
||||
let doc = &mut self.documents[self.active_doc];
|
||||
doc.zoom = z.clamp(ZOOM_MIN, ZOOM_MAX);
|
||||
|
||||
@@ -1169,7 +1169,7 @@ impl canvas::Program<Message> for OverlayProgram {
|
||||
renderer: &iced::Renderer,
|
||||
_theme: &iced::Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: Cursor,
|
||||
cursor: Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let (origin_x, origin_y, _display_w, _display_h) = self.canvas_origin(bounds.size());
|
||||
let mut geometries = Vec::new();
|
||||
@@ -2010,8 +2010,12 @@ impl canvas::Program<Message> for OverlayProgram {
|
||||
}
|
||||
|
||||
// ── Crosshair cursor ─────────────────────────────────────────────
|
||||
if state.is_hovered {
|
||||
if let Some(cursor) = state.cursor_pos {
|
||||
// Strategy 4: Use the `cursor` parameter from iced's current render
|
||||
// frame instead of the retained `state.cursor_pos` (which lags by
|
||||
// one Elm update cycle). This eliminates the 1-frame positional
|
||||
// delay between the OS pointer and the brush-circle indicator.
|
||||
if cursor.is_over(bounds) {
|
||||
if let Some(cursor_point) = cursor.position_in(bounds) {
|
||||
let mut crosshair_frame = Frame::new(renderer, bounds.size());
|
||||
let crosshair_size = 10.0;
|
||||
let crosshair_color = iced::Color::from_rgb(1.0, 1.0, 1.0);
|
||||
@@ -2023,7 +2027,7 @@ impl canvas::Program<Message> for OverlayProgram {
|
||||
| hcie_engine_api::Tool::Spray
|
||||
) {
|
||||
let footprint =
|
||||
Path::circle(cursor, (self.brush_size * self.zoom * 0.5).max(1.0));
|
||||
Path::circle(cursor_point, (self.brush_size * self.zoom * 0.5).max(1.0));
|
||||
crosshair_frame.stroke(
|
||||
&footprint,
|
||||
Stroke {
|
||||
@@ -2035,8 +2039,8 @@ impl canvas::Program<Message> for OverlayProgram {
|
||||
}
|
||||
crosshair_frame.stroke(
|
||||
&Path::line(
|
||||
Point::new(cursor.x - crosshair_size, cursor.y),
|
||||
Point::new(cursor.x + crosshair_size, cursor.y),
|
||||
Point::new(cursor_point.x - crosshair_size, cursor_point.y),
|
||||
Point::new(cursor_point.x + crosshair_size, cursor_point.y),
|
||||
),
|
||||
Stroke {
|
||||
style: canvas::stroke::Style::Solid(crosshair_color),
|
||||
@@ -2046,8 +2050,8 @@ impl canvas::Program<Message> for OverlayProgram {
|
||||
);
|
||||
crosshair_frame.stroke(
|
||||
&Path::line(
|
||||
Point::new(cursor.x, cursor.y - crosshair_size),
|
||||
Point::new(cursor.x, cursor.y + crosshair_size),
|
||||
Point::new(cursor_point.x, cursor_point.y - crosshair_size),
|
||||
Point::new(cursor_point.x, cursor_point.y + crosshair_size),
|
||||
),
|
||||
Stroke {
|
||||
style: canvas::stroke::Style::Solid(crosshair_color),
|
||||
@@ -2147,6 +2151,14 @@ impl canvas::Program<Message> for OverlayProgram {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Strategy 1: Emit a lightweight redraw message on every CursorMoved
|
||||
// event so iced triggers a view() rebuild and the overlay's draw()
|
||||
// runs with the latest cursor position. Without this, iced may
|
||||
// skip repaints during idle cursor movement because no application
|
||||
// state changed.
|
||||
if matches!(event, canvas::Event::Mouse(mouse::Event::CursorMoved { .. })) {
|
||||
return (canvas::event::Status::Ignored, Some(Message::CursorOverlayRedraw));
|
||||
}
|
||||
// Always pass events through to the shader widget below
|
||||
(canvas::event::Status::Ignored, None)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user