Overhaul the VR menu UI: header, tabs, live video options, hover/click feedback, i18n, and dragging
build / build (push) Successful in 2m25s
build / build (push) Successful in 2m25s
- Header with title, git-tag-derived version, and a live FPS/frame-time/ CPU/GPU-utilization/free-memory stats ribbon below the tab bar (FPS via a new per-frame tracker in xr_session.c; CPU/GPU via the OpenXR XR_META_performance_metrics extension, gracefully unavailable on runtimes that don't support it) - Bottom tab bar with a live-editable Video Options tab (renderer/ resolution), applying changes immediately via qs_config.c - Hover and press feedback on every menu/keyboard button via a StateListDrawable (Android's default ripple never animates on this permanently unattached view tree), with a lighter default button color for visibility - i18n for all menu/keyboard chrome text, following the game's own language setting (Strings.java) - The main menu is now draggable via its header, matching the keyboard; xr_input.c's drag handling is generalized to per-overlay state instead of being keyboard-only - The keyboard's title bar now matches the menu header's style
This commit is contained in:
@@ -26,17 +26,24 @@ static const float kHandColor[2][4] = {
|
||||
{1.0f, 0.85f, 0.1f, 1.0f}, // right: amber
|
||||
};
|
||||
|
||||
// The keyboard overlay's title bar (drag handle + Close button) is handled
|
||||
// entirely here, before a hit would normally be dispatched to
|
||||
// KeyboardOverlay as a touch - these fractions must agree with
|
||||
// KeyboardOverlay.java's TITLE_BAR_HEIGHT/CLOSE_BUTTON_WIDTH constants (in
|
||||
// pixels, out of KeyboardOverlay.WIDTH/HEIGHT there). A hit in the top
|
||||
// TITLE_BAR_V_FRACTION of the quad, at or left of CLOSE_BUTTON_U_FRACTION,
|
||||
// is the drag handle; right of that (still within the title bar) is the
|
||||
// Close button, which is dispatched as an ordinary click instead. The menu
|
||||
// launcher overlay has no title bar - it's a plain click-only panel.
|
||||
#define TITLE_BAR_V_FRACTION (90.0f / 768.0f)
|
||||
#define CLOSE_BUTTON_U_FRACTION (1.0f - 160.0f / 1024.0f)
|
||||
// Both overlays' title bars (drag handle, plus the keyboard's Close button)
|
||||
// are handled entirely here, before a hit would normally be dispatched to
|
||||
// the Java side as a touch. A hit in the top *_TITLE_BAR_V_FRACTION of a
|
||||
// quad, at or left of its own drag-handle-U-fraction, is the drag handle;
|
||||
// right of that (still within the title bar) is dispatched as an ordinary
|
||||
// click instead - the keyboard's Close button, or (the menu has no such
|
||||
// button - MENU_DRAG_HANDLE_U_FRACTION is 1.0f, so its whole title bar
|
||||
// width is the drag handle) nothing.
|
||||
//
|
||||
// KEYBOARD_TITLE_BAR_V_FRACTION/KEYBOARD_CLOSE_BUTTON_U_FRACTION must agree
|
||||
// with KeyboardOverlay.java's TITLE_BAR_HEIGHT/CLOSE_BUTTON_WIDTH constants
|
||||
// (in pixels, out of KeyboardOverlay.WIDTH/HEIGHT there);
|
||||
// MENU_TITLE_BAR_V_FRACTION with MenuOverlay.java's HEADER_HEIGHT (out of
|
||||
// MenuOverlay.HEIGHT).
|
||||
#define KEYBOARD_TITLE_BAR_V_FRACTION (90.0f / 768.0f)
|
||||
#define KEYBOARD_CLOSE_BUTTON_U_FRACTION (1.0f - 160.0f / 1024.0f)
|
||||
#define MENU_TITLE_BAR_V_FRACTION (56.0f / 552.0f)
|
||||
#define MENU_DRAG_HANDLE_U_FRACTION 1.0f
|
||||
|
||||
static XrInstance g_instance = XR_NULL_HANDLE;
|
||||
static XrSession g_session = XR_NULL_HANDLE;
|
||||
@@ -75,17 +82,24 @@ static bool g_menu_touch_active[2] = {false, false};
|
||||
static bool g_keyboard_prev_hit[2] = {false, false};
|
||||
static bool g_keyboard_touch_active[2] = {false, false};
|
||||
|
||||
// Drag state for repositioning the keyboard overlay via its title bar (see
|
||||
// TITLE_BAR_V_FRACTION/CLOSE_BUTTON_U_FRACTION above) - the menu launcher
|
||||
// has no title bar, so it's never draggable. g_keyboard_drag_hand is -1
|
||||
// when g_keyboard_dragging is false. The offset is the vector from the
|
||||
// quad's center to the grabbed point at drag-start, kept constant for the
|
||||
// whole gesture so the quad follows the hand rigidly rather than
|
||||
// re-centering under the ray each frame.
|
||||
static bool g_keyboard_dragging = false;
|
||||
static int g_keyboard_drag_hand = -1;
|
||||
static float g_keyboard_drag_offset_x = 0.0f;
|
||||
static float g_keyboard_drag_offset_y = 0.0f;
|
||||
// Drag state for repositioning an overlay via its title bar (see the
|
||||
// *_TITLE_BAR_V_FRACTION/*_DRAG_HANDLE_U_FRACTION or
|
||||
// *_CLOSE_BUTTON_U_FRACTION defines above). hand is -1 when dragging is
|
||||
// false. offset_x/offset_y is the vector from the quad's center to the
|
||||
// grabbed point at drag-start, kept constant for the whole gesture so the
|
||||
// quad follows the hand rigidly rather than re-centering under the ray
|
||||
// each frame. Each draggable overlay (keyboard, menu) gets its own
|
||||
// instance so they can be dragged independently/simultaneously by
|
||||
// different hands.
|
||||
typedef struct {
|
||||
bool dragging;
|
||||
int hand;
|
||||
float offset_x;
|
||||
float offset_y;
|
||||
} XrDragState;
|
||||
|
||||
static XrDragState g_keyboard_drag = {false, -1, 0.0f, 0.0f};
|
||||
static XrDragState g_menu_drag = {false, -1, 0.0f, 0.0f};
|
||||
|
||||
// A "view" reference space, located once per frame (not per hand) to get
|
||||
// an approximate head position for the laser-beam billboard math below -
|
||||
@@ -341,17 +355,21 @@ bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_for
|
||||
|
||||
// Hit-tests/dispatches this hand's ray against one overlay quad - shared
|
||||
// by the menu launcher and keyboard overlays in xr_input_sync_and_draw()
|
||||
// below. dragCapable enables the keyboard-only title-bar/drag-handle
|
||||
// handling (see TITLE_BAR_V_FRACTION/CLOSE_BUTTON_U_FRACTION); the menu
|
||||
// launcher has no title bar, so it's always false there and every hit is a
|
||||
// plain click. Returns true only when this hand's ray is actually
|
||||
// relevant to this overlay this frame - landing on it now, or continuing
|
||||
// a touch/drag begun on a previous frame for this hand that hasn't been
|
||||
// released yet - not merely because the overlay is visible. The caller
|
||||
// should only stop trying other targets (menu/keyboard/game quad) when
|
||||
// this returns true; a visible-but-unclaimed overlay falls through so a
|
||||
// lower-priority target (ultimately the game quad) can still be aimed at.
|
||||
static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
||||
// below. titleBarVFraction/dragHandleUFraction and dragState select the
|
||||
// per-overlay title-bar/drag-handle geometry and drag bookkeeping (see the
|
||||
// *_TITLE_BAR_V_FRACTION/*_DRAG_HANDLE_U_FRACTION/*_CLOSE_BUTTON_U_FRACTION
|
||||
// defines above and g_keyboard_drag/g_menu_drag) - both overlays are
|
||||
// draggable via their title bar, and any hit below titleBarVFraction is
|
||||
// always a plain click regardless of overlay. Returns true only when this
|
||||
// hand's ray is actually relevant to this overlay this frame - landing on
|
||||
// it now, or continuing a touch/drag begun on a previous frame for this
|
||||
// hand that hasn't been released yet - not merely because the overlay is
|
||||
// visible. The caller should only stop trying other targets (menu/
|
||||
// keyboard/game quad) when this returns true; a visible-but-unclaimed
|
||||
// overlay falls through so a lower-priority target (ultimately the game
|
||||
// quad) can still be aimed at.
|
||||
static bool xr_input_try_overlay(XrOverlay *overlay, float titleBarVFraction,
|
||||
float dragHandleUFraction, XrDragState *dragState, int hand,
|
||||
const XrSpaceLocation *location, float fx, float fy, float fz,
|
||||
bool selectDownEdge, bool selectUpEdge, bool *touchActive,
|
||||
bool *prevHit, const char *quadName, float *outCursorU,
|
||||
@@ -367,7 +385,7 @@ static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
||||
// the ray this frame even if it has strayed off the panel's current
|
||||
// bounds - see `claims` below.
|
||||
bool hadTouch = *touchActive;
|
||||
bool hadDrag = dragCapable && g_keyboard_dragging && g_keyboard_drag_hand == hand;
|
||||
bool hadDrag = dragState->dragging && dragState->hand == hand;
|
||||
|
||||
float quadCenterX, quadCenterY, distance, halfWidth, halfHeight;
|
||||
xr_overlay_get_quad_extent(overlay, &quadCenterX, &quadCenterY, &distance, &halfWidth,
|
||||
@@ -417,34 +435,23 @@ static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
||||
// it's visible.
|
||||
bool claims = hit || hadTouch || hadDrag;
|
||||
|
||||
if (!dragCapable) {
|
||||
if (selectDownEdge && hit) {
|
||||
xr_overlay_touch(overlay, u, v, true);
|
||||
*touchActive = true;
|
||||
} else if (selectUpEdge && *touchActive) {
|
||||
xr_overlay_touch(overlay, u, v, false);
|
||||
*touchActive = false;
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
bool onTitleBar = hit && v < TITLE_BAR_V_FRACTION;
|
||||
bool onDragHandle = onTitleBar && u <= CLOSE_BUTTON_U_FRACTION;
|
||||
bool onTitleBar = hit && v < titleBarVFraction;
|
||||
bool onDragHandle = onTitleBar && u <= dragHandleUFraction;
|
||||
|
||||
if (selectDownEdge) {
|
||||
if (onDragHandle) {
|
||||
g_keyboard_dragging = true;
|
||||
g_keyboard_drag_hand = hand;
|
||||
g_keyboard_drag_offset_x = worldX - quadCenterX;
|
||||
g_keyboard_drag_offset_y = worldY - quadCenterY;
|
||||
dragState->dragging = true;
|
||||
dragState->hand = hand;
|
||||
dragState->offset_x = worldX - quadCenterX;
|
||||
dragState->offset_y = worldY - quadCenterY;
|
||||
} else if (hit) {
|
||||
xr_overlay_touch(overlay, u, v, true);
|
||||
*touchActive = true;
|
||||
}
|
||||
} else if (selectUpEdge) {
|
||||
if (g_keyboard_dragging && g_keyboard_drag_hand == hand) {
|
||||
g_keyboard_dragging = false;
|
||||
g_keyboard_drag_hand = -1;
|
||||
if (dragState->dragging && dragState->hand == hand) {
|
||||
dragState->dragging = false;
|
||||
dragState->hand = -1;
|
||||
} else if (*touchActive) {
|
||||
xr_overlay_touch(overlay, u, v, false);
|
||||
*touchActive = false;
|
||||
@@ -454,9 +461,9 @@ static bool xr_input_try_overlay(XrOverlay *overlay, bool dragCapable, int hand,
|
||||
// Re-intersects against the same fixed-distance plane every frame the
|
||||
// drag continues, so the quad tracks the hand in real time rather than
|
||||
// only jumping on the down/up edges above.
|
||||
if (g_keyboard_dragging && g_keyboard_drag_hand == hand && planeHit) {
|
||||
xr_overlay_set_position(overlay, worldX - g_keyboard_drag_offset_x,
|
||||
worldY - g_keyboard_drag_offset_y);
|
||||
if (dragState->dragging && dragState->hand == hand && planeHit) {
|
||||
xr_overlay_set_position(overlay, worldX - dragState->offset_x,
|
||||
worldY - dragState->offset_y);
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
@@ -630,8 +637,12 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
// A drag can't get stuck active across a hide/reopen - e.g. Close
|
||||
// being hit on one hand mid-drag on the other, or the session ending.
|
||||
if (!keyboardVisible) {
|
||||
g_keyboard_dragging = false;
|
||||
g_keyboard_drag_hand = -1;
|
||||
g_keyboard_drag.dragging = false;
|
||||
g_keyboard_drag.hand = -1;
|
||||
}
|
||||
if (!menuVisible) {
|
||||
g_menu_drag.dragging = false;
|
||||
g_menu_drag.hand = -1;
|
||||
}
|
||||
|
||||
// A hand only ever gets a laser-beam quad this frame if it actually
|
||||
@@ -813,11 +824,12 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
bool keyboardPlaneHit = false;
|
||||
float keyboardPlaneDistance = 0.0f;
|
||||
if (!g_game_touch_active[hand] &&
|
||||
xr_input_try_overlay(keyboardOverlay, true, hand, &location, fx, fy, fz,
|
||||
selectDownEdge, selectUpEdge, &g_keyboard_touch_active[hand],
|
||||
&g_keyboard_prev_hit[hand], "keyboard", &keyboardCursorU,
|
||||
&keyboardCursorV, &keyboardCursorHit, &keyboardPlaneHit,
|
||||
&keyboardPlaneDistance)) {
|
||||
xr_input_try_overlay(keyboardOverlay, KEYBOARD_TITLE_BAR_V_FRACTION,
|
||||
KEYBOARD_CLOSE_BUTTON_U_FRACTION, &g_keyboard_drag, hand,
|
||||
&location, fx, fy, fz, selectDownEdge, selectUpEdge,
|
||||
&g_keyboard_touch_active[hand], &g_keyboard_prev_hit[hand],
|
||||
"keyboard", &keyboardCursorU, &keyboardCursorV,
|
||||
&keyboardCursorHit, &keyboardPlaneHit, &keyboardPlaneDistance)) {
|
||||
if (haveHead && keyboardPlaneHit)
|
||||
xr_input_build_beam(hand, &location, fx, fy, fz,
|
||||
fminf(keyboardPlaneDistance, MAX_BEAM_LENGTH_METERS), &headLoc);
|
||||
@@ -826,10 +838,11 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
bool menuPlaneHit = false;
|
||||
float menuPlaneDistance = 0.0f;
|
||||
if (!g_game_touch_active[hand] &&
|
||||
xr_input_try_overlay(menuOverlay, false, hand, &location, fx, fy, fz, selectDownEdge,
|
||||
selectUpEdge, &g_menu_touch_active[hand], &g_menu_prev_hit[hand],
|
||||
"menu", &menuCursorU, &menuCursorV, &menuCursorHit, &menuPlaneHit,
|
||||
&menuPlaneDistance)) {
|
||||
xr_input_try_overlay(menuOverlay, MENU_TITLE_BAR_V_FRACTION,
|
||||
MENU_DRAG_HANDLE_U_FRACTION, &g_menu_drag, hand, &location, fx,
|
||||
fy, fz, selectDownEdge, selectUpEdge, &g_menu_touch_active[hand],
|
||||
&g_menu_prev_hit[hand], "menu", &menuCursorU, &menuCursorV,
|
||||
&menuCursorHit, &menuPlaneHit, &menuPlaneDistance)) {
|
||||
if (haveHead && menuPlaneHit)
|
||||
xr_input_build_beam(hand, &location, fx, fy, fz,
|
||||
fminf(menuPlaneDistance, MAX_BEAM_LENGTH_METERS), &headLoc);
|
||||
@@ -920,6 +933,8 @@ void xr_input_shutdown(void) {
|
||||
g_prev_menu = false;
|
||||
g_prev_space_key = false;
|
||||
g_prev_forward_key = false;
|
||||
g_keyboard_dragging = false;
|
||||
g_keyboard_drag_hand = -1;
|
||||
g_keyboard_drag.dragging = false;
|
||||
g_keyboard_drag.hand = -1;
|
||||
g_menu_drag.dragging = false;
|
||||
g_menu_drag.hand = -1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user