use std::ffi::c_void;
use std::io;
use std::mem::size_of;
use windows::Win32::Foundation::{HWND, LRESULT, RECT};
use windows::Win32::Graphics::Gdi::{
BI_RGB, BITMAPINFO, BITMAPINFOHEADER, BeginPaint, DIB_RGB_COLORS, EndPaint, HDC,
InvalidateRect, PAINTSTRUCT, RGBQUAD, SRCCOPY, StretchDIBits,
};
use windows::Win32::UI::WindowsAndMessaging::GetClientRect;
use super::config::{allocation_error, validate_frame_dimensions};
use super::native::NativeWindow;
use super::state::{PresentedFrame, SharedWindowState};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameRegion {
left: u32,
top: u32,
right: u32,
bottom: u32,
}
impl FrameRegion {
pub fn new(left: u32, top: u32, right: u32, bottom: u32) -> io::Result<Self> {
if right < left || bottom < top {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"frame region edges are out of order",
));
}
Ok(Self {
left,
top,
right,
bottom,
})
}
const fn whole(width: u32, height: u32) -> Self {
Self {
left: 0,
top: 0,
right: width,
bottom: height,
}
}
const fn is_empty(self) -> bool {
self.right == self.left || self.bottom == self.top
}
fn rect(self) -> io::Result<RECT> {
let edge = |value: u32| {
i32::try_from(value).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "frame region exceeds i32")
})
};
Ok(RECT {
left: edge(self.left)?,
top: edge(self.top)?,
right: edge(self.right)?,
bottom: edge(self.bottom)?,
})
}
}
impl NativeWindow {
pub fn present_argb8888(&mut self, width: u32, height: u32, pixels: &[u32]) -> io::Result<()> {
self.present_argb8888_region(width, height, pixels, FrameRegion::whole(width, height))
}
pub fn present_argb8888_region(
&mut self,
width: u32,
height: u32,
pixels: &[u32],
region: FrameRegion,
) -> io::Result<()> {
validate_frame_dimensions(width, height)?;
let count = usize::try_from(u64::from(width) * u64::from(height))
.map_err(|_| allocation_error())?;
if pixels.len() != count {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"ARGB frame length does not match dimensions",
));
}
if region.right > width || region.bottom > height {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"frame region lies outside the frame",
));
}
let retained = self.state.with(|state| {
state
.frame
.as_ref()
.is_some_and(|frame| frame.width == width && frame.height == height)
});
let whole = !retained || !self.client_matches(width, height);
let region = if whole {
FrameRegion::whole(width, height)
} else {
region
};
if region.is_empty() {
return Ok(());
}
if !retained {
let mut pixels = self
.state
.with(|state| state.frame.take())
.map(|frame| frame.pixels)
.unwrap_or_default();
pixels.clear();
pixels
.try_reserve_exact(count)
.map_err(|_| allocation_error())?;
pixels.resize(count, 0);
self.state.with(|state| {
state.frame = Some(PresentedFrame {
width,
height,
pixels,
});
});
}
let stride = usize::try_from(width).map_err(|_| allocation_error())?;
let fits = "invariant: a validated frame coordinate fits usize";
let (left, right) = (
usize::try_from(region.left).expect(fits),
usize::try_from(region.right).expect(fits),
);
self.state.with(|state| {
let frame = state
.frame
.as_mut()
.expect("invariant: a matching frame was retained or installed above");
for row in region.top..region.bottom {
let start = usize::try_from(row).expect(fits) * stride;
frame.pixels[start + left..start + right]
.copy_from_slice(&pixels[start + left..start + right]);
}
});
let rect = region.rect()?;
if !unsafe { InvalidateRect(Some(self.hwnd), Some(&rect), false) }.as_bool() {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn client_matches(&self, width: u32, height: u32) -> bool {
let mut client = RECT::default();
if unsafe { GetClientRect(self.hwnd, &mut client) }.is_err() {
return false;
}
i64::from(client.right - client.left) == i64::from(width)
&& i64::from(client.bottom - client.top) == i64::from(height)
}
}
pub(super) unsafe fn paint(hwnd: HWND, state: &SharedWindowState) -> LRESULT {
unsafe {
let mut paint = PAINTSTRUCT::default();
let hdc = BeginPaint(hwnd, &mut paint);
paint_frame(hwnd, state, hdc);
let _ = EndPaint(hwnd, &paint);
LRESULT(0)
}
}
pub(super) unsafe fn paint_frame(hwnd: HWND, state: &SharedWindowState, hdc: HDC) {
if hdc.is_invalid() {
return;
}
let Some(frame) = state.with(|state| state.frame.take()) else {
return;
};
unsafe { stretch_frame(hwnd, hdc, &frame) };
state.with(|state| {
if state.frame.is_none() {
state.frame = Some(frame);
}
});
}
unsafe fn stretch_frame(hwnd: HWND, hdc: HDC, frame: &PresentedFrame) {
unsafe {
let mut client = RECT::default();
if GetClientRect(hwnd, &mut client).is_err() {
return;
}
let dest_width = client.right.saturating_sub(client.left);
let dest_height = client.bottom.saturating_sub(client.top);
if dest_width <= 0 || dest_height <= 0 {
return;
}
let info = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: size_of::<BITMAPINFOHEADER>() as u32,
biWidth: frame.width as i32,
biHeight: -(frame.height as i32),
biPlanes: 1,
biBitCount: 32,
biCompression: BI_RGB.0,
..Default::default()
},
bmiColors: [RGBQUAD::default()],
};
let _ = StretchDIBits(
hdc,
0,
0,
dest_width,
dest_height,
0,
0,
frame.width as i32,
frame.height as i32,
Some(frame.pixels.as_ptr().cast::<c_void>()),
&info,
DIB_RGB_COLORS,
SRCCOPY,
);
}
}