Harbor

branch main
showing the latest snapshot on main
platform.odin 5.6 KB · Plain text
platform/platform.odin 0644 Raw
package platform

import "core:time"
import bk "../backend"
import window "window:window"

// Cursor mode re-export for vex API
Cursor_Mode :: window.Cursor_Mode

Platform_State :: struct {
	event_loop:   ^window.Event_Loop,
	win:          ^window.Window,
	should_close: bool,
	was_resized:  bool,
	new_size:     window.Physical_Size,
	owns_window:  bool, // true when platform created the window, false when external

	// Input
	input:   Input_State,
	actions: Action_State,

	// Timing
	time: Time_State,
}

init_platform :: proc(width, height: i32, title: string) -> (state: Platform_State, ok: bool) {
	loop, loop_err := window.create_event_loop()
	if loop_err != .None {
		return {}, false
	}

	attrs := window.default_window_attributes()
	attrs.title = title
	attrs.size = {u32(width), u32(height)}

	win, win_err := window.create_window(loop, attrs)
	if win_err != .None {
		window.destroy_event_loop(loop)
		return {}, false
	}

	state.event_loop = loop
	state.win = win
	state.should_close = false
	state.was_resized = false
	state.new_size = attrs.size
	state.owns_window = true
	init_input(&state.input)
	init_time(&state.time)

	return state, true
}

// Initialize platform state using an externally-owned window and event loop.
// The caller is responsible for creating and destroying the window.
init_platform_external :: proc(
	win: ^window.Window,
	loop: ^window.Event_Loop,
) -> (state: Platform_State, ok: bool) {
	if win == nil || loop == nil {
		return {}, false
	}
	state.event_loop = loop
	state.win = win
	state.should_close = false
	state.was_resized = false
	state.new_size = window.surface_size(win)
	state.owns_window = false
	init_input(&state.input)
	init_time(&state.time)
	return state, true
}

shutdown_platform :: proc(state: ^Platform_State) {
	g_polling_state = nil

	if state.owns_window {
		if state.win != nil {
			window.destroy_window(state.win)
			state.win = nil
		}
		if state.event_loop != nil {
			window.destroy_event_loop(state.event_loop)
			state.event_loop = nil
		}
	}
}

// Process all pending events in polling mode
poll_events :: proc(state: ^Platform_State) {
	set_polling_state(state)

	// Swap input buffers for pressed/released detection
	begin_input_frame(&state.input)

	// Poll all pending events
	window.poll(state.event_loop, proc(event: window.Event, loop: ^window.Event_Loop) -> window.Control_Flow {
		// We access platform state via the event loop's user data mechanism
		// For now, use a module-level pointer set before polling
		handle_event(event)
		return window.Poll{}
	})
}

// Module-level pointer to current platform state for event handler callback
@(private)
g_polling_state: ^Platform_State

@(private)
handle_event :: proc(event: window.Event) {
	state := g_polling_state
	if state == nil {
		return
	}

	switch e in event {
	case window.Window_Event_Data:
		switch we in e.event {
		case window.Close_Requested:
			state.should_close = true
		case window.Resized:
			state.was_resized = true
			state.new_size = we.size
		case window.Key_Input:
			handle_key_input(&state.input, we)
		case window.Mouse_Button_Input:
			handle_mouse_button_input(&state.input, we)
		case window.Pointer_Moved:
			handle_pointer_moved(&state.input, we)
		case window.Mouse_Wheel:
			handle_mouse_wheel(&state.input, we)
		case window.Focused:
		case window.Unfocused:
		case window.Moved:
		case window.Destroyed:
		case window.Char_Input:
		case window.Pointer_Entered:
		case window.Pointer_Left:
		case window.Scale_Factor_Changed:
		case window.Redraw_Requested:
		}
	case window.Device_Event_Data:
		switch de in e.event {
		case window.Pointer_Motion:
			handle_raw_motion(&state.input, de)
		case window.Device_Button:
		case window.Device_Key:
		}
	case window.New_Events:
	case window.About_To_Wait:
	case window.Loop_Exiting:
	}
}

// Must be called before poll_events to set the state pointer
@(private)
set_polling_state :: proc(state: ^Platform_State) {
	g_polling_state = state
}

// Window queries
get_window_width :: proc(state: ^Platform_State) -> i32 {
	if state.win == nil {
		return 0
	}
	size := window.surface_size(state.win)
	return i32(size.width)
}

get_window_height :: proc(state: ^Platform_State) -> i32 {
	if state.win == nil {
		return 0
	}
	size := window.surface_size(state.win)
	return i32(size.height)
}

toggle_fullscreen :: proc(state: ^Platform_State) {
	if state.win == nil {
		return
	}
	current := window.get_fullscreen(state.win)
	if current == nil {
		window.set_fullscreen(state.win, window.Borderless{nil})
	} else {
		window.set_fullscreen(state.win, nil)
	}
}

set_cursor_mode :: proc(state: ^Platform_State, mode: Cursor_Mode) {
	if state.win != nil {
		window.set_cursor_mode(state.win, mode)
	}
}

// Raw window handle for Vulkan surface creation
get_raw_window_handle :: proc(state: ^Platform_State) -> window.Raw_Window_Handle {
	if state.win == nil {
		return nil
	}
	return window.raw_window_handle(state.win)
}

get_raw_display_handle :: proc(state: ^Platform_State) -> window.Raw_Display_Handle {
	if state.event_loop == nil {
		return nil
	}
	return window.raw_display_handle(state.event_loop)
}

surface_desc :: proc(state: ^Platform_State) -> bk.Surface_Desc {
	if state == nil || state.win == nil {
		return {}
	}

	handle := window.raw_window_handle(state.win)
	switch h in handle {
	case window.X11_Window_Handle:
		return {
			kind = .X11,
			x11  = {display = h.display, window = h.window},
		}
	case window.Wayland_Window_Handle:
		return {
			kind    = .Wayland,
			wayland = {display = h.display, surface = h.surface},
		}
	case window.Win32_Window_Handle:
		return {
			kind  = .Win32,
			win32 = {hwnd = h.hwnd, hinstance = h.hinstance},
		}
	}

	return {}
}