commit f03f8b909eed45c80cc2e624620801f19ce953d8 Author: samstalhandske Date: Fri Nov 14 11:55:19 2025 +0100 Initial commit. diff --git a/window.odin b/window.odin new file mode 100644 index 0000000..e15009e --- /dev/null +++ b/window.odin @@ -0,0 +1,117 @@ +package window + +import "core:fmt" +Window :: struct { + title: string, + width, height: u16, + + backend: Backend_Info, +} + +Event :: union { + Event_Quit, + Event_Key, + Event_Resize, +} + +Event_Quit :: struct {} + +Event_Key_State :: enum { + Down, + Up, +} +Virtual_Key :: enum { + Unknown, + + Number_0, Number_1, Number_2, Number_3, Number_4, Number_5, Number_6, Number_7, Number_8, Number_9, + + Letter_A, Letter_B, Letter_C, Letter_D, Letter_E, Letter_F, Letter_G, Letter_H, Letter_I, Letter_J, + Letter_K, Letter_L, Letter_M, Letter_N, Letter_O, Letter_P, Letter_Q, Letter_R, Letter_S, Letter_T, + Letter_U, Letter_V, Letter_W, Letter_X, Letter_Y, Letter_Z, + + Space, Enter, Escape, Tab, Backspace, CapsLock, + Shift, Control, Alt, + Arrow_Up, Arrow_Down, Arrow_Left, Arrow_Right, +} +Event_Key :: struct { + virtual_key: Virtual_Key, + state: Event_Key_State, +} +Event_Resize :: struct { + old_width, old_height: u16, + new_width, new_height: u16, +} + +create :: proc(width, height: u16) -> (^Window, bool) { + assert(width > 0 && height > 0) + + w := new(Window) + w.width = width + w.height = height + + when ODIN_OS == .Windows { + if !init_window_windows(w) { + free(w) + return nil, false + } + } + else { + #assert(false, "Missing implementation for 'create'.") + } + + set_title(w, "Window") + + return w, true +} + +set_title :: proc(window: ^Window, title: string) { + assert(window != nil) + + window.title = title + + when ODIN_OS == .Windows { + set_title_windows(window) + } + else { + #assert(false, "Missing implementation for 'set_title'.") + } +} + +get_pointer_to_surface :: proc(window: ^Window) -> rawptr { + when ODIN_OS == .Windows { + // fmt.printfln("HWND: %v.", window.backend.hwnd) + return window.backend.hwnd + } + else { + #assert(false, "Missing implementation for 'get_pointer_to_surface'.") + } +} + +update :: proc(window: ^Window, out_event: ^Event) -> bool { + assert(window != nil) + + event: Event + when ODIN_OS == .Windows { + event = update_windows(window) + } + else { + #assert(false, "Missing implementation for 'set_title'.") + } + + out_event^ = event + + if out_event^ == nil { + return false + } + + return true +} + +destroy :: proc(window: ^Window) { + assert(window != nil) + + destroy_windows(window) + + free(window) + window^ = {} +} \ No newline at end of file diff --git a/window_windows.odin b/window_windows.odin new file mode 100644 index 0000000..9e9ebac --- /dev/null +++ b/window_windows.odin @@ -0,0 +1,209 @@ +package window + +import "core:fmt" +import win "core:sys/windows" + +Backend_Info :: struct { + instance: win.HINSTANCE, + hwnd: win.HWND, +} + +init_window_windows :: proc(window: ^Window) -> bool { + assert(window != nil) + + instance := win.HINSTANCE(win.GetModuleHandleW(nil)) + assert(instance != nil, "Failed to fetch current instance") + + CLASS_NAME :: "ENSENN_WINDOW" + + cls := win.WNDCLASSW { + lpfnWndProc = proc "stdcall" (hwnd: win.HWND, msg: win.UINT, wparam: win.WPARAM, lparam: win.LPARAM) -> win.LRESULT { + switch(msg) { + case win.WM_CLOSE: { + win.DestroyWindow(hwnd) + } + case win.WM_DESTROY: { + win.PostQuitMessage(0) + } + case: { + return win.DefWindowProcW(hwnd, msg, wparam, lparam) + } + } + + return 0 + }, + lpszClassName = win.utf8_to_wstring(CLASS_NAME), + hInstance = instance, + hCursor = win.LoadCursorA(nil, win.IDC_ARROW), + } + + class := win.RegisterClassW(&cls) + assert(class != 0, "Class creation failed") + + screen_width := win.GetSystemMetrics(win.SM_CXSCREEN) + screen_height := win.GetSystemMetrics(win.SM_CYSCREEN) + + window_style := win.WS_OVERLAPPEDWINDOW | win.WS_VISIBLE | win.CS_OWNDC // TODO: SS - Allow customizing these? + + rect := win.RECT { 0, 0, i32(window.width), i32(window.height) } + win.AdjustWindowRectEx(&rect, window_style, win.FALSE, 0) + + actual_window_width := rect.right - rect.left + actual_window_height := rect.bottom - rect.top + + hwnd := win.CreateWindowW( + lpClassName = win.L(CLASS_NAME), + lpWindowName = win.L(CLASS_NAME), + dwStyle = window_style, + X = (screen_width - i32(window.width)) / 2, + Y = (screen_height - i32(window.height)) / 2, + nWidth = actual_window_width, + nHeight = actual_window_height, + hWndParent = nil, + hMenu = nil, + hInstance = instance, + lpParam = nil + ) + assert(hwnd != nil, "Window creation failed") + + window.backend = { + instance = instance, + hwnd = hwnd, + } + + // fmt.printfln("Window size: %vx%v.", actual_window_width, actual_window_height) + + return true +} + +set_title_windows :: proc(window: ^Window) { + assert(window != nil) + win.SetWindowTextW(window.backend.hwnd, win.utf8_to_wstring(window.title)) +} + +update_windows :: proc(window: ^Window) -> Event { + msg: win.MSG + + { // Resizing. + rect: win.RECT + win.GetClientRect(window.backend.hwnd, &rect) + + new_width := u16(rect.right - rect.left) + new_height := u16(rect.bottom - rect.top) + + if new_width != window.width || new_height != window.height { + old_width := window.width + old_height := window.height + + window.width = new_width + window.height = new_height + + if new_width > 0 && new_height > 0 { + return Event_Resize { + old_width, old_height, + window.width, window.height, + } + } + } + } + + ok := win.PeekMessageW( + lpMsg = &msg, + hWnd = nil, // NOTE: SS - Don't like that this needs to be nil but apparently it does for this window to receive the WM_CLOSE and WM_QUIT messages. + // If wMsgFilterMin and wMsgFilterMax are both zero, + // PeekMessage returns all available messages (that is, no range filtering is performed). + wMsgFilterMin = 0, wMsgFilterMax = 0, + wRemoveMsg = win.PM_REMOVE // If 'PM_REMOVE', messages are removed from the queue after processing by PeekMessage. + ) + if !ok { + // No messages. + return nil + } + + win.TranslateMessage(&msg) + win.DispatchMessageW(&msg) + + // fmt.printfln("Message type: %x", msg.message) + + switch msg.message { + case win.WM_KEYDOWN, win.WM_SYSKEYDOWN: { + return Event_Key { virtual_key = get_virtual_key_windows(i32(msg.wParam)), state = .Down } + } + case win.WM_KEYUP, win.WM_SYSKEYUP: { + return Event_Key { virtual_key = get_virtual_key_windows(i32(msg.wParam)), state = .Up } + } + case win.WM_QUIT: { + return Event_Quit {} + } + } + + return nil +} + +destroy_windows :: proc(window: ^Window) { + assert(window != nil) + assert(window.backend.hwnd != nil) + + win.DestroyWindow(window.backend.hwnd) + window.backend.hwnd = nil +} + +@(private) get_virtual_key_windows :: proc(vk: i32) -> Virtual_Key { + switch vk { + case win.VK_0: return .Number_0 + case win.VK_1: return .Number_1 + case win.VK_2: return .Number_2 + case win.VK_3: return .Number_3 + case win.VK_4: return .Number_4 + case win.VK_5: return .Number_5 + case win.VK_6: return .Number_6 + case win.VK_7: return .Number_7 + case win.VK_8: return .Number_8 + case win.VK_9: return .Number_9 + + case win.VK_A: return .Letter_A + case win.VK_B: return .Letter_B + case win.VK_C: return .Letter_C + case win.VK_D: return .Letter_D + case win.VK_E: return .Letter_E + case win.VK_F: return .Letter_F + case win.VK_G: return .Letter_G + case win.VK_H: return .Letter_H + case win.VK_I: return .Letter_I + case win.VK_J: return .Letter_J + case win.VK_K: return .Letter_K + case win.VK_L: return .Letter_L + case win.VK_M: return .Letter_M + case win.VK_N: return .Letter_N + case win.VK_O: return .Letter_O + case win.VK_P: return .Letter_P + case win.VK_Q: return .Letter_Q + case win.VK_R: return .Letter_R + case win.VK_S: return .Letter_S + case win.VK_T: return .Letter_T + case win.VK_U: return .Letter_U + case win.VK_V: return .Letter_V + case win.VK_W: return .Letter_W + case win.VK_X: return .Letter_X + case win.VK_Y: return .Letter_Y + case win.VK_Z: return .Letter_Z + + case win.VK_SPACE: return .Space + case win.VK_RETURN: return .Enter + case win.VK_ESCAPE: return .Escape + case win.VK_TAB: return .Tab + case win.VK_BACK: return .Backspace + case win.VK_CAPITAL: return .CapsLock + + case win.VK_SHIFT: return .Shift + case win.VK_CONTROL: return .Control + case win.VK_MENU: return .Alt + + case win.VK_LEFT: return .Arrow_Left + case win.VK_RIGHT: return .Arrow_Right + case win.VK_UP: return .Arrow_Up + case win.VK_DOWN: return .Arrow_Down + + case: return .Unknown + } +} \ No newline at end of file