mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-21 13:36:50 -04:00
Touch controls (#2053)
* WIP touch controls * Action icons * Updates * Don't mutate freeCamera config; allow switching between touch and controller cam * Wow * Fix build & add Skip button * Fix build & add settings * RCSS cleanup * Dpad and fishing, might redo * Add menu mouse controls * More pointer & fix icons * Optimizations & introduce layout system * Update aurora * Implement touch controls layout editor * Cleanup & fixes * Allow disabling mouse/touch in menus * More fixes
This commit is contained in:
+223
-51
@@ -1,19 +1,23 @@
|
||||
#include "dusk/config.hpp"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "fmt/format.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/io.hpp"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/main.h"
|
||||
|
||||
using namespace dusk::config;
|
||||
|
||||
@@ -26,6 +30,104 @@ aurora::Module DuskConfigLog("dusk::config");
|
||||
static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars;
|
||||
static bool RegistrationDone = false;
|
||||
|
||||
static std::optional<dusk::ui::ControlAnchor> parse_control_anchor(std::string_view value) {
|
||||
if (value == "none") {
|
||||
return dusk::ui::ControlAnchor::None;
|
||||
}
|
||||
if (value == "top") {
|
||||
return dusk::ui::ControlAnchor::Top;
|
||||
}
|
||||
if (value == "left") {
|
||||
return dusk::ui::ControlAnchor::Left;
|
||||
}
|
||||
if (value == "bottom") {
|
||||
return dusk::ui::ControlAnchor::Bottom;
|
||||
}
|
||||
if (value == "right") {
|
||||
return dusk::ui::ControlAnchor::Right;
|
||||
}
|
||||
if (value == "topLeft") {
|
||||
return dusk::ui::ControlAnchor::TopLeft;
|
||||
}
|
||||
if (value == "topRight") {
|
||||
return dusk::ui::ControlAnchor::TopRight;
|
||||
}
|
||||
if (value == "bottomLeft") {
|
||||
return dusk::ui::ControlAnchor::BottomLeft;
|
||||
}
|
||||
if (value == "bottomRight") {
|
||||
return dusk::ui::ControlAnchor::BottomRight;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static const char* control_anchor_value(dusk::ui::ControlAnchor anchor) {
|
||||
switch (anchor) {
|
||||
case dusk::ui::ControlAnchor::None:
|
||||
return "none";
|
||||
case dusk::ui::ControlAnchor::Top:
|
||||
return "top";
|
||||
case dusk::ui::ControlAnchor::Left:
|
||||
return "left";
|
||||
case dusk::ui::ControlAnchor::Bottom:
|
||||
return "bottom";
|
||||
case dusk::ui::ControlAnchor::Right:
|
||||
return "right";
|
||||
case dusk::ui::ControlAnchor::TopLeft:
|
||||
return "topLeft";
|
||||
case dusk::ui::ControlAnchor::TopRight:
|
||||
return "topRight";
|
||||
case dusk::ui::ControlAnchor::BottomLeft:
|
||||
return "bottomLeft";
|
||||
case dusk::ui::ControlAnchor::BottomRight:
|
||||
return "bottomRight";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
static std::optional<float> json_finite_float(const json& object, const char* key) {
|
||||
const auto iter = object.find(key);
|
||||
if (iter == object.end() || !iter->is_number()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const float value = iter->get<float>();
|
||||
if (!std::isfinite(value)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::optional<dusk::ui::ControlProps> parse_control_props(const json& value) {
|
||||
if (!value.is_object()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto x = json_finite_float(value, "x");
|
||||
const auto y = json_finite_float(value, "y");
|
||||
const auto w = json_finite_float(value, "w");
|
||||
const auto h = json_finite_float(value, "h");
|
||||
const auto scale = json_finite_float(value, "scale");
|
||||
const auto anchorIter = value.find("anchor");
|
||||
if (!x || !y || !w || !h || !scale || anchorIter == value.end() || !anchorIter->is_string()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto anchor = parse_control_anchor(anchorIter->get<std::string>());
|
||||
if (!anchor || *w <= 0.0f || *h <= 0.0f || *scale <= 0.0f) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return dusk::ui::ControlProps{
|
||||
.x = *x,
|
||||
.y = *y,
|
||||
.w = *w,
|
||||
.h = *h,
|
||||
.scale = *scale,
|
||||
.anchor = *anchor,
|
||||
};
|
||||
}
|
||||
|
||||
static std::filesystem::path GetConfigJsonPath() {
|
||||
return dusk::ConfigPath / ConfigFileName;
|
||||
}
|
||||
@@ -46,8 +148,8 @@ static void ReplaceFile(const std::filesystem::path& source, const std::filesyst
|
||||
}
|
||||
}
|
||||
|
||||
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {
|
||||
}
|
||||
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl)
|
||||
: name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {}
|
||||
|
||||
const char* ConfigVarBase::getName() const noexcept {
|
||||
return name;
|
||||
@@ -72,11 +174,13 @@ static T sanitizeEnumValue(const ConfigVar<T>& cVar, T value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
template<ConfigValue T>
|
||||
template <ConfigValue T>
|
||||
void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
|
||||
if constexpr (std::is_enum_v<T>) {
|
||||
if (jsonValue.is_boolean()) {
|
||||
DuskConfigLog.error("Doing default migration of CVar {} from bool, enum values may not be what is expected!", cVar.getName());
|
||||
DuskConfigLog.error("Doing default migration of CVar {} from bool, enum values may not "
|
||||
"be what is expected!",
|
||||
cVar.getName());
|
||||
|
||||
using Underlying = std::underlying_type_t<T>;
|
||||
const bool b = jsonValue.get<bool>();
|
||||
@@ -91,13 +195,14 @@ void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
|
||||
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<T>()), false);
|
||||
}
|
||||
|
||||
template<ConfigValue T>
|
||||
template <ConfigValue T>
|
||||
nlohmann::json ConfigImpl<T>::dumpToJson(const ConfigVar<T>& cVar) {
|
||||
return cVar.getValueForSave();
|
||||
}
|
||||
|
||||
template<ConfigValue T> requires std::is_integral_v<T> && std::is_signed_v<T>
|
||||
static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
template <ConfigValue T>
|
||||
requires std::is_integral_v<T>&& std::is_signed_v<T> static void loadFromArgImpl(
|
||||
ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
const auto result = std::stoll(str);
|
||||
if (result >= std::numeric_limits<T>::min() && result <= std::numeric_limits<T>::max()) {
|
||||
@@ -107,8 +212,9 @@ static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringVal
|
||||
}
|
||||
}
|
||||
|
||||
template<ConfigValue T> requires std::is_integral_v<T> && std::is_unsigned_v<T>
|
||||
static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
template <ConfigValue T>
|
||||
requires std::is_integral_v<T>&& std::is_unsigned_v<T> static void loadFromArgImpl(
|
||||
ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
const std::string str(stringValue);
|
||||
const auto result = std::stoull(str);
|
||||
if (result <= std::numeric_limits<T>::max()) {
|
||||
@@ -134,14 +240,17 @@ static void loadFromArgImpl(ConfigVar<std::string>& cVar, const std::string_view
|
||||
cVar.setOverrideValue(std::string(stringValue));
|
||||
}
|
||||
|
||||
template<ConfigValue T> requires std::is_enum_v<T>
|
||||
static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
template <ConfigValue T>
|
||||
requires std::is_enum_v<T> static void loadFromArgImpl(
|
||||
ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
using Underlying = std::underlying_type_t<T>;
|
||||
const std::string str(stringValue);
|
||||
|
||||
if constexpr (std::is_signed_v<Underlying>) {
|
||||
const auto result = std::stoll(str);
|
||||
if (result >= std::numeric_limits<Underlying>::min() && result <= std::numeric_limits<Underlying>::max()) {
|
||||
if (result >= std::numeric_limits<Underlying>::min() &&
|
||||
result <= std::numeric_limits<Underlying>::max())
|
||||
{
|
||||
cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast<T>(result)));
|
||||
} else {
|
||||
throw std::out_of_range("Value is too large");
|
||||
@@ -156,16 +265,20 @@ static void loadFromArgImpl(ConfigVar<T>& cVar, const std::string_view stringVal
|
||||
}
|
||||
}
|
||||
|
||||
template<ConfigValue T>
|
||||
template <ConfigValue T>
|
||||
void ConfigImpl<T>::loadFromArg(ConfigVar<T>& cVar, const std::string_view stringValue) {
|
||||
loadFromArgImpl(cVar, stringValue);
|
||||
}
|
||||
|
||||
template<>
|
||||
template <>
|
||||
void ConfigImpl<bool>::loadFromArg(ConfigVar<bool>& cVar, const std::string_view stringValue) {
|
||||
if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" || stringValue == "True") {
|
||||
if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" ||
|
||||
stringValue == "True")
|
||||
{
|
||||
cVar.setOverrideValue(true);
|
||||
} else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" || stringValue == "False") {
|
||||
} else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" ||
|
||||
stringValue == "False")
|
||||
{
|
||||
cVar.setOverrideValue(false);
|
||||
} else {
|
||||
throw InvalidConfigError("Value cannot be parsed as boolean");
|
||||
@@ -174,42 +287,103 @@ void ConfigImpl<bool>::loadFromArg(ConfigVar<bool>& cVar, const std::string_view
|
||||
|
||||
// My IDE is convinced this namespace is necessary. It shouldn't be AFAICT?
|
||||
namespace dusk::config {
|
||||
template class ConfigImpl<bool>;
|
||||
template class ConfigImpl<s8>;
|
||||
template class ConfigImpl<u8>;
|
||||
template class ConfigImpl<s16>;
|
||||
template class ConfigImpl<u16>;
|
||||
template class ConfigImpl<s32>;
|
||||
template class ConfigImpl<u32>;
|
||||
template class ConfigImpl<s64>;
|
||||
template class ConfigImpl<u64>;
|
||||
template class ConfigImpl<f32>;
|
||||
template class ConfigImpl<f64>;
|
||||
template class ConfigImpl<std::string>;
|
||||
template class ConfigImpl<dusk::BloomMode>;
|
||||
template class ConfigImpl<dusk::DepthOfFieldMode>;
|
||||
template class ConfigImpl<dusk::DiscVerificationState>;
|
||||
template class ConfigImpl<dusk::GameLanguage>;
|
||||
template class ConfigImpl<dusk::GyroMode>;
|
||||
template class ConfigImpl<bool>;
|
||||
template class ConfigImpl<s8>;
|
||||
template class ConfigImpl<u8>;
|
||||
template class ConfigImpl<s16>;
|
||||
template class ConfigImpl<u16>;
|
||||
template class ConfigImpl<s32>;
|
||||
template class ConfigImpl<u32>;
|
||||
template class ConfigImpl<s64>;
|
||||
template class ConfigImpl<u64>;
|
||||
template class ConfigImpl<f32>;
|
||||
template class ConfigImpl<f64>;
|
||||
template class ConfigImpl<std::string>;
|
||||
template class ConfigImpl<dusk::BloomMode>;
|
||||
template class ConfigImpl<dusk::DepthOfFieldMode>;
|
||||
template class ConfigImpl<dusk::DiscVerificationState>;
|
||||
template class ConfigImpl<dusk::GameLanguage>;
|
||||
|
||||
template<> void ConfigImpl<FrameInterpMode>::loadFromJson(ConfigVar<FrameInterpMode>& cVar, const json& jsonValue) {
|
||||
if (jsonValue.is_boolean()) {
|
||||
const bool b = jsonValue.get<bool>();
|
||||
template <>
|
||||
void ConfigImpl<FrameInterpMode>::loadFromJson(
|
||||
ConfigVar<FrameInterpMode>& cVar, const json& jsonValue) {
|
||||
if (jsonValue.is_boolean()) {
|
||||
const bool b = jsonValue.get<bool>();
|
||||
|
||||
const FrameInterpMode mode = b ? FrameInterpMode::Unlimited : FrameInterpMode::Off;
|
||||
const FrameInterpMode mode = b ? FrameInterpMode::Unlimited : FrameInterpMode::Off;
|
||||
|
||||
cVar.setValue(sanitizeEnumValue(cVar, mode), false);
|
||||
return;
|
||||
cVar.setValue(sanitizeEnumValue(cVar, mode), false);
|
||||
return;
|
||||
}
|
||||
|
||||
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<FrameInterpMode>()), false);
|
||||
}
|
||||
|
||||
template <>
|
||||
void ConfigImpl<ui::ControlLayout>::loadFromJson(
|
||||
ConfigVar<ui::ControlLayout>& cVar, const json& jsonValue) {
|
||||
if (!jsonValue.is_object()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int version = jsonValue.value("version", 0);
|
||||
if (version != ui::ControlLayout::Version) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto controlsIter = jsonValue.find("controls");
|
||||
if (controlsIter == jsonValue.end() || !controlsIter->is_object()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ui::ControlLayout layout{.version = version};
|
||||
for (const auto& control : controlsIter->items()) {
|
||||
if (!ui::is_control_layout_id(control.key())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get<FrameInterpMode>()), false);
|
||||
if (const auto props = parse_control_props(control.value())) {
|
||||
layout.controls[control.key()] = *props;
|
||||
}
|
||||
}
|
||||
template class ConfigImpl<dusk::FrameInterpMode>;
|
||||
template class ConfigImpl<dusk::MenuScaling>;
|
||||
template class ConfigImpl<dusk::Resampler>;
|
||||
template class ConfigImpl<dusk::MagicArmorMode>;
|
||||
|
||||
cVar.setValue(std::move(layout), false);
|
||||
}
|
||||
|
||||
template <>
|
||||
void ConfigImpl<ui::ControlLayout>::loadFromArg(
|
||||
ConfigVar<ui::ControlLayout>&, const std::string_view) {
|
||||
throw InvalidConfigError("Touch control layout cannot be parsed from launch arguments");
|
||||
}
|
||||
|
||||
template <>
|
||||
nlohmann::json ConfigImpl<ui::ControlLayout>::dumpToJson(const ConfigVar<ui::ControlLayout>& cVar) {
|
||||
const auto& layout = cVar.getValueForSave();
|
||||
json controls = json::object();
|
||||
for (const auto& [id, props] : layout.controls) {
|
||||
controls[id] = {
|
||||
{"x", props.x},
|
||||
{"y", props.y},
|
||||
{"w", props.w},
|
||||
{"h", props.h},
|
||||
{"scale", props.scale},
|
||||
{"anchor", control_anchor_value(props.anchor)},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
{"version", ui::ControlLayout::Version},
|
||||
{"controls", std::move(controls)},
|
||||
};
|
||||
}
|
||||
|
||||
template class ConfigImpl<dusk::FrameInterpMode>;
|
||||
template class ConfigImpl<dusk::MenuScaling>;
|
||||
template class ConfigImpl<dusk::Resampler>;
|
||||
template class ConfigImpl<dusk::MagicArmorMode>;
|
||||
template class ConfigImpl<dusk::ui::ControlLayout>;
|
||||
} // namespace dusk::config
|
||||
|
||||
void dusk::config::Register(ConfigVarBase& configVar) {
|
||||
const auto& name = configVar.getName();
|
||||
if (RegistrationDone) {
|
||||
@@ -298,9 +472,7 @@ void dusk::config::Save() {
|
||||
}
|
||||
const auto configPathString = io::fs_path_to_string(configJsonPath);
|
||||
|
||||
DuskConfigLog.info(
|
||||
"Saving config to '{}'",
|
||||
configPathString);
|
||||
DuskConfigLog.info("Saving config to '{}'", configPathString);
|
||||
|
||||
json j;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user