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;
// ControllerEventKind_CursorMove
Vec2I32 cursor_pos;
Vec2 cursor_pos;
// ControllerEventKind_MouseMove
Vec2I32 mouse_delta;
Vec2 mouse_delta;
};
Struct(ControllerEventsArray)

View File

@ -569,7 +569,7 @@ void V_TickForever(WaveLaneCtx *lane)
frame->is_editing = prev_frame->is_editing;
frame->ui_debug = prev_frame->ui_debug;
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->equipped_tile = prev_frame->equipped_tile;
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);
WND_Frame window_frame = ui_frame->window_frame;
WND_SetCursor(window_frame, WND_CursorKind_Default);
WND_PushCmd(window_frame, .kind = WND_CmdKind_SetLockedCursor, .v = 0);
// 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_keyboard_focus = 1;
if (!window_frame.has_focus)
Vec2 mouse_delta = Zi;
{
has_mouse_focus = 0;
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 (!window_frame.has_focus)
{
if (!has_mouse_focus)
{
frame->held_buttons[btn] = 0;
}
has_mouse_focus = 0;
has_keyboard_focus = 0;
}
else
{
if (!has_keyboard_focus)
{
frame->held_buttons[btn] = 0;
}
}
}
for (u64 i = 0; i < window_frame.controller_events.count; ++i)
{
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)
//- Reset held buttons
for (Button btn = 0; btn < countof(frame->held_buttons); ++btn)
{
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)
{
ignore = 1;
frame->held_buttons[btn] = 0;
}
}
else
{
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;
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];
ControllerEvent cev = window_frame.controller_events.events[cev_idx];
b32 down = cev.kind == ControllerEventKind_ButtonDown;
b32 up = cev.kind == ControllerEventKind_ButtonUp;
b32 ignore = 0;
if (down)
{
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 (cev.button == Button_M1 || cev.button == Button_M2 || cev.button == Button_M3)
{
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);
Vec2 guy_center = P_WorldShapeFromEnt(guy).centroid;
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 = 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_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
//
@ -1369,7 +1396,7 @@ void V_TickForever(WaveLaneCtx *lane)
control.tick = control_tick;
control.orig_tick = control_tick;
control.move = frame->move;
control.look = NormRot(frame->look);
control.look = frame->look;
control.fire_held = frame->fire_held;
// FIXME: Don't propagate fire presses over multiple sim frames
control.fire_presses = frame->fire_presses;
@ -1964,7 +1991,11 @@ void V_TickForever(WaveLaneCtx *lane)
Vec2 line_end = AddVec2(line_start, fire_dir);
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);
}

View File

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

View File

@ -226,17 +226,33 @@ LRESULT CALLBACK WND_W32_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l
} break;
//- Cursor kind
case WM_SETCURSOR:
{
if ((HWND)wparam == hwnd && LOWORD(lparam) == HTCLIENT)
{
HCURSOR desired_cursor = (HCURSOR)Atomic64Fetch(&window->desired_cursor);
b32 desired_cursor_hidden = !Atomic64Fetch(&window->desired_cursor_hidden);
if (desired_cursor != window->active_cursor)
{
SetCursor(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;
}
else
@ -392,7 +408,7 @@ LRESULT CALLBACK WND_W32_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l
i32 y = GET_Y_LPARAM(lparam);
ControllerEvent event = Zi;
event.kind = ControllerEventKind_CursorMove;
event.cursor_pos = VEC2I32(x, y);
event.cursor_pos = VEC2(x, y);
WND_W32_PushEvent(window, event);
} break;
@ -402,25 +418,26 @@ LRESULT CALLBACK WND_W32_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l
TempArena scratch = BeginScratchNoConflict();
{
// Read raw input buffer
UINT buff_size;
UINT buff_size = 0;
GetRawInputData((HRAWINPUT)lparam, RID_INPUT, 0, &buff_size, sizeof(RAWINPUTHEADER));
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");
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);
@ -579,6 +596,8 @@ void WND_EndFrame(WND_Frame frame, i32 vsync)
// Process cmds
b32 was_restored = 0;
b32 desires_locked_cursor = 0;
b32 desires_hidden_cursor = 0;
HCURSOR desired_cursor = (HCURSOR)Atomic64Fetch(&window->desired_cursor);
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);
} break;
//- Cursor
//- Set cursor
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;
//- Restore
@ -715,12 +747,80 @@ void WND_EndFrame(WND_Frame frame, i32 vsync)
BringWindowToTop(hwnd);
}
// Set cursor
// Set/Clip/Hide cursor
{
HCURSOR old_desired_cursor = (HCURSOR)Atomic64FetchSet(&window->desired_cursor, (i64)desired_cursor);
if (old_desired_cursor != desired_cursor)
RECT virtual_screen_rect = Zi;
{
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
u16 previous_utf16_high_surrogate;
HCURSOR active_cursor;
b32 active_cursor_hidden;
// User state
Arena *frame_arena;
@ -27,6 +28,7 @@ Struct(WND_W32_Window)
// User -> Window proc
Atomic64 desired_cursor;
Atomic64 desired_cursor_hidden;
};
////////////////////////////////////////////////////////////