Add basic window creation (Win32)

This commit is contained in:
Musa Mahmood 2025-12-02 14:06:39 -05:00
parent 56dec1cf93
commit 15cf7e68fd
5 changed files with 303 additions and 21 deletions

View File

@ -34,6 +34,10 @@ struct OS_System_Info {
u64 large_page_size;
u64 allocation_granularity;
string machine_name;
// Monitor stuff:
b32 monitors_enumerated;
Array<Monitor> monitors; // Back with GPAllocator
};
struct OS_Process_Info {
@ -44,6 +48,10 @@ struct OS_Process_Info {
string user_program_data_path;
Array<string> module_load_paths;
Array<string> environment_paths;
b32 window_class_initialized;
Arena* event_arena;
Array<Window_Info> windows;
};
struct OS_State_Win32 {
@ -53,11 +61,11 @@ struct OS_State_Win32 {
OS_Process_Info process_info;
};
global OS_State_Win32 os_state_w32;
internal b32 win32_g_is_quiet = 0; // No console output
global OS_State_Win32 global_win32_state;
internal b32 global_win32_is_quiet = 0; // No console output
internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs) {
if (win32_g_is_quiet) { ExitProcess(1); }
if (global_win32_is_quiet) { ExitProcess(1); }
local_persist volatile LONG first = 0;
if(InterlockedCompareExchange(&first, 1, 0) != 0)
@ -69,6 +77,8 @@ internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs)
// #Exception handling code (TODO)
ExitProcess(1);
return 0;
}
@ -102,18 +112,18 @@ internal void Win32_Entry_Point (int argc, WCHAR **argv) {
push_arena(thread_context()->arena);
{ OS_System_Info* info = &os_state_w32.system_info;
{ OS_System_Info* info = &global_win32_state.system_info;
info->logical_processor_count = (s32)sysinfo.dwNumberOfProcessors;
info->page_size = sysinfo.dwPageSize;
info->large_page_size = GetLargePageMinimum();
info->allocation_granularity = sysinfo.dwAllocationGranularity;
}
{ OS_Process_Info* info = &os_state_w32.process_info;
{ OS_Process_Info* info = &global_win32_state.process_info;
info->large_pages_allowed = false;
info->process_id = GetCurrentProcessId();
}
// #cpuid
/*{ OS_System_Info* info = &os_state_w32.system_info;
/*{ OS_System_Info* info = &global_win32_state.system_info;
// [ ] Extract input args
u32 length;
GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, (PDWORD)&length);
@ -154,7 +164,8 @@ internal void Win32_Entry_Point (int argc, WCHAR **argv) {
}
// info->secondary_core_count = ;
*/
{ OS_System_Info* info = &os_state_w32.system_info;
{ OS_System_Info* info = &global_win32_state.system_info;
info->monitors.allocator = GPAllocator();
u8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0};
DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
if(GetComputerNameA((char*)buffer, &size)) {
@ -163,7 +174,8 @@ internal void Win32_Entry_Point (int argc, WCHAR **argv) {
}
}
{ OS_Process_Info* info = &os_state_w32.process_info;
{ OS_Process_Info* info = &global_win32_state.process_info;
info->windows.allocator = GPAllocator();
DWORD length = GetCurrentDirectoryW(0, 0);
// This can be freed later when we call temp_reset();
u16* memory = NewArray<u16>(temp(), length + 1);
@ -172,6 +184,11 @@ internal void Win32_Entry_Point (int argc, WCHAR **argv) {
Assert(is_valid(info->working_path));
}
// Setup event arena, allocators for Array<> types.
if (!global_win32_state.process_info.event_arena) {
global_win32_state.process_info.event_arena = next_arena(Arena_Reserve::Size_64K);
}
// [ ] Get Working directory (info->working_path)
// [ ] GetEnvironmentStringsW
temp_reset();
@ -504,10 +521,211 @@ internal bool write_entire_file (string file_path, ArrayView<u8> file_data) {
return write_entire_file(file_path, file_data.data, file_data.count);
}
internal LRESULT // see os_w32_wnd_proc from raddebugger.
win32_wnd_proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
LRESULT result = 0;
bool good = true;
Arena* event_arena = global_win32_state.process_info.event_arena;
Assert(event_arena != nullptr);
switch (uMsg) {
default: {
result = DefWindowProcW(hwnd, uMsg, wParam, lParam);
} break;
case WM_CLOSE: {
ExitProcess(0); // #temp.
} break;
}
return result;
}
internal BOOL
monitor_enum_proc (HMONITOR hMonitor, HDC hdc, RECT* rect, LPARAM data) {
Monitor monitor = {};
monitor.left = rect->left;
monitor.top = rect->top;
monitor.right = rect->right;
monitor.bottom = rect->bottom;
monitor.monitor_info.cbSize = sizeof(MONITORINFO);
GetMonitorInfoW(hMonitor, &monitor.monitor_info);
monitor.primary = !!(monitor.monitor_info.dwFlags & 0x1);
monitor.present = true;
array_add(global_win32_state.system_info.monitors, monitor);
return true;
}
// #TODO: how do I setup a callback if monitors configuration is changed?
// we handle WM_DISPLAYCHANGE or WM_DEVICECHANGE and call EnumDisplayMonitors
internal void os_enumerate_monitors () {
if (!global_win32_state.system_info.monitors_enumerated) {
// should reset array?
if (!EnumDisplayMonitors(nullptr, nullptr, monitor_enum_proc, 0)) {
log_error("Failed to enumerate monitors\n");
Assert(false); // Failed to enumerate monitors
ExitProcess(1);
}
global_win32_state.system_info.monitors_enumerated = true;
}
}
Window_Dimensions platform_get_centered_window_dimensions (bool open_on_largest_monitor=false) {
os_enumerate_monitors();
Assert(global_win32_state.system_info.monitors.count > 0); // must have at least 1 monitor!
Array<Monitor> monitors = global_win32_state.system_info.monitors;
Monitor monitor = monitors[0];
if (open_on_largest_monitor) {
s64 max_area = 0;
for (s64 i = 0; i < monitors.count; i += 1) {
s64 width = monitors[i].right - monitors[i].left;
s64 height = monitors[i].bottom - monitors[i].top;
s64 area = width * height;
if (max_area < area) {
monitor = monitors[i];
max_area = area;
} else if (max_area == area && monitors[i].primary) {
// if monitors are the same dimension, then just use primary monitor
monitor = monitors[i];
}
}
} else { // Opens on whatever monitor is marked as "primary."
for (s64 i = 0; i < monitors.count; i += 1) {
if (monitors[i].primary) {
monitor = monitors[i];
break;
}
}
}
s64 monitor_width = monitor.right - monitor.left;
s64 monitor_height = monitor.bottom - monitor.top;
s64 window_width = (s64)((f64)monitor_width / 1.125);
s64 window_height = (s64)((f64)monitor_height / 1.25);
s64 window_x = (monitor.left + (monitor_width / 2) - (window_width / 2));
s64 window_y = (monitor.top + (monitor_height / 2) - (window_height / 2));
Window_Dimensions dimensions = {
(s32)window_x,
(s32)window_y,
(s32)window_width,
(s32)window_height,
};
return dimensions;
}
// #window_creation -> put API in OS_Win32.h
// void initialize_window_class(float background_color[3]) {}
Window_Type create_window (string new_window_name, bool center_window) {
return 0;
// Instead of returning WindowType, return the handle + other information.
bool os_create_window (string new_window_name, Window_Type parent, bool center_window, bool open_on_largest_monitor, bool display_window) {
const ArrayView<f32> background_color = array_view_from_values<f32>(0.0f, 0.0f, 0.0f);
local_persist string class_name = "Win32_Window_Class";
if (!global_win32_state.process_info.window_class_initialized) {
global_win32_state.process_info.window_class_initialized = true;
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = win32_wnd_proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = nullptr;
wc.hIcon = (HICON)LoadImageW(nullptr, (LPCWSTR)utf8_to_wide("tmp.ico").data, IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
wc.hCursor = LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW);
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = (LPCWSTR)utf8_to_wide(class_name).data;
if (RegisterClassExW(&wc) == 0) {
log_error("RegisterClassExW Failed.");
return false;
}
}
Window_Dimensions wd = { 100, 100, 640, 480 };
if (center_window) {
wd = platform_get_centered_window_dimensions(open_on_largest_monitor);
log("[Window_Dimensions] location: (%d, %d); size: %dx%d px",
wd.window_x, wd.window_y, wd.window_width, wd.window_height);
}
HWND hwnd = CreateWindowExW(
WS_EX_APPWINDOW,
(LPCWSTR)utf8_to_wide(class_name).data,
(LPCWSTR)utf8_to_wide(new_window_name).data,
WS_OVERLAPPEDWINDOW,
wd.window_x, wd.window_y, wd.window_width, wd.window_height,
nullptr, nullptr, nullptr, nullptr
);
if (!hwnd) {
Assert(false);
DestroyWindow(hwnd);
return false;
}
// Display the window:
if (display_window) {
UpdateWindow(hwnd);
ShowWindow(hwnd, SW_SHOW);
}
// Save window to stack
Window_Info info = {};
info.window = hwnd;
info.initial_dimensions = wd;
info.is_main_window = (parent == nullptr);
// Should we mutex this? Seems very unlikely we'll ever need to call this
// from multiple threads at the same time.
array_add(global_win32_state.process_info.windows, info);
return true;
}
Window_Info get_main_window() {
Array<Window_Info> windows = global_win32_state.process_info.windows;
Assert(windows.count > 0);
for (s64 i = 0; i < windows.count; i += 1) {
if (windows[i].is_main_window) {
return windows[i];
}
}
return {};
}
bool get_window_dimensions(Window_Info* info, s32* width, s32* height) {
if (info == nullptr || width == nullptr || height == nullptr) return false;
Assert(width && height);
RECT c = {};
bool success = GetClientRect(info->window, &c);
if (!success) {
(*width) = 0;
(*height) = 0;
return false;
}
(*width) = (s32)(c.right - c.left);
(*height) = (s32)(c.bottom - c.top);
return true;
}
// #TODO: #window_creation

View File

@ -1,12 +1,8 @@
#pragma comment(lib, "user32")
f64 GetUnixTimestamp ();
s64 GetUnixTimestampNanoseconds ();
// struct File_Contents {
// File file = {};
// ArrayView<u8> file_data = {};
// bool read_success = false;
// };
enum class Wait_For_Result : s32 {
SUCCESS = 0,
TIMEOUT = 1,
@ -51,3 +47,41 @@ internal bool write_entire_file (string file_path, ArrayView<u8> file_data);
// #window_creation
typedef HWND Window_Type;
struct Window_Dimensions {
s32 window_x;
s32 window_y;
s32 window_width;
s32 window_height;
};
struct Window_Info {
Window_Type window;
Window_Dimensions initial_dimensions;
b32 is_main_window;
};
// #move: Monitor - platform-independent hardware monitor information:
struct Monitor {
s64 left;
s64 top;
s64 right;
s64 bottom;
bool primary;
bool present;
#if OS_WINDOWS
MONITORINFO monitor_info;
#endif
};
bool os_create_window (string new_window_name, Window_Type parent=nullptr, bool center_window=true, bool open_on_largest_monitor=true, bool display_window=true);
Window_Info get_main_window();
// struct File_Contents {
// File file = {};
// ArrayView<u8> file_data = {};
// bool read_success = false;
// };

15
lib/UI/Layout.h Normal file
View File

@ -0,0 +1,15 @@
struct Rect {
f32 x; f32 y; f32 w; f32 h;
};
force_inline bool operator==(const Rect& a, const Rect& b) {
return a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h;
}
Rect make_rect_int (s64 x, s64 y, s64 w, s64 h) {
return {(f32)x, (f32)y, (f32)w, (f32)h};
}
Rect make_rect (f32 x, f32 y, f32 w, f32 h) {
return {x, y, w, h};
}

View File

@ -24,6 +24,7 @@
#if OS_WINDOWS
# include "lib/OS/OS_Win32.h"
#endif
#include "lib/UI/Layout.h"
#include "lib/Base/Logger.h"
#include "lib/Base/String.cpp"

View File

@ -35,11 +35,25 @@ internal void Main_Entry_Point (int argc, WCHAR **argv) {
run_post_setup_tests();
#endif
// 2. Setup Window
// 3. Initialize Graphics Backend (DX11 or OpenGL3)
// 2. Create Window
bool success = os_create_window("App Main Window", nullptr, true, true);
Assert(success);
Window_Info main_window = get_main_window();
s32 window_width; s32 window_height;
success = get_window_dimensions(&main_window, &window_width, &window_height);
Assert(success);
// #TODO: Set in global state for convenience:
Rect main_screen = make_rect_int(0, 0, window_width, window_height);
// renderer.//set_render_target(main_window.window, main_screen);
// get_render_dimensions
// 3. Init Graphics (DX11 or OpenGL3)
// Maybe start with OpenGL3
// #TODO: #Main - `Main_Entry_Point`
// [ ] Setup Mouse and Keyboard Inputs
// [ ] Launch second thread
// 4. [ ] Setup Mouse and Keyboard Inputs
// 5. [ ] Launch second thread; thread groups
}