mod actions;
pub mod autostart;
mod combo;
mod controls;
mod layout;
mod pages;
mod recorder;
mod render;
mod state;
mod wndproc;
use crate::theme::{self, Palette};
use combo::ComboPopup;
use controls::{Fonts, Vis};
use layout::relayout;
use pages::Layout;
use state::SettingsState;
use std::cell::RefCell;
use std::ptr::{null, null_mut};
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::Foundation::RECT;
use windows_sys::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, FindWindowW, GetClientRect, GetSystemMetrics, SetForegroundWindow,
SetWindowPos, ShowWindow, CW_USEDEFAULT, MSG, SM_CXSCREEN, SM_CYSCREEN, SWP_NOACTIVATE,
SWP_NOZORDER, SW_RESTORE, SW_SHOW, WS_OVERLAPPEDWINDOW,
};
const WM_MOUSELEAVE: u32 = 0x02A3;
use winspaces_common::i18n::t;
use winspaces_common::{log_info, Msg};
use winspaces_win32::dpi;
use winspaces_win32::dwm::{extend_frame_full, set_backdrop, set_dark_mode, Backdrop};
use winspaces_win32::module::{app_instance, win_build};
use winspaces_win32::text::encode_wide;
use winspaces_win32::window_class::register_class;
const SETTINGS_CLASS: &str = "WinSpacesSettingsClass";
const POPUP_CLASS: &str = "WinSpacesSettingsPopup";
const WM_APP_COMBO_COMMIT: u32 = windows_sys::Win32::UI::WindowsAndMessaging::WM_APP + 71;
const WM_APP_FILE_DIALOG: u32 = windows_sys::Win32::UI::WindowsAndMessaging::WM_APP + 72;
const WM_DPICHANGED: u32 = 0x02E0;
const TIMER_BANNER: usize = 1;
const TIMER_CAPTURE: usize = 2;
const NAV_W: i32 = 240;
const NAV_ITEM_H: i32 = 40;
const NAV_ITEM_GAP: i32 = 4;
struct Win {
hwnd: HWND,
scale: f32,
fonts: Fonts,
pal: Palette,
mica: bool,
state: SettingsState,
layout: Layout,
nav_rects: [RECT; 4],
scroll: i32,
hover: Option<pages::ControlId>,
pressed: Option<pages::ControlId>,
focus: Option<usize>,
keyboard_nav: bool,
combo: Option<ComboPopup>,
tracking_leave: bool,
scrollbar_drag: Option<i32>,
client_w: i32,
client_h: i32,
}
thread_local! {
static WIN: RefCell<Option<Win>> = const { RefCell::new(None) };
}
fn with_win<F: FnOnce(&mut Win)>(f: F) {
WIN.with(|s| {
if let Ok(mut borrow) = s.try_borrow_mut() {
if let Some(win) = borrow.as_mut() {
f(win);
}
}
});
}
pub fn run_settings() {
unsafe {
let class_name = encode_wide(SETTINGS_CLASS);
let existing = FindWindowW(class_name.as_ptr(), null());
if !existing.is_null() {
ShowWindow(existing, SW_RESTORE);
SetForegroundWindow(existing);
return;
}
let state = SettingsState::new();
let title = encode_wide(t(Msg::SettingsTitle));
let mica = win_build() >= 22621;
let pal = theme::build_palette(state.theme_pref, mica);
register_class(SETTINGS_CLASS, Some(wndproc::settings_wnd_proc));
register_class(POPUP_CLASS, Some(combo::popup_wnd_proc));
let hwnd = CreateWindowExW(
0,
class_name.as_ptr(),
title.as_ptr(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
960,
720,
null_mut(),
null_mut(),
app_instance(),
null_mut(),
);
if hwnd.is_null() {
log_info!("Failed to create settings window");
return;
}
let scale = dpi::scale_for_window(hwnd);
let w = (960.0 * scale).round() as i32;
let h = (720.0 * scale).round() as i32;
let sw = GetSystemMetrics(SM_CXSCREEN);
let sh = GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(
hwnd,
null_mut(),
((sw - w) / 2).max(0),
((sh - h) / 2).max(0),
w,
h,
SWP_NOZORDER | SWP_NOACTIVATE,
);
apply_frame_attributes(hwnd, &pal, mica);
let fonts = controls::create_fonts(scale);
let mut win = Win {
hwnd,
scale,
fonts,
pal,
mica,
state,
layout: Layout {
items: Vec::new(),
content_h: 0,
controls: Vec::new(),
},
nav_rects: [RECT {
left: 0,
top: 0,
right: 0,
bottom: 0,
}; 4],
scroll: 0,
hover: None,
pressed: None,
focus: None,
keyboard_nav: false,
combo: None,
tracking_leave: false,
scrollbar_drag: None,
client_w: 0,
client_h: 0,
};
let mut rc: RECT = std::mem::zeroed();
GetClientRect(hwnd, &mut rc);
win.client_w = rc.right;
win.client_h = rc.bottom;
relayout(&mut win);
WIN.with(|s| *s.borrow_mut() = Some(win));
ShowWindow(hwnd, SW_SHOW);
SetForegroundWindow(hwnd);
let mut msg: MSG = std::mem::zeroed();
while windows_sys::Win32::UI::WindowsAndMessaging::GetMessageW(&mut msg, null_mut(), 0, 0)
> 0
{
windows_sys::Win32::UI::WindowsAndMessaging::TranslateMessage(&msg);
windows_sys::Win32::UI::WindowsAndMessaging::DispatchMessageW(&msg);
}
let win = WIN.with(|s| s.borrow_mut().take());
if let Some(win) = win {
controls::delete_fonts(&win.fonts);
}
}
}
unsafe fn apply_frame_attributes(hwnd: HWND, pal: &Palette, mica: bool) {
set_dark_mode(hwnd, !pal.light);
if mica && !pal.high_contrast {
extend_frame_full(hwnd);
set_backdrop(hwnd, Backdrop::Mica);
}
}