use crate::paint;
use crate::theme;
pub const HIT_NONE: u8 = 0;
pub const HIT_MINIMIZE_BUTTON: u8 = 1;
pub const HIT_MAXIMIZE_BUTTON: u8 = 2;
pub const HIT_CLOSE_BUTTON: u8 = 3;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResizeEdge {
None,
Top,
Bottom,
Left,
Right,
TopLeft,
TopRight,
BottomLeft,
BottomRight,
}
pub fn get_resize_edge(window_width: u32, window_height: u32, x: f32, y: f32) -> ResizeEdge {
let span = 2.0 * window_width as f32 * window_height as f32 / (window_width as f32 + window_height as f32);
let resize_border = (span / 32.0).ceil();
let at_left = x < resize_border;
let at_right = x > (window_width as f32 - resize_border);
let at_top = y < resize_border;
let at_bottom = y > (window_height as f32 - resize_border);
if at_top && at_left { ResizeEdge::TopLeft }
else if at_top && at_right { ResizeEdge::TopRight }
else if at_bottom && at_left { ResizeEdge::BottomLeft }
else if at_bottom && at_right { ResizeEdge::BottomRight }
else if at_top { ResizeEdge::Top }
else if at_bottom { ResizeEdge::Bottom }
else if at_left { ResizeEdge::Left }
else if at_right { ResizeEdge::Right }
else { ResizeEdge::None }
}
pub fn draw_window_controls(
pixels: &mut [u32],
hit_test_map: &mut [u8],
window_width: u32,
window_height: u32,
ru: f32,
) -> (usize, Vec<(u16, u8, u8)>, usize, usize) {
let window_width = window_width as usize;
let window_height = window_height as usize;
let span = 2.0 * window_width as f32 * window_height as f32
/ (window_width as f32 + window_height as f32);
let button_height = (span / 32.0 * ru).ceil() as usize;
let button_width = button_height;
let total_width = button_width * 7 / 2;
let mut x_start = window_width - total_width;
let y_start = 0;
let radius = span * ru / 4.;
let squirdleyness = 24;
let mut crossings: Vec<(u16, u8, u8)> = Vec::new();
let mut y = 1f32;
loop {
let y_norm = y / radius;
let x_norm = (1.0 - y_norm.powi(squirdleyness)).powf(1.0 / squirdleyness as f32);
let x = x_norm * radius;
let inset = radius - x;
if inset > 0. {
crossings.push((
inset as u16,
(inset.fract().sqrt() * 256.) as u8,
((1. - inset.fract()).sqrt() * 256.) as u8,
));
}
if x < y {
break;
}
y += 1.;
}
let start = (radius - y) as usize;
let crossings: Vec<(u16, u8, u8)> = crossings.into_iter().rev().collect();
let edge_colour = theme::WINDOW_LIGHT_EDGE;
let bg_colour = theme::WINDOW_CONTROLS_BG;
let mut y_offset = start;
for (inset, l, h) in &crossings {
if y_offset >= button_height {
break;
}
let py = y_start + button_height - 1 - y_offset;
let col_end = total_width.min(window_width - x_start);
for col in (*inset as usize + 2)..col_end - 1 {
let px = x_start + col;
let pixel_idx = py * window_width + px;
pixels[pixel_idx] = bg_colour;
let button_area_x_start = x_start + button_width / 4;
let button_id = if px < button_area_x_start {
HIT_MINIMIZE_BUTTON
} else {
let x_in_button_area = px - button_area_x_start;
if x_in_button_area < button_width {
HIT_MINIMIZE_BUTTON
} else if x_in_button_area < button_width * 2 {
HIT_MAXIMIZE_BUTTON
} else {
HIT_CLOSE_BUTTON
}
};
hit_test_map[pixel_idx] = button_id;
}
let px = x_start + *inset as usize;
let pixel_idx = py * window_width + px;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], edge_colour, *l, *h);
let px = x_start + *inset as usize + 1;
let pixel_idx = py * window_width + px;
pixels[pixel_idx] = paint::blend_rgb_only(bg_colour, edge_colour, *h, *l);
let button_area_x_start = x_start + button_width / 4;
let button_id = if px < button_area_x_start {
HIT_MINIMIZE_BUTTON
} else {
let x_in_button_area = px - button_area_x_start;
if x_in_button_area < button_width {
HIT_MINIMIZE_BUTTON
} else if x_in_button_area < button_width * 2 {
HIT_MAXIMIZE_BUTTON
} else {
HIT_CLOSE_BUTTON
}
};
hit_test_map[pixel_idx] = button_id;
y_offset += 1;
}
let mut x_offset = start;
let crossing_limit = crossings.len().min(window_width - (x_start + start));
for &(inset, l, h) in &crossings[..crossing_limit] {
let i = inset as usize;
let px = x_start + x_offset;
let py = y_start + button_height - 1 - i;
let pixel_idx = py * window_width + px;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], edge_colour, l, h);
for row in (i + 2)..start {
let py = y_start + button_height - 1 - row;
let pixel_idx = py * window_width + px;
pixels[pixel_idx] = bg_colour;
let button_area_x_start = x_start + button_width / 4;
let button_id = if px < button_area_x_start {
HIT_MINIMIZE_BUTTON
} else {
let x_in_button_area = px - button_area_x_start;
if x_in_button_area < button_width {
HIT_MINIMIZE_BUTTON
} else if x_in_button_area < button_width * 2 {
HIT_MAXIMIZE_BUTTON
} else {
HIT_CLOSE_BUTTON
}
};
hit_test_map[pixel_idx] = button_id;
}
let py = y_start + button_height - 1 - (i + 1);
let pixel_idx = py * window_width + px;
pixels[pixel_idx] = paint::blend_rgb_only(bg_colour, edge_colour, h, l);
let button_area_x_start = x_start + button_width / 4;
let button_id = if px < button_area_x_start {
HIT_MINIMIZE_BUTTON
} else {
let x_in_button_area = px - button_area_x_start;
if x_in_button_area < button_width {
HIT_MINIMIZE_BUTTON
} else if x_in_button_area < button_width * 2 {
HIT_MAXIMIZE_BUTTON
} else {
HIT_CLOSE_BUTTON
}
};
hit_test_map[pixel_idx] = button_id;
x_offset += 1;
}
let linear_start_x = x_start + start + crossings.len();
let edge_y = y_start + button_height - 1;
for px in linear_start_x..window_width {
let pixel_idx = edge_y * window_width + px;
pixels[pixel_idx] = edge_colour;
for row in 1..start {
let py = edge_y - row;
let pixel_idx = py * window_width + px;
pixels[pixel_idx] = bg_colour;
hit_test_map[pixel_idx] = HIT_CLOSE_BUTTON;
}
}
x_start += button_width / 4;
paint::glyph::minimize_symbol(
pixels,
window_width,
x_start + button_width / 2,
y_start + button_width / 2,
button_width / 4,
theme::MINIMIZE_GLYPH,
);
paint::glyph::maximize_symbol(
pixels,
window_width,
x_start + button_width + button_width / 2,
y_start + button_width / 2,
button_width / 4,
theme::MAXIMIZE_GLYPH,
theme::MAXIMIZE_GLYPH_INTERIOR,
);
paint::glyph::close_symbol(
pixels,
window_width,
x_start + button_width * 2 + button_width / 2,
y_start + button_width / 2,
button_width / 4,
theme::CLOSE_GLYPH,
);
(start, crossings, x_start, button_height)
}
pub fn draw_window_edges_and_mask(
pixels: &mut [u32],
hit_test_map: &mut [u8],
width: u32,
height: u32,
start: usize,
crossings: &[(u16, u8, u8)],
) {
let light_colour = theme::WINDOW_LIGHT_EDGE;
let shadow_colour = theme::WINDOW_SHADOW_EDGE;
for x in 0..width {
let idx = 0 * width + x;
pixels[idx as usize] = light_colour;
}
for x in 0..width {
let idx = (height - 1) * width + x;
pixels[idx as usize] = shadow_colour;
}
for y in 0..height {
let idx = y * width + 0;
pixels[idx as usize] = light_colour;
}
for y in 0..height {
let idx = y * width + (width - 1);
pixels[idx as usize] = shadow_colour;
}
for row in 0..start {
for col in 0..start {
let idx = row * width as usize + col;
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
}
for row in 0..start {
for col in (width as usize - start)..width as usize {
let idx = row * width as usize + col;
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
}
for row in (height as usize - start)..height as usize {
for col in 0..start {
let idx = row * width as usize + col;
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
}
for row in (height as usize - start)..height as usize {
for col in (width as usize - start)..width as usize {
let idx = row * width as usize + col;
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
}
let mut y_top = start;
for crossing in 0..crossings.len() {
let (inset, l, h) = crossings[crossing];
for idx in y_top * width as usize..y_top * width as usize + inset as usize {
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
let pixel_idx = y_top * width as usize + inset as usize;
if paint::PREMULTIPLIED {
pixels[pixel_idx] = paint::scale_alpha(light_colour, h);
} else {
pixels[pixel_idx] = (light_colour & 0x00FFFFFF) | ((h as u32) << 24);
}
if h < 255 {
hit_test_map[pixel_idx] = HIT_NONE; }
let pixel_idx = pixel_idx + 1;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], light_colour, h, l);
let pixel_idx = y_top * width as usize + width as usize - 2 - inset as usize;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], shadow_colour, h, l);
let pixel_idx = pixel_idx + 1;
if paint::PREMULTIPLIED {
pixels[pixel_idx] = paint::scale_alpha(shadow_colour, h);
} else {
pixels[pixel_idx] = (shadow_colour & 0x00FFFFFF) | ((h as u32) << 24);
}
if h < 255 {
hit_test_map[pixel_idx] = HIT_NONE;
}
for idx in (y_top * width as usize + width as usize - inset as usize)
..((y_top + 1) * width as usize)
{
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
y_top += 1;
}
let mut y_bottom = height as usize - start - 1;
for crossing in 0..crossings.len() {
let (inset, l, h) = crossings[crossing];
for idx in y_bottom * width as usize..y_bottom * width as usize + inset as usize {
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
let pixel_idx = y_bottom * width as usize + inset as usize;
if paint::PREMULTIPLIED {
pixels[pixel_idx] = paint::scale_alpha(light_colour, h);
} else {
pixels[pixel_idx] = (light_colour & 0x00FFFFFF) | ((h as u32) << 24);
}
if h < 255 {
hit_test_map[pixel_idx] = HIT_NONE;
}
let pixel_idx = pixel_idx + 1;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], light_colour, h, l);
let pixel_idx = y_bottom * width as usize + width as usize - 2 - inset as usize;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], shadow_colour, h, l);
let pixel_idx = pixel_idx + 1;
if paint::PREMULTIPLIED {
pixels[pixel_idx] = paint::scale_alpha(shadow_colour, h);
} else {
pixels[pixel_idx] = (shadow_colour & 0x00FFFFFF) | ((h as u32) << 24);
}
if h < 255 {
hit_test_map[pixel_idx] = HIT_NONE;
}
for idx in (y_bottom * width as usize + width as usize - inset as usize)
..((y_bottom + 1) * width as usize)
{
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
y_bottom -= 1;
}
let mut x_left = start;
for crossing in 0..crossings.len() {
let (inset, l, h) = crossings[crossing];
for row in 0..inset as usize {
let idx = row * width as usize + x_left;
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
let pixel_idx = inset as usize * width as usize + x_left;
if paint::PREMULTIPLIED {
pixels[pixel_idx] = paint::scale_alpha(light_colour, h);
} else {
pixels[pixel_idx] = (light_colour & 0x00FFFFFF) | ((h as u32) << 24);
}
if h < 255 {
hit_test_map[pixel_idx] = HIT_NONE;
}
let pixel_idx = (inset as usize + 1) * width as usize + x_left;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], light_colour, h, l);
let pixel_idx = (height as usize - 1 - inset as usize) * width as usize + x_left;
if paint::PREMULTIPLIED {
pixels[pixel_idx] = paint::scale_alpha(shadow_colour, h);
} else {
pixels[pixel_idx] = (shadow_colour & 0x00FFFFFF) | ((h as u32) << 24);
}
if h < 255 {
hit_test_map[pixel_idx] = HIT_NONE;
}
let pixel_idx = (height as usize - 2 - inset as usize) * width as usize + x_left;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], shadow_colour, h, l);
for row in (height as usize - inset as usize)..height as usize {
let idx = row * width as usize + x_left;
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
x_left += 1;
}
let mut x_right = width as usize - start - 1;
for crossing in 0..crossings.len() {
let (inset, l, h) = crossings[crossing];
for row in 0..inset as usize {
let idx = row * width as usize + x_right;
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
let pixel_idx = inset as usize * width as usize + x_right;
if paint::PREMULTIPLIED {
pixels[pixel_idx] = paint::scale_alpha(light_colour, h);
} else {
pixels[pixel_idx] = (light_colour & 0x00FFFFFF) | ((h as u32) << 24);
}
if h < 255 {
hit_test_map[pixel_idx] = HIT_NONE;
}
let pixel_idx = (inset as usize + 1) * width as usize + x_right;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], light_colour, h, l);
let pixel_idx = (height as usize - 1 - inset as usize) * width as usize + x_right;
if paint::PREMULTIPLIED {
pixels[pixel_idx] = paint::scale_alpha(shadow_colour, h);
} else {
pixels[pixel_idx] = (shadow_colour & 0x00FFFFFF) | ((h as u32) << 24);
}
if h < 255 {
hit_test_map[pixel_idx] = HIT_NONE;
}
let pixel_idx = (height as usize - 2 - inset as usize) * width as usize + x_right;
pixels[pixel_idx] = paint::blend_rgb_only(pixels[pixel_idx], shadow_colour, h, l);
for row in (height as usize - inset as usize)..height as usize {
let idx = row * width as usize + x_right;
pixels[idx] = 0;
hit_test_map[idx] = HIT_NONE;
}
x_right -= 1;
}
}
pub fn draw_button_hairlines(
pixels: &mut [u32],
hit_test_map: &mut [u8],
window_width: u32,
_window_height: u32,
button_x_start: usize,
button_height: usize,
_start: usize,
_crossings: &[(u16, u8, u8)],
) {
let width = window_width as usize;
let y_start = 0;
let button_width = button_height;
let left_px = button_x_start + button_width;
let right_px = button_x_start + button_width * 2;
let center_y = y_start + button_height / 2;
let edge_colour = theme::WINDOW_CONTROLS_HAIRLINE;
let center_colour = pixels[center_y * width + left_px];
for py in (y_start..=center_y).rev() {
let idx = py * width + left_px;
let diff = pixels[idx] != center_colour;
pixels[idx] = edge_colour;
hit_test_map[idx] = HIT_NONE;
if diff {
break;
}
}
for py in (center_y + 1)..(y_start + button_height) {
let idx = py * width + left_px;
let diff = pixels[idx] != center_colour;
pixels[idx] = edge_colour;
hit_test_map[idx] = HIT_NONE;
if diff {
break;
}
}
let center_colour_right = pixels[center_y * width + right_px];
for py in (y_start..=center_y).rev() {
let idx = py * width + right_px;
let diff = pixels[idx] != center_colour_right;
pixels[idx] = edge_colour;
hit_test_map[idx] = HIT_NONE;
if diff {
break;
}
}
for py in (center_y + 1)..(y_start + button_height) {
let idx = py * width + right_px;
let diff = pixels[idx] != center_colour_right;
pixels[idx] = edge_colour;
hit_test_map[idx] = HIT_NONE;
if diff {
break;
}
}
}
pub fn draw_button_hover_by_pixels(pixels: &mut [u32], pixel_list: &[usize], hover: bool, button_id: u8) {
let hover_delta = match button_id {
HIT_CLOSE_BUTTON => theme::CLOSE_HOVER,
HIT_MAXIMIZE_BUTTON => theme::MAXIMIZE_HOVER,
HIT_MINIMIZE_BUTTON => theme::MINIMIZE_HOVER,
_ => return,
};
for &hit_idx in pixel_list {
pixels[hit_idx] = if hover {
pixels[hit_idx].wrapping_add(hover_delta)
} else {
pixels[hit_idx].wrapping_sub(hover_delta)
};
}
}
pub fn pixels_for_button(hit_test_map: &[u8], button_id: u8) -> Vec<usize> {
if button_id == HIT_NONE { return Vec::new(); }
let mut out = Vec::new();
for (i, &id) in hit_test_map.iter().enumerate() {
if id == button_id { out.push(i); }
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pixels_for_button_returns_matching_indices() {
let mut map = vec![HIT_NONE; 16];
map[3] = HIT_CLOSE_BUTTON;
map[7] = HIT_CLOSE_BUTTON;
map[10] = HIT_MAXIMIZE_BUTTON;
let close = pixels_for_button(&map, HIT_CLOSE_BUTTON);
assert_eq!(close, vec![3, 7]);
let max = pixels_for_button(&map, HIT_MAXIMIZE_BUTTON);
assert_eq!(max, vec![10]);
}
#[test]
fn hover_by_pixels_shifts_listed_pixels_only() {
let mut pixels = vec![0xFF20_2024u32; 8];
draw_button_hover_by_pixels(&mut pixels, &[2, 5], true, HIT_CLOSE_BUTTON);
assert_ne!(pixels[2], 0xFF20_2024);
assert_ne!(pixels[5], 0xFF20_2024);
assert_eq!(pixels[0], 0xFF20_2024);
assert_eq!(pixels[7], 0xFF20_2024);
}
#[test]
fn resize_edge_corners_win() {
assert_eq!(get_resize_edge(1920, 1080, 0.0, 0.0), ResizeEdge::TopLeft);
assert_eq!(get_resize_edge(1920, 1080, 1919.9, 0.0), ResizeEdge::TopRight);
assert_eq!(get_resize_edge(1920, 1080, 0.0, 1079.9), ResizeEdge::BottomLeft);
assert_eq!(get_resize_edge(1920, 1080, 1919.9, 1079.9), ResizeEdge::BottomRight);
}
#[test]
fn resize_edge_none_in_center() {
assert_eq!(get_resize_edge(1920, 1080, 960.0, 540.0), ResizeEdge::None);
}
#[test]
fn draw_window_controls_writes_hit_map() {
let w = 800u32;
let h = 600u32;
let mut pixels = vec![0u32; (w * h) as usize];
let mut hit_map = vec![HIT_NONE; (w * h) as usize];
let _ = draw_window_controls(&mut pixels, &mut hit_map, w, h, 1.0);
let close_count = hit_map.iter().filter(|&&id| id == HIT_CLOSE_BUTTON).count();
let max_count = hit_map.iter().filter(|&&id| id == HIT_MAXIMIZE_BUTTON).count();
let min_count = hit_map.iter().filter(|&&id| id == HIT_MINIMIZE_BUTTON).count();
assert!(close_count > 0, "no close pixels");
assert!(max_count > 0, "no maximize pixels");
assert!(min_count > 0, "no minimize pixels");
}
}