diff --git a/crates/draw-engine/assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 b/crates/draw-engine/assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 index 51e6f53..4f335a7 100644 Binary files a/crates/draw-engine/assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 and b/crates/draw-engine/assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 differ diff --git a/crates/draw-engine/assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 b/crates/draw-engine/assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 index 1fe1443..ef33ce8 100644 Binary files a/crates/draw-engine/assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 and b/crates/draw-engine/assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 differ diff --git a/crates/draw-engine/src/engine/frame.rs b/crates/draw-engine/src/engine/frame.rs index 07712c1..566aa19 100644 --- a/crates/draw-engine/src/engine/frame.rs +++ b/crates/draw-engine/src/engine/frame.rs @@ -61,6 +61,8 @@ pub struct PaintView<'a> { /// picture of the board, which it changes on every press without touching. pub flowchart_pending: Vec<&'a DrawElement>, pub selected: Vec<&'a DrawElement>, + /// The selectable element currently beneath the pointer, before a press commits selection. + pub hovered: Option<&'a DrawElement>, /// Where a multi-selection's box and handles go: around what a transform carries, /// the same box the handles are hit on. `None` when fewer than two are carried. pub group_box: Option, @@ -169,6 +171,7 @@ fn without_editor_chrome(view: &mut PaintView<'_>) { view.flowchart_pending = Vec::new(); view.laser = Vec::new(); view.peer_lasers = Vec::new(); + view.hovered = None; view.peer_marks = Vec::new(); view.snap_guides = Vec::new(); view.linear_handles = Vec::new(); @@ -403,6 +406,11 @@ impl DrawEngine { .filter_map(|id| self.scene.get(id)) .filter(|el| !el.is_deleted) .collect(); + let hovered = self + .hovered_id + .as_deref() + .and_then(|id| self.scene.get(id)) + .filter(|el| !el.is_deleted); let lasso = match &self.interaction { Some(super::Interaction::Lasso { path, .. }) => path.clone(), _ => Vec::new(), @@ -554,6 +562,7 @@ impl DrawEngine { erasing_revision: self.erasing_revision, flowchart_pending, selected, + hovered, group_box, marquee, lasso, diff --git a/crates/draw-engine/src/engine/hover.rs b/crates/draw-engine/src/engine/hover.rs index c8eb686..099d295 100644 --- a/crates/draw-engine/src/engine/hover.rs +++ b/crates/draw-engine/src/engine/hover.rs @@ -99,8 +99,18 @@ impl DrawEngine { /// outline a few units off an intersection lights whatever that intersection is over, and /// near the tolerance's edge it lights **nothing** at all. pub fn hover_pointer(&mut self, sx: f64, sy: f64) { - if self.tool != DrawTool::Arrow || self.interaction.is_some() || self.multi_linear.is_some() - { + if self.interaction.is_some() || self.multi_linear.is_some() { + return; + } + if self.tool == DrawTool::Select { + let next = self.pressed_at(sx, sy).map(|el| el.id.clone()); + if next != self.hovered_id { + self.hovered_id = next; + self.request_draw(); + } + return; + } + if self.tool != DrawTool::Arrow { return; } let world = self.screen_to_world(sx, sy); @@ -145,10 +155,14 @@ impl DrawEngine { if self.interaction.is_some() || self.multi_linear.is_some() { return; } + let hover_changed = self.hovered_id.take().is_some(); if self.binding_highlight.take().is_some() { self.binding_point = None; self.request_draw(); } + if hover_changed { + self.request_draw(); + } } /// Ctrl/Cmd, as held for the pointer event about to be reported: while it is down an diff --git a/crates/draw-engine/src/engine/mod.rs b/crates/draw-engine/src/engine/mod.rs index 7fbd378..18b3c00 100644 --- a/crates/draw-engine/src/engine/mod.rs +++ b/crates/draw-engine/src/engine/mod.rs @@ -283,6 +283,8 @@ pub struct DrawEngine { settled_selection: std::rc::Rc, /// Between a press and its release, when what the press selects is not settled yet. pointer_open: bool, + /// Element currently under the pointer in the Select tool, for immediate hover feedback. + hovered_id: Option, /// The right-button session in progress, if one is. Outlives the gesture it holds, /// because the platform's `contextmenu` can arrive after the release that ended it — /// see [`Self::consumes_context_menu`] and [`crate::pan`]. @@ -406,6 +408,7 @@ impl DrawEngine { history_seq: 0, settled_selection: std::rc::Rc::default(), pointer_open: false, + hovered_id: None, secondary_pan: None, suppress_next_context_menu: false, remote_refused: std::collections::HashMap::new(), @@ -803,6 +806,9 @@ impl DrawEngine { } pub fn set_tool(&mut self, tool: DrawTool) { + if self.tool != tool { + self.hovered_id = None; + } self.set_tool_step(tool); self.refresh_live(); } diff --git a/crates/draw-engine/src/wasm/paint.rs b/crates/draw-engine/src/wasm/paint.rs index 64176e5..b2c9856 100644 --- a/crates/draw-engine/src/wasm/paint.rs +++ b/crates/draw-engine/src/wasm/paint.rs @@ -2059,6 +2059,16 @@ fn paint_overlay(ctx: &CanvasRenderingContext2d, view: &PaintView) { } } + if let Some(hovered) = view.hovered { + // Hover feedback is deliberately an outline only: it previews the click without + // adding handles or changing the committed selection. + ctx.save(); + set_stroke(ctx, &view.theme.accent); + ctx.set_global_alpha(0.75); + paint_element_outline(ctx, view, hovered); + ctx.restore(); + } + if view.selected.len() == 1 { paint_shape_selection(ctx, view, view.selected[0]); paint_radius_handles(ctx, view); diff --git a/crates/draw-engine/tests/ci_svg_fonts_bytes.rs b/crates/draw-engine/tests/ci_svg_fonts_bytes.rs index 2ac3f40..2a9f457 100644 --- a/crates/draw-engine/tests/ci_svg_fonts_bytes.rs +++ b/crates/draw-engine/tests/ci_svg_fonts_bytes.rs @@ -91,11 +91,11 @@ const SHIPPED_CRC32: [(&str, u32); 10] = [ ), ( "Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2", - 0x1380_bdc3, + 0x9a95_2276, ), ( "Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2", - 0x8a23_e0d0, + 0xda05_848c, ), ]; @@ -362,11 +362,11 @@ fn the_shipped_files_hash_to_the_recorded_values() { ), ( include_bytes!("../assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2"), - 0x1380_bdc3, + 0x9a95_2276, ), ( include_bytes!("../assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2"), - 0x8a23_e0d0, + 0xda05_848c, ), ]; for (bytes, expected) in &files {