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;
/// 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));
}
+34 -3
View File
@@ -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:");
}
}
+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).
/// 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!(