const MAX_CONTROL_LIST_ENTRIES: usize = 4096;
pub(crate) const STANDARD_BUTTON_OVAL: i16 = 10;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct CheckboxLayout {
pub(crate) indicator: (i16, i16, i16, i16),
pub(crate) label_left: i16,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct RadioButtonLayout {
pub(crate) indicator: (i16, i16, i16, i16),
pub(crate) label_left: i16,
}
pub(crate) fn standard_checkbox_layout(
(top, left, bottom, _right): (i16, i16, i16, i16),
) -> CheckboxLayout {
let indicator_size = 12.min(bottom.saturating_sub(top).max(1));
let indicator_top =
top.saturating_add(bottom.saturating_sub(top).saturating_sub(indicator_size) / 2);
let indicator_left = left.saturating_add(2);
CheckboxLayout {
indicator: (
indicator_top,
indicator_left,
indicator_top.saturating_add(indicator_size),
indicator_left.saturating_add(indicator_size),
),
label_left: indicator_left
.saturating_add(indicator_size)
.saturating_add(4),
}
}
pub(crate) fn standard_radio_button_layout(
(top, left, bottom, _right): (i16, i16, i16, i16),
) -> RadioButtonLayout {
let indicator_size = 12.min(bottom.saturating_sub(top).max(1));
let indicator_top =
top.saturating_add(bottom.saturating_sub(top).saturating_sub(indicator_size) / 2);
let indicator_left = left.saturating_add(2);
RadioButtonLayout {
indicator: (
indicator_top,
indicator_left,
indicator_top.saturating_add(indicator_size),
indicator_left.saturating_add(indicator_size),
),
label_left: indicator_left
.saturating_add(indicator_size)
.saturating_add(4),
}
}
pub(crate) fn for_each_standard_checkbox_mark_pixel(
size: i16,
mut visit: impl FnMut(i16, i16),
) {
for offset in 1..size.saturating_sub(1) {
visit(offset, offset);
visit(size.saturating_sub(1).saturating_sub(offset), offset);
}
}
pub(crate) fn centered_control_label_origin(
(top, left, bottom, right): (i16, i16, i16, i16),
text_advance: i16,
ascent: i16,
descent: i16,
) -> (i16, i16) {
(
left.saturating_add(right.saturating_sub(left).saturating_sub(text_advance) / 2),
top.saturating_add(
bottom
.saturating_sub(top)
.saturating_sub(ascent.saturating_add(descent))
/ 2,
)
.saturating_add(ascent),
)
}
pub(crate) fn control_draw_order<Handle>(
head: Handle,
mut next: impl FnMut(Handle) -> Option<Handle>,
) -> Vec<Handle>
where
Handle: Copy + Eq + Default,
{
let nil = Handle::default();
let mut newest_first = Vec::new();
let mut handle = head;
while handle != nil
&& newest_first.len() < MAX_CONTROL_LIST_ENTRIES
&& !newest_first.contains(&handle)
{
newest_first.push(handle);
handle = next(handle).unwrap_or(nil);
}
newest_first.reverse();
newest_first
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn draw_order_reverses_the_newest_first_guest_chain() {
let next = HashMap::from([(3u32, 2u32), (2, 1), (1, 0)]);
assert_eq!(
control_draw_order(3, |handle| next.get(&handle).copied()),
[1, 2, 3]
);
}
#[test]
fn draw_order_stops_at_a_corrupt_cycle() {
let next = HashMap::from([(3u32, 2u32), (2, 3)]);
assert_eq!(
control_draw_order(3, |handle| next.get(&handle).copied()),
[2, 3]
);
}
#[test]
fn standard_checkbox_layout_is_shared_guest_geometry() {
assert_eq!(
standard_checkbox_layout((255, 185, 279, 315)),
CheckboxLayout {
indicator: (261, 187, 273, 199),
label_left: 203,
}
);
}
#[test]
fn standard_checkbox_mark_is_the_two_interior_diagonals() {
let mut pixels = Vec::new();
for_each_standard_checkbox_mark_pixel(5, |x, y| pixels.push((x, y)));
assert_eq!(pixels, [(1, 1), (3, 1), (2, 2), (2, 2), (3, 3), (1, 3)]);
}
#[test]
fn standard_radio_button_layout_is_shared_guest_geometry() {
assert_eq!(
standard_radio_button_layout((70, 250, 90, 390)),
RadioButtonLayout {
indicator: (74, 252, 86, 264),
label_left: 268,
}
);
}
}