use super::{NativeControl, NativeDeco, NativeState, SystemLook};
use crate::geometry::PhysicalSize;
use crate::render::{Color, Pixmap};
use std::ffi::c_void;
type Handle = *mut c_void;
#[repr(C)]
struct Rect {
left: i32,
top: i32,
right: i32,
bottom: i32,
}
#[repr(C)]
struct BitmapInfoHeader {
size: u32,
width: i32,
height: i32,
planes: u16,
bit_count: u16,
compression: u32,
size_image: u32,
x_ppm: i32,
y_ppm: i32,
clr_used: u32,
clr_important: u32,
}
#[link(name = "uxtheme")]
unsafe extern "system" {
fn OpenThemeData(hwnd: Handle, classes: *const u16) -> Handle;
fn CloseThemeData(theme: Handle) -> i32;
fn DrawThemeBackground(
theme: Handle,
hdc: Handle,
part: i32,
state: i32,
rect: *const Rect,
clip: *const Rect,
) -> i32;
}
#[link(name = "gdi32")]
unsafe extern "system" {
fn CreateCompatibleDC(hdc: Handle) -> Handle;
fn DeleteDC(hdc: Handle) -> i32;
fn SelectObject(hdc: Handle, object: Handle) -> Handle;
fn DeleteObject(object: Handle) -> i32;
fn GdiFlush() -> i32;
fn CreateDIBSection(
hdc: Handle,
info: *const BitmapInfoHeader,
usage: u32,
bits: *mut *mut c_void,
section: Handle,
offset: u32,
) -> Handle;
}
#[link(name = "dwmapi")]
unsafe extern "system" {
fn DwmGetColorizationColor(color: *mut u32, opaque: *mut i32) -> i32;
}
#[link(name = "user32")]
unsafe extern "system" {
fn SystemParametersInfoW(action: u32, param: u32, data: *mut c_void, ini: u32) -> i32;
}
#[link(name = "advapi32")]
unsafe extern "system" {
fn RegGetValueW(
key: isize,
subkey: *const u16,
value: *const u16,
flags: u32,
kind: *mut u32,
data: *mut c_void,
size: *mut u32,
) -> i32;
}
pub const AVAILABLE: bool = true;
pub const PRIMARY_LABEL_ON_ACCENT: bool = false;
const HKEY_CURRENT_USER: isize = 0x8000_0001_u32 as i32 as isize;
const RRF_RT_REG_DWORD: u32 = 0x10;
const BP_PUSHBUTTON: i32 = 1;
const BP_RADIOBUTTON: i32 = 2;
const BP_CHECKBOX: i32 = 3;
const PP_BAR: i32 = 1;
const PP_FILL: i32 = 5;
const TKP_TRACK: i32 = 1;
const TKP_THUMB: i32 = 3;
const EP_EDITBORDER_NOSCROLL: i32 = 6;
fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
pub fn system_look() -> SystemLook {
let mut light: u32 = 1;
let mut size = 4_u32;
let subkey = wide(r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
let value = wide("AppsUseLightTheme");
let status = unsafe {
RegGetValueW(
HKEY_CURRENT_USER,
subkey.as_ptr(),
value.as_ptr(),
RRF_RT_REG_DWORD,
std::ptr::null_mut(),
&mut light as *mut u32 as *mut c_void,
&mut size,
)
};
let (mut argb, mut opaque) = (0_u32, 0_i32);
let accent = (unsafe { DwmGetColorizationColor(&mut argb, &mut opaque) } == 0)
.then(|| Color::rgb((argb >> 16) as u8, (argb >> 8) as u8, argb as u8));
SystemLook {
dark: status == 0 && light == 0,
accent,
}
}
const SPI_GETCLIENTAREAANIMATION: u32 = 0x1042;
pub fn motion_scale() -> f64 {
let mut on: i32 = 1;
let ok = unsafe {
SystemParametersInfoW(
SPI_GETCLIENTAREAANIMATION,
0,
&mut on as *mut i32 as *mut c_void,
0,
)
};
if ok != 0 && on == 0 { 0.0 } else { 1.0 }
}
pub fn supports(control: NativeControl) -> bool {
!matches!(control, NativeControl::Switch { .. })
}
pub fn size_hint(control: NativeControl) -> (Option<f64>, Option<f64>) {
match control {
NativeControl::Button { .. } => (None, Some(26.0)),
NativeControl::Checkbox { .. } | NativeControl::Radio { .. } => (Some(13.0), Some(13.0)),
NativeControl::Slider { .. } => (Some(160.0), Some(22.0)),
NativeControl::Progress { .. } => (Some(200.0), Some(15.0)),
NativeControl::TextField => (None, Some(24.0)),
NativeControl::Switch { .. } => (None, None),
}
}
pub fn draws_focus(control: NativeControl) -> bool {
matches!(control, NativeControl::TextField)
}
fn interaction(state: NativeState) -> i32 {
if state.pressed {
3
} else if state.hovered {
2
} else {
1
}
}
struct Part {
class: &'static str,
dark_class: Option<&'static str>,
part: i32,
state: i32,
rect: Rect,
}
fn parts(deco: NativeDeco, w: i32, h: i32, scale: f64) -> Vec<Part> {
let i = interaction(deco.state);
let full = || Rect {
left: 0,
top: 0,
right: w,
bottom: h,
};
let button = |part, state| Part {
class: "BUTTON",
dark_class: Some("DarkMode_Explorer::BUTTON"),
part,
state,
rect: full(),
};
match deco.control {
NativeControl::Button { primary } => {
let state = if primary && i == 1 { 5 } else { i };
vec![button(BP_PUSHBUTTON, state)]
}
NativeControl::Checkbox { checked } => {
vec![button(BP_CHECKBOX, if checked { i + 4 } else { i })]
}
NativeControl::Radio { selected } => {
vec![button(BP_RADIOBUTTON, if selected { i + 4 } else { i })]
}
NativeControl::Slider { value } => {
let thumb_w = (11.0 * scale).round() as i32;
let track_h = (4.0 * scale).round() as i32;
let x = ((w - thumb_w) as f64 * value as f64 / 1000.0).round() as i32;
let mid = h / 2;
let trackbar = |part, state, rect| Part {
class: "TRACKBAR",
dark_class: None,
part,
state,
rect,
};
vec![
trackbar(
TKP_TRACK,
1,
Rect {
left: 0,
top: mid - track_h / 2,
right: w,
bottom: mid - track_h / 2 + track_h,
},
),
trackbar(
TKP_THUMB,
i,
Rect {
left: x,
top: 0,
right: x + thumb_w,
bottom: h,
},
),
]
}
NativeControl::Progress { value } => {
let progress = |part, rect| Part {
class: "PROGRESS",
dark_class: None,
part,
state: 1,
rect,
};
vec![
progress(PP_BAR, full()),
progress(
PP_FILL,
Rect {
left: 0,
top: 0,
right: (w as f64 * value as f64 / 1000.0).round() as i32,
bottom: h,
},
),
]
}
NativeControl::TextField => {
let state = if deco.state.focused {
3
} else if deco.state.hovered {
2
} else {
1
};
vec![Part {
class: "EDIT",
dark_class: Some("DarkMode_CFD::EDIT"),
part: EP_EDITBORDER_NOSCROLL,
state,
rect: full(),
}]
}
NativeControl::Switch { .. } => Vec::new(),
}
}
pub fn render(deco: NativeDeco, size: PhysicalSize, scale: f64) -> Option<Pixmap> {
let (w, h) = (size.width as i32, size.height as i32);
let parts = parts(deco, w, h, scale);
if parts.is_empty() {
return None;
}
unsafe {
let dc = CreateCompatibleDC(std::ptr::null_mut());
if dc.is_null() {
return None;
}
let info = BitmapInfoHeader {
size: std::mem::size_of::<BitmapInfoHeader>() as u32,
width: w,
height: -h, planes: 1,
bit_count: 32,
compression: 0,
size_image: 0,
x_ppm: 0,
y_ppm: 0,
clr_used: 0,
clr_important: 0,
};
let mut bits: *mut c_void = std::ptr::null_mut();
let bitmap = CreateDIBSection(dc, &info, 0, &mut bits, std::ptr::null_mut(), 0);
if bitmap.is_null() || bits.is_null() {
DeleteDC(dc);
return None;
}
let old = SelectObject(dc, bitmap);
let len = (w * h * 4) as usize;
let pixels = std::slice::from_raw_parts_mut(bits as *mut u8, len);
let mut draw = |background: u8| -> Vec<u8> {
pixels.fill(background);
for p in &parts {
let mut theme = std::ptr::null_mut();
if deco.dark
&& let Some(dark) = p.dark_class
{
theme = OpenThemeData(std::ptr::null_mut(), wide(dark).as_ptr());
}
if theme.is_null() {
theme = OpenThemeData(std::ptr::null_mut(), wide(p.class).as_ptr());
}
if !theme.is_null() {
DrawThemeBackground(theme, dc, p.part, p.state, &p.rect, std::ptr::null());
CloseThemeData(theme);
}
}
GdiFlush();
pixels.to_vec()
};
let on_black = draw(0);
let on_white = draw(255);
SelectObject(dc, old);
DeleteObject(bitmap);
DeleteDC(dc);
let mut out = vec![0u8; len];
let black = on_black.as_chunks::<4>().0;
let white = on_white.as_chunks::<4>().0;
for ((d, b), wh) in out.as_chunks_mut::<4>().0.iter_mut().zip(black).zip(white) {
*d = straight_rgba(*b, *wh);
}
Some(Pixmap::from_rgba8(size, out))
}
}
fn straight_rgba(on_black: [u8; 4], on_white: [u8; 4]) -> [u8; 4] {
let alpha = 255 - (on_white[1] as i32 - on_black[1] as i32).clamp(0, 255);
if alpha == 0 {
return [0, 0, 0, 0];
}
let c = |v: u8| ((v as i32 * 255 + alpha / 2) / alpha).min(255) as u8;
[c(on_black[2]), c(on_black[1]), c(on_black[0]), alpha as u8]
}
#[cfg(test)]
mod tests {
use super::straight_rgba;
#[test]
fn recovers_alpha_from_black_and_white() {
assert_eq!(
straight_rgba([0, 0, 0, 0], [255, 255, 255, 0]),
[0, 0, 0, 0]
);
assert_eq!(
straight_rgba([30, 20, 10, 0], [30, 20, 10, 0]),
[10, 20, 30, 255]
);
}
}