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
This commit is contained in:
Karim Jerbi committed 2026-09-04 16:26:13 +01:00
1 parent b53cd580f6
commit a9fda66ac0
3 files changed
+46 -11

No files matched your search

+9 -8
View File
@@ -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; const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_REGION_PX;
/// Base surface directly under the crosshair and selection marquee. /// Base surface directly under the crosshair, selection marquee, and grid overlay.
/// In paper space, the drafting surface is the white sheet (`tab.scene.paper_bg_color`). /// In paper space, drafting surface context is unconditionally the white sheet (`tab.scene.paper_bg_color`),
fn crosshair_background(tab: &DocumentTab, is_paper: bool, _desk: [f32; 4]) -> [f32; 4] { /// 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 { if !is_paper {
return tab.scene.bg_color; return tab.scene.bg_color;
} }
@@ -395,8 +398,7 @@ impl OpenCADStudio {
} }
}) })
.collect(); .collect();
let desk_bg = self.model_space.resolve_desk_bg(); let bg = crosshair_background(tab, is_paper);
let bg = crosshair_background(tab, is_paper, desk_bg);
let bg_lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2]; let bg_lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2];
let grid_style = crate::ui::overlay::GridStyle { let grid_style = crate::ui::overlay::GridStyle {
opacity: self.model_space.grid_opacity, opacity: self.model_space.grid_opacity,
@@ -751,7 +753,7 @@ impl OpenCADStudio {
tab.pan_mode || tab.orbit_mode || tab.zoom_dynamic_mode, tab.pan_mode || tab.orbit_mode || tab.zoom_dynamic_mode,
self.ribbon.open_dropdown.is_some(), self.ribbon.open_dropdown.is_some(),
hover_locked, hover_locked,
crosshair_background(tab, is_paper, self.model_space.resolve_desk_bg()), crosshair_background(tab, is_paper),
crate::ui::overlay::CrosshairOptions { crate::ui::overlay::CrosshairOptions {
size_percent: self.cursor_size, size_percent: self.cursor_size,
pick_box: self.pick_box, pick_box: self.pick_box,
@@ -3915,8 +3917,7 @@ mod tests {
#[test] #[test]
fn test_crosshair_background_paper_space() { fn test_crosshair_background_paper_space() {
let tab = crate::app::document::DocumentTab::new_drawing(1); 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);
let bg = crosshair_background(&tab, true, desk_bg);
assert_eq!(bg, tab.scene.paper_bg_color); assert_eq!(bg, tab.scene.paper_bg_color);
assert!(crate::ui::style::common::canvas_is_light(bg)); assert!(crate::ui::style::common::canvas_is_light(bg));
} }
+34 -3
View File
@@ -326,7 +326,12 @@ impl CommandLine {
self.push(EntryKind::Error, text); self.push(EntryKind::Error, text);
} }
pub fn push_info(&mut self, msg: &str) { 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) { fn push(&mut self, kind: EntryKind, text: String) {
self.history.push(HistoryEntry { self.history.push(HistoryEntry {
@@ -356,10 +361,15 @@ impl CommandLine {
e.created_at = Instant::now(); e.created_at = Instant::now();
} }
if let Some(p) = &prompt { 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; // Reuse the prompt line dispatch/step-transition just pushed;
// otherwise add it. // otherwise add it.
if self.history.last().map(|e| &e.text) != Some(p) { if self.history.last().map(|e| &e.text) != Some(&formatted) {
self.push(EntryKind::Info, p.clone()); self.push(EntryKind::Info, formatted);
} }
if let Some(last) = self.history.last_mut() { if let Some(last) = self.history.last_mut() {
last.pinned = true; last.pinned = true;
@@ -1059,4 +1069,25 @@ mod tests {
Some("MOVE 0,0 10,0") 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:");
}
} }
+3
View File
@@ -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). /// 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 { pub(crate) fn canvas_is_light(bg: [f32; 4]) -> bool {
wcag_luminance(Color::from_rgb(bg[0], bg[1], bg[2])) > 0.5 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). /// 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 { pub(crate) fn is_wide(c: char) -> bool {
let u = c as u32; let u = c as u32;
matches!( matches!(