#![allow(unsafe_op_in_unsafe_fn)]
use crate::ui::controls::apply_button_theme;
use crate::ui::builder::ButtonBuilder;
use crate::utils::to_wstring;
use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows_sys::Win32::Graphics::Gdi::{HBRUSH, COLOR_WINDOW, InvalidateRect};
use windows_sys::Win32::UI::WindowsAndMessaging::{
WM_CTLCOLOREDIT, WM_COMMAND,
CreateWindowExW, DefWindowProcW, DestroyWindow,
LoadCursorW, RegisterClassW, ShowWindow,
CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, IDC_ARROW, SW_SHOW, WM_DESTROY, WNDCLASSW,
WS_OVERLAPPEDWINDOW, WS_VISIBLE, WM_CREATE, WM_SIZE, SetWindowPos, SWP_NOZORDER,
WS_CHILD, WS_VSCROLL, ES_MULTILINE, ES_READONLY, ES_AUTOVSCROLL,
SendMessageW, GetWindowTextLengthW, GetWindowTextW, SetWindowTextW,
HMENU,
};
use windows_sys::Win32::UI::Controls::{EM_SETSEL, EM_REPLACESEL, SetWindowTheme};
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
use windows_sys::Win32::System::DataExchange::{OpenClipboard, CloseClipboard, EmptyClipboard, SetClipboardData};
use windows_sys::Win32::System::Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE};
const CONSOLE_TITLE: &str = "Debug Console";
const IDC_EDIT_CONSOLE: i32 = 1001;
const IDC_BTN_COPY: i32 = 1002;
const IDC_BTN_CLEAR: i32 = 1003;
const BUTTON_HEIGHT: i32 = 30;
static mut CONSOLE_HWND: Option<HWND> = None;
static mut EDIT_HWND: Option<HWND> = None;
static mut BTN_COPY_HWND: Option<HWND> = None;
static mut BTN_CLEAR_HWND: Option<HWND> = None;
static mut IS_DARK_MODE: bool = false;
pub unsafe fn show_console_window(_parent: HWND, initial_logs: &[Vec<u16>], is_dark: bool) {
IS_DARK_MODE = is_dark;
if let Some(hwnd) = CONSOLE_HWND {
update_console_theme(hwnd, is_dark);
ShowWindow(hwnd, SW_SHOW);
return;
}
let instance = GetModuleHandleW(std::ptr::null());
let cls_name = to_wstring("CompactRS_Console");
let title = to_wstring(CONSOLE_TITLE);
let icon = crate::ui::utils::load_app_icon(instance);
let wc = WNDCLASSW {
hCursor: LoadCursorW(std::ptr::null_mut(), IDC_ARROW),
hInstance: instance,
hIcon: icon,
lpszClassName: cls_name.as_ptr(),
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(wnd_proc),
hbrBackground: (COLOR_WINDOW + 1) as HBRUSH,
cbClsExtra: 0,
cbWndExtra: 0,
lpszMenuName: std::ptr::null(),
};
let _ = RegisterClassW(&wc);
let hwnd = CreateWindowExW(
0,
cls_name.as_ptr(),
title.as_ptr(),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
600,
400,
std::ptr::null_mut(),
std::ptr::null_mut(),
instance,
std::ptr::null()
);
if hwnd != std::ptr::null_mut() {
CONSOLE_HWND = Some(hwnd);
update_console_theme(hwnd, is_dark);
for log in initial_logs {
append_log(EDIT_HWND.unwrap_or(std::ptr::null_mut()), log.clone());
}
}
}
pub unsafe fn append_log_msg(msg: Vec<u16>) {
if let Some(edit) = EDIT_HWND {
append_log(edit, msg);
}
}
unsafe fn append_log(edit: HWND, mut text: Vec<u16>) {
if edit.is_null() { return; }
let len = GetWindowTextLengthW(edit);
SendMessageW(edit, EM_SETSEL, len as WPARAM, len as LPARAM);
if text.last() == Some(&0) {
text.pop();
}
text.push(13); text.push(10); text.push(0);
SendMessageW(edit, EM_REPLACESEL, 0, text.as_ptr() as LPARAM);
}
pub unsafe fn close_console() {
if let Some(hwnd) = CONSOLE_HWND {
DestroyWindow(hwnd);
CONSOLE_HWND = None;
EDIT_HWND = None;
}
}
unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
if let Some(result) = crate::ui::theme::handle_standard_colors(hwnd, msg, wparam, IS_DARK_MODE) {
return result;
}
match msg {
WM_CREATE => {
let instance = GetModuleHandleW(std::ptr::null());
let edit_cls = to_wstring("EDIT");
let edit = CreateWindowExW(
0,
edit_cls.as_ptr(),
std::ptr::null(),
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
(ES_MULTILINE as u32) | (ES_READONLY as u32) | (ES_AUTOVSCROLL as u32),
0, 0, 0, 0,
hwnd,
IDC_EDIT_CONSOLE as isize as HMENU,
instance,
std::ptr::null()
);
EDIT_HWND = Some(edit);
let btn_copy = ButtonBuilder::new(hwnd, IDC_BTN_COPY as u16)
.text("Copy").pos(0, 0).size(80, BUTTON_HEIGHT).dark_mode(IS_DARK_MODE).build();
BTN_COPY_HWND = Some(btn_copy);
let btn_clear = ButtonBuilder::new(hwnd, IDC_BTN_CLEAR as u16)
.text("Clear").pos(90, 0).size(80, BUTTON_HEIGHT).dark_mode(IS_DARK_MODE).build();
BTN_CLEAR_HWND = Some(btn_clear);
if IS_DARK_MODE {
let dark_mode = to_wstring("DarkMode_Explorer");
SetWindowTheme(edit, dark_mode.as_ptr(), std::ptr::null());
}
0
},
WM_SIZE => {
let width = (lparam & 0xFFFF) as i32;
let height = ((lparam >> 16) & 0xFFFF) as i32;
if let Some(edit) = EDIT_HWND {
SetWindowPos(edit, std::ptr::null_mut(), 0, 0, width, height - BUTTON_HEIGHT - 5, SWP_NOZORDER);
}
let btn_y = height - BUTTON_HEIGHT;
if let Some(btn) = BTN_COPY_HWND {
SetWindowPos(btn, std::ptr::null_mut(), 5, btn_y, 80, BUTTON_HEIGHT - 5, SWP_NOZORDER);
}
if let Some(btn) = BTN_CLEAR_HWND {
SetWindowPos(btn, std::ptr::null_mut(), 90, btn_y, 80, BUTTON_HEIGHT - 5, SWP_NOZORDER);
}
0
},
WM_COMMAND => {
let id = (wparam & 0xFFFF) as i32;
match id {
IDC_BTN_COPY => {
if let Some(edit) = EDIT_HWND {
let len = GetWindowTextLengthW(edit);
if len > 0 {
let mut buffer: Vec<u16> = vec![0; (len + 1) as usize];
GetWindowTextW(edit, buffer.as_mut_ptr(), len + 1);
if OpenClipboard(hwnd) != 0 {
let _ = EmptyClipboard();
let size = (buffer.len() * 2) as usize;
let hmem = GlobalAlloc(GMEM_MOVEABLE, size);
if hmem != std::ptr::null_mut() {
let ptr = GlobalLock(hmem);
if !ptr.is_null() {
std::ptr::copy_nonoverlapping(buffer.as_ptr(), ptr as *mut u16, buffer.len());
GlobalUnlock(hmem);
SetClipboardData(13, hmem);
}
}
CloseClipboard();
}
}
}
},
IDC_BTN_CLEAR => {
if let Some(edit) = EDIT_HWND {
let empty = to_wstring("");
SetWindowTextW(edit, empty.as_ptr());
}
},
_ => {}
}
0
},
WM_DESTROY => {
CONSOLE_HWND = None;
EDIT_HWND = None;
BTN_COPY_HWND = None;
BTN_CLEAR_HWND = None;
0
},
WM_CTLCOLOREDIT => {
if let Some(result) = crate::ui::theme::handle_standard_colors(hwnd, msg, wparam, IS_DARK_MODE) {
return result;
}
DefWindowProcW(hwnd, msg, wparam, lparam)
},
0x8002 => {
let new_is_dark = wparam == 1;
IS_DARK_MODE = new_is_dark;
if let Some(edit) = EDIT_HWND {
if new_is_dark {
let dark_mode = to_wstring("DarkMode_Explorer");
SetWindowTheme(edit, dark_mode.as_ptr(), std::ptr::null());
} else {
let explorer = to_wstring("Explorer");
SetWindowTheme(edit, explorer.as_ptr(), std::ptr::null());
}
}
if let Some(btn) = BTN_COPY_HWND {
apply_button_theme(btn, new_is_dark);
}
if let Some(btn) = BTN_CLEAR_HWND {
apply_button_theme(btn, new_is_dark);
}
update_console_theme(hwnd, new_is_dark);
InvalidateRect(hwnd, std::ptr::null(), 1);
0
},
_ => DefWindowProcW(hwnd, msg, wparam, lparam),
}
}
unsafe fn update_console_theme(hwnd: HWND, is_dark: bool) {
crate::ui::theme::set_window_frame_theme(hwnd, is_dark);
if let Some(edit) = EDIT_HWND {
InvalidateRect(edit, std::ptr::null(), 1);
}
}