mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-17 19:00:48 +02:00
The router inferred relative mode from SDL reporting zero absolute coordinates. X11 does; Wayland reports a position that accumulates the deltas and leaves the window, so every camera-drag event took the absolute branch and walked Roblox's cursor to the viewport edge. Motion, button and wheel events now carry the window's relative mouse mode, and the RMB camera-drag fallback only captures inside an experience so the app surface keeps a free cursor.
267 lines
7.3 KiB
C++
267 lines
7.3 KiB
C++
#ifndef MOCKTAIL_PLATFORM_PLATFORM_RUNTIME_H_
|
|
#define MOCKTAIL_PLATFORM_PLATFORM_RUNTIME_H_
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <variant>
|
|
|
|
#include "mocktail/status.h"
|
|
|
|
namespace mocktail {
|
|
namespace platform {
|
|
|
|
enum class WindowSurfaceApi {
|
|
kAngleEgl = 0,
|
|
kDirectVulkan,
|
|
};
|
|
|
|
enum class NativeWindowSystem {
|
|
kUnknown = 0,
|
|
kX11,
|
|
kWayland,
|
|
};
|
|
|
|
struct WindowOptions {
|
|
int width = 1280;
|
|
int height = 720;
|
|
std::string title = "Roblox";
|
|
WindowSurfaceApi surface_api = WindowSurfaceApi::kAngleEgl;
|
|
bool high_pixel_density = false;
|
|
bool initially_hidden = true;
|
|
|
|
// Production callers should keep this true. Tests using SDL's dummy driver
|
|
// may opt out because that driver intentionally has no native WSI handle.
|
|
bool require_native_window = true;
|
|
};
|
|
|
|
// Non-owning handles captured from SDL window properties. backend_window is
|
|
// an opaque SDL_Window pointer used only by SDL-specific graphics adapters.
|
|
struct NativeWindowDescriptor {
|
|
NativeWindowSystem window_system = NativeWindowSystem::kUnknown;
|
|
WindowSurfaceApi surface_api = WindowSurfaceApi::kAngleEgl;
|
|
void* backend_window = nullptr;
|
|
void* native_display = nullptr;
|
|
std::uintptr_t native_window = 0;
|
|
void* native_surface = nullptr;
|
|
void* native_egl_window = nullptr;
|
|
|
|
bool HasPlatformHandle() const {
|
|
if (window_system == NativeWindowSystem::kX11) {
|
|
return native_display != nullptr && native_window != 0;
|
|
}
|
|
if (window_system == NativeWindowSystem::kWayland) {
|
|
return native_display != nullptr && native_surface != nullptr;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct QuitEvent {};
|
|
|
|
// Android exposes pointer and TextBox geometry in density-independent surface
|
|
// coordinates. SDL exposes pointer positions in logical window coordinates,
|
|
// while Vulkan presents to the pixel-sized drawable. Keep all three spaces in
|
|
// one value so input, IME placement and same-surface overlays use exact inverse
|
|
// transforms.
|
|
struct SurfaceCoordinateTransform {
|
|
int32_t logical_width = 0;
|
|
int32_t logical_height = 0;
|
|
int32_t pixel_width = 0;
|
|
int32_t pixel_height = 0;
|
|
float density_scale = 1.0F;
|
|
|
|
bool valid() const {
|
|
return logical_width > 0 && logical_height > 0 && pixel_width > 0 &&
|
|
pixel_height > 0 && std::isfinite(density_scale) &&
|
|
density_scale > 0.0F;
|
|
}
|
|
|
|
float guest_width() const {
|
|
return valid() ? static_cast<float>(pixel_width) / density_scale : 0.0F;
|
|
}
|
|
float guest_height() const {
|
|
return valid() ? static_cast<float>(pixel_height) / density_scale : 0.0F;
|
|
}
|
|
int32_t rounded_guest_width() const {
|
|
return valid() ? static_cast<int32_t>(std::lround(guest_width())) : 0;
|
|
}
|
|
int32_t rounded_guest_height() const {
|
|
return valid() ? static_cast<int32_t>(std::lround(guest_height())) : 0;
|
|
}
|
|
|
|
float HostLogicalToGuestX(float coordinate) const {
|
|
return valid() ? coordinate * static_cast<float>(pixel_width) /
|
|
static_cast<float>(logical_width) / density_scale
|
|
: coordinate;
|
|
}
|
|
float HostLogicalToGuestY(float coordinate) const {
|
|
return valid() ? coordinate * static_cast<float>(pixel_height) /
|
|
static_cast<float>(logical_height) / density_scale
|
|
: coordinate;
|
|
}
|
|
float GuestToHostLogicalX(float coordinate) const {
|
|
return valid() ? coordinate * density_scale *
|
|
static_cast<float>(logical_width) /
|
|
static_cast<float>(pixel_width)
|
|
: coordinate;
|
|
}
|
|
float GuestToHostLogicalY(float coordinate) const {
|
|
return valid() ? coordinate * density_scale *
|
|
static_cast<float>(logical_height) /
|
|
static_cast<float>(pixel_height)
|
|
: coordinate;
|
|
}
|
|
};
|
|
|
|
struct WindowResizedEvent {
|
|
int logical_width = 0;
|
|
int logical_height = 0;
|
|
int pixel_width = 0;
|
|
int pixel_height = 0;
|
|
// Android DisplayMetrics.density equivalent. This is independent of the
|
|
// logical-to-pixel drawable ratio on X11/XWayland.
|
|
float density_scale = 1.0F;
|
|
};
|
|
|
|
struct WindowFocusEvent {
|
|
bool focused = false;
|
|
};
|
|
|
|
struct KeyEvent {
|
|
bool pressed = false;
|
|
bool repeat = false;
|
|
std::uint32_t scancode = 0;
|
|
std::uint32_t keycode = 0;
|
|
std::uint32_t modifiers = 0;
|
|
};
|
|
|
|
struct TextInputEvent {
|
|
std::string text;
|
|
};
|
|
|
|
struct TextEditingEvent {
|
|
std::string text;
|
|
int start = 0;
|
|
int length = 0;
|
|
};
|
|
|
|
struct MouseMotionEvent {
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
float delta_x = 0.0f;
|
|
float delta_y = 0.0f;
|
|
std::uint32_t buttons = 0;
|
|
// True while the window holds relative mouse mode. The absolute coordinates
|
|
// are meaningless then: X11 reports zeroes, Wayland reports a position that
|
|
// accumulates the deltas and can leave the window entirely.
|
|
bool relative = false;
|
|
};
|
|
|
|
struct MouseButtonEvent {
|
|
bool pressed = false;
|
|
std::uint8_t button = 0;
|
|
std::uint8_t clicks = 0;
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
// See MouseMotionEvent::relative.
|
|
bool relative = false;
|
|
};
|
|
|
|
struct MouseWheelEvent {
|
|
float delta_x = 0.0f;
|
|
float delta_y = 0.0f;
|
|
float mouse_x = 0.0f;
|
|
float mouse_y = 0.0f;
|
|
// See MouseMotionEvent::relative.
|
|
bool relative = false;
|
|
};
|
|
|
|
struct TouchEvent {
|
|
enum class Action {
|
|
kDown = 0,
|
|
kMove,
|
|
kUp,
|
|
kCancelled,
|
|
};
|
|
|
|
Action action = Action::kMove;
|
|
std::int64_t touch_id = 0;
|
|
std::int64_t finger_id = 0;
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
float delta_x = 0.0f;
|
|
float delta_y = 0.0f;
|
|
float pressure = 0.0f;
|
|
};
|
|
|
|
enum class GamepadFamily {
|
|
kUnknown,
|
|
kXbox,
|
|
kPlayStation4,
|
|
kPlayStation5,
|
|
};
|
|
|
|
struct GamepadDescriptor {
|
|
GamepadFamily family = GamepadFamily::kUnknown;
|
|
std::string name;
|
|
// Bit positions are SDL's standardized gamepad buttons and axes, not raw
|
|
// joystick indices or Android key codes.
|
|
std::uint32_t buttons = 0;
|
|
std::uint32_t axes = 0;
|
|
};
|
|
|
|
struct GamepadConnectionEvent {
|
|
std::int64_t instance_id = 0;
|
|
bool connected = false;
|
|
GamepadDescriptor descriptor{};
|
|
bool remapped = false;
|
|
};
|
|
|
|
struct GamepadAxisEvent {
|
|
std::int64_t instance_id = 0;
|
|
std::uint8_t axis = 0;
|
|
std::int16_t value = 0;
|
|
};
|
|
|
|
struct GamepadButtonEvent {
|
|
std::int64_t instance_id = 0;
|
|
std::uint8_t button = 0;
|
|
bool pressed = false;
|
|
};
|
|
|
|
using PlatformEventPayload =
|
|
std::variant<QuitEvent, WindowResizedEvent, WindowFocusEvent, KeyEvent,
|
|
TextInputEvent, TextEditingEvent, MouseMotionEvent,
|
|
MouseButtonEvent, MouseWheelEvent, TouchEvent,
|
|
GamepadConnectionEvent, GamepadAxisEvent, GamepadButtonEvent>;
|
|
|
|
struct PlatformEvent {
|
|
std::uint64_t timestamp_ns = 0;
|
|
PlatformEventPayload payload = QuitEvent{};
|
|
};
|
|
|
|
// Main-thread-owned platform boundary. Implementations never create an EGL or
|
|
// Vulkan device/context; those belong to the selected graphics backend.
|
|
class PlatformRuntime {
|
|
public:
|
|
virtual ~PlatformRuntime() = default;
|
|
|
|
virtual Status Initialize(const WindowOptions& options) = 0;
|
|
virtual void Shutdown() = 0;
|
|
virtual bool IsInitialized() const = 0;
|
|
|
|
// Returns OK with has_event=false when the queue currently contains no
|
|
// supported platform event.
|
|
virtual Status PollEvent(PlatformEvent* event, bool* has_event) = 0;
|
|
virtual Status SetTextInputEnabled(bool enabled) = 0;
|
|
virtual Status ShowWindow() = 0;
|
|
|
|
virtual NativeWindowDescriptor GetNativeWindow() const = 0;
|
|
};
|
|
|
|
} // namespace platform
|
|
} // namespace mocktail
|
|
|
|
#endif // MOCKTAIL_PLATFORM_PLATFORM_RUNTIME_H_
|