use a7n_proto::v1::Edge;
use super::super::Size;
use crate::input::Bounds;
const PROGRESS_FULL: u64 = 1000;
const GLOW_THICKNESS: u32 = 6;
pub(super) fn pick_monitor(monitors: &[Bounds], display: Bounds, x: i32, y: i32) -> Bounds {
monitors
.iter()
.find(|monitor| contains(**monitor, x, y))
.and_then(|monitor| intersect(*monitor, display))
.unwrap_or(display)
}
pub(super) fn glow_rect(
monitor: Bounds,
edge: Edge,
along: i32,
progress: u16,
size: Size,
) -> Option<Bounds> {
let horizontal = match edge {
Edge::Top | Edge::Bottom => true,
Edge::Left | Edge::Right => false,
Edge::Unspecified => return None,
};
let at_far_side = matches!(edge, Edge::Bottom | Edge::Right);
let (start, span, depth_start, depth_span) = if horizontal {
(monitor.x, monitor.width, monitor.y, monitor.height)
} else {
(monitor.y, monitor.height, monitor.x, monitor.width)
};
let (from, length) = strip(start, span, along, progress);
let thickness = size.pixels(GLOW_THICKNESS).min(depth_span);
let depth = if at_far_side {
depth_start.saturating_add_unsigned(depth_span - thickness)
} else {
depth_start
};
Some(if horizontal {
Bounds {
x: from,
y: depth,
width: length,
height: thickness,
}
} else {
Bounds {
x: depth,
y: from,
width: thickness,
height: length,
}
})
}
fn strip(start: i32, span: u32, along: i32, progress: u16) -> (i32, u32) {
let progress = u64::from(progress).min(PROGRESS_FULL);
let scaled = u64::from(span) * progress / PROGRESS_FULL;
let length = u32::try_from(scaled).unwrap_or(span).clamp(1, span.max(1));
let last = start.saturating_add_unsigned(span.saturating_sub(length));
let from = along.saturating_sub_unsigned(length / 2).clamp(start, last);
(from, length)
}
fn contains(bounds: Bounds, x: i32, y: i32) -> bool {
x >= bounds.x
&& y >= bounds.y
&& i64::from(x) < i64::from(bounds.x) + i64::from(bounds.width)
&& i64::from(y) < i64::from(bounds.y) + i64::from(bounds.height)
}
fn intersect(a: Bounds, b: Bounds) -> Option<Bounds> {
let left = a.x.max(b.x);
let top = a.y.max(b.y);
let right = (i64::from(a.x) + i64::from(a.width)).min(i64::from(b.x) + i64::from(b.width));
let bottom = (i64::from(a.y) + i64::from(a.height)).min(i64::from(b.y) + i64::from(b.height));
let width = u32::try_from(right - i64::from(left))
.ok()
.filter(|width| *width > 0)?;
let height = u32::try_from(bottom - i64::from(top))
.ok()
.filter(|height| *height > 0)?;
Some(Bounds {
x: left,
y: top,
width,
height,
})
}
#[cfg(test)]
mod tests {
use super::*;
const fn bounds(x: i32, y: i32, width: u32, height: u32) -> Bounds {
Bounds {
x,
y,
width,
height,
}
}
#[test]
fn glow_rect_edge_and_progress_places_and_sizes_strip() {
let monitor = bounds(1920, 0, 1000, 800);
let cases = [
(Edge::Top, 2420, 500, Some(bounds(2170, 0, 500, 6))),
(Edge::Bottom, 1930, 500, Some(bounds(1920, 794, 500, 6))),
(Edge::Left, 100, 1000, Some(bounds(1920, 0, 6, 800))),
(Edge::Right, 5000, 250, Some(bounds(2914, 600, 6, 200))),
(Edge::Unspecified, 0, 500, None),
];
for (edge, along, progress, expected) in cases {
let rect = glow_rect(monitor, edge, along, progress, Size::Medium);
assert_eq!(rect, expected, "{edge:?}");
}
}
#[test]
fn pick_monitor_pointer_position_selects_containing_monitor() {
let monitors = [bounds(0, 0, 1920, 1080), bounds(1920, 0, 1280, 1024)];
let display = bounds(0, 0, 3200, 1080);
let cases = [
(10, 10, bounds(0, 0, 1920, 1080)),
(2000, 10, bounds(1920, 0, 1280, 1024)),
(-5, 10, display),
];
for (x, y, expected) in cases {
let monitor = pick_monitor(&monitors, display, x, y);
assert_eq!(monitor, expected, "({x}, {y})");
}
}
}