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,
|
x: u32,
|
||||||
y: 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),
|
ModifiersChanged(iced::keyboard::Modifiers),
|
||||||
SpacePanChanged(bool),
|
SpacePanChanged(bool),
|
||||||
|
|
||||||
@@ -3554,6 +3559,11 @@ impl HcieIcedApp {
|
|||||||
self.canvas_cursor_pos = Some((x, y));
|
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) => {
|
Message::CanvasZoomSet(z) => {
|
||||||
let doc = &mut self.documents[self.active_doc];
|
let doc = &mut self.documents[self.active_doc];
|
||||||
doc.zoom = z.clamp(ZOOM_MIN, ZOOM_MAX);
|
doc.zoom = z.clamp(ZOOM_MIN, ZOOM_MAX);
|
||||||
|
|||||||
@@ -1169,7 +1169,7 @@ impl canvas::Program<Message> for OverlayProgram {
|
|||||||
renderer: &iced::Renderer,
|
renderer: &iced::Renderer,
|
||||||
_theme: &iced::Theme,
|
_theme: &iced::Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: Cursor,
|
cursor: Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) -> Vec<canvas::Geometry> {
|
||||||
let (origin_x, origin_y, _display_w, _display_h) = self.canvas_origin(bounds.size());
|
let (origin_x, origin_y, _display_w, _display_h) = self.canvas_origin(bounds.size());
|
||||||
let mut geometries = Vec::new();
|
let mut geometries = Vec::new();
|
||||||
@@ -2010,8 +2010,12 @@ impl canvas::Program<Message> for OverlayProgram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Crosshair cursor ─────────────────────────────────────────────
|
// ── Crosshair cursor ─────────────────────────────────────────────
|
||||||
if state.is_hovered {
|
// Strategy 4: Use the `cursor` parameter from iced's current render
|
||||||
if let Some(cursor) = state.cursor_pos {
|
// 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 mut crosshair_frame = Frame::new(renderer, bounds.size());
|
||||||
let crosshair_size = 10.0;
|
let crosshair_size = 10.0;
|
||||||
let crosshair_color = iced::Color::from_rgb(1.0, 1.0, 1.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
|
| hcie_engine_api::Tool::Spray
|
||||||
) {
|
) {
|
||||||
let footprint =
|
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(
|
crosshair_frame.stroke(
|
||||||
&footprint,
|
&footprint,
|
||||||
Stroke {
|
Stroke {
|
||||||
@@ -2035,8 +2039,8 @@ impl canvas::Program<Message> for OverlayProgram {
|
|||||||
}
|
}
|
||||||
crosshair_frame.stroke(
|
crosshair_frame.stroke(
|
||||||
&Path::line(
|
&Path::line(
|
||||||
Point::new(cursor.x - crosshair_size, cursor.y),
|
Point::new(cursor_point.x - crosshair_size, cursor_point.y),
|
||||||
Point::new(cursor.x + crosshair_size, cursor.y),
|
Point::new(cursor_point.x + crosshair_size, cursor_point.y),
|
||||||
),
|
),
|
||||||
Stroke {
|
Stroke {
|
||||||
style: canvas::stroke::Style::Solid(crosshair_color),
|
style: canvas::stroke::Style::Solid(crosshair_color),
|
||||||
@@ -2046,8 +2050,8 @@ impl canvas::Program<Message> for OverlayProgram {
|
|||||||
);
|
);
|
||||||
crosshair_frame.stroke(
|
crosshair_frame.stroke(
|
||||||
&Path::line(
|
&Path::line(
|
||||||
Point::new(cursor.x, cursor.y - crosshair_size),
|
Point::new(cursor_point.x, cursor_point.y - crosshair_size),
|
||||||
Point::new(cursor.x, cursor.y + crosshair_size),
|
Point::new(cursor_point.x, cursor_point.y + crosshair_size),
|
||||||
),
|
),
|
||||||
Stroke {
|
Stroke {
|
||||||
style: canvas::stroke::Style::Solid(crosshair_color),
|
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
|
// Always pass events through to the shader widget below
|
||||||
(canvas::event::Status::Ignored, None)
|
(canvas::event::Status::Ignored, None)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user