use oxiui_accessibility::FocusRing;
use oxiui_core::{
geometry::{Point, Rect},
paint::{DrawList, PathData, StrokeStyle},
Color,
};
pub fn push_focus_ring(list: &mut DrawList, widget_rect: Rect, ring: &FocusRing) {
let outset = ring.offset;
let expanded = Rect::new(
widget_rect.left() - outset,
widget_rect.top() - outset,
widget_rect.width() + 2.0 * outset,
widget_rect.height() + 2.0 * outset,
);
let color = Color(ring.color[0], ring.color[1], ring.color[2], ring.color[3]);
let style = StrokeStyle {
width: ring.width,
..StrokeStyle::default()
};
if ring.radius <= 0.0 {
let path = rect_path(expanded);
list.push_stroke_path(path, style, color);
} else {
let path = rounded_rect_path(expanded, ring.radius);
list.push_stroke_path(path, style, color);
}
}
fn rect_path(r: Rect) -> PathData {
let mut p = PathData::new();
p.move_to(Point::new(r.left(), r.top()));
p.line_to(Point::new(r.right(), r.top()));
p.line_to(Point::new(r.right(), r.bottom()));
p.line_to(Point::new(r.left(), r.bottom()));
p.close();
p
}
fn rounded_rect_path(r: Rect, radius: f32) -> PathData {
const KAPPA: f32 = 0.552_284_8;
let max_r = (r.width().min(r.height()) * 0.5).max(0.0);
let rr = radius.min(max_r);
let k = rr * KAPPA;
let l = r.left();
let t = r.top();
let ri = r.right();
let b = r.bottom();
let mut p = PathData::new();
p.move_to(Point::new(l + rr, t));
p.line_to(Point::new(ri - rr, t));
p.cubic_to(
Point::new(ri - rr + k, t),
Point::new(ri, t + rr - k),
Point::new(ri, t + rr),
);
p.line_to(Point::new(ri, b - rr));
p.cubic_to(
Point::new(ri, b - rr + k),
Point::new(ri - rr + k, b),
Point::new(ri - rr, b),
);
p.line_to(Point::new(l + rr, b));
p.cubic_to(
Point::new(l + rr - k, b),
Point::new(l, b - rr + k),
Point::new(l, b - rr),
);
p.line_to(Point::new(l, t + rr));
p.cubic_to(
Point::new(l, t + rr - k),
Point::new(l + rr - k, t),
Point::new(l + rr, t),
);
p.close();
p
}
pub fn is_high_contrast_active() -> bool {
oxiui_accessibility::OsA11yPrefs::query().high_contrast
}
#[cfg(test)]
mod tests {
use super::*;
use oxiui_accessibility::FocusRing;
use oxiui_core::{geometry::Rect, paint::DrawList};
fn sample_rect() -> Rect {
Rect::new(20.0, 20.0, 120.0, 60.0)
}
#[test]
fn focus_ring_sharp_corners_produces_one_command() {
let mut list = DrawList::new();
let ring = FocusRing {
color: [0, 120, 215, 255],
width: 2.0,
offset: 2.0,
radius: 0.0,
};
push_focus_ring(&mut list, sample_rect(), &ring);
assert_eq!(list.len(), 1, "one StrokePath for sharp-corner focus ring");
}
#[test]
fn focus_ring_rounded_corners_produces_one_command() {
let mut list = DrawList::new();
let ring = FocusRing::default();
push_focus_ring(&mut list, sample_rect(), &ring);
assert_eq!(list.len(), 1, "one StrokePath for rounded focus ring");
}
#[test]
fn focus_ring_expands_beyond_widget_rect() {
let mut list = DrawList::new();
let ring = FocusRing {
color: [0, 0, 0, 255],
width: 2.0,
offset: 4.0,
radius: 0.0,
};
push_focus_ring(&mut list, sample_rect(), &ring);
assert!(!list.is_empty());
}
#[test]
fn focus_ring_degenerate_rect_no_panic() {
let mut list = DrawList::new();
let ring = FocusRing {
radius: 100.0, ..FocusRing::default()
};
let tiny = Rect::new(0.0, 0.0, 4.0, 4.0);
push_focus_ring(&mut list, tiny, &ring);
assert_eq!(list.len(), 1);
}
#[test]
fn high_contrast_query_does_not_panic() {
let _ = is_high_contrast_active();
}
}