Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion gpu/vk/vk_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
extern "C" {
#endif

#define MKWVK_API_VERSION 6
#define MKWVK_API_VERSION 7
enum { HOS_OPT_CHEAT_HEARTS = 32, HOS_OPT_CHEAT_FLIGHT, HOS_OPT_CHEAT_ACTION, HOS_OPT_CHEAT_CAMERA };

/* (hookshot) the controls' names (host/nn_hid.c, host/pad_sdl.c, the pad_ and key_ lines of hookshot_options.ini):
Expand All @@ -31,6 +31,17 @@ enum { HOS_BIND_PAD, HOS_BIND_KEY };
enum { HOS_KEY_MOVE_UP = HOS_SW_N, HOS_KEY_MOVE_DOWN, HOS_KEY_MOVE_LEFT, HOS_KEY_MOVE_RIGHT, HOS_KEY_CAM_UP,
HOS_KEY_CAM_DOWN, HOS_KEY_CAM_LEFT, HOS_KEY_CAM_RIGHT, HOS_KEY_N };

/* (hookshot) the mouse over the window while it is free (not captured): the Insert menu's (host/display.c,
host/posix/display_sdl.c). x and y: the cursor in the window's pixels, of a window width x height pixels (the
swapchain's, as a rule); held: the buttons down now, pressed: those pressed since the last read (1 left, 2 right,
4 middle); wheel: the wheel's notches since then, away from the player positive */
enum { HOS_MOUSE_LEFT = 1, HOS_MOUSE_RIGHT = 2, HOS_MOUSE_MIDDLE = 4 };
typedef struct MkwVkPointer {
int32_t x, y, width, height;
uint32_t held, pressed;
int32_t wheel;
} MkwVkPointer;

/* what the executable gives the back end */
typedef struct MkwVkHost {
uint32_t version; /* MKWVK_API_VERSION */
Expand Down Expand Up @@ -84,6 +95,10 @@ typedef struct MkwVkHost {
/// The guest camera aspect explicitly applied at startup (0: its default aspect)
float (*aspect_ratio)(void);
int (*take_key_press)(int key);
/* (hookshot) the free mouse (MkwVkPointer), read and its presses and wheel cleared: 1 when the cursor is over the
window and the window has the focus. The Insert menu releases the mouse while it is open: WM_APP + 8 to the
window, wParam 1 open, 0 closed (the capture it had then comes back) */
int (*pointer)(MkwVkPointer *out);
} MkwVkHost;

/* what the back end gives the executable: the rx_* functions (gpu/render/rx_nvn.h, rx_core) */
Expand Down
268 changes: 230 additions & 38 deletions gpu/vk/vk_menu.cpp

Large diffs are not rendered by default.

152 changes: 152 additions & 0 deletions gpu/vk/vk_menu_hit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// The Insert menu's layout (vk_menu.cpp draws with these sizes, times its scale) and what a point over it is on: the
// mouse's hover and clicks while the menu is open. Plain arithmetic, no Vulkan or GDI: tests/test_menu_hit.cpp checks it.
#pragma once

namespace mkwvk::menu_hit {

// ---- the options page, at a scale of 1 (the window 720 pixels high; 1440 and up: 2, and so on) ----
constexpr int kWidth = 480;
constexpr int kTabY = 44, kTabH = 32; // the tab strip
constexpr int kTabSide = 85; // either side of the tab titles: [Q / LB] and [E / RB], the previous and next tab
constexpr int kRowGap = 8, kRowH = 28; // the rows, from kTabY + kTabH + kRowGap
constexpr int kRowX = 8; // a row's band, kRowX in from either side
constexpr int kValueRight = 18; // a value ends kValueRight from the right
constexpr int kSliderL = 200, kSliderR = 330; // a slider row's track (Sharpness, Mouse sensitivity)
constexpr int kSliderGrab = 6; // (a click this far past either end still takes the track's end)
// ---- the Controls page ----
constexpr int kCtlWidth = 600, kCtlTop = 64, kCtlRowH = 24, kCtlPad = 200, kCtlKey = 390, kCtlCellPad = 6;
// ---- the Cheats page (no tab strip), and its lists: the teleport list and the inventory ----
constexpr int kCheatTop = 44, kCheatRowH = 28; // the cheats' rows, from kCheatTop
constexpr int kListTop = 48, kListRowH = 24; // a list's lines (a section's heading is one too), from kListTop
constexpr int kListValueRight = 16; // a cheat's or a list line's value ends kListValueRight from the right

enum Part {
kNone, // off the menu's controls (still over the menu: a click does nothing)
kOutside, // off the menu
kTabPrev, // the tab strip's left end
kTab, // a tab: index
kTabNext, // its right end
kRow, // an option's row: index (the tab's own), side -1 the value's left half, 1 its right half, 0 the name
kSlider, // a slider row's track: index, permille 0 to 1000 along it
kCtlRow, // the Controls page: a row's name (index; index == the count: Reset to defaults)
kCtlCell, // a binding: index the row, side 0 the gamepad's, 1 the keyboard's
kCheatRow, // the Cheats page: a row (index), side -1 the value's left half, 1 its right half, 0 the name
kListLine, // a list (the teleport list, the inventory): a line as shown (index, 0 the top one), side as kCheatRow
};
struct Hit {
Part part = kNone;
int index = -1;
int side = 0;
int permille = 0;
};

/// A point in the window's pixels (the cursor's, in a window win_w x win_h pixels) in the window's image (img_w x img_h:
/// the swapchain's; the same size as a rule, but not while a resize is on its way)
inline void to_image(int x, int y, int win_w, int win_h, int img_w, int img_h, int* ix, int* iy) {
*ix = win_w > 0 ? (int)((long long)x * img_w / win_w) : x;
*iy = win_h > 0 ? (int)((long long)y * img_h / win_h) : y;
}
/// The menu's top left in the window's image: centred, as menu_record copies it
inline void origin(int img_w, int img_h, int menu_w, int menu_h, int* x0, int* y0) {
*x0 = (img_w - menu_w) / 2;
*y0 = (img_h - menu_h) / 2;
}
inline int rows_top(int s) { return (kTabY + kTabH + kRowGap) * s; }
inline int tab_slot_w(int s, int w) { return (w - 2 * kTabSide * s) / 3; }
/// A slider's value for a point along its track (lo to hi in steps of step), nearest step, held to its ends
inline int slider_value(int permille, int lo, int hi, int step) {
if (step <= 0 || hi <= lo) return lo;
const int steps = (hi - lo) / step;
const int k = (permille * steps + 500) / 1000;
return lo + (k < 0 ? 0 : k > steps ? steps : k) * step;
}
/// Where along the track a value is, in permille (the knob's place)
inline int slider_permille(int v, int lo, int hi) {
if (hi <= lo) return 0;
const int p = (int)((long long)(v - lo) * 1000 / (hi - lo));
return p < 0 ? 0 : p > 1000 ? 1000 : p;
}

/// The options page: (x, y) in the menu's own pixels (0, 0 its top left), w x h its size, s its scale; rows the shown
/// tab's row count; value_l[i] the left edge of row i's value as last drawn; slider[i] whether row i has a track.
/// A drag (dragging) keeps to the track whatever the point's height: slider_drag
inline Hit options(int x, int y, int s, int w, int h, int tabs, int rows, const int* value_l, const bool* slider) {
Hit hit;
if (x < 0 || y < 0 || x >= w || y >= h) return hit.part = kOutside, hit;
if (y >= kTabY * s && y < (kTabY + kTabH) * s) {
const int side = kTabSide * s, slot = tab_slot_w(s, w);
if (x < side) hit.part = kTabPrev;
else if (x >= w - side) hit.part = kTabNext;
else if (slot > 0) {
hit.part = kTab;
hit.index = (x - side) / slot;
if (hit.index >= tabs) hit.index = tabs - 1;
}
return hit;
}
const int top = rows_top(s);
if (y < top || x < kRowX * s || x >= w - kRowX * s) return hit;
const int i = (y - top) / (kRowH * s);
if (i >= rows) return hit;
hit.index = i;
if (slider && slider[i] && x >= (kSliderL - kSliderGrab) * s && x < (kSliderR + kSliderGrab) * s) {
hit.part = kSlider;
const int p = (int)((long long)(x - kSliderL * s) * 1000 / ((kSliderR - kSliderL) * s));
hit.permille = p < 0 ? 0 : p > 1000 ? 1000 : p;
return hit;
}
hit.part = kRow;
const int vl = value_l ? value_l[i] : w;
const int vr = w - kValueRight * s;
if (x >= vl && vl < vr) hit.side = x < (vl + vr) / 2 ? -1 : 1;
return hit;
}
/// A drag that started on a slider: the point's place along the track, held to its ends, wherever the cursor went
inline int slider_drag(int x, int s) {
const int p = (int)((long long)(x - kSliderL * s) * 1000 / ((kSliderR - kSliderL) * s));
return p < 0 ? 0 : p > 1000 ? 1000 : p;
}

/// The Controls page: (x, y) in its pixels, w x h its size, s its scale; count the controls (then Reset to defaults)
inline Hit controls(int x, int y, int s, int w, int h, int count) {
Hit hit;
if (x < 0 || y < 0 || x >= w || y >= h) return hit.part = kOutside, hit;
const int top = kCtlTop * s;
if (y < top || x < kRowX * s || x >= w - kRowX * s) return hit;
const int i = (y - top) / (kCtlRowH * s);
if (i > count) return hit;
hit.index = i;
if (i < count && x >= (kCtlPad - kCtlCellPad) * s) {
hit.part = kCtlCell;
hit.side = x >= (kCtlKey - kCtlCellPad) * s ? 1 : 0;
} else {
hit.part = kCtlRow;
}
return hit;
}

/// Rows top + i * row_h high (times s), in the band kRowX in from either side: part with the row's index and the side of
/// its value it is on (value_l[i] the left edge of row i's value as last drawn, w or more: none; null: no values)
inline Hit row_list(int x, int y, int s, int w, int h, int top, int row_h, int count, const int* value_l, Part part) {
Hit hit;
if (x < 0 || y < 0 || x >= w || y >= h) return hit.part = kOutside, hit;
if (y < top * s || x < kRowX * s || x >= w - kRowX * s) return hit;
const int i = (y - top * s) / (row_h * s);
if (i >= count) return hit;
hit.part = part;
hit.index = i;
const int vl = value_l ? value_l[i] : w;
const int vr = w - kListValueRight * s;
if (x >= vl && vl < vr) hit.side = x < (vl + vr) / 2 ? -1 : 1;
return hit;
}
/// The Cheats page: count its rows
inline Hit cheats(int x, int y, int s, int w, int h, int count, const int* value_l) {
return row_list(x, y, s, w, h, kCheatTop, kCheatRowH, count, value_l, kCheatRow);
}
/// A list page: count the lines shown (headings included); index is the line from the top as shown, not the item
inline Hit list_lines(int x, int y, int s, int w, int h, int count, const int* value_l) {
return row_list(x, y, s, w, h, kListTop, kListRowH, count, value_l, kListLine);
}

} // namespace mkwvk::menu_hit
73 changes: 71 additions & 2 deletions host/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
HOS_WINDOW_NOACTIVATE=1: the window opens behind the one in front (test runs do not take the keyboard).
HOS_WINDOW_SIZE=<w>x<h>: the client area (default 1280x720 at 96 dpi). F11 or Alt+Enter: fullscreen.
Mouse: a click in the window captures the mouse (hidden, kept in the window), Esc or leaving the window releases
it (nn_hid.c reads its motion). */
it (nn_hid.c reads its motion). While the Insert menu is open the mouse is free (the cursor shown, for the menu's
clicks: hos_mouse_pointer) and the game gets none of it; the capture it had comes back when the menu closes. */
#include "hos.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "../gpu/render/rx.h"
#include "../gpu/vk/vk_api.h"
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif
Expand All @@ -39,6 +41,24 @@ void hos_mouse_give_back(int32_t dx, int32_t dy) {
InterlockedExchangeAdd(&mouse_dy, dy);
}

/* the free mouse, for the Insert menu (gpu/vk/vk_api.h MkwVkPointer): set on the window's thread, read on the renderer's */
static volatile LONG ptr_x, ptr_y, ptr_w, ptr_h, ptr_in, ptr_pressed, ptr_wheel; /* (client pixels; wheel in WHEEL_DELTA units) */
static int menu_open, captured_before_menu;

int hos_mouse_pointer(MkwVkPointer *out) {
memset(out, 0, sizeof *out);
if (!hwnd || mouse_captured) return 0;
out->x = ptr_x, out->y = ptr_y, out->width = ptr_w, out->height = ptr_h;
out->held = ((GetAsyncKeyState(VK_LBUTTON) & 0x8000) ? HOS_MOUSE_LEFT : 0) |
((GetAsyncKeyState(VK_RBUTTON) & 0x8000) ? HOS_MOUSE_RIGHT : 0) |
((GetAsyncKeyState(VK_MBUTTON) & 0x8000) ? HOS_MOUSE_MIDDLE : 0);
out->pressed = (uint32_t)InterlockedExchange(&ptr_pressed, 0);
const LONG wheel = InterlockedExchange(&ptr_wheel, 0);
out->wheel = wheel / WHEEL_DELTA;
if (wheel % WHEEL_DELTA) InterlockedExchangeAdd(&ptr_wheel, wheel % WHEEL_DELTA); /* (a part of a notch: kept) */
return ptr_in && GetForegroundWindow() == hwnd && out->width > 0 && out->height > 0;
}

static HANDLE renderer_up;
static int fullscreen;
static WINDOWPLACEMENT windowed_place;
Expand Down Expand Up @@ -98,8 +118,55 @@ static void mouse_capture(HWND w, int on) {
}
}

/* the menu opened: the mouse let go (its capture kept for later); closed: the capture back, if the window still has the focus */
static void menu_mouse(HWND w, int open) {
if (open == menu_open) return;
menu_open = open;
if (open) {
captured_before_menu = (int)mouse_captured;
mouse_capture(w, 0);
InterlockedExchange(&ptr_pressed, 0), InterlockedExchange(&ptr_wheel, 0);
} else {
if (captured_before_menu && GetForegroundWindow() == w) mouse_capture(w, 1);
captured_before_menu = 0;
}
}
static void menu_press(HWND w, LPARAM lp, LONG bit) { /* (a click while the menu is open: the menu's) */
ptr_x = (short)LOWORD(lp), ptr_y = (short)HIWORD(lp), ptr_in = 1;
InterlockedOr(&ptr_pressed, bit);
SetCapture(w); /* (a drag past the window's edge still reaches it) */
}

static LRESULT CALLBACK wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_MOUSEMOVE:
ptr_x = (short)LOWORD(lp), ptr_y = (short)HIWORD(lp);
if (!ptr_in) {
TRACKMOUSEEVENT t = {sizeof t, TME_LEAVE, w, 0};
TrackMouseEvent(&t);
ptr_in = 1;
}
return 0;
case WM_MOUSELEAVE:
ptr_in = 0;
return 0;
case WM_MOUSEWHEEL:
if (menu_open) InterlockedExchangeAdd(&ptr_wheel, GET_WHEEL_DELTA_WPARAM(wp));
return 0;
case WM_RBUTTONDOWN:
if (menu_open) menu_press(w, lp, HOS_MOUSE_RIGHT);
return 0;
case WM_MBUTTONDOWN:
if (menu_open) menu_press(w, lp, HOS_MOUSE_MIDDLE);
return 0;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
if (GetCapture() == w && !(wp & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON))) ReleaseCapture();
return 0;
case WM_APP + 8: /* (the Insert menu opened or closed: gpu/vk/vk_menu.cpp) */
menu_mouse(w, (int)wp);
return 0;
case WM_TIMER:
return 0;
case WM_INPUT: {
Expand All @@ -113,7 +180,8 @@ static LRESULT CALLBACK wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
return DefWindowProcA(w, msg, wp, lp);
}
case WM_LBUTTONDOWN:
if (renderer && !mouse_captured) mouse_capture(w, 1);
if (menu_open) menu_press(w, lp, HOS_MOUSE_LEFT);
else if (renderer && !mouse_captured) mouse_capture(w, 1);
return 0;
case WM_KEYDOWN: {
extern void hos_vk_note_key_press(unsigned key);
Expand Down Expand Up @@ -155,6 +223,7 @@ static LRESULT CALLBACK wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
if (mouse_captured) { mouse_capture(w, 0); mouse_capture(w, 1); } /* (the clip follows the window) */
break;
case WM_SIZE:
ptr_w = LOWORD(lp), ptr_h = HIWORD(lp);
if (mouse_captured) { mouse_capture(w, 0); mouse_capture(w, 1); }
if (renderer) rx_resize(LOWORD(lp), HIWORD(lp));
return 0;
Expand Down
44 changes: 38 additions & 6 deletions host/hos_sdl.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ static struct {
/* window */
void *CreateWindow, *SetWindowPosition, *SetWindowFullscreen, *SetRelativeMouseMode, *GetPrimaryDisplay,
*GetDesktopDisplayMode, *Vulkan_CreateSurface, *PixelSize, *GetKeyboardState, *GetMouseState, *WaitEventTimeout,
*PollEvent, *LoadPNG, *SetWindowIcon, *DestroySurface;
*PollEvent, *GetWindowSize, *LoadPNG, *SetWindowIcon, *DestroySurface;
/* sound */
void *OpenAudioDevice, *PauseAudioDevice, *OpenAudioDeviceStream, *ResumeAudioStreamDevice, *PutAudioStreamData;
/* controllers */
Expand Down Expand Up @@ -47,6 +47,7 @@ static int get_all(HMODULE m, int three) {
GET2(PixelSize, "SDL_GetWindowSizeInPixels", "SDL_Vulkan_GetDrawableSize");
GET(GetKeyboardState, "SDL_GetKeyboardState");
GET(GetMouseState, "SDL_GetMouseState");
GET(GetWindowSize, "SDL_GetWindowSize");
GET(WaitEventTimeout, "SDL_WaitEventTimeout");
GET(PollEvent, "SDL_PollEvent");
GET2(PadOpen, "SDL_OpenGamepad", "SDL_GameControllerOpen");
Expand Down Expand Up @@ -183,6 +184,10 @@ uint32_t hos_sdl_mouse_buttons(void) {
return version == 3 ? CALL(uint32_t (*)(float *, float *), GetMouseState)(NULL, NULL)
: CALL(uint32_t (*)(int *, int *), GetMouseState)(NULL, NULL);
}
void hos_sdl_window_size(void *window, int *width, int *height) {
if (version == 3) CALL(_Bool (*)(void *, int *, int *), GetWindowSize)(window, width, height);
else CALL(void (*)(void *, int *, int *), GetWindowSize)(window, width, height);
}

/* ---- events ---- */
static void read_event(const uint8_t *raw, HosSdlEvent *e) {
Expand All @@ -203,18 +208,32 @@ static void read_event(const uint8_t *raw, HosSdlEvent *e) {
memcpy(i, raw + 24, 4), e->scancode = i[0];
memcpy(&e->mod, raw + 32, 2), e->repeat = raw[37];
break;
case 0x400: /* (SDL_MouseMotionEvent: xrel and yrel at 36 and 40, floats) */
case 0x20c: e->type = HOS_SDL_EV_MOUSE_ENTER; break;
case 0x400: /* (SDL_MouseMotionEvent: x and y at 28 and 32, xrel and yrel at 36 and 40, floats) */
e->type = HOS_SDL_EV_MOUSE_MOTION;
memcpy(&e->x, raw + 28, 4), memcpy(&e->y, raw + 32, 4);
memcpy(&e->dx, raw + 36, 4), memcpy(&e->dy, raw + 40, 4);
break;
case 0x401: e->type = HOS_SDL_EV_MOUSE_DOWN; break;
case 0x401:
case 0x402: /* (SDL_MouseButtonEvent: button at 24, x and y at 28 and 32, floats) */
e->type = type == 0x401 ? HOS_SDL_EV_MOUSE_DOWN : HOS_SDL_EV_MOUSE_UP;
e->button = raw[24];
memcpy(&e->x, raw + 28, 4), memcpy(&e->y, raw + 32, 4);
break;
case 0x403: { /* (SDL_MouseWheelEvent: y at 28, a float; direction at 32, 1 flipped) */
uint32_t dir;
e->type = HOS_SDL_EV_MOUSE_WHEEL;
memcpy(&e->wheel, raw + 28, 4), memcpy(&dir, raw + 32, 4);
if (dir == 1) e->wheel = -e->wheel;
break;
}
}
return;
}
switch (type) {
case 0x100: e->type = HOS_SDL_EV_QUIT; break;
case 0x200: /* (SDL_WindowEvent: what at 12) */
e->type = raw[12] == 6 ? HOS_SDL_EV_RESIZED : raw[12] == 11 ? HOS_SDL_EV_MOUSE_LEAVE
e->type = raw[12] == 6 ? HOS_SDL_EV_RESIZED : raw[12] == 10 ? HOS_SDL_EV_MOUSE_ENTER : raw[12] == 11 ? HOS_SDL_EV_MOUSE_LEAVE
: raw[12] == 12 ? HOS_SDL_EV_FOCUS_GAINED : raw[12] == 13 ? HOS_SDL_EV_FOCUS_LOST
: raw[12] == 14 ? HOS_SDL_EV_CLOSE : HOS_SDL_EV_OTHER;
break;
Expand All @@ -223,11 +242,24 @@ static void read_event(const uint8_t *raw, HosSdlEvent *e) {
memcpy(i, raw + 16, 4), e->scancode = i[0];
memcpy(&e->mod, raw + 24, 2), e->repeat = raw[13];
break;
case 0x400: /* (SDL_MouseMotionEvent: xrel and yrel at 28 and 32, whole pixels) */
case 0x400: /* (SDL_MouseMotionEvent: x and y at 20 and 24, xrel and yrel at 28 and 32, whole pixels) */
e->type = HOS_SDL_EV_MOUSE_MOTION;
memcpy(i, raw + 20, 8), e->x = (float)i[0], e->y = (float)i[1];
memcpy(i, raw + 28, 8), e->dx = (float)i[0], e->dy = (float)i[1];
break;
case 0x401: e->type = HOS_SDL_EV_MOUSE_DOWN; break;
case 0x401:
case 0x402: /* (SDL_MouseButtonEvent: button at 16, x and y at 20 and 24, whole pixels) */
e->type = type == 0x401 ? HOS_SDL_EV_MOUSE_DOWN : HOS_SDL_EV_MOUSE_UP;
e->button = raw[16];
memcpy(i, raw + 20, 8), e->x = (float)i[0], e->y = (float)i[1];
break;
case 0x403: { /* (SDL_MouseWheelEvent: y at 20, whole notches; direction at 24, 1 flipped) */
uint32_t dir;
e->type = HOS_SDL_EV_MOUSE_WHEEL;
memcpy(i, raw + 20, 4), memcpy(&dir, raw + 24, 4);
e->wheel = (float)(dir == 1 ? -i[0] : i[0]);
break;
}
}
}
int hos_sdl_wait_event(HosSdlEvent *e, int timeout_ms) {
Expand Down
Loading
Loading