compo_window/window/
win.rs

1use {
2    compo::prelude::*,
3    tracing::{error, info},
4    windows::{
5        Win32::{
6            Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM},
7            Graphics::Gdi::HBRUSH,
8            System::LibraryLoader::GetModuleHandleW,
9            UI::{
10                Input::KeyboardAndMouse::EnableWindow,
11                WindowsAndMessaging::{
12                    CREATESTRUCTW, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, CreateWindowExW,
13                    DefWindowProcW, DestroyWindow, GWLP_USERDATA, GetClientRect, HCURSOR, HICON,
14                    PostQuitMessage, RegisterClassW, SW_SHOW, SWP_NOZORDER, SetWindowLongPtrW,
15                    SetWindowPos, SetWindowTextW, ShowWindow, WM_CREATE, WM_DESTROY, WNDCLASSW,
16                    WS_EX_LEFT, WS_OVERLAPPEDWINDOW,
17                },
18            },
19        },
20        core::{PCWSTR, w},
21    },
22};
23
24// Window procedure callback function
25unsafe extern "system" fn window_proc(
26    hwnd: HWND,
27    msg: u32,
28    wparam: WPARAM,
29    lparam: LPARAM,
30) -> LRESULT {
31    match msg {
32        WM_CREATE => {
33            let create_struct = lparam.0 as *const CREATESTRUCTW;
34            let window_ptr = unsafe { (*create_struct).lpCreateParams };
35            unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, window_ptr as isize) };
36            LRESULT::default()
37        }
38        WM_DESTROY => {
39            unsafe { PostQuitMessage(0) };
40            LRESULT::default()
41        }
42        _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) },
43    }
44}
45
46// Window component
47#[component]
48pub async fn window(
49    #[default = "Window"] title: &str,
50    #[default = 800] width: i32,
51    #[default = 600] height: i32,
52    #[default = CW_USEDEFAULT] left: i32,
53    #[default = CW_USEDEFAULT] top: i32,
54    #[default = true] visible: bool,
55    #[default = true] enabled: bool,
56) {
57    #[field]
58    // This is a field of the component's internal structure, not a variable in the current scope, so it can persist across multiple renders
59    let hwnd: Option<HWND> = None;
60
61    if *visible {
62        if hwnd.is_none() {
63            // Register window class
64            let h_instance = match unsafe { GetModuleHandleW(PCWSTR::null()) } {
65                Err(e) => {
66                    error!(?e, "Can't get module handle.");
67                    return;
68                }
69                Ok(h) => h.into(),
70            };
71
72            let class_name = w!("CompoWindow");
73            let wc = WNDCLASSW {
74                style: CS_HREDRAW | CS_VREDRAW,
75                lpfnWndProc: Some(window_proc),
76                cbClsExtra: 0,
77                cbWndExtra: 0,
78                hInstance: h_instance,
79                hIcon: HICON::default(),
80                hCursor: HCURSOR::default(),
81                hbrBackground: HBRUSH::default(),
82                lpszMenuName: PCWSTR::null(),
83                lpszClassName: class_name,
84            };
85
86            unsafe {
87                RegisterClassW(&wc);
88
89                // Create window
90                // Convert string to UTF-16 and ensure it ends with null
91                let mut window_title: Vec<u16> = title.encode_utf16().collect();
92                window_title.push(0); // Add null terminator
93                let hwnd_value = match CreateWindowExW(
94                    WS_EX_LEFT,
95                    class_name,
96                    PCWSTR(window_title.as_ptr()),
97                    WS_OVERLAPPEDWINDOW,
98                    *left,
99                    *top,
100                    *width,
101                    *height,
102                    None,
103                    None,
104                    Some(h_instance),
105                    None,
106                ) {
107                    Ok(h) => h,
108                    Err(e) => {
109                        error!(?e, "Can't create window.");
110                        return;
111                    }
112                };
113
114                hwnd.replace(hwnd_value);
115            }
116        }
117
118        // Show window and update parameters
119        if let Some(hwnd) = hwnd {
120            let _ = unsafe { ShowWindow(*hwnd, SW_SHOW) };
121
122            // Update window title (supports reactive updates)
123            let mut window_title: Vec<u16> = title.encode_utf16().collect();
124            window_title.push(0); // Add null terminator
125            let _ = unsafe { SetWindowTextW(*hwnd, PCWSTR(window_title.as_ptr())) };
126
127            // Update window position and size (supports reactive updates)
128            let _ = unsafe {
129                SetWindowPos(
130                    *hwnd,
131                    None,
132                    *left,
133                    *top,
134                    *width,
135                    *height,
136                    SWP_NOZORDER, // Don't change Z-order
137                )
138            };
139
140            // Update window enabled state (supports reactive updates)
141            let _ = unsafe { EnableWindow(*hwnd, *enabled) };
142
143            // Get client area size
144            let mut rect = RECT {
145                left: 0,
146                top: 0,
147                right: 0,
148                bottom: 0,
149            };
150            let _ = unsafe { GetClientRect(*hwnd, &mut rect) };
151
152            info!(
153                "Window updated with client area: {}x{}",
154                rect.right - rect.left,
155                rect.bottom - rect.top
156            );
157        } else {
158            error!("Failed to create window.");
159        }
160    } else if let Some(hwnd) = hwnd.take() {
161        // If shown is false and window exists, destroy the window
162        let _ = unsafe { DestroyWindow(hwnd) };
163    }
164}