raw mouse look. cursor hiding & clipping

This commit is contained in:
jacob 2026-01-31 18:36:17 -06:00
parent bd0b22b889
commit eda5e44d3b
5 changed files with 255 additions and 120 deletions

View File

@ -142,10 +142,10 @@ Struct(ControllerEvent)
u32 text_codepoint; u32 text_codepoint;
// ControllerEventKind_CursorMove // ControllerEventKind_CursorMove
Vec2I32 cursor_pos; Vec2 cursor_pos;
// ControllerEventKind_MouseMove // ControllerEventKind_MouseMove
Vec2I32 mouse_delta; Vec2 mouse_delta;
}; };
Struct(ControllerEventsArray) Struct(ControllerEventsArray)

View File

@ -569,7 +569,7 @@ void V_TickForever(WaveLaneCtx *lane)
frame->is_editing = prev_frame->is_editing; frame->is_editing = prev_frame->is_editing;
frame->ui_debug = prev_frame->ui_debug; frame->ui_debug = prev_frame->ui_debug;
frame->show_console = prev_frame->show_console; frame->show_console = prev_frame->show_console;
frame->look = NormRot(prev_frame->look); frame->look = prev_frame->look;
frame->edit_mode = prev_frame->edit_mode; frame->edit_mode = prev_frame->edit_mode;
frame->equipped_tile = prev_frame->equipped_tile; frame->equipped_tile = prev_frame->equipped_tile;
frame->edit_camera_pos = prev_frame->edit_camera_pos; frame->edit_camera_pos = prev_frame->edit_camera_pos;
@ -653,6 +653,7 @@ void V_TickForever(WaveLaneCtx *lane)
UI_Frame *ui_frame = UI_BeginFrame(ui_frame_flags); UI_Frame *ui_frame = UI_BeginFrame(ui_frame_flags);
WND_Frame window_frame = ui_frame->window_frame; WND_Frame window_frame = ui_frame->window_frame;
WND_SetCursor(window_frame, WND_CursorKind_Default); WND_SetCursor(window_frame, WND_CursorKind_Default);
WND_PushCmd(window_frame, .kind = WND_CmdKind_SetLockedCursor, .v = 0);
// Restore window // Restore window
{ {
@ -734,80 +735,150 @@ void V_TickForever(WaveLaneCtx *lane)
b32 has_mouse_focus = UI_IsKeyNil(ui_frame->hot_box) || UI_MatchKey(ui_frame->hot_box, vis_box); b32 has_mouse_focus = UI_IsKeyNil(ui_frame->hot_box) || UI_MatchKey(ui_frame->hot_box, vis_box);
b32 has_keyboard_focus = 1; b32 has_keyboard_focus = 1;
if (!window_frame.has_focus) Vec2 mouse_delta = Zi;
{ {
has_mouse_focus = 0; if (!window_frame.has_focus)
has_keyboard_focus = 0;
}
for (Button btn = 0; btn < countof(frame->held_buttons); ++btn)
{
if (btn == Button_M1 || btn == Button_M2 || btn == Button_M3)
{ {
if (!has_mouse_focus) has_mouse_focus = 0;
{ has_keyboard_focus = 0;
frame->held_buttons[btn] = 0;
}
} }
else
{
if (!has_keyboard_focus)
{
frame->held_buttons[btn] = 0;
}
}
}
for (u64 i = 0; i < window_frame.controller_events.count; ++i) //- Reset held buttons
{ for (Button btn = 0; btn < countof(frame->held_buttons); ++btn)
ControllerEvent cev = window_frame.controller_events.events[i];
b32 down = cev.kind == ControllerEventKind_ButtonDown;
b32 up = cev.kind == ControllerEventKind_ButtonUp;
b32 ignore = 0;
if (down)
{ {
if (cev.button == Button_M1 || cev.button == Button_M2 || cev.button == Button_M3) if (btn == Button_M1 || btn == Button_M2 || btn == Button_M3)
{ {
if (!has_mouse_focus) if (!has_mouse_focus)
{ {
ignore = 1; frame->held_buttons[btn] = 0;
} }
} }
else else
{ {
if (!has_keyboard_focus) if (!has_keyboard_focus)
{ {
ignore = 1; frame->held_buttons[btn] = 0;
} }
} }
} }
if (!ignore) //- Convert events into cmds
for (u64 cev_idx = 0; cev_idx < window_frame.controller_events.count; ++cev_idx)
{ {
V_Hotkey hotkey = Zi; ControllerEvent cev = window_frame.controller_events.events[cev_idx];
hotkey.button = cev.button; b32 down = cev.kind == ControllerEventKind_ButtonDown;
hotkey.ctrl = frame->held_buttons[Button_Ctrl]; b32 up = cev.kind == ControllerEventKind_ButtonUp;
hotkey.shift = frame->held_buttons[Button_Shift];
hotkey.alt = frame->held_buttons[Button_Alt]; b32 ignore = 0;
if (down)
{ {
u64 hotkey_hash = HashString(StringFromStruct(&hotkey)); if (cev.button == Button_M1 || cev.button == Button_M2 || cev.button == Button_M3)
V_ShortcutBin *bin = &shortcut_bins[hotkey_hash % shortcut_bins_count];
V_Shortcut *shortcut = bin->first;
for (; shortcut; shortcut = shortcut->next_in_bin)
{ {
if (shortcut->hotkey_hash == hotkey_hash && MatchStruct(&shortcut->hotkey, &hotkey)) if (!has_mouse_focus)
{ {
break; ignore = 1;
} }
} }
if (shortcut != 0 && down) else
{ {
V_PushVisCmd(shortcut->cmd_name); if (!has_keyboard_focus)
{
ignore = 1;
}
} }
} }
frame->held_buttons[hotkey.button] = down;
if (!ignore)
{
V_Hotkey hotkey = Zi;
hotkey.button = cev.button;
hotkey.ctrl = frame->held_buttons[Button_Ctrl];
hotkey.shift = frame->held_buttons[Button_Shift];
hotkey.alt = frame->held_buttons[Button_Alt];
{
u64 hotkey_hash = HashString(StringFromStruct(&hotkey));
V_ShortcutBin *bin = &shortcut_bins[hotkey_hash % shortcut_bins_count];
V_Shortcut *shortcut = bin->first;
for (; shortcut; shortcut = shortcut->next_in_bin)
{
if (shortcut->hotkey_hash == hotkey_hash && MatchStruct(&shortcut->hotkey, &hotkey))
{
break;
}
}
if (shortcut != 0 && down)
{
V_PushVisCmd(shortcut->cmd_name);
}
}
frame->held_buttons[hotkey.button] = down;
}
} }
//- Compute mouse delta from events
for (u64 cev_idx = 0; cev_idx < window_frame.controller_events.count; ++cev_idx)
{
ControllerEvent cev = window_frame.controller_events.events[cev_idx];
if (cev.kind == ControllerEventKind_MouseMove)
{
mouse_delta = AddVec2(mouse_delta, cev.mouse_delta);
}
}
}
//////////////////////////////
//- Compute movement & look
if (!frame->is_editing && !frame->palette.is_showing)
{
WND_PushCmd(window_frame, .kind = WND_CmdKind_SetLockedCursor, .v = 1);
WND_SetCursor(window_frame, WND_CursorKind_Hidden);
}
{
Vec2 move = Zi;
{
if (frame->held_buttons[Button_A]) move.x -= 1;
if (frame->held_buttons[Button_D]) move.x += 1;
if (frame->held_buttons[Button_W]) move.y -= 1;
if (frame->held_buttons[Button_S]) move.y += 1;
}
move = ClampVec2Len(move, 1);
f32 fire_held = frame->held_buttons[Button_M1];
Vec2 look = Zi;
f32 fire_presses = fire_held && !prev_frame->held_buttons[Button_M1];
{
// Vec2 center = P_WorldShapeFromEnt(local_guy).centroid;
// look = SubVec2(frame->world_cursor, center);
// TODO: Adjustable sensitivity
f32 mouse_scale_factor = 0.005;
look = frame->look;
look = AddVec2(look, MulVec2(mouse_delta, mouse_scale_factor));
}
if (frame->is_editing)
{
if (!frame->is_panning)
{
f32 edit_move_speed = 20.0 * MaxF32(frame->edit_camera_zoom, min_zoom);
frame->edit_camera_pos = AddVec2(frame->edit_camera_pos, MulVec2(move, edit_move_speed * frame->dt));
}
// FIXME: Remove this
frame->move = prev_frame->move;
frame->look = prev_frame->look;
frame->fire_held = prev_frame->fire_held;
frame->fire_presses = prev_frame->fire_presses;
}
else
{
frame->move = move;
frame->look = look;
frame->fire_held = fire_held;
frame->fire_presses = fire_presses;
}
frame->look = frame->look;
} }
////////////////////////////// //////////////////////////////
@ -858,9 +929,9 @@ void V_TickForever(WaveLaneCtx *lane)
P_Ent *guy = P_EntFromKey(local_world->last_frame, player->guy); P_Ent *guy = P_EntFromKey(local_world->last_frame, player->guy);
Vec2 guy_center = P_WorldShapeFromEnt(guy).centroid; Vec2 guy_center = P_WorldShapeFromEnt(guy).centroid;
Vec2 screen_center = MulVec2(frame->screen_dims, 0.5); Vec2 screen_center = MulVec2(frame->screen_dims, 0.5);
Vec2 look = MulAffineBasisVec2(prev_frame->af.screen_to_world, SubVec2(ui_frame->cursor_pos, screen_center)); // Vec2 look = MulAffineBasisVec2(prev_frame->af.screen_to_world, SubVec2(ui_frame->cursor_pos, screen_center));
target_camera_pos = guy_center; target_camera_pos = guy_center;
target_camera_pos = AddVec2(target_camera_pos, MulVec2Vec2(look, look_ratio)); target_camera_pos = AddVec2(target_camera_pos, MulVec2Vec2(frame->look, look_ratio));
target_camera_zoom = 1; target_camera_zoom = 1;
} }
target_camera_pos.x = ClampF32(target_camera_pos.x, -world_pitch / 2, world_pitch / 2); target_camera_pos.x = ClampF32(target_camera_pos.x, -world_pitch / 2, world_pitch / 2);
@ -1268,50 +1339,6 @@ void V_TickForever(WaveLaneCtx *lane)
} }
} }
//////////////////////////////
//- Compute movement & look
{
Vec2 move = Zi;
{
if (frame->held_buttons[Button_A]) move.x -= 1;
if (frame->held_buttons[Button_D]) move.x += 1;
if (frame->held_buttons[Button_W]) move.y -= 1;
if (frame->held_buttons[Button_S]) move.y += 1;
}
move = ClampVec2Len(move, 1);
f32 fire_held = frame->held_buttons[Button_M1];
Vec2 look = Zi;
f32 fire_presses = fire_held && !prev_frame->held_buttons[Button_M1];
{
Vec2 center = P_WorldShapeFromEnt(local_guy).centroid;
look = SubVec2(frame->world_cursor, center);
}
if (frame->is_editing)
{
if (!frame->is_panning)
{
f32 edit_move_speed = 20.0 * MaxF32(frame->edit_camera_zoom, min_zoom);
frame->edit_camera_pos = AddVec2(frame->edit_camera_pos, MulVec2(move, edit_move_speed * frame->dt));
}
// FIXME: Remove this
frame->move = prev_frame->move;
frame->look = prev_frame->look;
frame->fire_held = prev_frame->fire_held;
frame->fire_presses = prev_frame->fire_presses;
}
else
{
frame->move = move;
frame->look = look;
frame->fire_held = fire_held;
frame->fire_presses = fire_presses;
}
frame->look = NormRot(frame->look);
}
////////////////////////////// //////////////////////////////
//- Determine local simulation bounds //- Determine local simulation bounds
// //
@ -1369,7 +1396,7 @@ void V_TickForever(WaveLaneCtx *lane)
control.tick = control_tick; control.tick = control_tick;
control.orig_tick = control_tick; control.orig_tick = control_tick;
control.move = frame->move; control.move = frame->move;
control.look = NormRot(frame->look); control.look = frame->look;
control.fire_held = frame->fire_held; control.fire_held = frame->fire_held;
// FIXME: Don't propagate fire presses over multiple sim frames // FIXME: Don't propagate fire presses over multiple sim frames
control.fire_presses = frame->fire_presses; control.fire_presses = frame->fire_presses;
@ -1964,7 +1991,11 @@ void V_TickForever(WaveLaneCtx *lane)
Vec2 line_end = AddVec2(line_start, fire_dir); Vec2 line_end = AddVec2(line_start, fire_dir);
P_DebugDrawLine(line_start, line_end, Color_Yellow); P_DebugDrawLine(line_start, line_end, Color_Yellow);
Vec2 cross = fire_pos; // Vec2 cross = fire_pos;
// Vec2 cross = MulAffineVec2(frame->af.screen_to_world, frame->look);
Vec2 cross = AddVec2(ent->xf.t, frame->look);
P_DebugDrawPoint(cross, Color_Red); P_DebugDrawPoint(cross, Color_Red);
} }

View File

@ -12,6 +12,7 @@ Struct(WND_Handle)
Enum(WND_CursorKind) Enum(WND_CursorKind)
{ {
WND_CursorKind_Default, WND_CursorKind_Default,
WND_CursorKind_Hidden,
WND_CursorKind_Text, WND_CursorKind_Text,
WND_CursorKind_No, WND_CursorKind_No,
WND_CursorKind_Hand, WND_CursorKind_Hand,
@ -35,6 +36,7 @@ Enum(WND_CmdKind)
WND_CmdKind_SetFullscreen, WND_CmdKind_SetFullscreen,
WND_CmdKind_SetForcedTop, WND_CmdKind_SetForcedTop,
WND_CmdKind_SetCursor, WND_CmdKind_SetCursor,
WND_CmdKind_SetLockedCursor,
WND_CmdKind_Restore, WND_CmdKind_Restore,
}; };

View File

@ -226,17 +226,33 @@ LRESULT CALLBACK WND_W32_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l
} break; } break;
//- Cursor kind //- Cursor kind
case WM_SETCURSOR: case WM_SETCURSOR:
{ {
if ((HWND)wparam == hwnd && LOWORD(lparam) == HTCLIENT) if ((HWND)wparam == hwnd && LOWORD(lparam) == HTCLIENT)
{ {
HCURSOR desired_cursor = (HCURSOR)Atomic64Fetch(&window->desired_cursor); HCURSOR desired_cursor = (HCURSOR)Atomic64Fetch(&window->desired_cursor);
b32 desired_cursor_hidden = !Atomic64Fetch(&window->desired_cursor_hidden);
if (desired_cursor != window->active_cursor) if (desired_cursor != window->active_cursor)
{ {
SetCursor(desired_cursor); SetCursor(desired_cursor);
window->active_cursor = desired_cursor; window->active_cursor = desired_cursor;
} }
if (desired_cursor_hidden != window->active_cursor_hidden)
{
if (desired_cursor_hidden)
{
while (ShowCursor(1) < 0)
{
}
}
else
{
while (ShowCursor(0) >= 0)
{
}
}
window->active_cursor_hidden = desired_cursor_hidden;
}
result = 1; result = 1;
} }
else else
@ -392,7 +408,7 @@ LRESULT CALLBACK WND_W32_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l
i32 y = GET_Y_LPARAM(lparam); i32 y = GET_Y_LPARAM(lparam);
ControllerEvent event = Zi; ControllerEvent event = Zi;
event.kind = ControllerEventKind_CursorMove; event.kind = ControllerEventKind_CursorMove;
event.cursor_pos = VEC2I32(x, y); event.cursor_pos = VEC2(x, y);
WND_W32_PushEvent(window, event); WND_W32_PushEvent(window, event);
} break; } break;
@ -402,25 +418,26 @@ LRESULT CALLBACK WND_W32_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l
TempArena scratch = BeginScratchNoConflict(); TempArena scratch = BeginScratchNoConflict();
{ {
// Read raw input buffer // Read raw input buffer
UINT buff_size; UINT buff_size = 0;
GetRawInputData((HRAWINPUT)lparam, RID_INPUT, 0, &buff_size, sizeof(RAWINPUTHEADER)); GetRawInputData((HRAWINPUT)lparam, RID_INPUT, 0, &buff_size, sizeof(RAWINPUTHEADER));
u8 *buff = PushStructs(scratch.arena, u8, buff_size); u8 *buff = PushStructs(scratch.arena, u8, buff_size);
if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buff, &buff_size, sizeof(RAWINPUTHEADER)) != buff_size) if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buff, &buff_size, sizeof(RAWINPUTHEADER)) == buff_size)
{
RAWINPUT raw = Zi;
CopyBytes(&raw, buff, sizeof(RAWINPUT));
if (raw.header.dwType == RIM_TYPEMOUSE)
{
i32 x = raw.data.mouse.lLastX;
i32 y = raw.data.mouse.lLastY;
ControllerEvent event = Zi;
event.kind = ControllerEventKind_MouseMove;
event.mouse_delta = VEC2(x, y);
WND_W32_PushEvent(window, event);
}
}
else
{ {
LogErrorF("GetRawInputData did not return correct size"); LogErrorF("GetRawInputData did not return correct size");
break;
}
RAWINPUT raw = Zi;
CopyBytes(&raw, buff, sizeof(RAWINPUT));
if (raw.header.dwType == RIM_TYPEMOUSE)
{
i32 x = raw.data.mouse.lLastX;
i32 y = raw.data.mouse.lLastY;
ControllerEvent event = Zi;
event.kind = ControllerEventKind_MouseMove;
event.mouse_delta = VEC2I32(x, y);
WND_W32_PushEvent(window, event);
} }
} }
EndScratch(scratch); EndScratch(scratch);
@ -579,6 +596,8 @@ void WND_EndFrame(WND_Frame frame, i32 vsync)
// Process cmds // Process cmds
b32 was_restored = 0; b32 was_restored = 0;
b32 desires_locked_cursor = 0;
b32 desires_hidden_cursor = 0;
HCURSOR desired_cursor = (HCURSOR)Atomic64Fetch(&window->desired_cursor); HCURSOR desired_cursor = (HCURSOR)Atomic64Fetch(&window->desired_cursor);
for (WND_W32_CmdNode *n = window->first_cmd; n; n = n->next) for (WND_W32_CmdNode *n = window->first_cmd; n; n = n->next)
{ {
@ -649,10 +668,23 @@ void WND_EndFrame(WND_Frame frame, i32 vsync)
SetWindowPos(hwnd, window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); SetWindowPos(hwnd, window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
} break; } break;
//- Cursor //- Set cursor
case WND_CmdKind_SetCursor: case WND_CmdKind_SetCursor:
{ {
desired_cursor = WND_W32.cursors[cmd.cursor]; if (cmd.cursor == WND_CursorKind_Hidden)
{
desires_hidden_cursor = 1;
}
else
{
desired_cursor = WND_W32.cursors[cmd.cursor];
}
} break;
//- Set locked cursor
case WND_CmdKind_SetLockedCursor:
{
desires_locked_cursor = !!cmd.v;
} break; } break;
//- Restore //- Restore
@ -715,12 +747,80 @@ void WND_EndFrame(WND_Frame frame, i32 vsync)
BringWindowToTop(hwnd); BringWindowToTop(hwnd);
} }
// Set cursor // Set/Clip/Hide cursor
{ {
HCURSOR old_desired_cursor = (HCURSOR)Atomic64FetchSet(&window->desired_cursor, (i64)desired_cursor); RECT virtual_screen_rect = Zi;
if (old_desired_cursor != desired_cursor)
{ {
PostMessage(window->hwnd, WM_SETCURSOR, (WPARAM)window->hwnd, (LPARAM)HTCLIENT); virtual_screen_rect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
virtual_screen_rect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
virtual_screen_rect.right = virtual_screen_rect.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
virtual_screen_rect.bottom = virtual_screen_rect.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);
}
RECT client_screen_rect = Zi;
{
GetClientRect(hwnd, (LPRECT)&client_screen_rect);
ClientToScreen(hwnd, (LPPOINT)&client_screen_rect.left);
ClientToScreen(hwnd, (LPPOINT)&client_screen_rect.right);
}
b32 is_cursor_in_client_rect = 0;
{
POINT pos = Zi;
GetCursorPos(&pos);
is_cursor_in_client_rect = (
pos.x >= client_screen_rect.left &&
pos.x < client_screen_rect.right &&
pos.y >= client_screen_rect.top &&
pos.y < client_screen_rect.bottom
);
}
RECT clipped_screen_rect = Zi;
{
GetClipCursor(&clipped_screen_rect);
}
b32 is_cursor_clipped_any = !MatchStruct(&clipped_screen_rect, &virtual_screen_rect);
b32 is_cursor_clipped_client = MatchStruct(&clipped_screen_rect, &client_screen_rect);
b32 final_desires_locked_cursor = desires_locked_cursor && frame.has_focus && is_cursor_in_client_rect;
// Clip cursor
{
if (final_desires_locked_cursor && !is_cursor_clipped_client)
{
RECT rect = Zi;
{
GetClientRect(hwnd, (LPRECT)&rect);
ClientToScreen(hwnd, (LPPOINT)&rect.left);
ClientToScreen(hwnd, (LPPOINT)&rect.right);
}
ClipCursor(&rect);
}
else if (!final_desires_locked_cursor && is_cursor_clipped_any)
{
ClipCursor(0);
}
}
if (frame.has_focus)
{
// Set cursor
{
HCURSOR old_desired_cursor = (HCURSOR)Atomic64FetchSet(&window->desired_cursor, (i64)desired_cursor);
if (old_desired_cursor != desired_cursor)
{
PostMessage(window->hwnd, WM_SETCURSOR, (WPARAM)window->hwnd, (LPARAM)HTCLIENT);
}
}
// Hide cursor
{
b32 final_desires_hidden_cursor = desires_hidden_cursor && is_cursor_in_client_rect;
b32 old_desired_cursor_hidden = !!Atomic64FetchSet(&window->desired_cursor_hidden, final_desires_hidden_cursor);
if (old_desired_cursor_hidden != final_desires_hidden_cursor)
{
PostMessage(window->hwnd, WM_SETCURSOR, (WPARAM)window->hwnd, (LPARAM)HTCLIENT);
}
}
} }
} }

View File

@ -11,6 +11,7 @@ Struct(WND_W32_Window)
// Window proc state // Window proc state
u16 previous_utf16_high_surrogate; u16 previous_utf16_high_surrogate;
HCURSOR active_cursor; HCURSOR active_cursor;
b32 active_cursor_hidden;
// User state // User state
Arena *frame_arena; Arena *frame_arena;
@ -27,6 +28,7 @@ Struct(WND_W32_Window)
// User -> Window proc // User -> Window proc
Atomic64 desired_cursor; Atomic64 desired_cursor;
Atomic64 desired_cursor_hidden;
}; };
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////