forked from 1b-agent/opencadstudio-upstream
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:
1 parent
b53cd580f6
commit
a9fda66ac0
3 files changed
+46
-11
No files matched your search
+9
-8
@@ -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
@@ -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:");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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!(
|
||||||
|
|||||||
Reference in new issue
Block a user