From a9fda66ac01b91582dd4d8f8dad1c8e1ffefa688 Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Fri, 4 Sep 2026 16:14:43 +0100 Subject: [PATCH] fix(ui): add non-color marker to CLI info entries and drop dead desk parameter - Add 'i ' prefix to EntryKind::Info and coordinate set_step_prompt deduplication to satisfy WCAG 1.4.1 across all 22 themes - Drop unused _desk parameter from crosshair_background and clean up its call sites - Document AutoCAD-standard context-based crosshair drafting surface architecture in crosshair_background - Add doc comments documenting deliberate alpha omission in canvas_is_light and backlog scope in is_wide --- src/app/view/mod.rs | 17 +++++++++-------- src/ui/command_line.rs | 37 ++++++++++++++++++++++++++++++++++--- src/ui/style/common.rs | 3 +++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 29ff085fb..0e6e3c4e3 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -36,9 +36,12 @@ pub(in crate::app) const VIEWPORT_CAPTURE_BOUNDS_ID: &str = "viewport-capture-bo const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_REGION_PX; -/// Base surface directly under the crosshair and selection marquee. -/// In paper space, the drafting surface is the white sheet (`tab.scene.paper_bg_color`). -fn crosshair_background(tab: &DocumentTab, is_paper: bool, _desk: [f32; 4]) -> [f32; 4] { +/// Base surface directly under the crosshair, selection marquee, and grid overlay. +/// In paper space, drafting surface context is unconditionally the white sheet (`tab.scene.paper_bg_color`), +/// matching AutoCAD's model (where crosshairs and overlays adapt per context — model vs. sheet/layout — +/// rather than per-pixel cursor sampling). This avoids flicker across sheet edges and provides a clean +/// dark crosshair that maintains ~5.85:1 contrast against the default medium-gray desk margin (#8A8A8A). +fn crosshair_background(tab: &DocumentTab, is_paper: bool) -> [f32; 4] { if !is_paper { return tab.scene.bg_color; } @@ -395,8 +398,7 @@ impl OpenCADStudio { } }) .collect(); - let desk_bg = self.model_space.resolve_desk_bg(); - let bg = crosshair_background(tab, is_paper, desk_bg); + let bg = crosshair_background(tab, is_paper); let bg_lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2]; let grid_style = crate::ui::overlay::GridStyle { opacity: self.model_space.grid_opacity, @@ -751,7 +753,7 @@ impl OpenCADStudio { tab.pan_mode || tab.orbit_mode || tab.zoom_dynamic_mode, self.ribbon.open_dropdown.is_some(), hover_locked, - crosshair_background(tab, is_paper, self.model_space.resolve_desk_bg()), + crosshair_background(tab, is_paper), crate::ui::overlay::CrosshairOptions { size_percent: self.cursor_size, pick_box: self.pick_box, @@ -3915,8 +3917,7 @@ mod tests { #[test] fn test_crosshair_background_paper_space() { let tab = crate::app::document::DocumentTab::new_drawing(1); - let desk_bg = [0.1, 0.1, 0.1, 1.0]; // Dark themed desk background - let bg = crosshair_background(&tab, true, desk_bg); + let bg = crosshair_background(&tab, true); assert_eq!(bg, tab.scene.paper_bg_color); assert!(crate::ui::style::common::canvas_is_light(bg)); } diff --git a/src/ui/command_line.rs b/src/ui/command_line.rs index 3ab4a610c..e2a78cd49 100644 --- a/src/ui/command_line.rs +++ b/src/ui/command_line.rs @@ -326,7 +326,12 @@ impl CommandLine { self.push(EntryKind::Error, text); } pub fn push_info(&mut self, msg: &str) { - self.push(EntryKind::Info, msg.to_string()); + let text = if msg.starts_with("i ") { + msg.to_string() + } else { + format!("i {msg}") + }; + self.push(EntryKind::Info, text); } fn push(&mut self, kind: EntryKind, text: String) { self.history.push(HistoryEntry { @@ -356,10 +361,15 @@ impl CommandLine { e.created_at = Instant::now(); } if let Some(p) = &prompt { + let formatted = if p.starts_with("i ") { + p.clone() + } else { + format!("i {p}") + }; // Reuse the prompt line dispatch/step-transition just pushed; // otherwise add it. - if self.history.last().map(|e| &e.text) != Some(p) { - self.push(EntryKind::Info, p.clone()); + if self.history.last().map(|e| &e.text) != Some(&formatted) { + self.push(EntryKind::Info, formatted); } if let Some(last) = self.history.last_mut() { last.pinned = true; @@ -1059,4 +1069,25 @@ mod tests { Some("MOVE 0,0 10,0") ); } + + #[test] + fn info_entries_carry_prefix_marker() { + let mut line = CommandLine::new(); + line.push_info("Object selected."); + assert_eq!(line.history.last().map(|e| e.text.as_str()), Some("i Object selected.")); + assert_eq!(line.history.last().map(|e| &e.kind), Some(&super::EntryKind::Info)); + } + + #[test] + fn set_step_prompt_reuses_push_info_entry() { + let mut line = CommandLine::new(); + let initial_len = line.history.len(); + line.push_info("Specify first point:"); + assert_eq!(line.history.len(), initial_len + 1); + line.set_step_prompt(Some("Specify first point:".to_string())); + assert_eq!(line.history.len(), initial_len + 1, "prompt must reuse push_info entry without duplicating"); + let last = line.history.last().unwrap(); + assert!(last.pinned); + assert_eq!(last.text, "i Specify first point:"); + } } diff --git a/src/ui/style/common.rs b/src/ui/style/common.rs index d5d57da63..82a3b9dba 100644 --- a/src/ui/style/common.rs +++ b/src/ui/style/common.rs @@ -70,11 +70,14 @@ pub(crate) fn accessible_accent(accent: Color, bg: Color, fallback: Color) -> Co } /// Returns true if the given canvas background color has a WCAG relative luminance > 0.5 (light surface). +/// Ignores alpha assuming canvas backgrounds are opaque (or composited against default viewport clear). pub(crate) fn canvas_is_light(bg: [f32; 4]) -> bool { wcag_luminance(Color::from_rgb(bg[0], bg[1], bg[2])) > 0.5 } /// True if a character is East Asian Wide or Fullwidth (CJK ideographs, kana, hangul, fullwidth forms). +/// Approximation of East Asian Wide/Fullwidth; not exhaustive (e.g. emoji ranges and CJK Extension B+ +/// beyond 0x2FA1F omitted for the backlog). pub(crate) fn is_wide(c: char) -> bool { let u = c as u32; matches!(