Files
2026-09-03 15:31:39 +08:00

134 lines
5.4 KiB
C++

#include "Input_Event.hpp"
#include <magic_enum/magic_enum.hpp>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
namespace aethera::web {
namespace {
[[nodiscard]] Event_Timeline_Time input_timeline_time(double milliseconds) {
constexpr long double nanoseconds_per_millisecond{1'000'000.0L};
constexpr long double maximum_milliseconds =
static_cast<long double>(std::numeric_limits<std::uint64_t>::max()) /
nanoseconds_per_millisecond;
if (!std::isfinite(milliseconds) || milliseconds < 0.0 ||
static_cast<long double>(milliseconds) > maximum_milliseconds)
throw std::invalid_argument(
"input time_milliseconds must be finite, non-negative and representable");
return Event_Timeline_Time{static_cast<std::uint64_t>(
static_cast<long double>(milliseconds) *
nanoseconds_per_millisecond)};
}
void read_point(const nlohmann::json& input, std::string_view key,
Input_Point& point, Input_Viewport viewport,
bool viewport_relative) {
const auto value = input.find(key);
if (value == input.end() || !value->is_object()) return;
const auto x = value->value("x", 0.0);
const auto y = value->value("y", 0.0);
point.x = viewport_relative && viewport.width != 0
? std::clamp(x, 0.0, static_cast<double>(viewport.width)) : x;
point.y = viewport_relative && viewport.height != 0
? std::clamp(y, 0.0, static_cast<double>(viewport.height)) : y;
}
void read_pointer(const nlohmann::json& input,
Basic_Pointer_Event<Input_Point>& event,
Input_Viewport viewport) {
read_point(input, "position", event.position, viewport, true);
read_point(input, "global_position", event.global_position, viewport, false);
event.button = magic_enum::enum_cast<Mouse_Button>(
input.value("button", std::string{"none"})).value_or(Mouse_Button::none);
event.buttons = static_cast<Mouse_Button_Mask>(
std::clamp(input.value("buttons", 0), 0, 255));
event.modifiers = static_cast<Keyboard_Modifier>(
std::clamp(input.value("modifiers", 0), 0, 15));
}
}
Input_Resize_Event::Input_Resize_Event(Event_Timeline_Time occurred_at)
: Event(Event_Type::resize, occurred_at) {}
Input_Pointer_Event::Input_Pointer_Event(
Event_Type type, Event_Timeline_Time occurred_at)
: Basic_Pointer_Event(type, occurred_at) {}
Input_Wheel_Event::Input_Wheel_Event(Event_Timeline_Time occurred_at)
: Basic_Wheel_Event(occurred_at) {}
Input_Key_Event::Input_Key_Event(
Event_Type type, Event_Timeline_Time occurred_at)
: Key_Event(type, occurred_at) {}
std::unique_ptr<Event> make_input_event(const nlohmann::json& input, Input_Viewport viewport) {
if (!input.is_object())
throw std::invalid_argument("input event must be an object");
const auto type = magic_enum::enum_cast<Event_Type>(
input.value("type", std::string{}));
if (!type) throw std::invalid_argument("input event type is unknown");
const auto occurred_at = input_timeline_time(
input.at("time_milliseconds").get<double>());
switch (*type) {
case Event_Type::resize: {
auto event = std::make_unique<Input_Resize_Event>(occurred_at);
if (const auto size = input.find("old_size");
size != input.end() && size->is_object()) {
event->old_size.width = size->value("width", 0U);
event->old_size.height = size->value("height", 0U);
}
if (const auto size = input.find("new_size");
size != input.end() && size->is_object()) {
event->new_size.width = size->value("width", 0U);
event->new_size.height = size->value("height", 0U);
}
return event;
}
case Event_Type::pointer_move:
case Event_Type::pointer_press:
case Event_Type::pointer_release: {
auto event = std::make_unique<Input_Pointer_Event>(*type, occurred_at);
read_pointer(input, *event, viewport);
return event;
}
case Event_Type::wheel: {
auto event = std::make_unique<Input_Wheel_Event>(occurred_at);
read_pointer(input, *event, viewport);
event->pixel_delta_x = std::clamp(
input.value("pixel_delta_x", 0.0), -4096.0, 4096.0);
event->pixel_delta_y = std::clamp(
input.value("pixel_delta_y", 0.0), -4096.0, 4096.0);
event->angle_delta_x = std::clamp(
input.value("angle_delta_x", 0.0), -120.0, 120.0);
event->angle_delta_y = std::clamp(
input.value("angle_delta_y", 0.0), -120.0, 120.0);
return event;
}
case Event_Type::key_press:
case Event_Type::key_release: {
auto event = std::make_unique<Input_Key_Event>(*type, occurred_at);
event->key = magic_enum::enum_cast<Key>(
input.value("key", std::string{"unknown"})).value_or(Key::unknown);
event->native_key = input.value("native_key", 0U);
event->modifiers = static_cast<Keyboard_Modifier>(
std::clamp(input.value("modifiers", 0), 0, 15));
event->auto_repeat = input.value("auto_repeat", false);
return event;
}
case Event_Type::show:
case Event_Type::hide:
case Event_Type::leave:
return std::make_unique<Event>(*type, occurred_at);
}
throw std::logic_error("unhandled input event type");
}
}