mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-16 05:55:37 -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:
Vendored
+1
-1
Submodule extern/aurora updated: e3fd6b1900...f49d3c5f58
+12
@@ -1434,6 +1434,8 @@ set(DUSK_FILES
|
||||
src/dusk/game_clock.cpp
|
||||
src/dusk/globals.cpp
|
||||
src/dusk/gyro.cpp
|
||||
include/dusk/menu_pointer.h
|
||||
src/dusk/menu_pointer.cpp
|
||||
src/dusk/mouse.cpp
|
||||
src/dusk/gamepad_color.cpp
|
||||
src/dusk/autosave.cpp
|
||||
@@ -1447,6 +1449,7 @@ set(DUSK_FILES
|
||||
src/dusk/stubs.cpp
|
||||
include/dusk/texture_replacements.hpp
|
||||
src/dusk/texture_replacements.cpp
|
||||
src/dusk/touch_camera.cpp
|
||||
src/dusk/update_check.cpp
|
||||
src/dusk/update_check.hpp
|
||||
#src/dusk/m_Do_ext_dusk.cpp
|
||||
@@ -1476,6 +1479,7 @@ set(DUSK_FILES
|
||||
src/dusk/ui/button.hpp
|
||||
src/dusk/ui/component.cpp
|
||||
src/dusk/ui/component.hpp
|
||||
src/dusk/ui/controls.hpp
|
||||
src/dusk/ui/controller_config.cpp
|
||||
src/dusk/ui/controller_config.hpp
|
||||
src/dusk/ui/document.cpp
|
||||
@@ -1488,6 +1492,8 @@ set(DUSK_FILES
|
||||
src/dusk/ui/graphics_tuner.hpp
|
||||
src/dusk/ui/input.cpp
|
||||
src/dusk/ui/input.hpp
|
||||
src/dusk/ui/icon_provider.cpp
|
||||
src/dusk/ui/icon_provider.hpp
|
||||
src/dusk/ui/modal.cpp
|
||||
src/dusk/ui/modal.hpp
|
||||
src/dusk/ui/nav_types.hpp
|
||||
@@ -1513,6 +1519,12 @@ set(DUSK_FILES
|
||||
src/dusk/ui/string_button.hpp
|
||||
src/dusk/ui/tab_bar.cpp
|
||||
src/dusk/ui/tab_bar.hpp
|
||||
src/dusk/ui/touch_controls_common.cpp
|
||||
src/dusk/ui/touch_controls_common.hpp
|
||||
src/dusk/ui/touch_controls.cpp
|
||||
src/dusk/ui/touch_controls.hpp
|
||||
src/dusk/ui/touch_controls_editor.cpp
|
||||
src/dusk/ui/touch_controls_editor.hpp
|
||||
src/dusk/ui/ui.cpp
|
||||
src/dusk/ui/ui.hpp
|
||||
src/dusk/ui/warp.cpp
|
||||
|
||||
@@ -1037,7 +1037,7 @@ public:
|
||||
bool test1Camera(s32);
|
||||
bool test2Camera(s32);
|
||||
#if TARGET_PC
|
||||
static bool canUseFreeCam();
|
||||
static bool isAimActive();
|
||||
bool freeCamera();
|
||||
bool executeDebugFlyCam();
|
||||
void deactivateDebugFlyCam();
|
||||
|
||||
@@ -416,6 +416,10 @@ public:
|
||||
bool yesnoWakuAlpahAnm(u8);
|
||||
#if TARGET_PC
|
||||
void fileSelectWide();
|
||||
bool pointerDataSelect();
|
||||
bool pointerMenuSelect();
|
||||
bool pointerCopyDataToSelect();
|
||||
bool pointerYesNoSelect(bool errorSelect);
|
||||
#endif
|
||||
void _draw();
|
||||
void errorMoveAnmInitSet(int, int);
|
||||
|
||||
@@ -74,6 +74,8 @@ public:
|
||||
|
||||
#if TARGET_PC
|
||||
void menuCollectWide();
|
||||
bool pointerWait();
|
||||
void pointerActivateCurrent();
|
||||
#endif
|
||||
|
||||
void _create();
|
||||
|
||||
@@ -51,6 +51,10 @@ public:
|
||||
void setBButtonString(u16);
|
||||
void setHIO(bool);
|
||||
|
||||
#if TARGET_PC
|
||||
bool pointerWait();
|
||||
#endif
|
||||
|
||||
virtual void draw() { _draw(); }
|
||||
virtual ~dMenu_Insect_c();
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ public:
|
||||
u8 getLetterNum();
|
||||
void setHIO(bool);
|
||||
|
||||
#if TARGET_PC
|
||||
bool pointerWait();
|
||||
#endif
|
||||
|
||||
virtual void draw() { _draw(); }
|
||||
virtual ~dMenu_Letter_c();
|
||||
|
||||
|
||||
@@ -80,6 +80,9 @@ public:
|
||||
void setBButtonString(u16);
|
||||
bool isRumbleSupported();
|
||||
bool dpdMenuMove();
|
||||
#if TARGET_PC
|
||||
bool pointerConfirmSelect();
|
||||
#endif
|
||||
void paneResize(u64);
|
||||
void initialize();
|
||||
void yesnoMenuMoveAnmInitSet(int, int);
|
||||
|
||||
@@ -74,6 +74,9 @@ public:
|
||||
void clacEllipsePlotAverage(int, f32, f32);
|
||||
bool dpdMove();
|
||||
u8 openExplain(u8);
|
||||
#if TARGET_PC
|
||||
bool pointerMove();
|
||||
#endif
|
||||
|
||||
virtual void draw() { _draw(); }
|
||||
virtual ~dMenu_Ring_c();
|
||||
|
||||
@@ -266,6 +266,8 @@ public:
|
||||
|
||||
#if TARGET_PC
|
||||
void menuSaveWide();
|
||||
bool pointerSaveSelect();
|
||||
bool pointerYesNoSelect(bool errorSelect, u8 errParam = 0, u8 soundParam = 0);
|
||||
#endif
|
||||
|
||||
void _draw2();
|
||||
|
||||
@@ -49,6 +49,10 @@ public:
|
||||
u8 getSkillNum();
|
||||
void setHIO(bool);
|
||||
|
||||
#if TARGET_PC
|
||||
bool pointerWait();
|
||||
#endif
|
||||
|
||||
virtual void draw() { _draw(); }
|
||||
virtual ~dMenu_Skill_c();
|
||||
|
||||
|
||||
@@ -49,6 +49,10 @@ public:
|
||||
void selectScale();
|
||||
void selectTrans();
|
||||
void selectAnimeTransform(int);
|
||||
#if TARGET_PC
|
||||
bool pointerMove();
|
||||
bool consumePointerClick();
|
||||
#endif
|
||||
|
||||
void setOffsetX(f32 i_offsetX) { mOffsetX = i_offsetX; }
|
||||
bool isAnimeUpdate(int param_0) { return (field_0x114 & (u8)(1 << param_0)) ? TRUE : FALSE; }
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace dusk {
|
||||
enum class ActionBinds {
|
||||
FIRST_PERSON_CAMERA,
|
||||
CALL_MIDNA,
|
||||
OPEN_MAP_SCREEN,
|
||||
TOGGLE_MINIMAP,
|
||||
OPEN_DUSKLIGHT_MENU,
|
||||
TURBO_SPEED_BUTTON,
|
||||
COUNT,
|
||||
@@ -32,6 +34,12 @@ bool isActionBound(ActionBinds action, u32 port);
|
||||
|
||||
void updateActionBindings();
|
||||
|
||||
void setVirtualActionBind(ActionBinds action, u32 port, bool pressed, bool available = true);
|
||||
|
||||
void clearVirtualActionBind(ActionBinds action, u32 port);
|
||||
|
||||
void clearAllVirtualActionBinds();
|
||||
|
||||
bool getActionBindTrig(ActionBinds action, u32 port);
|
||||
|
||||
bool getActionBindHold(ActionBinds action, u32 port);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "dolphin/types.h"
|
||||
#include <type_traits>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
@@ -139,11 +140,16 @@ concept ConfigValueInteger =
|
||||
|| std::is_same_v<T, s64>
|
||||
|| std::is_same_v<T, u64>;
|
||||
|
||||
template <typename T>
|
||||
struct ConfigValueTraits {
|
||||
static constexpr bool enabled = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Concept that defines the legal set of types that can be used for CVar values.
|
||||
*
|
||||
* Valid types cannot be cv-qualified and must be basic primitive types (int, float, bool),
|
||||
* strings, or enums of the basic primitives.
|
||||
* strings, enums of the basic primitives, or explicitly-enabled structured settings.
|
||||
*/
|
||||
template <typename T>
|
||||
concept ConfigValue =
|
||||
@@ -154,7 +160,8 @@ concept ConfigValue =
|
||||
|| std::is_same_v<T, f32>
|
||||
|| std::is_same_v<T, f64>
|
||||
|| std::is_same_v<T, std::string>
|
||||
|| (std::is_enum_v<T> && ConfigValueInteger<std::underlying_type_t<T>>));
|
||||
|| (std::is_enum_v<T> && ConfigValueInteger<std::underlying_type_t<T>>)
|
||||
|| ConfigValueTraits<T>::enabled);
|
||||
|
||||
template <ConfigValue T>
|
||||
const ConfigImplBase* GetConfigImpl();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "dolphin/types.h"
|
||||
|
||||
class CPaneMgr;
|
||||
|
||||
namespace dusk::menu_pointer {
|
||||
|
||||
enum class Context {
|
||||
None,
|
||||
FileSelect,
|
||||
Save,
|
||||
ItemWheel,
|
||||
Collection,
|
||||
Options,
|
||||
Dialog,
|
||||
};
|
||||
|
||||
enum class Phase {
|
||||
Move,
|
||||
Press,
|
||||
Release,
|
||||
Cancel,
|
||||
};
|
||||
|
||||
struct State {
|
||||
f32 x = 0.0f;
|
||||
f32 y = 0.0f;
|
||||
bool valid = false;
|
||||
bool down = false;
|
||||
bool pressed = false;
|
||||
bool released = false;
|
||||
bool clicked = false;
|
||||
bool touch = false;
|
||||
};
|
||||
|
||||
void begin_game_frame() noexcept;
|
||||
void end_game_frame() noexcept;
|
||||
void begin_context(Context context) noexcept;
|
||||
bool handle_fallthrough_pointer(f32 x, f32 y, Phase phase, bool touch, s32 mouseButton = -1) noexcept;
|
||||
|
||||
bool active() noexcept;
|
||||
bool enabled() noexcept;
|
||||
bool mouse_capture_active() noexcept;
|
||||
const State& state() noexcept;
|
||||
bool consume_click() noexcept;
|
||||
void set_dialog_choice(u8 choice, bool clicked) noexcept;
|
||||
bool get_dialog_choice(u8& choice) noexcept;
|
||||
bool consume_dialog_click(u8& choice) noexcept;
|
||||
void defer_activation(Context context, u8 target) noexcept;
|
||||
bool consume_deferred_activation(Context context, u8 target) noexcept;
|
||||
void clear_deferred_activation(Context context) noexcept;
|
||||
u32 suppressed_pad_buttons(u32 port) noexcept;
|
||||
void finish_pad_suppression_read(u32 port) noexcept;
|
||||
|
||||
bool hit_rect(f32 left, f32 top, f32 right, f32 bottom, f32 padding = 0.0f) noexcept;
|
||||
bool hit_pane(CPaneMgr* pane, f32 padding = 0.0f) noexcept;
|
||||
bool hit_pane(J2DPane* pane, f32 padding = 0.0f) noexcept;
|
||||
|
||||
} // namespace dusk::menu_pointer
|
||||
+15
-16
@@ -1,9 +1,9 @@
|
||||
#ifndef DUSK_CONFIG_H
|
||||
#define DUSK_CONFIG_H
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "dusk/config_var.hpp"
|
||||
#include "dusk/ui/controls.hpp"
|
||||
|
||||
namespace dusk {
|
||||
|
||||
@@ -40,11 +40,6 @@ enum class DiscVerificationState : u8 {
|
||||
HashMismatch,
|
||||
};
|
||||
|
||||
enum class GyroMode : u8 {
|
||||
Sensor = 0,
|
||||
Mouse = 1,
|
||||
};
|
||||
|
||||
enum class FrameInterpMode : u8 {
|
||||
Off = 0,
|
||||
Capped = 1,
|
||||
@@ -96,12 +91,6 @@ struct ConfigEnumRange<DiscVerificationState> {
|
||||
static constexpr auto max = DiscVerificationState::HashMismatch;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ConfigEnumRange<GyroMode> {
|
||||
static constexpr auto min = GyroMode::Sensor;
|
||||
static constexpr auto max = GyroMode::Mouse;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ConfigEnumRange<FrameInterpMode> {
|
||||
static constexpr auto min = FrameInterpMode::Off;
|
||||
@@ -119,6 +108,11 @@ struct ConfigEnumRange<MagicArmorMode> {
|
||||
static constexpr auto min = MagicArmorMode::NORMAL;
|
||||
static constexpr auto max = MagicArmorMode::COSMETIC;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ConfigValueTraits<ui::ControlLayout> {
|
||||
static constexpr bool enabled = true;
|
||||
};
|
||||
} // namespace config
|
||||
|
||||
// Persistent user settings
|
||||
@@ -221,6 +215,9 @@ struct UserSettings {
|
||||
ConfigVar<float> mouseCameraSensitivity;
|
||||
ConfigVar<bool> invertMouseY;
|
||||
ConfigVar<bool> freeCamera;
|
||||
ConfigVar<bool> enableTouchControls;
|
||||
ConfigVar<bool> enableMenuPointer;
|
||||
ConfigVar<ui::ControlLayout> touchControlsLayout;
|
||||
ConfigVar<bool> invertCameraXAxis;
|
||||
ConfigVar<bool> invertCameraYAxis;
|
||||
ConfigVar<bool> invertFirstPersonXAxis;
|
||||
@@ -229,6 +226,8 @@ struct UserSettings {
|
||||
ConfigVar<bool> invertAirSwimY;
|
||||
ConfigVar<float> freeCameraXSensitivity;
|
||||
ConfigVar<float> freeCameraYSensitivity;
|
||||
ConfigVar<float> touchCameraXSensitivity;
|
||||
ConfigVar<float> touchCameraYSensitivity;
|
||||
ConfigVar<bool> debugFlyCam;
|
||||
ConfigVar<bool> debugFlyCamLockEvents;
|
||||
ConfigVar<bool> allowBackgroundInput;
|
||||
@@ -287,6 +286,8 @@ struct UserSettings {
|
||||
struct {
|
||||
std::array<ActionBindConfigVar, 4> firstPersonCamera;
|
||||
std::array<ActionBindConfigVar, 4> callMidna;
|
||||
std::array<ActionBindConfigVar, 4> openMapScreen;
|
||||
std::array<ActionBindConfigVar, 4> toggleMinimap;
|
||||
std::array<ActionBindConfigVar, 4> openDusklightMenu;
|
||||
std::array<ActionBindConfigVar, 4> turboSpeedButton;
|
||||
} actionBindings;
|
||||
@@ -318,6 +319,4 @@ struct TransientSettings {
|
||||
|
||||
TransientSettings& getTransientSettings();
|
||||
|
||||
}
|
||||
|
||||
#endif // DUSK_CONFIG_H
|
||||
} // namespace dusk
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace dusk::touch_camera {
|
||||
|
||||
constexpr float YAW_DEGREES_PER_DP = 0.34f;
|
||||
constexpr float PITCH_DEGREES_PER_DP = 0.22f;
|
||||
|
||||
void add_delta(float yaw_dp, float pitch_dp) noexcept;
|
||||
bool consume_delta(float& yaw_dp, float& pitch_dp) noexcept;
|
||||
void clear() noexcept;
|
||||
|
||||
} // namespace dusk::touch_camera
|
||||
@@ -212,6 +212,9 @@ public:
|
||||
void setCornerColor(JUtility::TColor c0) {
|
||||
setCornerColor(c0, c0, c0, c0);
|
||||
}
|
||||
#if TARGET_PC
|
||||
JUtility::TColor corner(size_t index) const { return mCornerColor[index]; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/* 0x100 */ JUTTexture* mTexture[2];
|
||||
|
||||
@@ -16,7 +16,7 @@ body {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-family: "Fira Sans Condensed";
|
||||
font-weight: bold;
|
||||
color: rgba(248, 244, 232, 90%);
|
||||
z-index: 1;
|
||||
filter: opacity(0);
|
||||
transition: filter 0.2s linear-in-out;
|
||||
}
|
||||
|
||||
body[open] {
|
||||
filter: opacity(1);
|
||||
}
|
||||
|
||||
body:not([open]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
decorator: none;
|
||||
padding: 0;
|
||||
border: 1dp rgba(255, 255, 255, 22%);
|
||||
background-color: rgba(22, 24, 28, 48%);
|
||||
color: rgba(248, 244, 232, 90%);
|
||||
text-align: center;
|
||||
/* backdrop-filter: blur(7dp); */
|
||||
/* box-shadow: 0 6dp 18dp rgba(0, 0, 0, 28%); */
|
||||
transform-origin: center;
|
||||
transition: background-color border-color filter transform 0.08s linear-in-out,
|
||||
opacity 0.2s linear-in-out;
|
||||
}
|
||||
|
||||
button.pressed,
|
||||
button.active {
|
||||
background-color: rgba(63, 78, 90, 68%);
|
||||
border-color: rgba(255, 255, 255, 48%);
|
||||
filter: brightness(1.18);
|
||||
}
|
||||
|
||||
button:hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button span {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
button icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
button icon glyph {
|
||||
display: block;
|
||||
font-family: "Material Symbols Rounded";
|
||||
font-weight: normal;
|
||||
font-size: 24dp;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.midna-icon,
|
||||
.item-icon,
|
||||
.item-count,
|
||||
.oil-meter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.midna-icon.visible,
|
||||
.item-icon.visible,
|
||||
.item-count.visible,
|
||||
.oil-meter.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.control {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.trigger-l.active {
|
||||
background-color: rgba(57, 116, 133, 74%);
|
||||
border-color: rgba(128, 222, 234, 72%);
|
||||
}
|
||||
|
||||
.trigger,
|
||||
.skip {
|
||||
border-radius: 23dp;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
font-size: 22dp;
|
||||
}
|
||||
|
||||
.button-z {
|
||||
background-color: rgba(118, 79, 158, 58%);
|
||||
border-color: rgba(203, 170, 255, 36%);
|
||||
}
|
||||
|
||||
.midna-icon {
|
||||
position: absolute;
|
||||
left: 9dp;
|
||||
top: -1dp;
|
||||
height: 48dp;
|
||||
}
|
||||
|
||||
.button-z.has-icon span,
|
||||
.face.has-item span {
|
||||
position: absolute;
|
||||
font-size: 13dp;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.button-z.has-icon span {
|
||||
right: 9dp;
|
||||
bottom: 7dp;
|
||||
}
|
||||
|
||||
.button-z.pressed {
|
||||
background-color: rgba(139, 91, 187, 82%);
|
||||
border-color: rgba(220, 194, 255, 70%);
|
||||
}
|
||||
|
||||
action-bar {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: stretch;
|
||||
border: 1dp rgba(255, 255, 255, 22%);
|
||||
border-radius: 23dp;
|
||||
background-color: rgba(22, 24, 28, 48%);
|
||||
/* backdrop-filter: blur(7dp); */
|
||||
/* box-shadow: 0 -6dp 18dp rgba(0, 0, 0, 28%); */
|
||||
overflow: hidden;
|
||||
opacity: 1;
|
||||
transform-origin: center;
|
||||
transition: opacity 0.2s linear-in-out;
|
||||
}
|
||||
|
||||
action-bar:hidden,
|
||||
action-bar:hidden button,
|
||||
action-bar:hidden separator {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.utility {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
width: 56dp;
|
||||
height: 44dp;
|
||||
margin: 0;
|
||||
border-width: 0dp;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.utility,
|
||||
.skip {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.utility.pressed {
|
||||
background-color: rgba(63, 78, 90, 68%);
|
||||
}
|
||||
|
||||
.utility.pressed,
|
||||
.skip.pressed {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.skip {
|
||||
z-index: 1;
|
||||
border-color: rgba(255, 255, 255, 36%);
|
||||
}
|
||||
|
||||
separator {
|
||||
display: block;
|
||||
flex: 0 0 1dp;
|
||||
width: 1dp;
|
||||
height: 24dp;
|
||||
background-color: rgba(255, 255, 255, 18%);
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s linear-in-out;
|
||||
}
|
||||
|
||||
.face {
|
||||
position: absolute;
|
||||
border-radius: 29dp;
|
||||
font-size: 24dp;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 76%;
|
||||
max-height: 76%;
|
||||
}
|
||||
|
||||
.item-count {
|
||||
position: absolute;
|
||||
left: 6dp;
|
||||
bottom: 5dp;
|
||||
min-width: 17dp;
|
||||
height: 15dp;
|
||||
padding: 1dp 3dp;
|
||||
border-radius: 7dp;
|
||||
background-color: rgba(0, 0, 0, 52%);
|
||||
color: rgba(255, 255, 255, 92%);
|
||||
font-size: 12dp;
|
||||
line-height: 13dp;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.oil-meter {
|
||||
position: absolute;
|
||||
left: 12dp;
|
||||
bottom: -5dp;
|
||||
width: 34dp;
|
||||
height: 8dp;
|
||||
padding: 2dp;
|
||||
border: 1dp rgba(42, 32, 18, 82%);
|
||||
border-radius: 4dp;
|
||||
background-color: rgba(18, 14, 10, 70%);
|
||||
/* box-shadow: 0 2dp 6dp rgba(0, 0, 0, 35%); */
|
||||
}
|
||||
|
||||
oil-fill {
|
||||
display: block;
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
border-radius: 2dp;
|
||||
background-color: rgb(255, 232, 74);
|
||||
}
|
||||
|
||||
.face.has-item span {
|
||||
right: 6dp;
|
||||
bottom: 6dp;
|
||||
color: rgba(255, 255, 255, 88%);
|
||||
}
|
||||
|
||||
.face.a {
|
||||
border-radius: 37dp;
|
||||
font-size: 31dp;
|
||||
background-color: rgba(34, 112, 123, 62%);
|
||||
}
|
||||
|
||||
.face.b {
|
||||
background-color: rgba(161, 61, 66, 58%);
|
||||
}
|
||||
|
||||
.face.x {
|
||||
background-color: rgba(83, 115, 151, 56%);
|
||||
}
|
||||
|
||||
.face.y {
|
||||
background-color: rgba(113, 91, 150, 54%);
|
||||
}
|
||||
|
||||
button.control.docked-top,
|
||||
action-bar.docked-top {
|
||||
border-top-width: 0dp;
|
||||
border-top-left-radius: 0dp;
|
||||
border-top-right-radius: 0dp;
|
||||
}
|
||||
|
||||
button.control.docked-bottom,
|
||||
action-bar.docked-bottom {
|
||||
border-bottom-width: 0dp;
|
||||
border-bottom-left-radius: 0dp;
|
||||
border-bottom-right-radius: 0dp;
|
||||
}
|
||||
|
||||
button.control.docked-left,
|
||||
action-bar.docked-left {
|
||||
border-left-width: 0dp;
|
||||
border-top-left-radius: 0dp;
|
||||
border-bottom-left-radius: 0dp;
|
||||
}
|
||||
|
||||
button.control.docked-right,
|
||||
action-bar.docked-right {
|
||||
border-right-width: 0dp;
|
||||
border-top-right-radius: 0dp;
|
||||
border-bottom-right-radius: 0dp;
|
||||
}
|
||||
|
||||
touch-stick {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 124dp;
|
||||
height: 124dp;
|
||||
border-radius: 62dp;
|
||||
background-color: rgba(18, 20, 24, 35%);
|
||||
border: 1dp rgba(255, 255, 255, 20%);
|
||||
/* backdrop-filter: blur(7dp); */
|
||||
/* box-shadow: 0 8dp 24dp rgba(0, 0, 0, 24%); */
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.18s linear-in-out;
|
||||
}
|
||||
|
||||
touch-stick.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
stick-ring {
|
||||
position: absolute;
|
||||
left: 18dp;
|
||||
top: 18dp;
|
||||
width: 88dp;
|
||||
height: 88dp;
|
||||
border-radius: 44dp;
|
||||
border: 1dp rgba(255, 255, 255, 18%);
|
||||
}
|
||||
|
||||
stick-knob {
|
||||
position: absolute;
|
||||
width: 48dp;
|
||||
height: 48dp;
|
||||
border-radius: 24dp;
|
||||
background-color: rgba(238, 236, 226, 55%);
|
||||
border: 1dp rgba(255, 255, 255, 45%);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
body.touch-editor {
|
||||
background-color: rgba(4, 6, 8, 34%);
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
body.touch-editor .control,
|
||||
body.touch-editor action-bar {
|
||||
opacity: 0.88;
|
||||
cursor: move;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
body.touch-editor .control:hover,
|
||||
body.touch-editor action-bar:hover,
|
||||
body.touch-editor .control.editor-selected,
|
||||
body.touch-editor action-bar.editor-selected {
|
||||
border-color: rgba(255, 232, 128, 80%);
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
|
||||
body.touch-editor action-bar button,
|
||||
body.touch-editor action-bar separator {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
selection-frame {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
border: 2dp rgba(255, 232, 128, 88%);
|
||||
background-color: rgba(255, 232, 128, 7%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
selection-frame.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
resize-handle {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 22dp;
|
||||
height: 22dp;
|
||||
border: 2dp rgba(255, 244, 190, 96%);
|
||||
border-radius: 11dp;
|
||||
background-color: rgba(34, 37, 42, 86%);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
resize-handle.left {
|
||||
left: -12dp;
|
||||
}
|
||||
|
||||
resize-handle.right {
|
||||
right: -12dp;
|
||||
}
|
||||
|
||||
resize-handle.top {
|
||||
top: -12dp;
|
||||
}
|
||||
|
||||
resize-handle.bottom {
|
||||
bottom: -12dp;
|
||||
}
|
||||
|
||||
resize-handle.horizontal {
|
||||
top: 50%;
|
||||
margin-top: -11dp;
|
||||
}
|
||||
|
||||
resize-handle.vertical {
|
||||
left: 50%;
|
||||
margin-left: -11dp;
|
||||
}
|
||||
|
||||
resize-handle.corner.left {
|
||||
left: -12dp;
|
||||
}
|
||||
|
||||
resize-handle.corner.right {
|
||||
right: -12dp;
|
||||
}
|
||||
|
||||
resize-handle.corner.top {
|
||||
top: -12dp;
|
||||
}
|
||||
|
||||
resize-handle.corner.bottom {
|
||||
bottom: -12dp;
|
||||
}
|
||||
|
||||
editor-toolbar {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
left: 24dp;
|
||||
right: 24dp;
|
||||
top: 50%;
|
||||
z-index: 30;
|
||||
height: 48dp;
|
||||
margin-top: -24dp;
|
||||
gap: 8dp;
|
||||
justify-content: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
editor-toolbar button.editor-command {
|
||||
flex: 0 1 150dp;
|
||||
min-width: 96dp;
|
||||
height: 48dp;
|
||||
padding: 0 14dp;
|
||||
border-radius: 8dp;
|
||||
border: 1dp rgba(255, 255, 255, 26%);
|
||||
background-color: rgba(17, 19, 24, 88%);
|
||||
color: rgba(255, 250, 232, 94%);
|
||||
font-family: "Fira Sans";
|
||||
font-size: 18dp;
|
||||
line-height: 48dp;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
editor-toolbar button.editor-command span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
line-height: 48dp;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
editor-toolbar button.editor-command.primary {
|
||||
border-color: rgba(255, 232, 128, 70%);
|
||||
background-color: rgba(96, 82, 38, 90%);
|
||||
}
|
||||
|
||||
editor-toolbar button.editor-command:hover,
|
||||
editor-toolbar button.editor-command:focus-visible {
|
||||
border-color: rgba(255, 244, 190, 92%);
|
||||
background-color: rgba(78, 85, 96, 92%);
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/gyro.h"
|
||||
#include "dusk/mouse.h"
|
||||
#include "dusk/touch_camera.h"
|
||||
#endif
|
||||
|
||||
bool daAlink_c::checkNoSubjectModeCamera() {
|
||||
@@ -172,6 +173,32 @@ BOOL daAlink_c::setBodyAngleToCamera() {
|
||||
sp8 = mBodyAngle.x;
|
||||
}
|
||||
}
|
||||
|
||||
if (dusk::getSettings().game.enableTouchControls && checkAimContext()) {
|
||||
f32 touchYawDp = 0.0f;
|
||||
f32 touchPitchDp = 0.0f;
|
||||
if (dusk::touch_camera::consume_delta(touchYawDp, touchPitchDp)) {
|
||||
f32 scale = 1.0f;
|
||||
if (checkWolfEyeUp()) {
|
||||
scale *= 0.6f;
|
||||
}
|
||||
if (dComIfGp_checkPlayerStatus0(0, 0x200000)) {
|
||||
scale /= dComIfGp_getCameraZoomScale(field_0x317c);
|
||||
}
|
||||
|
||||
const f32 yawDeg = -touchYawDp * dusk::touch_camera::YAW_DEGREES_PER_DP * scale *
|
||||
dusk::getSettings().game.touchCameraXSensitivity;
|
||||
const f32 pitchDeg = touchPitchDp * dusk::touch_camera::PITCH_DEGREES_PER_DP *
|
||||
scale * dusk::getSettings().game.touchCameraYSensitivity;
|
||||
|
||||
shape_angle.y = shape_angle.y + cM_deg2s(yawDeg);
|
||||
sp8 = sp8 + cM_deg2s(pitchDeg);
|
||||
|
||||
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
|
||||
sp8 = mBodyAngle.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (checkNotItemSinkLimit() && sp8 > 0) {
|
||||
|
||||
+42
-7
@@ -34,6 +34,7 @@
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/mouse.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/touch_camera.h"
|
||||
#include "imgui.h"
|
||||
#endif
|
||||
|
||||
@@ -7499,6 +7500,15 @@ static constexpr s16 FLYCAM_ROLL_SPEED = 256;
|
||||
static ImVec2 sFlyCamLastMousePos = {-1.f, -1.f};
|
||||
|
||||
#if TARGET_PC
|
||||
static constexpr f32 TOUCH_CAMERA_CSTICK_EXIT_THRESHOLD = 0.05f;
|
||||
static bool sTouchFreeCameraActive = false;
|
||||
|
||||
bool dCamera_c::isAimActive() {
|
||||
auto* link = daAlink_getAlinkActorClass();
|
||||
return link != nullptr && link->checkAimContext() &&
|
||||
dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10);
|
||||
}
|
||||
|
||||
bool dCamera_c::executeDebugFlyCam() {
|
||||
if (!dusk::getSettings().game.debugFlyCam) {
|
||||
if (mDebugFlyCam.initialized) {
|
||||
@@ -7640,16 +7650,30 @@ void dCamera_c::deactivateDebugFlyCam() {
|
||||
mDebugFlyCam.initialized = false;
|
||||
}
|
||||
|
||||
bool dCamera_c::canUseFreeCam() {
|
||||
return dusk::getSettings().game.freeCamera || dusk::getSettings().game.enableMouseCamera;
|
||||
}
|
||||
|
||||
bool dCamera_c::freeCamera() {
|
||||
if (canUseFreeCam() && mGear == 1) {
|
||||
f32 touchYawDp = 0.0f;
|
||||
f32 touchPitchDp = 0.0f;
|
||||
bool touchCameraMoved = false;
|
||||
const bool touchControlsEnabled = dusk::getSettings().game.enableTouchControls;
|
||||
if (touchControlsEnabled && !isAimActive()) {
|
||||
touchCameraMoved = dusk::touch_camera::consume_delta(touchYawDp, touchPitchDp);
|
||||
}
|
||||
if (!touchControlsEnabled ||
|
||||
mPadInfo.mCStick.mLastValue > TOUCH_CAMERA_CSTICK_EXIT_THRESHOLD)
|
||||
{
|
||||
sTouchFreeCameraActive = false;
|
||||
}
|
||||
if (touchCameraMoved) {
|
||||
sTouchFreeCameraActive = true;
|
||||
}
|
||||
|
||||
const bool useFreeCamera = dusk::getSettings().game.freeCamera ||
|
||||
dusk::getSettings().game.enableMouseCamera || sTouchFreeCameraActive;
|
||||
if (useFreeCamera && mGear == 1) {
|
||||
mGear = 0;
|
||||
}
|
||||
|
||||
if (!canUseFreeCam() || mCamStyle == 70)
|
||||
if (!useFreeCamera || mCamStyle == 70)
|
||||
{
|
||||
mCamParam.mManualMode = 0;
|
||||
return false;
|
||||
@@ -7660,6 +7684,17 @@ bool dCamera_c::freeCamera() {
|
||||
mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree();
|
||||
}
|
||||
|
||||
if (touchCameraMoved) {
|
||||
mCamParam.mManualMode = 1;
|
||||
const f32 yawInput = dusk::getSettings().game.invertCameraXAxis ? -touchYawDp : touchYawDp;
|
||||
const f32 pitchInput =
|
||||
touchPitchDp * (dusk::getSettings().game.invertCameraYAxis ? -1.0f : 1.0f);
|
||||
mCamParam.freeXAngle += yawInput * dusk::getSettings().game.touchCameraXSensitivity *
|
||||
dusk::touch_camera::YAW_DEGREES_PER_DP;
|
||||
mCamParam.freeYAngle += pitchInput * dusk::getSettings().game.touchCameraYSensitivity *
|
||||
dusk::touch_camera::PITCH_DEGREES_PER_DP;
|
||||
}
|
||||
|
||||
cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f};
|
||||
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
|
||||
|
||||
@@ -11359,7 +11394,7 @@ static int camera_execute(camera_process_class* i_this) {
|
||||
const auto target = get_target_trim_height(i_this);
|
||||
const auto step = dusk::frame_interp::get_interpolation_step();
|
||||
const auto cur = camera->TrimHeight();
|
||||
const auto prev = (4.0f * cur - target) / 3.0f;
|
||||
const auto prev = (4.0f * cur - target) / 3.0f;
|
||||
const auto trim_height = prev + (cur - prev) * step;
|
||||
|
||||
widezoom_correction(i_this, trim_height);
|
||||
|
||||
@@ -23,8 +23,22 @@
|
||||
#include "m_Do/m_Do_graphic.h"
|
||||
#include <cstring>
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/string.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr u8 pointer_target(u8 group, u8 index) noexcept {
|
||||
return static_cast<u8>((group << 4) | (index & 0x0F));
|
||||
}
|
||||
|
||||
constexpr u8 s_pointerDataSelectTarget = 0;
|
||||
constexpr u8 s_pointerMenuSelectTarget = 1;
|
||||
constexpr u8 s_pointerCopySelectTarget = 2;
|
||||
constexpr u8 s_pointerYesNoSelectTarget = 3;
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
static s32 SelStartFrameTbl[3] = {
|
||||
59,
|
||||
99,
|
||||
@@ -756,8 +770,143 @@ void dFile_select_c::dataSelectInit() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
bool dFile_select_c::pointerDataSelect() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
|
||||
for (u8 i = 0; i < 3; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mSelFilePanes[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
const bool clicked = dusk::menu_pointer::consume_click();
|
||||
if (mSelectNum != i) {
|
||||
mDoAud_seStart(Z2SE_FILE_SELECT_CURSOR, NULL, 0, 0);
|
||||
mLastSelectNum = mSelectNum;
|
||||
mSelectNum = i;
|
||||
if (clicked) {
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerDataSelectTarget, i));
|
||||
}
|
||||
dataSelectAnmSet();
|
||||
mDataSelProc = DATASELPROC_DATA_SELECT_MOVE_ANIME;
|
||||
return true;
|
||||
}
|
||||
if (clicked) {
|
||||
dataSelectStart();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dFile_select_c::pointerMenuSelect() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
|
||||
for (u8 i = 0; i < 3; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(m3mSelPane[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
const bool clicked = dusk::menu_pointer::consume_click();
|
||||
if (!mIsDataNew[mSelectNum] && mSelectMenuNum != i) {
|
||||
mDoAud_seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0);
|
||||
mLastSelectMenuNum = mSelectMenuNum;
|
||||
mSelectMenuNum = i;
|
||||
if (clicked) {
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerMenuSelectTarget, i));
|
||||
}
|
||||
menuSelectAnmSet();
|
||||
mDataSelProc = DATASELPROC_MENU_SELECT_MOVE_ANM;
|
||||
return true;
|
||||
}
|
||||
if (clicked) {
|
||||
menuSelectStart();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dFile_select_c::pointerCopyDataToSelect() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
|
||||
for (u8 i = 0; i < 2; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mCpSelPane[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
const bool clicked = dusk::menu_pointer::consume_click();
|
||||
if (field_0x026b != i) {
|
||||
mDoAud_seStart(Z2SE_FILE_SELECT_CURSOR, NULL, 0, 0);
|
||||
field_0x026c = field_0x026b;
|
||||
field_0x026b = i;
|
||||
if (clicked) {
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerCopySelectTarget, i));
|
||||
}
|
||||
copyDataToSelectMoveAnmSet();
|
||||
mDataSelProc = DATASELPROC_COPY_DATA_TO_SELECT_MOVE_ANM;
|
||||
return true;
|
||||
}
|
||||
if (clicked) {
|
||||
copyDataToSelectStart();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dFile_select_c::pointerYesNoSelect(bool errorSelect) {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
|
||||
for (u8 i = 0; i < 2; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mYnSelPane[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
const bool clicked =
|
||||
(!errorSelect || field_0x0268 == i) && dusk::menu_pointer::consume_click();
|
||||
if (field_0x0268 != i) {
|
||||
field_0x0269 = field_0x0268;
|
||||
field_0x0268 = i;
|
||||
if (errorSelect) {
|
||||
errCurMove(0);
|
||||
return false;
|
||||
} else {
|
||||
mDoAud_seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0);
|
||||
if (clicked) {
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerYesNoSelectTarget, i));
|
||||
}
|
||||
yesnoSelectAnmSet();
|
||||
mDataSelProc = DATASELPROC_YES_NO_CURSOR_MOVE_ANM;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (clicked) {
|
||||
if (errorSelect) {
|
||||
if (field_0x0268 != 0) {
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_OK, 0, 0, 0);
|
||||
} else {
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_CANCEL, 0, 0, 0);
|
||||
}
|
||||
mSelIcon->setAlphaRate(0.0f);
|
||||
} else {
|
||||
yesNoSelectStart();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// handles switching between quest logs
|
||||
void dFile_select_c::dataSelect() {
|
||||
#if TARGET_PC
|
||||
if (pointerDataSelect()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
stick->checkTrigger();
|
||||
|
||||
// If A or Start was pressed
|
||||
@@ -801,6 +950,9 @@ static u16 msgTbl[3] = {
|
||||
};
|
||||
|
||||
void dFile_select_c::dataSelectStart() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::FileSelect);
|
||||
#endif
|
||||
mSelIcon->setAlphaRate(0.0f);
|
||||
|
||||
if (mIsNoData[mSelectNum]) {
|
||||
@@ -949,6 +1101,16 @@ void dFile_select_c::dataSelectAnmSet() {
|
||||
}
|
||||
|
||||
void dFile_select_c::dataSelectMoveAnime() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
|
||||
if (mSelectNum != 0xFF && dusk::menu_pointer::hit_pane(mSelFilePanes[mSelectNum], 8.0f) &&
|
||||
dusk::menu_pointer::consume_click())
|
||||
{
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerDataSelectTarget, mSelectNum));
|
||||
}
|
||||
#endif
|
||||
bool iVar7 = true;
|
||||
bool iVar6 = true;
|
||||
bool bVar1 = true;
|
||||
@@ -997,6 +1159,14 @@ void dFile_select_c::dataSelectMoveAnime() {
|
||||
mSelFilePanes[mLastSelectNum]->getPanePtr()->setAnimation((J2DAnmTransform*)NULL);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::menu_pointer::consume_deferred_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerDataSelectTarget, mSelectNum))) {
|
||||
dataSelectStart();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mDataSelProc = DATASELPROC_DATA_SELECT;
|
||||
}
|
||||
}
|
||||
@@ -1161,6 +1331,12 @@ void dFile_select_c::selectDataOpenEraseMove() {
|
||||
|
||||
// Handles selecting between copy / start / delete menus in quest log
|
||||
void dFile_select_c::menuSelect() {
|
||||
#if TARGET_PC
|
||||
if (pointerMenuSelect()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
stick->checkTrigger();
|
||||
|
||||
// if a was pressed, do the menu selection process
|
||||
@@ -1191,6 +1367,9 @@ void dFile_select_c::menuSelect() {
|
||||
|
||||
// Handles copy / start / delete actions depending on which menu is selected from menuSelect
|
||||
void dFile_select_c::menuSelectStart() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::FileSelect);
|
||||
#endif
|
||||
#if TARGET_PC
|
||||
if (!dusk::getSettings().game.hideTvSettingsScreen || mSelectMenuNum != 1) {
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
|
||||
@@ -1312,6 +1491,17 @@ void dFile_select_c::menuSelectAnmSet() {
|
||||
}
|
||||
|
||||
void dFile_select_c::menuSelectMoveAnm() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
|
||||
if (mSelectMenuNum != 0xFF &&
|
||||
dusk::menu_pointer::hit_pane(m3mSelPane[mSelectMenuNum], 8.0f) &&
|
||||
dusk::menu_pointer::consume_click())
|
||||
{
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerMenuSelectTarget, mSelectMenuNum));
|
||||
}
|
||||
#endif
|
||||
bool tmp1 = true;
|
||||
|
||||
if (mSelectMenuNum != 0xFF &&
|
||||
@@ -1369,6 +1559,14 @@ void dFile_select_c::menuSelectMoveAnm() {
|
||||
m3mSelPane[mLastSelectMenuNum]->getPanePtr()->setAnimation((J2DAnmTransform*)NULL);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::menu_pointer::consume_deferred_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerMenuSelectTarget, mSelectMenuNum))) {
|
||||
menuSelectStart();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mDataSelProc = DATASELPROC_MENU_SELECT;
|
||||
}
|
||||
}
|
||||
@@ -1698,6 +1896,12 @@ void dFile_select_c::setSaveDataForCopySel() {
|
||||
}
|
||||
|
||||
void dFile_select_c::copyDataToSelect() {
|
||||
#if TARGET_PC
|
||||
if (pointerCopyDataToSelect()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
stick->checkTrigger();
|
||||
|
||||
if (mDoCPd_c::getTrigA(PAD_1)) {
|
||||
@@ -1722,6 +1926,9 @@ void dFile_select_c::copyDataToSelect() {
|
||||
}
|
||||
|
||||
void dFile_select_c::copyDataToSelectStart() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::FileSelect);
|
||||
#endif
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
|
||||
|
||||
mCpDataToNum = getCptoNum(field_0x026b);
|
||||
@@ -1787,6 +1994,17 @@ void dFile_select_c::copyDataToSelectMoveAnmSet() {
|
||||
}
|
||||
|
||||
void dFile_select_c::copyDataToSelectMoveAnm() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
|
||||
if (field_0x026b != 0xFF &&
|
||||
dusk::menu_pointer::hit_pane(mCpSelPane[field_0x026b], 8.0f) &&
|
||||
dusk::menu_pointer::consume_click())
|
||||
{
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerCopySelectTarget, field_0x026b));
|
||||
}
|
||||
#endif
|
||||
bool iVar7 = true;
|
||||
bool iVar6 = true;
|
||||
bool bVar1 = true;
|
||||
@@ -1836,6 +2054,14 @@ void dFile_select_c::copyDataToSelectMoveAnm() {
|
||||
mSelIcon2->setAlphaRate(1.0f);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::menu_pointer::consume_deferred_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerCopySelectTarget, field_0x026b))) {
|
||||
copyDataToSelectStart();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mDataSelProc = DATASELPROC_COPY_DATA_TO_SELECT;
|
||||
}
|
||||
}
|
||||
@@ -2105,6 +2331,12 @@ void dFile_select_c::yesnoCursorShow() {
|
||||
}
|
||||
|
||||
void dFile_select_c::YesNoSelect() {
|
||||
#if TARGET_PC
|
||||
if (pointerYesNoSelect(false)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
stick->checkTrigger();
|
||||
|
||||
if (mDoCPd_c::getTrigA(PAD_1)) {
|
||||
@@ -2129,6 +2361,9 @@ void dFile_select_c::YesNoSelect() {
|
||||
}
|
||||
|
||||
void dFile_select_c::yesNoSelectStart() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::FileSelect);
|
||||
#endif
|
||||
if (field_0x0268 != 0) {
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
|
||||
field_0x03b1 = 1;
|
||||
@@ -2284,10 +2519,29 @@ void dFile_select_c::YesNoCancelMove() {
|
||||
}
|
||||
|
||||
void dFile_select_c::yesNoCursorMoveAnm() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::FileSelect);
|
||||
if (field_0x0268 != 0xFF &&
|
||||
dusk::menu_pointer::hit_pane(mYnSelPane[field_0x0268], 8.0f) &&
|
||||
dusk::menu_pointer::consume_click())
|
||||
{
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerYesNoSelectTarget, field_0x0268));
|
||||
}
|
||||
#endif
|
||||
bool isYnSelMove = yesnoSelectMoveAnm();
|
||||
bool isYnWakuAlpha = yesnoWakuAlpahAnm(field_0x0269);
|
||||
if (isYnSelMove == true && isYnWakuAlpha == true) {
|
||||
yesnoCursorShow();
|
||||
#if TARGET_PC
|
||||
if (dusk::menu_pointer::consume_deferred_activation(
|
||||
dusk::menu_pointer::Context::FileSelect,
|
||||
pointer_target(s_pointerYesNoSelectTarget, field_0x0268))) {
|
||||
yesNoSelectStart();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mDataSelProc = DATASELPROC_YES_NO_SELECT;
|
||||
}
|
||||
}
|
||||
@@ -5238,6 +5492,12 @@ void dFile_select_c::MemCardMsgWindowClose() {
|
||||
|
||||
bool dFile_select_c::errYesNoSelect() {
|
||||
bool rv = false;
|
||||
#if TARGET_PC
|
||||
if (pointerYesNoSelect(true)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
stick->checkTrigger();
|
||||
|
||||
if (mDoCPd_c::getTrigA(PAD_1)) {
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
#include "d/d_menu_window.h"
|
||||
#include "JSystem/J3DGraphBase/J3DMaterial.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#endif
|
||||
|
||||
typedef void (dMenu_Collect2D_c::*initFunc)();
|
||||
static DUSK_CONSTEXPR initFunc init[] = {
|
||||
&dMenu_Collect2D_c::wait_init, &dMenu_Collect2D_c::save_open_init,
|
||||
@@ -1786,6 +1790,12 @@ void dMenu_Collect2D_c::wait_init() {
|
||||
}
|
||||
|
||||
void dMenu_Collect2D_c::wait_proc() {
|
||||
#if TARGET_PC
|
||||
if (pointerWait()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dMw_A_TRIGGER()) {
|
||||
if (mCursorX == 0 && mCursorY == 5) {
|
||||
if (mDoGph_gInf_c::getFader()->mStatus == 1) {
|
||||
@@ -1887,6 +1897,87 @@ void dMenu_Collect2D_c::wait_proc() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
void dMenu_Collect2D_c::pointerActivateCurrent() {
|
||||
if (mCursorX == 0 && mCursorY == 5) {
|
||||
if (mDoGph_gInf_c::getFader()->mStatus == 1) {
|
||||
mSubWindowOpenCheck = 1;
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_MENU_CHANGE_WINDOW, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_set2DVibrationM();
|
||||
}
|
||||
} else if (mCursorX == 1 && mCursorY == 5) {
|
||||
if (mDoGph_gInf_c::getFader()->mStatus == 1) {
|
||||
mSubWindowOpenCheck = 2;
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_MENU_CHANGE_WINDOW, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_set2DVibrationM();
|
||||
}
|
||||
} else if (mCursorX == 3 && mCursorY == 4) {
|
||||
if (field_0x22d[3][4] != 0 && mDoGph_gInf_c::getFader()->mStatus == 1) {
|
||||
mSubWindowOpenCheck = 3;
|
||||
dMeter2Info_set2DVibration();
|
||||
}
|
||||
} else if (mCursorX == 2 && mCursorY == 4) {
|
||||
if (isFishIconVisible() && mDoGph_gInf_c::getFader()->mStatus == 1) {
|
||||
mSubWindowOpenCheck = 4;
|
||||
dMeter2Info_set2DVibration();
|
||||
}
|
||||
} else if (mCursorX == 3 && mCursorY == 3) {
|
||||
if (isSkillIconVisible() && mDoGph_gInf_c::getFader()->mStatus == 1) {
|
||||
mSubWindowOpenCheck = 5;
|
||||
dMeter2Info_set2DVibration();
|
||||
}
|
||||
} else if (mCursorX == 2 && mCursorY == 3) {
|
||||
if (isInsectIconVisible() && mDoGph_gInf_c::getFader()->mStatus == 1) {
|
||||
mSubWindowOpenCheck = 6;
|
||||
dMeter2Info_set2DVibration();
|
||||
}
|
||||
} else if (field_0x22d[mCursorX][mCursorY] != 0 && !mIsWolf) {
|
||||
if ((mCursorX >= 3 && mCursorX <= 4) || (mCursorX == 5 && mCursorY == 2)) {
|
||||
u8 cursorY = mCursorY;
|
||||
if (cursorY == 0) {
|
||||
if (daPy_getPlayerActorClass()->getSwordChangeWaitTimer() == 0) {
|
||||
changeSword();
|
||||
}
|
||||
} else if (cursorY == 1) {
|
||||
if (daPy_getPlayerActorClass()->getShieldChangeWaitTimer() == 0) {
|
||||
changeShield();
|
||||
}
|
||||
} else if (cursorY == 2 &&
|
||||
daPy_getPlayerActorClass()->getClothesChangeWaitTimer() == 0)
|
||||
{
|
||||
changeClothe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool dMenu_Collect2D_c::pointerWait() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Collection);
|
||||
for (u8 y = 0; y < 6; ++y) {
|
||||
for (u8 x = 0; x < 7; ++x) {
|
||||
if (getItemTag(x, y, true) == 0 || !dusk::menu_pointer::hit_pane(mpSelPm[x][y], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
if (mCursorX != x || mCursorY != y) {
|
||||
mDoAud_seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0);
|
||||
mCursorX = x;
|
||||
mCursorY = y;
|
||||
cursorPosSet();
|
||||
setItemNameString(mCursorX, mCursorY);
|
||||
}
|
||||
if (dusk::menu_pointer::consume_click()) {
|
||||
pointerActivateCurrent();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void dMenu_Collect2D_c::save_open_init() {
|
||||
JKRHeap* heap = mDoExt_setCurrentHeap(mpSubHeap);
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
#include "d/d_msg_scrn_explain.h"
|
||||
#include "m_Do/m_Do_graphic.h"
|
||||
#include "d/actor/d_a_midna.h"
|
||||
#if TARGET_PC
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/ui/touch_controls.hpp"
|
||||
#endif
|
||||
#include <cstring>
|
||||
|
||||
#if TARGET_PC
|
||||
@@ -2509,6 +2512,10 @@ dMenu_Fmap2DTop_c::dMenu_Fmap2DTop_c(JKRExpHeap* i_heap, STControl* i_stick) {
|
||||
}
|
||||
|
||||
dMenu_Fmap2DTop_c::~dMenu_Fmap2DTop_c() {
|
||||
#if TARGET_PC
|
||||
dusk::ui::set_control_override(dusk::ui::Control::Z, dusk::ui::ControlOverride::Default);
|
||||
#endif
|
||||
|
||||
deleteExplain();
|
||||
JKR_DELETE(mpTitleScreen);
|
||||
mpTitleScreen = NULL;
|
||||
@@ -2782,6 +2789,12 @@ void dMenu_Fmap2DTop_c::setZButtonString(u32 param_0, u8 i_alpha) {
|
||||
param_0 = 0x533;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
dusk::ui::set_control_override(dusk::ui::Control::Z,
|
||||
param_0 != 0 && isWarpAccept() ? dusk::ui::ControlOverride::Action :
|
||||
dusk::ui::ControlOverride::Default);
|
||||
#endif
|
||||
|
||||
#if VERSION == VERSION_GCN_JPN
|
||||
static const u64 cont_zt[5] = {MULTI_CHAR('cont_zt'), MULTI_CHAR('cont_zt1'), MULTI_CHAR('cont_zt2'), MULTI_CHAR('cont_zt3'), MULTI_CHAR('cont_zt4')};
|
||||
#define setZButtonString_font_zt cont_zt
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#endif
|
||||
|
||||
typedef void (dMenu_Insect_c::*initFunc)();
|
||||
static initFunc map_init_process[] = {
|
||||
&dMenu_Insect_c::wait_init, &dMenu_Insect_c::explain_open_init,
|
||||
@@ -280,6 +284,12 @@ void dMenu_Insect_c::wait_init() {
|
||||
|
||||
void dMenu_Insect_c::wait_move() {
|
||||
if (mDoGph_gInf_c::getFader()->getStatus() == 1) {
|
||||
#if TARGET_PC
|
||||
if (pointerWait()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mDoCPd_c::getTrigB(PAD_1) || field_0xf7 == 0) {
|
||||
if (mDoCPd_c::getTrigB(PAD_1) && field_0xf6 == 1) {
|
||||
dMeter2Info_setInsectSelectType(0);
|
||||
@@ -301,6 +311,39 @@ void dMenu_Insect_c::wait_move() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
bool dMenu_Insect_c::pointerWait() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Collection);
|
||||
for (u8 y = 0; y < 4; ++y) {
|
||||
for (u8 x = 0; x < 6; ++x) {
|
||||
const int index = x + y * 6;
|
||||
if (!isGetInsect(x, y) || !dusk::menu_pointer::hit_pane(mpINSParent[index], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field_0xf4 != x || field_0xf5 != y) {
|
||||
field_0xf4 = x;
|
||||
field_0xf5 = y;
|
||||
setCursorPos();
|
||||
setAButtonString(0x368);
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_ITEM, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
}
|
||||
if (dusk::menu_pointer::consume_click()) {
|
||||
field_0xf3 = 1;
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_EXP_WIN_OPEN, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_set2DVibration();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void dMenu_Insect_c::explain_open_init() {
|
||||
char local_78[32];
|
||||
char local_98[32];
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include "dusk/achievements.h"
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/ui/touch_controls.hpp"
|
||||
|
||||
static void enable_turn_page_controls(bool enabled) {
|
||||
const auto controlOverride =
|
||||
enabled ? dusk::ui::ControlOverride::Action : dusk::ui::ControlOverride::Default;
|
||||
dusk::ui::set_control_override(dusk::ui::Control::L, controlOverride);
|
||||
dusk::ui::set_control_override(dusk::ui::Control::R, controlOverride);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if VERSION == VERSION_GCN_JPN
|
||||
@@ -82,6 +91,10 @@ dMenu_Letter_c::dMenu_Letter_c(JKRExpHeap* i_heap, STControl* i_stick, CSTContro
|
||||
|
||||
|
||||
dMenu_Letter_c::~dMenu_Letter_c() {
|
||||
#if TARGET_PC
|
||||
enable_turn_page_controls(false);
|
||||
#endif
|
||||
|
||||
JKR_DELETE(mpDrawCursor);
|
||||
mpDrawCursor = NULL;
|
||||
|
||||
@@ -357,6 +370,10 @@ int dMenu_Letter_c::_open() {
|
||||
}
|
||||
|
||||
int dMenu_Letter_c::_close() {
|
||||
#if TARGET_PC
|
||||
enable_turn_page_controls(false);
|
||||
#endif
|
||||
|
||||
s16 closeWindowFrame =
|
||||
g_drawHIO.mLetterSelectScreen.mCloseFrame[dMeter_drawLetterHIO_c::WINDOW_FRAME];
|
||||
field_0x368 = 0;
|
||||
@@ -386,6 +403,10 @@ int dMenu_Letter_c::_close() {
|
||||
}
|
||||
|
||||
void dMenu_Letter_c::wait_init() {
|
||||
#if TARGET_PC
|
||||
enable_turn_page_controls(field_0x374 > 1);
|
||||
#endif
|
||||
|
||||
setAButtonString(0x40c);
|
||||
setBButtonString(0x3f9);
|
||||
}
|
||||
@@ -393,6 +414,12 @@ void dMenu_Letter_c::wait_init() {
|
||||
void dMenu_Letter_c::wait_move() {
|
||||
u8 oldIndex = mIndex;
|
||||
if (mDoGph_gInf_c::getFader()->getStatus() == 1) {
|
||||
#if TARGET_PC
|
||||
if (pointerWait()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mDoCPd_c::getTrigB(PAD_1) != 0) {
|
||||
mpDrawCursor->offPlayAnime(0);
|
||||
mStatus = 3;
|
||||
@@ -448,8 +475,40 @@ void dMenu_Letter_c::wait_move() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
bool dMenu_Letter_c::pointerWait() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Collection);
|
||||
for (u8 i = 0; i < field_0x373; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mpLetterParent[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mIndex != i) {
|
||||
mIndex = i;
|
||||
changeActiveColor();
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_ITEM, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
}
|
||||
if (dusk::menu_pointer::consume_click()) {
|
||||
mProcess = 3;
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_LETTER_OPEN, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_set2DVibration();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void dMenu_Letter_c::slide_right_init() {
|
||||
#if TARGET_PC
|
||||
enable_turn_page_controls(false);
|
||||
#endif
|
||||
|
||||
field_0x358 = -field_0x1ec->getWidth() * mDoGph_gInf_c::getInvScale();
|
||||
field_0x35c = field_0x1ec->getWidth() IF_NOT_DUSK(* mDoGph_gInf_c::getInvScale());
|
||||
changePageLight();
|
||||
@@ -467,6 +526,10 @@ void dMenu_Letter_c::slide_right_move() {
|
||||
}
|
||||
|
||||
void dMenu_Letter_c::slide_left_init() {
|
||||
#if TARGET_PC
|
||||
enable_turn_page_controls(false);
|
||||
#endif
|
||||
|
||||
field_0x358 = field_0x1ec->getWidth() * mDoGph_gInf_c::getInvScale();
|
||||
field_0x35c = -field_0x1ec->getWidth() IF_NOT_DUSK(* mDoGph_gInf_c::getInvScale());
|
||||
changePageLight();
|
||||
@@ -484,6 +547,10 @@ void dMenu_Letter_c::slide_left_move() {
|
||||
}
|
||||
|
||||
void dMenu_Letter_c::read_open_init() {
|
||||
#if TARGET_PC
|
||||
enable_turn_page_controls(false);
|
||||
#endif
|
||||
|
||||
field_0x36a = 0;
|
||||
u8 idx = field_0x3ac[field_0x36f * 6 + mIndex] - 1;
|
||||
field_0x3e3 = 1;
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
|
||||
#include "JSystem/JAudio2/JASDriverIF.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/ui/touch_controls.hpp"
|
||||
#endif
|
||||
|
||||
typedef void (dMenu_Option_c::*initFunc)();
|
||||
static initFunc init[] = {
|
||||
&dMenu_Option_c::atten_init,
|
||||
@@ -293,6 +298,10 @@ void dMenu_Option_c::_create() {
|
||||
}
|
||||
|
||||
void dMenu_Option_c::_delete() {
|
||||
#if TARGET_PC
|
||||
dusk::ui::set_control_override(dusk::ui::Control::Z, dusk::ui::ControlOverride::Default);
|
||||
#endif
|
||||
|
||||
JKR_DELETE(mpString);
|
||||
mpString = NULL;
|
||||
|
||||
@@ -518,6 +527,15 @@ void dMenu_Option_c::_move() {
|
||||
(this->*init[field_0x3ef])();
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (field_0x3f4 == 5 && field_0x3ef != SelectType3 && field_0x3f3 == 5 &&
|
||||
field_0x3ef != SelectType4 && field_0x3ef != SelectType5 && field_0x3ef != SelectType6 &&
|
||||
field_0x3ef != SelectType7 && pointerConfirmSelect())
|
||||
{
|
||||
goto skip;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
skip:
|
||||
u8 oldValue = field_0x3ef;
|
||||
@@ -1074,6 +1092,34 @@ void dMenu_Option_c::confirm_move_move() {
|
||||
bool leftTrigger = checkLeftTrigger();
|
||||
bool rightTrigger = checkRightTrigger();
|
||||
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options);
|
||||
for (u8 i = 0; i < 2; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mpYesNoSelBase_c[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
if (field_0x3f9 != i) {
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
field_0x3fa = field_0x3f9;
|
||||
field_0x3f9 = i;
|
||||
yesnoSelectAnmSet();
|
||||
field_0x3ef = SelectType6;
|
||||
mpWarning->_move();
|
||||
setAnimation();
|
||||
return;
|
||||
}
|
||||
if (dusk::menu_pointer::consume_click()) {
|
||||
yesNoSelectStart();
|
||||
field_0x3ef = SelectType7;
|
||||
dMeter2Info_set2DVibrationM();
|
||||
mpWarning->_move();
|
||||
setAnimation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mDoCPd_c::getTrigA(PAD_1) != 0) {
|
||||
yesNoSelectStart();
|
||||
field_0x3ef = SelectType7;
|
||||
@@ -2063,6 +2109,11 @@ void dMenu_Option_c::cursorAnime(f32 i_cursorValue) {
|
||||
}
|
||||
|
||||
void dMenu_Option_c::setZButtonString(u16 i_stringID) {
|
||||
#if TARGET_PC
|
||||
dusk::ui::set_control_override(dusk::ui::Control::Z,
|
||||
i_stringID != 0 ? dusk::ui::ControlOverride::Action : dusk::ui::ControlOverride::Default);
|
||||
#endif
|
||||
|
||||
if (i_stringID == 0) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (mpZButtonText[i] != NULL) {
|
||||
@@ -2142,7 +2193,88 @@ bool dMenu_Option_c::isRumbleSupported() {
|
||||
return JUTGamePad::sRumbleSupported >> 0x1f;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
bool dMenu_Option_c::pointerConfirmSelect() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options);
|
||||
if (!dusk::menu_pointer::state().clicked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (u8 i = 0; i < SelectType3; ++i) {
|
||||
if (dusk::menu_pointer::hit_pane(mpMenuPane[i], 8.0f)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dusk::menu_pointer::consume_click()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
field_0x3f7 = 1;
|
||||
field_0x3f5 = field_0x3ef;
|
||||
field_0x3ef = SelectType4;
|
||||
dMeter2Info_set2DVibration();
|
||||
(this->*init[field_0x3ef])();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool dMenu_Option_c::dpdMenuMove() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Options);
|
||||
for (u8 i = 0; i < SelectType3; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mpMenuPane[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
if (getSelectType() != i) {
|
||||
field_0x3ef = i;
|
||||
setCursorPos(i);
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
}
|
||||
if (!dusk::menu_pointer::consume_click()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
case SelectType0:
|
||||
field_0x3e4 ^= 1;
|
||||
field_0x3da = 5;
|
||||
field_0x3ef = SelectType3;
|
||||
field_0x3f5 = SelectType0;
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_OPTION_SWITCH, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
return true;
|
||||
case SelectType1:
|
||||
if (isRumbleSupported()) {
|
||||
field_0x3ea ^= 1;
|
||||
if (field_0x3ea != 0) {
|
||||
mDoCPd_c::startMotorWave(0, &field_0x3e0, JUTGamePad::CRumble::VAL_0, 0x3c);
|
||||
}
|
||||
field_0x3da = 5;
|
||||
field_0x3ef = SelectType3;
|
||||
field_0x3f5 = SelectType1;
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_OPTION_SWITCH, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
}
|
||||
return true;
|
||||
case SelectType2:
|
||||
if (field_0x3e9 == 0) {
|
||||
field_0x3e9 = 2;
|
||||
} else {
|
||||
field_0x3e9--;
|
||||
}
|
||||
field_0x3da = 5;
|
||||
mDoAud_setOutputMode(dMo_soundMode[field_0x3e9]);
|
||||
setSoundMode(dMo_soundMode[field_0x3e9]);
|
||||
field_0x3ef = SelectType3;
|
||||
field_0x3f5 = SelectType2;
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_OPTION_SWITCH, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/game_clock.h"
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/touch_controls.hpp"
|
||||
#endif
|
||||
|
||||
typedef void (dMenu_Ring_c::*initFunc)();
|
||||
@@ -614,6 +616,9 @@ void dMenu_Ring_c::_delete() {
|
||||
* initializes a new process if mStatus changes
|
||||
*/
|
||||
void dMenu_Ring_c::_move() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::ItemWheel);
|
||||
#endif
|
||||
mRingRadiusH = g_ringHIO.mRingRadiusH;
|
||||
mRingRadiusV = g_ringHIO.mRingRadiusV;
|
||||
mOldStatus = mStatus; // Save current status for check
|
||||
@@ -1517,6 +1522,11 @@ void dMenu_Ring_c::stick_wait_proc() {
|
||||
setDoStatus(0);
|
||||
return;
|
||||
}
|
||||
#if TARGET_PC
|
||||
if (pointerMove()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (dMw_A_TRIGGER() && !dMeter2Info_isTouchKeyCheck(0xe)) {
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0);
|
||||
}
|
||||
@@ -1528,6 +1538,49 @@ void dMenu_Ring_c::stick_wait_proc() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
bool dMenu_Ring_c::pointerMove() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::ItemWheel);
|
||||
const auto& pointer = dusk::menu_pointer::state();
|
||||
if (!pointer.valid || mItemsTotal == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int hoveredSlot = -1;
|
||||
f32 bestDistance = 42.0f;
|
||||
for (u8 i = 0; i < mItemsTotal; ++i) {
|
||||
const f32 x = mItemSlotPosX[i] + mCenterPosX;
|
||||
const f32 y = mItemSlotPosY[i] + mCenterPosY;
|
||||
const f32 distance = calcDistance(pointer.x, pointer.y, x, y);
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
hoveredSlot = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (hoveredSlot < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mCurrentSlot != hoveredSlot) {
|
||||
mDirectSelectCursorPos.x = mItemSlotPosX[mCurrentSlot];
|
||||
mDirectSelectCursorPos.z = mItemSlotPosY[mCurrentSlot];
|
||||
mCurrentSlot = hoveredSlot;
|
||||
mDirectSelectActive = true;
|
||||
field_0x670 = field_0x63e[mCurrentSlot];
|
||||
setStatus(STATUS_MOVE);
|
||||
field_0x6b2 = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dusk::menu_pointer::consume_click()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void dMenu_Ring_c::stick_move_init() {
|
||||
if (mCursorSpeed == 0) {
|
||||
mCursorSpeed = g_ringHIO.mCursorInitSpeed;
|
||||
@@ -1672,12 +1725,40 @@ void dMenu_Ring_c::drawSelectItem() {
|
||||
#else
|
||||
if (field_0x674[i] < 10) {
|
||||
#endif
|
||||
#if TARGET_PC
|
||||
f32 initSizeX;
|
||||
f32 initSizeY;
|
||||
f32 initScaleX;
|
||||
f32 initScaleY;
|
||||
Vec pos;
|
||||
dusk::ui::EquipTarget touchTarget;
|
||||
if (dusk::ui::get_equip_target(i, touchTarget)) {
|
||||
initSizeX = touchTarget.width;
|
||||
initSizeY = touchTarget.height;
|
||||
initScaleX = 1.0f;
|
||||
initScaleY = 1.0f;
|
||||
pos.x = touchTarget.left;
|
||||
pos.y = touchTarget.top;
|
||||
pos.z = 0.0f;
|
||||
} else {
|
||||
CPaneMgr* meterItemPane = dMeter2Info_getMeterItemPanePtr(i);
|
||||
if (meterItemPane == NULL) {
|
||||
continue;
|
||||
}
|
||||
initSizeX = meterItemPane->getInitSizeX() * 1.7f;
|
||||
initSizeY = meterItemPane->getInitSizeY() * 1.7f;
|
||||
initScaleX = meterItemPane->getInitScaleX();
|
||||
initScaleY = meterItemPane->getInitScaleY();
|
||||
pos = meterItemPane->getGlobalVtxCenter(meterItemPane->mPane, true, 0);
|
||||
}
|
||||
#else
|
||||
f32 initSizeX = dMeter2Info_getMeterItemPanePtr(i)->getInitSizeX() * 1.7f;
|
||||
f32 initSizeY = dMeter2Info_getMeterItemPanePtr(i)->getInitSizeY() * 1.7f;
|
||||
f32 initScaleX = dMeter2Info_getMeterItemPanePtr(i)->getInitScaleX();
|
||||
f32 initScaleY = dMeter2Info_getMeterItemPanePtr(i)->getInitScaleY();
|
||||
Vec pos = dMeter2Info_getMeterItemPanePtr(i)->getGlobalVtxCenter(
|
||||
dMeter2Info_getMeterItemPanePtr(i)->mPane, true, 0);
|
||||
#endif
|
||||
|
||||
#if TARGET_PC
|
||||
f32 fVar14 = 0.1f + 0.8f * u;
|
||||
|
||||
+153
-2
@@ -18,11 +18,15 @@
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
#include "m_Do/m_Do_graphic.h"
|
||||
#include "d/d_msg_scrn_explain.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "JSystem/J2DGraph/J2DAnmLoader.h"
|
||||
#include "f_op/f_op_msg_mng.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/settings.h"
|
||||
#endif
|
||||
|
||||
static int SelStartFrameTbl[3] = {
|
||||
59,
|
||||
99,
|
||||
@@ -54,6 +58,17 @@ static int YnSelStartFrameTbl[2][2] = {
|
||||
|
||||
static int YnSelEndFrameTbl[2][2] = {{2138, 3171}, {2150, 3181}};
|
||||
|
||||
#if TARGET_PC
|
||||
namespace {
|
||||
constexpr u8 pointer_target(u8 group, u8 index) noexcept {
|
||||
return static_cast<u8>((group << 4) | (index & 0x0F));
|
||||
}
|
||||
|
||||
constexpr u8 s_pointerSaveSelectTarget = 0;
|
||||
constexpr u8 s_pointerYesNoSelectTarget = 1;
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
static dMs_HIO_c g_msHIO;
|
||||
|
||||
dMs_HIO_c::dMs_HIO_c() {
|
||||
@@ -1766,6 +1781,12 @@ void dMenu_save_c::openSaveSelect3() {
|
||||
|
||||
void dMenu_save_c::saveSelect() {
|
||||
if (!mDoRst::isReset()) {
|
||||
#if TARGET_PC
|
||||
if (pointerSaveSelect()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
stick->checkTrigger();
|
||||
|
||||
if (mDoCPd_c::getTrigA(PAD_1)) {
|
||||
@@ -1792,7 +1813,84 @@ void dMenu_save_c::saveSelect() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
bool dMenu_save_c::pointerSaveSelect() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Save);
|
||||
for (u8 i = 0; i < 3; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mpSelData[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
const bool clicked = dusk::menu_pointer::consume_click();
|
||||
if (mSelectedFile != i) {
|
||||
mDoAud_seStart(Z2SE_FILE_SELECT_CURSOR, NULL, 0, 0);
|
||||
mLastSelFile = mSelectedFile;
|
||||
mSelectedFile = i;
|
||||
if (clicked) {
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::Save,
|
||||
pointer_target(s_pointerSaveSelectTarget, i));
|
||||
}
|
||||
dataSelectAnmSet();
|
||||
mMenuProc = PROC_SAVE_SELECT_MOVE_ANM;
|
||||
return true;
|
||||
}
|
||||
if (clicked) {
|
||||
saveSelectStart();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dMenu_save_c::pointerYesNoSelect(bool errorSelect, u8 errParam, u8 soundParam) {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Save);
|
||||
for (u8 i = 0; i < 2; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mpNoYes[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
const bool clicked =
|
||||
(!errorSelect || mYesNoCursor == i) && dusk::menu_pointer::consume_click();
|
||||
if (mYesNoCursor != i) {
|
||||
if (errorSelect) {
|
||||
errCurMove(errParam, soundParam);
|
||||
return false;
|
||||
}
|
||||
mDoAud_seStart(Z2SE_SY_MENU_CURSOR_COMMON, NULL, 0, 0);
|
||||
mYesNoPrevCursor = mYesNoCursor;
|
||||
mYesNoCursor = i;
|
||||
if (clicked) {
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::Save,
|
||||
pointer_target(s_pointerYesNoSelectTarget, i));
|
||||
}
|
||||
yesnoSelectAnmSet(0);
|
||||
mMenuProc = PROC_YES_NO_CURSOR_MOVE_ANM;
|
||||
return true;
|
||||
}
|
||||
if (clicked) {
|
||||
if (errorSelect) {
|
||||
if (mYesNoCursor != CURSOR_NO) {
|
||||
if (soundParam == 0) {
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
|
||||
}
|
||||
} else if (soundParam == 0) {
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_CANCEL, NULL, 0, 0);
|
||||
}
|
||||
mSelIcon->setAlphaRate(0.0f);
|
||||
} else {
|
||||
yesnoSelectStart();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void dMenu_save_c::saveSelectStart() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::Save);
|
||||
#endif
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
|
||||
selectDataMoveAnmInitSet(SelOpenStartFrameTbl[mSelectedFile],
|
||||
SelOpenEndFrameTbl[mSelectedFile]);
|
||||
@@ -1851,6 +1949,17 @@ void dMenu_save_c::dataSelectAnmSet() {
|
||||
}
|
||||
|
||||
void dMenu_save_c::saveSelectMoveAnime() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Save);
|
||||
if (mSelectedFile != 0xFF &&
|
||||
dusk::menu_pointer::hit_pane(mpSelData[mSelectedFile], 8.0f) &&
|
||||
dusk::menu_pointer::consume_click())
|
||||
{
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::Save,
|
||||
pointer_target(s_pointerSaveSelectTarget, mSelectedFile));
|
||||
}
|
||||
#endif
|
||||
bool bookWakuAnmComplete = true;
|
||||
bool selWakuAnmComplete = true;
|
||||
bool var_r29 = true;
|
||||
@@ -1900,12 +2009,26 @@ void dMenu_save_c::saveSelectMoveAnime() {
|
||||
if (mLastSelFile != 0xFF) {
|
||||
mpSelData[mLastSelFile]->getPanePtr()->setAnimation((J2DAnmTransformKey*)NULL);
|
||||
}
|
||||
#if TARGET_PC
|
||||
if (dusk::menu_pointer::consume_deferred_activation(
|
||||
dusk::menu_pointer::Context::Save,
|
||||
pointer_target(s_pointerSaveSelectTarget, mSelectedFile))) {
|
||||
saveSelectStart();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mMenuProc = PROC_SAVE_SELECT;
|
||||
}
|
||||
}
|
||||
|
||||
void dMenu_save_c::saveYesNoSelect() {
|
||||
if (!mDoRst::isReset()) {
|
||||
#if TARGET_PC
|
||||
if (pointerYesNoSelect(false)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
stick->checkTrigger();
|
||||
|
||||
if (mDoCPd_c::getTrigA(PAD_1)) {
|
||||
@@ -1933,6 +2056,9 @@ void dMenu_save_c::saveYesNoSelect() {
|
||||
}
|
||||
|
||||
void dMenu_save_c::yesnoSelectStart() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::clear_deferred_activation(dusk::menu_pointer::Context::Save);
|
||||
#endif
|
||||
if (mYesNoCursor != CURSOR_NO) {
|
||||
mDoAud_seStart(Z2SE_SY_CURSOR_OK, NULL, 0, 0);
|
||||
mSelIcon->setAlphaRate(0.0f);
|
||||
@@ -2001,11 +2127,30 @@ void dMenu_save_c::yesnoSelectAnmSet(u8 param_0) {
|
||||
}
|
||||
|
||||
void dMenu_save_c::yesNoCursorMoveAnm() {
|
||||
#if TARGET_PC
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Save);
|
||||
if (mYesNoCursor != 0xFF &&
|
||||
dusk::menu_pointer::hit_pane(mpNoYes[mYesNoCursor], 8.0f) &&
|
||||
dusk::menu_pointer::consume_click())
|
||||
{
|
||||
dusk::menu_pointer::defer_activation(
|
||||
dusk::menu_pointer::Context::Save,
|
||||
pointer_target(s_pointerYesNoSelectTarget, mYesNoCursor));
|
||||
}
|
||||
#endif
|
||||
bool selAnmComplete = yesnoSelectMoveAnm(0);
|
||||
bool wakuAnmComplete = yesnoWakuAlpahAnm(mYesNoPrevCursor);
|
||||
|
||||
if (selAnmComplete == true && wakuAnmComplete == true) {
|
||||
yesnoCursorShow();
|
||||
#if TARGET_PC
|
||||
if (dusk::menu_pointer::consume_deferred_activation(
|
||||
dusk::menu_pointer::Context::Save,
|
||||
pointer_target(s_pointerYesNoSelectTarget, mYesNoCursor))) {
|
||||
yesnoSelectStart();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
mMenuProc = PROC_SAVE_YES_NO_SELECT;
|
||||
}
|
||||
}
|
||||
@@ -2181,6 +2326,12 @@ bool dMenu_save_c::errYesNoSelect(u8 param_0, u8 param_1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (pointerYesNoSelect(true, param_0, param_1)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
stick->checkTrigger();
|
||||
|
||||
if (mDoCPd_c::getTrigA(PAD_1)) {
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
#include "m_Do/m_Do_graphic.h"
|
||||
#include <cstring>
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#endif
|
||||
|
||||
typedef void (dMenu_Skill_c::*initFunc)();
|
||||
static initFunc map_init_process[] = {
|
||||
&dMenu_Skill_c::wait_init,
|
||||
@@ -275,6 +279,12 @@ void dMenu_Skill_c::wait_init() {
|
||||
void dMenu_Skill_c::wait_move() {
|
||||
u8 oldIndex = mIndex;
|
||||
if (mDoGph_gInf_c::getFader()->getStatus() == 1) {
|
||||
#if TARGET_PC
|
||||
if (pointerWait()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mDoCPd_c::getTrigB(PAD_1) != 0) {
|
||||
mpDrawCursor->offPlayAnime(0);
|
||||
mStatus = 3;
|
||||
@@ -299,6 +309,34 @@ void dMenu_Skill_c::wait_move() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
bool dMenu_Skill_c::pointerWait() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Collection);
|
||||
for (u8 i = 0; i < mSkillNum; ++i) {
|
||||
if (!dusk::menu_pointer::hit_pane(mpLetterParent[i], 8.0f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mIndex != i) {
|
||||
mIndex = i;
|
||||
changeActiveColor();
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_CURSOR_ITEM, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
}
|
||||
if (dusk::menu_pointer::consume_click()) {
|
||||
mProcess = PROC_WAIT_MOVE;
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_EXP_WIN_OPEN, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_set2DVibration();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void dMenu_Skill_c::read_open_init() {
|
||||
static const u32 i_id[7] = {
|
||||
1716, 1715, 1717, 1718, 1719, 1720, 1721,
|
||||
|
||||
@@ -2966,7 +2966,15 @@ void dMeter2_c::alphaAnimeButtonCross() {
|
||||
field_0x190++;
|
||||
}
|
||||
} else {
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableTouchControls) {
|
||||
mpMeterDraw->setAlphaButtonCrossAnimeMin();
|
||||
} else {
|
||||
mpMeterDraw->setAlphaButtonCrossAnimeMax();
|
||||
}
|
||||
#else
|
||||
mpMeterDraw->setAlphaButtonCrossAnimeMax();
|
||||
#endif
|
||||
|
||||
if (field_0x190 < 5) {
|
||||
field_0x190++;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/icon_provider.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
@@ -653,10 +654,22 @@ void dMeter2Draw_c::draw() {
|
||||
J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort();
|
||||
graf_ctx->setup2D();
|
||||
|
||||
#if TARGET_PC
|
||||
const bool touchControlsEnabled = dusk::getSettings().game.enableTouchControls;
|
||||
if (touchControlsEnabled) {
|
||||
mpButtonParent->hide();
|
||||
} else {
|
||||
mpButtonParent->show();
|
||||
}
|
||||
#endif
|
||||
|
||||
mpScreen->draw(0.0f, 0.0f, graf_ctx);
|
||||
drawKanteraScreen(1);
|
||||
drawKanteraScreen(2);
|
||||
|
||||
#if TARGET_PC
|
||||
if (!touchControlsEnabled) {
|
||||
#endif
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (mpItemXY[i] != NULL) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
@@ -705,6 +718,9 @@ void dMeter2Draw_c::draw() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#if TARGET_PC
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mpLightDropParent->getAlphaRate() != 0.0f) {
|
||||
f32 var_f28 = g_drawHIO.mLightDrop.mPikariScaleNormal;
|
||||
@@ -787,7 +803,11 @@ void dMeter2Draw_c::draw() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (!touchControlsEnabled && field_0x738 > 0.0f) {
|
||||
#else
|
||||
if (field_0x738 > 0.0f) {
|
||||
#endif
|
||||
drawPikari(mpButtonMidona, &field_0x738, g_drawHIO.mMidnaIconPikariScale,
|
||||
g_drawHIO.mMidnaIconPikariFrontOuter, g_drawHIO.mMidnaIconPikariFrontInner,
|
||||
g_drawHIO.mMidnaIconPikariBackOuter, g_drawHIO.mMidnaIconPikariBackInner,
|
||||
@@ -2452,6 +2472,11 @@ void dMeter2Draw_c::drawButtonB(u8 i_action, bool param_1, f32 i_posX, f32 i_pos
|
||||
SAFE_STRCPY(static_cast<J2DTextBox*>(mpBText[i]->getPanePtr())->getStringPtr(), mp_string);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableTouchControls) {
|
||||
mpScreen->search(MULTI_CHAR('item_b_n'))->hide();
|
||||
} else
|
||||
#endif
|
||||
if (i_action == 0x26 || i_action == 0x2E) {
|
||||
mpScreen->search(MULTI_CHAR('item_b_n'))->show();
|
||||
var_r31 = 1;
|
||||
@@ -2729,6 +2754,12 @@ void dMeter2Draw_c::drawButtonXY(int i_no, u8 i_itemNo, u8 i_action, bool param_
|
||||
mpTextXY[i_no]->scale(g_drawHIO.mButtonXYTextScale, g_drawHIO.mButtonXYTextScale);
|
||||
mpTextXY[i_no]->paneTrans(g_drawHIO.mButtonXYTextPosX, g_drawHIO.mButtonXYTextPosY);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableTouchControls) {
|
||||
mpScreen->search(tag[i_no])->hide();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3294,6 +3325,10 @@ void dMeter2Draw_c::setButtonIconMidonaAlpha(u32 param_0) {
|
||||
}
|
||||
|
||||
mpButtonXY[2]->setAlpha(255.0f * field_0x724 * temp_f30_2);
|
||||
|
||||
#if TARGET_PC
|
||||
dusk::ui::update_midna_icon_texture(mpButtonMidona != NULL ? mpButtonMidona->getPanePtr() : NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void dMeter2Draw_c::setButtonIconAlpha(int i_no, u8 unused0, u32 unused1, bool unused2) {
|
||||
|
||||
+51
-2
@@ -22,6 +22,10 @@
|
||||
#endif
|
||||
#include <cstring>
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/action_bindings.h"
|
||||
#endif
|
||||
|
||||
#if (PLATFORM_WII || PLATFORM_SHIELD)
|
||||
dMeter_map_HIO_c g_meter_mapHIO;
|
||||
#endif
|
||||
@@ -738,7 +742,38 @@ void dMeterMap_c::ctrlShowMap() {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!mDoCPd_c::getTrigUp(PAD_1) && !mDoCPd_c::getTrigDown(PAD_1)) {
|
||||
}
|
||||
#if TARGET_PC
|
||||
else if (!isEventRunCheck() &&
|
||||
(dMeter2Info_getMapStatus() == 0 || dMeter2Info_getMapStatus() == 1) &&
|
||||
!dMeter2Info_isSub2DStatus(1) && (isFmapScreen() || isDmapScreen()) &&
|
||||
dusk::getActionBindTrig(dusk::ActionBinds::OPEN_MAP_SCREEN, PAD_1))
|
||||
{
|
||||
dMeter2Info_setMapStatus(2);
|
||||
dMeter2Info_setMapKeyDirection(0x400);
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_OPEN_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_set2DVibration();
|
||||
} else if (!isEventRunCheck() &&
|
||||
(dMeter2Info_getMapStatus() == 0 || dMeter2Info_getMapStatus() == 1) &&
|
||||
isEnableDispMapAndMapDispSizeTypeNo() &&
|
||||
dusk::getActionBindTrig(dusk::ActionBinds::TOGGLE_MINIMAP, PAD_1))
|
||||
{
|
||||
if (isDispPosInsideFlg()) {
|
||||
setDispPosOutsideFlg_SE_On();
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_CLOSE_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_setMapStatus(0);
|
||||
} else {
|
||||
setDispPosInsideFlg_SE_On();
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_OPEN_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_set2DVibration();
|
||||
dMeter2Info_setMapStatus(1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (!mDoCPd_c::getTrigUp(PAD_1) && !mDoCPd_c::getTrigDown(PAD_1)) {
|
||||
keyCheck();
|
||||
}
|
||||
|
||||
@@ -833,7 +868,21 @@ void dMeterMap_c::meter_map_move(u32 param_0) {
|
||||
dMeter2Info_set2DVibration();
|
||||
}
|
||||
dMeter2Info_resetPauseStatus();
|
||||
} else if (
|
||||
}
|
||||
#if TARGET_PC
|
||||
else if (!dComIfGp_event_runCheck() && !dMsgObject_isTalkNowCheck() &&
|
||||
(dMeter2Info_getMapStatus() == 0 || dMeter2Info_getMapStatus() == 1) &&
|
||||
!dMeter2Info_isSub2DStatus(1) && (isFmapScreen() || isDmapScreen()) &&
|
||||
dusk::getActionBindTrig(dusk::ActionBinds::OPEN_MAP_SCREEN, PAD_1))
|
||||
{
|
||||
dMeter2Info_setMapStatus(2);
|
||||
dMeter2Info_setMapKeyDirection(0x400);
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_OPEN_S, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
dMeter2Info_set2DVibration();
|
||||
}
|
||||
#endif
|
||||
else if (
|
||||
#if DEBUG
|
||||
dMw_RIGHT_TRIGGER() &&
|
||||
#else
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "JSystem/JUtility/JUTFont.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/scope_guard.hpp"
|
||||
#endif
|
||||
|
||||
@@ -575,6 +576,20 @@ void jmessage_tReference::pageSend() {
|
||||
|
||||
void jmessage_tReference::selectMessage() {
|
||||
if (mSelectNum != 0) {
|
||||
#if TARGET_PC
|
||||
u8 pointerChoice = 0xFF;
|
||||
if (dusk::menu_pointer::get_dialog_choice(pointerChoice) && pointerChoice < mSelectNum &&
|
||||
pointerChoice != mSelectPos)
|
||||
{
|
||||
mSelectPos = pointerChoice;
|
||||
if (mSelectType != 0) {
|
||||
getObjectPtr()->getSequenceProcessor()->calcStringLength();
|
||||
}
|
||||
Z2GetAudioMgr()->seStart(Z2SE_SY_TALK_CURSOR, NULL, 0, 0, 1.0f, 1.0f, -1.0f,
|
||||
-1.0f, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
mpStick->checkTrigger();
|
||||
|
||||
if (mSelectType == 0) {
|
||||
|
||||
+18
-2
@@ -26,12 +26,13 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "JSystem/JKernel/JKRExpHeap.h"
|
||||
#include "dusk/version.hpp"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
#include "m_Do/m_Do_lib.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/version.hpp"
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
@@ -1123,7 +1124,20 @@ void dMsgObject_c::selectProc() {
|
||||
dComIfGp_setAStatusForce(0x2a, 0);
|
||||
}
|
||||
}
|
||||
if (mDoCPd_c::getTrigA(0)) {
|
||||
#if TARGET_PC
|
||||
jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference();
|
||||
u8 pointerChoice = 0xFF;
|
||||
bool pointerConfirm = dusk::menu_pointer::consume_dialog_click(pointerChoice) &&
|
||||
pointerChoice < pRef->getSelectNum();
|
||||
if (pointerConfirm) {
|
||||
pRef->setSelectPos(pointerChoice);
|
||||
}
|
||||
#endif
|
||||
if (mDoCPd_c::getTrigA(0)
|
||||
#if TARGET_PC
|
||||
|| pointerConfirm
|
||||
#endif
|
||||
) {
|
||||
if (getSelectCursorPosLocal() != 0xff) {
|
||||
field_0x1a3 = 1;
|
||||
}
|
||||
@@ -1145,7 +1159,9 @@ void dMsgObject_c::selectProc() {
|
||||
}
|
||||
field_0x1a3 = 2;
|
||||
}
|
||||
#ifndef TARGET_PC
|
||||
jmessage_tReference* pRef = (jmessage_tReference*)mpRenProc->getReference();
|
||||
#endif
|
||||
if (getStatusLocal() == 8) {
|
||||
if (isMidonaMessage() && field_0x1a3 != 0) {
|
||||
if (field_0x1a3 == 2 && getSelectCancelPos() == 3) {
|
||||
|
||||
@@ -16,6 +16,17 @@
|
||||
#include "d/d_msg_object.h"
|
||||
#include "d/d_pane_class.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
|
||||
namespace {
|
||||
bool hit_choice_pane(CPaneMgr* pane, f32 padding) {
|
||||
return pane != NULL && pane->getPanePtr() != NULL && pane->getPanePtr()->isVisible() &&
|
||||
dusk::menu_pointer::hit_pane(pane, padding);
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
typedef void (dMsgScrn3Select_c::*processFn)();
|
||||
processFn process[] = {
|
||||
&dMsgScrn3Select_c::open1Proc, &dMsgScrn3Select_c::open2Proc, &dMsgScrn3Select_c::waitProc,
|
||||
@@ -470,6 +481,9 @@ bool dMsgScrn3Select_c::selAnimeMove(u8 i_selNum, u8 param_1, bool param_2) {
|
||||
mSelNum = i_selNum;
|
||||
field_0x114 = 0;
|
||||
field_0x108 = param_2;
|
||||
#if TARGET_PC
|
||||
pointerMove();
|
||||
#endif
|
||||
|
||||
(this->*process[mProcess])();
|
||||
|
||||
@@ -518,6 +532,47 @@ bool dMsgScrn3Select_c::selAnimeMove(u8 i_selNum, u8 param_1, bool param_2) {
|
||||
return mProcess == PROC_SELECT_e ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
bool dMsgScrn3Select_c::pointerMove() {
|
||||
dusk::menu_pointer::begin_context(dusk::menu_pointer::Context::Dialog);
|
||||
mDPDPoint = 0xFF;
|
||||
|
||||
const u8 firstPane = mSelNum == 2 ? 1 : 0;
|
||||
for (u8 choice = 0; choice < mSelNum; ++choice) {
|
||||
const u8 paneIndex = firstPane + choice;
|
||||
if (paneIndex >= 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: this sucks and should be replaced with Wii mpTouchArea
|
||||
bool hit = hit_choice_pane(mpSel_c[paneIndex], 8.0f) ||
|
||||
hit_choice_pane(mpTmSel_c[paneIndex], 24.0f) ||
|
||||
hit_choice_pane(mpTmrSel_c[paneIndex], 24.0f) ||
|
||||
hit_choice_pane(mpKahen_c[paneIndex], 8.0f) ||
|
||||
hit_choice_pane(mpCursor_c[paneIndex], 8.0f);
|
||||
for (int i = 0; i < 5 && !hit; ++i) {
|
||||
hit = hit_choice_pane(mpSelCldw_c[i][paneIndex], 8.0f);
|
||||
}
|
||||
|
||||
if (!hit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mDPDPoint = choice;
|
||||
field_0x110 = paneIndex;
|
||||
dusk::menu_pointer::set_dialog_choice(choice, dusk::menu_pointer::state().clicked);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dMsgScrn3Select_c::consumePointerClick() {
|
||||
u8 choice = 0xFF;
|
||||
return dusk::menu_pointer::consume_dialog_click(choice);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool dMsgScrn3Select_c::selAnimeEnd() {
|
||||
if (mProcess == PROC_MAX_e) {
|
||||
return true;
|
||||
|
||||
@@ -643,6 +643,10 @@ f32 dMsgScrnExplain_c::getAlphaRatio() {
|
||||
bool dMsgScrnExplain_c::checkTriggerA() {
|
||||
if (mDoCPd_c::getTrigA(PAD_1)) {
|
||||
return true;
|
||||
#if TARGET_PC
|
||||
} else if (mpSelect_c != NULL && mpSelect_c->consumePointerClick()) {
|
||||
return true;
|
||||
#endif
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,19 @@ namespace dusk {
|
||||
|
||||
static std::array<std::array<ActionBindPressData, static_cast<int>(ActionBinds::COUNT)>, PAD_CHANMAX> actionPressData{};
|
||||
|
||||
struct VirtualActionBindData {
|
||||
bool pressed = false;
|
||||
bool available = false;
|
||||
};
|
||||
|
||||
static std::array<std::array<VirtualActionBindData, static_cast<int>(ActionBinds::COUNT)>, PAD_CHANMAX> virtualActionData{};
|
||||
|
||||
ActionBindsMap& getActionBinds() {
|
||||
static ActionBindsMap actionBinds = {
|
||||
{ActionBinds::FIRST_PERSON_CAMERA, {&getSettings().actionBindings.firstPersonCamera, "First Person Camera"}},
|
||||
{ActionBinds::CALL_MIDNA, {&getSettings().actionBindings.callMidna, "Call Midna"}},
|
||||
{ActionBinds::OPEN_MAP_SCREEN, {&getSettings().actionBindings.openMapScreen, "Open Map Screen"}},
|
||||
{ActionBinds::TOGGLE_MINIMAP, {&getSettings().actionBindings.toggleMinimap, "Toggle Minimap"}},
|
||||
{ActionBinds::OPEN_DUSKLIGHT_MENU, {&getSettings().actionBindings.openDusklightMenu, "Open Dusklight Menu"}},
|
||||
{ActionBinds::TURBO_SPEED_BUTTON, {&getSettings().actionBindings.turboSpeedButton, "Turbo Speed Button"}},
|
||||
};
|
||||
@@ -25,6 +34,10 @@ bool isActionBound(ActionBinds action, u32 port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (port < PAD_CHANMAX && virtualActionData[port][static_cast<int>(action)].available) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID;
|
||||
}
|
||||
|
||||
@@ -41,43 +54,71 @@ void updateActionBindings() {
|
||||
// If the action isn't bound, or if documents are visible and the action isn't
|
||||
// opening the dusklight menu, don't update. Otherwise, we may accidentally
|
||||
// perform actions while the dusklight menu is open.
|
||||
if (!isActionBound(action, port) ||
|
||||
const int button = boundAction.configVars->at(port);
|
||||
const bool virtualAvailable = virtualActionData[port][static_cast<int>(action)].available;
|
||||
if ((button == PAD_NATIVE_BUTTON_INVALID && !virtualAvailable) ||
|
||||
(ui::any_document_visible() && action != ActionBinds::OPEN_DUSKLIGHT_MENU)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int button = boundAction.configVars->at(port);
|
||||
|
||||
// If keyboard is active for this port
|
||||
u32 count = 0;
|
||||
if (PADGetKeyButtonBindings(port, &count) != nullptr) {
|
||||
int numKeys = 0;
|
||||
const bool* kbState = SDL_GetKeyboardState(&numKeys);
|
||||
if (kbState[button]) {
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
|
||||
}
|
||||
} else {
|
||||
// If controller is active
|
||||
auto controller = aurora::input::get_controller_for_player(port);
|
||||
if (controller) {
|
||||
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(button))) {
|
||||
if (button != PAD_NATIVE_BUTTON_INVALID) {
|
||||
// If keyboard is active for this port
|
||||
u32 count = 0;
|
||||
if (PADGetKeyButtonBindings(port, &count) != nullptr) {
|
||||
int numKeys = 0;
|
||||
const bool* kbState = SDL_GetKeyboardState(&numKeys);
|
||||
if (kbState[button]) {
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
|
||||
}
|
||||
} else {
|
||||
// If controller is active
|
||||
auto controller = aurora::input::get_controller_for_player(port);
|
||||
if (controller) {
|
||||
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(button))) {
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [action, _] : getActionBinds()) {
|
||||
const auto& virtualAction = virtualActionData[port][static_cast<int>(action)];
|
||||
if (virtualAction.available && virtualAction.pressed && !ui::any_document_visible()) {
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setVirtualActionBind(ActionBinds action, u32 port, bool pressed, bool available) {
|
||||
if (port >= PAD_CHANMAX) {
|
||||
return;
|
||||
}
|
||||
virtualActionData[port][static_cast<int>(action)] = {
|
||||
.pressed = pressed,
|
||||
.available = available,
|
||||
};
|
||||
}
|
||||
|
||||
void clearVirtualActionBind(ActionBinds action, u32 port) {
|
||||
if (port >= PAD_CHANMAX) {
|
||||
return;
|
||||
}
|
||||
virtualActionData[port][static_cast<int>(action)] = {};
|
||||
}
|
||||
|
||||
void clearAllVirtualActionBinds() {
|
||||
virtualActionData = {};
|
||||
}
|
||||
|
||||
bool getActionBindTrig(ActionBinds action, u32 port) {
|
||||
return isActionBound(action, port) &&
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
|
||||
return actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
|
||||
!actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
|
||||
}
|
||||
|
||||
bool getActionBindHold(ActionBinds action, u32 port) {
|
||||
return isActionBound(action, port) &&
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
|
||||
return actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
|
||||
actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
|
||||
}
|
||||
|
||||
|
||||
+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;
|
||||
|
||||
|
||||
+1
-10
@@ -70,16 +70,7 @@ bool rollgoal_gyro_enabled() {
|
||||
}
|
||||
|
||||
bool queryGyroAimContext() {
|
||||
if (!static_cast<bool>(getSettings().game.enableGyroAim)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
if (link == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return link->checkAimContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10);
|
||||
return getSettings().game.enableGyroAim.getValue() && dCamera_c::isAimActive();
|
||||
}
|
||||
|
||||
void read(float dt) {
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
#include "dusk/menu_pointer.h"
|
||||
|
||||
#include "m_Do/m_Do_graphic.h"
|
||||
#include "d/d_pane_class.h"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
#include <aurora/rmlui.hpp>
|
||||
#include <dolphin/pad.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace dusk::menu_pointer {
|
||||
namespace {
|
||||
State s_state;
|
||||
bool s_clickConsumed = false;
|
||||
Context s_lastContext = Context::None;
|
||||
Context s_currentContext = Context::None;
|
||||
u8 s_lastDialogChoice = 0xFF;
|
||||
u8 s_currentDialogChoice = 0xFF;
|
||||
bool s_lastDialogChoiceValid = false;
|
||||
bool s_currentDialogChoiceValid = false;
|
||||
bool s_lastDialogClicked = false;
|
||||
bool s_currentDialogClicked = false;
|
||||
bool s_mouseActive = false;
|
||||
bool s_mouseButtonCaptured = false;
|
||||
s32 s_mouseButton = -1;
|
||||
u32 s_suppressedPadHoldMask = 0;
|
||||
u32 s_suppressedPadNextReadMask = 0;
|
||||
Context s_deferredActivationContext = Context::None;
|
||||
u8 s_deferredActivationTarget = 0xFF;
|
||||
|
||||
s32 scancode_from_rml_button(s32 button) noexcept {
|
||||
switch (button) {
|
||||
case 0:
|
||||
return PAD_KEY_MOUSE_LEFT;
|
||||
case 1:
|
||||
return PAD_KEY_MOUSE_RIGHT;
|
||||
case 2:
|
||||
return PAD_KEY_MOUSE_MIDDLE;
|
||||
default:
|
||||
return PAD_KEY_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_mouse_scancode(s32 scancode) noexcept {
|
||||
return scancode >= PAD_KEY_MOUSE_X2 && scancode <= PAD_KEY_MOUSE_LEFT;
|
||||
}
|
||||
|
||||
PADButton pad_button_for_scancode(u32 port, s32 scancode) noexcept {
|
||||
u32 count = 0;
|
||||
PADKeyButtonBinding* bindings = PADGetKeyButtonBindings(port, &count);
|
||||
if (bindings == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
if (bindings[i].scancode == scancode) {
|
||||
return bindings[i].padButton;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 menu_confirm_mouse_scancode() noexcept {
|
||||
constexpr u32 port = PAD_CHAN0;
|
||||
u32 count = 0;
|
||||
PADKeyButtonBinding* bindings = PADGetKeyButtonBindings(port, &count);
|
||||
if (bindings == nullptr) {
|
||||
return PAD_KEY_MOUSE_LEFT;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
if (bindings[i].padButton == PAD_BUTTON_A && is_mouse_scancode(bindings[i].scancode)) {
|
||||
return bindings[i].scancode;
|
||||
}
|
||||
}
|
||||
|
||||
return pad_button_for_scancode(port, PAD_KEY_MOUSE_LEFT) != 0 ? PAD_KEY_INVALID :
|
||||
PAD_KEY_MOUSE_LEFT;
|
||||
}
|
||||
|
||||
bool mouse_button_is_menu_confirm(s32 button) noexcept {
|
||||
const s32 scancode = scancode_from_rml_button(button);
|
||||
return scancode != PAD_KEY_INVALID && scancode == menu_confirm_mouse_scancode();
|
||||
}
|
||||
|
||||
void suppress_pad_for_mouse_button(s32 button, bool held) noexcept {
|
||||
const s32 scancode = scancode_from_rml_button(button);
|
||||
if (scancode == PAD_KEY_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PADButton padButton = pad_button_for_scancode(PAD_CHAN0, scancode);
|
||||
if (padButton == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_suppressedPadNextReadMask |= padButton;
|
||||
if (held) {
|
||||
s_suppressedPadHoldMask |= padButton;
|
||||
} else {
|
||||
s_suppressedPadHoldMask &= ~padButton;
|
||||
}
|
||||
}
|
||||
|
||||
void set_position_from_rml(f32 x, f32 y) noexcept {
|
||||
auto* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dimensions = context->GetDimensions();
|
||||
const f32 width = std::max(static_cast<f32>(dimensions.x), 1.0f);
|
||||
const f32 height = std::max(static_cast<f32>(dimensions.y), 1.0f);
|
||||
|
||||
s_state.x = mDoGph_gInf_c::getMinXF() + x / width * mDoGph_gInf_c::getWidthF();
|
||||
s_state.y = mDoGph_gInf_c::getMinYF() + y / height * mDoGph_gInf_c::getHeightF();
|
||||
s_state.valid = true;
|
||||
}
|
||||
|
||||
void clear_input_state() noexcept {
|
||||
s_state = {};
|
||||
s_clickConsumed = false;
|
||||
s_lastDialogChoice = 0xFF;
|
||||
s_currentDialogChoice = 0xFF;
|
||||
s_lastDialogChoiceValid = false;
|
||||
s_currentDialogChoiceValid = false;
|
||||
s_lastDialogClicked = false;
|
||||
s_currentDialogClicked = false;
|
||||
s_mouseActive = false;
|
||||
s_mouseButtonCaptured = false;
|
||||
s_mouseButton = -1;
|
||||
s_suppressedPadHoldMask = 0;
|
||||
s_suppressedPadNextReadMask = 0;
|
||||
s_deferredActivationContext = Context::None;
|
||||
s_deferredActivationTarget = 0xFF;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool handle_fallthrough_pointer(f32 x, f32 y, Phase phase, bool touch, s32 mouseButton) noexcept {
|
||||
if (!enabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s_clickConsumed = false;
|
||||
|
||||
if (!touch) {
|
||||
if (phase == Phase::Press) {
|
||||
if (!mouse_button_is_menu_confirm(mouseButton)) {
|
||||
return false;
|
||||
}
|
||||
s_mouseButtonCaptured = true;
|
||||
s_mouseButton = mouseButton;
|
||||
suppress_pad_for_mouse_button(mouseButton, true);
|
||||
} else if (phase == Phase::Release) {
|
||||
if (!s_mouseButtonCaptured || s_mouseButton != mouseButton) {
|
||||
return false;
|
||||
}
|
||||
suppress_pad_for_mouse_button(mouseButton, false);
|
||||
s_mouseButtonCaptured = false;
|
||||
s_mouseButton = -1;
|
||||
} else if (phase == Phase::Cancel) {
|
||||
if (s_mouseButtonCaptured) {
|
||||
suppress_pad_for_mouse_button(s_mouseButton, false);
|
||||
s_mouseButtonCaptured = false;
|
||||
s_mouseButton = -1;
|
||||
} else if (!s_mouseActive) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
s_mouseActive = true;
|
||||
}
|
||||
|
||||
if (phase != Phase::Cancel) {
|
||||
set_position_from_rml(x, y);
|
||||
}
|
||||
s_state.touch = touch;
|
||||
|
||||
switch (phase) {
|
||||
case Phase::Press:
|
||||
s_state.down = true;
|
||||
s_state.pressed = true;
|
||||
break;
|
||||
case Phase::Release:
|
||||
s_state.down = false;
|
||||
s_state.released = true;
|
||||
s_state.clicked = true;
|
||||
break;
|
||||
case Phase::Cancel:
|
||||
s_state.down = false;
|
||||
break;
|
||||
case Phase::Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void begin_game_frame() noexcept {
|
||||
s_currentContext = Context::None;
|
||||
s_currentDialogChoice = 0xFF;
|
||||
s_currentDialogChoiceValid = false;
|
||||
s_currentDialogClicked = false;
|
||||
s_clickConsumed = false;
|
||||
if (!enabled()) {
|
||||
clear_input_state();
|
||||
}
|
||||
}
|
||||
|
||||
void end_game_frame() noexcept {
|
||||
s_lastContext = s_currentContext;
|
||||
s_lastDialogChoice = s_currentDialogChoice;
|
||||
s_lastDialogChoiceValid = s_currentDialogChoiceValid;
|
||||
s_lastDialogClicked = s_currentDialogClicked;
|
||||
s_state.pressed = false;
|
||||
s_state.released = false;
|
||||
s_state.clicked = false;
|
||||
if (!s_state.down) {
|
||||
s_state.valid = false;
|
||||
}
|
||||
s_clickConsumed = false;
|
||||
}
|
||||
|
||||
void begin_context(Context context) noexcept {
|
||||
if (context == Context::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_lastContext == Context::None && s_currentContext == Context::None) {
|
||||
s_state = {};
|
||||
s_mouseActive = false;
|
||||
s_mouseButtonCaptured = false;
|
||||
s_mouseButton = -1;
|
||||
s_suppressedPadHoldMask = 0;
|
||||
s_suppressedPadNextReadMask = 0;
|
||||
s_deferredActivationContext = Context::None;
|
||||
s_deferredActivationTarget = 0xFF;
|
||||
}
|
||||
|
||||
s_currentContext = context;
|
||||
}
|
||||
|
||||
bool active() noexcept {
|
||||
return s_currentContext != Context::None || s_lastContext != Context::None;
|
||||
}
|
||||
|
||||
bool enabled() noexcept {
|
||||
return getSettings().game.enableMenuPointer.getValue();
|
||||
}
|
||||
|
||||
bool mouse_capture_active() noexcept {
|
||||
return enabled() && s_mouseButtonCaptured;
|
||||
}
|
||||
|
||||
const State& state() noexcept {
|
||||
return s_state;
|
||||
}
|
||||
|
||||
bool consume_click() noexcept {
|
||||
if (!s_state.clicked || s_clickConsumed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s_clickConsumed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_dialog_choice(u8 choice, bool clicked) noexcept {
|
||||
s_currentDialogChoice = choice;
|
||||
s_currentDialogChoiceValid = true;
|
||||
s_currentDialogClicked = clicked;
|
||||
}
|
||||
|
||||
bool get_dialog_choice(u8& choice) noexcept {
|
||||
if (s_currentDialogChoiceValid) {
|
||||
choice = s_currentDialogChoice;
|
||||
return true;
|
||||
}
|
||||
if (s_lastDialogChoiceValid) {
|
||||
choice = s_lastDialogChoice;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool consume_dialog_click(u8& choice) noexcept {
|
||||
if (s_currentDialogChoiceValid && s_currentDialogClicked) {
|
||||
choice = s_currentDialogChoice;
|
||||
s_currentDialogClicked = false;
|
||||
return true;
|
||||
}
|
||||
if (s_lastDialogChoiceValid && s_lastDialogClicked) {
|
||||
choice = s_lastDialogChoice;
|
||||
s_lastDialogClicked = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void defer_activation(Context context, u8 target) noexcept {
|
||||
s_deferredActivationContext = context;
|
||||
s_deferredActivationTarget = target;
|
||||
}
|
||||
|
||||
bool consume_deferred_activation(Context context, u8 target) noexcept {
|
||||
if (s_deferredActivationContext != context || s_deferredActivationTarget != target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s_deferredActivationContext = Context::None;
|
||||
s_deferredActivationTarget = 0xFF;
|
||||
return true;
|
||||
}
|
||||
|
||||
void clear_deferred_activation(Context context) noexcept {
|
||||
if (s_deferredActivationContext != context) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_deferredActivationContext = Context::None;
|
||||
s_deferredActivationTarget = 0xFF;
|
||||
}
|
||||
|
||||
u32 suppressed_pad_buttons(u32 port) noexcept {
|
||||
if (port != PAD_CHAN0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return s_suppressedPadHoldMask | s_suppressedPadNextReadMask;
|
||||
}
|
||||
|
||||
void finish_pad_suppression_read(u32 port) noexcept {
|
||||
if (port != PAD_CHAN0) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_suppressedPadNextReadMask = 0;
|
||||
}
|
||||
|
||||
bool hit_rect(f32 left, f32 top, f32 right, f32 bottom, f32 padding) noexcept {
|
||||
const auto& state = menu_pointer::state();
|
||||
if (!state.valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (left > right) {
|
||||
std::swap(left, right);
|
||||
}
|
||||
if (top > bottom) {
|
||||
std::swap(top, bottom);
|
||||
}
|
||||
|
||||
return state.x >= left - padding && state.x <= right + padding && state.y >= top - padding &&
|
||||
state.y <= bottom + padding;
|
||||
}
|
||||
|
||||
bool hit_pane(CPaneMgr* pane, f32 padding) noexcept {
|
||||
if (pane == nullptr || pane->getPanePtr() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Mtx mtx;
|
||||
Vec v0 = pane->getGlobalVtx(&mtx, 0, false, 0);
|
||||
Vec v1 = pane->getGlobalVtx(&mtx, 1, false, 0);
|
||||
Vec v2 = pane->getGlobalVtx(&mtx, 2, false, 0);
|
||||
Vec v3 = pane->getGlobalVtx(&mtx, 3, false, 0);
|
||||
const f32 left = std::min({v0.x, v1.x, v2.x, v3.x});
|
||||
const f32 right = std::max({v0.x, v1.x, v2.x, v3.x});
|
||||
const f32 top = std::min({v0.y, v1.y, v2.y, v3.y});
|
||||
const f32 bottom = std::max({v0.y, v1.y, v2.y, v3.y});
|
||||
return hit_rect(left, top, right, bottom, padding);
|
||||
}
|
||||
|
||||
bool hit_pane(J2DPane* pane, f32 padding) noexcept {
|
||||
if (pane == nullptr || !pane->isVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const JGeometry::TBox2<f32>& bounds = pane->getBounds();
|
||||
return hit_rect(bounds.i.x, bounds.i.y, bounds.f.x, bounds.f.y, padding);
|
||||
}
|
||||
|
||||
} // namespace dusk::menu_pointer
|
||||
+3
-11
@@ -1,4 +1,5 @@
|
||||
#include "dusk/mouse.h"
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
@@ -26,16 +27,7 @@ void reset_deltas() {
|
||||
}
|
||||
|
||||
bool queryMouseAimContext() {
|
||||
if (!getSettings().game.enableMouseAim) {
|
||||
return false;
|
||||
}
|
||||
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
if (link == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return link->checkAimContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10);
|
||||
return getSettings().game.enableMouseAim.getValue() && dCamera_c::isAimActive();
|
||||
}
|
||||
|
||||
bool wantMouseCapture() {
|
||||
@@ -50,7 +42,7 @@ bool isWindowFocused(SDL_Window* window) {
|
||||
}
|
||||
|
||||
bool shouldCaptureMouse(SDL_Window* window) {
|
||||
if (window == nullptr || ui::any_document_visible()) {
|
||||
if (window == nullptr || ui::any_document_visible() || menu_pointer::active()) {
|
||||
return false;
|
||||
}
|
||||
return wantMouseCapture() && isWindowFocused(window);
|
||||
|
||||
@@ -95,6 +95,9 @@ UserSettings g_userSettings = {
|
||||
.mouseCameraSensitivity {"game.mouseCameraSensitivity", 1.0f},
|
||||
.invertMouseY {"game.invertMouseY", false},
|
||||
.freeCamera {"game.freeCamera", false},
|
||||
.enableTouchControls {"game.enableTouchControls", false},
|
||||
.enableMenuPointer {"game.enableMenuPointer", true},
|
||||
.touchControlsLayout {"game.touchControlsLayout", ui::ControlLayout{}},
|
||||
.invertCameraXAxis {"game.invertCameraXAxis", false},
|
||||
.invertCameraYAxis {"game.invertCameraYAxis", false},
|
||||
.invertFirstPersonXAxis {"game.invertFirstPersonXAxis", false},
|
||||
@@ -103,6 +106,8 @@ UserSettings g_userSettings = {
|
||||
.invertAirSwimY {"game.invertAirSwimY", false},
|
||||
.freeCameraXSensitivity {"game.freeCameraXSensitivity", 1.0f},
|
||||
.freeCameraYSensitivity {"game.freeCameraYSensitivity", 1.0f},
|
||||
.touchCameraXSensitivity {"game.touchCameraXSensitivity", 1.0f},
|
||||
.touchCameraYSensitivity {"game.touchCameraYSensitivity", 1.0f},
|
||||
.debugFlyCam {"game.debugFlyCam", false},
|
||||
.debugFlyCamLockEvents {"game.debugFlyCamLockEvents", true},
|
||||
.allowBackgroundInput {"game.allowBackgroundInput", true},
|
||||
@@ -176,6 +181,18 @@ UserSettings g_userSettings = {
|
||||
ActionBindConfigVar{"actionBindings.callMidna_port2", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.callMidna_port3", PAD_NATIVE_BUTTON_INVALID},
|
||||
},
|
||||
.openMapScreen {
|
||||
ActionBindConfigVar{"actionBindings.openMapScreen_port0", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.openMapScreen_port1", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.openMapScreen_port2", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.openMapScreen_port3", PAD_NATIVE_BUTTON_INVALID},
|
||||
},
|
||||
.toggleMinimap {
|
||||
ActionBindConfigVar{"actionBindings.toggleMinimap_port0", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.toggleMinimap_port1", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.toggleMinimap_port2", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.toggleMinimap_port3", PAD_NATIVE_BUTTON_INVALID},
|
||||
},
|
||||
.openDusklightMenu {
|
||||
ActionBindConfigVar{"actionBindings.openDusklightMenu_port0", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.openDusklightMenu_port1", PAD_NATIVE_BUTTON_INVALID},
|
||||
@@ -246,6 +263,8 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.invertAirSwimY);
|
||||
Register(g_userSettings.game.freeCameraXSensitivity);
|
||||
Register(g_userSettings.game.freeCameraYSensitivity);
|
||||
Register(g_userSettings.game.touchCameraXSensitivity);
|
||||
Register(g_userSettings.game.touchCameraYSensitivity);
|
||||
Register(g_userSettings.game.minimalHUD);
|
||||
Register(g_userSettings.game.hudScale);
|
||||
Register(g_userSettings.game.pauseOnFocusLost);
|
||||
@@ -310,6 +329,9 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.mouseCameraSensitivity);
|
||||
Register(g_userSettings.game.invertMouseY);
|
||||
Register(g_userSettings.game.freeCamera);
|
||||
Register(g_userSettings.game.enableTouchControls);
|
||||
Register(g_userSettings.game.enableMenuPointer);
|
||||
Register(g_userSettings.game.touchControlsLayout);
|
||||
Register(g_userSettings.game.debugFlyCam);
|
||||
Register(g_userSettings.game.debugFlyCamLockEvents);
|
||||
Register(g_userSettings.game.allowBackgroundInput);
|
||||
@@ -337,6 +359,14 @@ void registerSettings() {
|
||||
Register(g_userSettings.actionBindings.callMidna[1]);
|
||||
Register(g_userSettings.actionBindings.callMidna[2]);
|
||||
Register(g_userSettings.actionBindings.callMidna[3]);
|
||||
Register(g_userSettings.actionBindings.openMapScreen[0]);
|
||||
Register(g_userSettings.actionBindings.openMapScreen[1]);
|
||||
Register(g_userSettings.actionBindings.openMapScreen[2]);
|
||||
Register(g_userSettings.actionBindings.openMapScreen[3]);
|
||||
Register(g_userSettings.actionBindings.toggleMinimap[0]);
|
||||
Register(g_userSettings.actionBindings.toggleMinimap[1]);
|
||||
Register(g_userSettings.actionBindings.toggleMinimap[2]);
|
||||
Register(g_userSettings.actionBindings.toggleMinimap[3]);
|
||||
Register(g_userSettings.actionBindings.openDusklightMenu[0]);
|
||||
Register(g_userSettings.actionBindings.openDusklightMenu[1]);
|
||||
Register(g_userSettings.actionBindings.openDusklightMenu[2]);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "dusk/touch_camera.h"
|
||||
|
||||
namespace dusk::touch_camera {
|
||||
namespace {
|
||||
float s_yaw_dp = 0.0f;
|
||||
float s_pitch_dp = 0.0f;
|
||||
} // namespace
|
||||
|
||||
void add_delta(float yaw_dp, float pitch_dp) noexcept {
|
||||
s_yaw_dp += yaw_dp;
|
||||
s_pitch_dp += pitch_dp;
|
||||
}
|
||||
|
||||
bool consume_delta(float& yaw_dp, float& pitch_dp) noexcept {
|
||||
yaw_dp = s_yaw_dp;
|
||||
pitch_dp = s_pitch_dp;
|
||||
clear();
|
||||
return yaw_dp != 0.0f || pitch_dp != 0.0f;
|
||||
}
|
||||
|
||||
void clear() noexcept {
|
||||
s_yaw_dp = 0.0f;
|
||||
s_pitch_dp = 0.0f;
|
||||
}
|
||||
|
||||
} // namespace dusk::touch_camera
|
||||
@@ -19,8 +19,6 @@ public:
|
||||
void set_text(const Rml::String& text);
|
||||
Button& on_pressed(ButtonCallback callback);
|
||||
|
||||
const Rml::String& get_text() const { return mProps.text; }
|
||||
|
||||
private:
|
||||
void update_props(Props props);
|
||||
|
||||
|
||||
@@ -861,6 +861,20 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
|
||||
break;
|
||||
}
|
||||
case Page::Rumble: {
|
||||
if (PADCanForceDeviceRumble(static_cast<u32>(port))) {
|
||||
pane.add_child<BoolButton>(BoolButton::Props{
|
||||
.key = "Use Device Haptics",
|
||||
.getValue = [port] { return PADGetForceDeviceRumble(static_cast<u32>(port)); },
|
||||
.setValue =
|
||||
[port](bool value) {
|
||||
PADSetForceDeviceRumble(static_cast<u32>(port), value ? TRUE : FALSE);
|
||||
PADSerializeMappings();
|
||||
},
|
||||
.isDisabled = [this] { return mRumbleTestActive; },
|
||||
});
|
||||
pane.add_text("Use native device haptics instead of controller rumble. "
|
||||
"Useful for devices with built-in gamepads.");
|
||||
}
|
||||
auto& rumbleTest = pane.add_select_button({
|
||||
.key = "Test Rumble",
|
||||
.getValue =
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
struct EquipTarget {
|
||||
float left = 0.0f;
|
||||
float top = 0.0f;
|
||||
float width = 0.0f;
|
||||
float height = 0.0f;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
enum class Control {
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
L,
|
||||
R,
|
||||
FIRST_PERSON,
|
||||
ITEMS,
|
||||
COLLECTIONS,
|
||||
MAP,
|
||||
SKIP,
|
||||
DPAD_UP,
|
||||
DPAD_DOWN,
|
||||
DPAD_LEFT,
|
||||
DPAD_RIGHT,
|
||||
COUNT,
|
||||
};
|
||||
|
||||
enum class ControlAnchor : u8 {
|
||||
None,
|
||||
Top,
|
||||
Left,
|
||||
Bottom,
|
||||
Right,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
};
|
||||
|
||||
struct ControlProps {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float w = 0.0f;
|
||||
float h = 0.0f;
|
||||
float scale = 1.0f;
|
||||
ControlAnchor anchor = ControlAnchor::None;
|
||||
};
|
||||
|
||||
struct ControlRect {
|
||||
float l = 0.0f;
|
||||
float t = 0.0f;
|
||||
float w = 0.0f;
|
||||
float h = 0.0f;
|
||||
};
|
||||
|
||||
struct ResolvedControlLayout {
|
||||
ControlRect visual;
|
||||
ControlRect box;
|
||||
float scale = 1.0f;
|
||||
};
|
||||
|
||||
struct ControlLayoutSize {
|
||||
float w = 0.0f;
|
||||
float h = 0.0f;
|
||||
};
|
||||
|
||||
struct ControlLayout {
|
||||
static constexpr int Version = 1;
|
||||
|
||||
int version = Version;
|
||||
std::map<std::string, ControlProps, std::less<> > controls;
|
||||
};
|
||||
|
||||
constexpr std::array<std::string_view, 9> kControlLayoutIds = {
|
||||
"actionBar",
|
||||
"buttonA",
|
||||
"buttonB",
|
||||
"buttonX",
|
||||
"buttonY",
|
||||
"buttonZ",
|
||||
"skip",
|
||||
"triggerL",
|
||||
"triggerR",
|
||||
};
|
||||
|
||||
constexpr bool is_control_layout_id(std::string_view id) noexcept {
|
||||
for (const auto knownId : kControlLayoutIds) {
|
||||
if (id == knownId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr ControlRect resolve_anchored_rect(
|
||||
ControlAnchor anchor, float x, float y, float w, float h, ControlLayoutSize docSize) noexcept {
|
||||
switch (anchor) {
|
||||
case ControlAnchor::None:
|
||||
return {x * docSize.w - w * 0.5f, y * docSize.h - h * 0.5f, w, h};
|
||||
case ControlAnchor::Top:
|
||||
return {x * docSize.w - w * 0.5f, y, w, h};
|
||||
case ControlAnchor::Bottom:
|
||||
return {x * docSize.w - w * 0.5f, docSize.h - y - h, w, h};
|
||||
case ControlAnchor::Left:
|
||||
return {x, y * docSize.h - h * 0.5f, w, h};
|
||||
case ControlAnchor::Right:
|
||||
return {docSize.w - x - w, y * docSize.h - h * 0.5f, w, h};
|
||||
case ControlAnchor::TopLeft:
|
||||
return {x, y, w, h};
|
||||
case ControlAnchor::TopRight:
|
||||
return {docSize.w - x - w, y, w, h};
|
||||
case ControlAnchor::BottomLeft:
|
||||
return {x, docSize.h - y - h, w, h};
|
||||
case ControlAnchor::BottomRight:
|
||||
return {docSize.w - x - w, docSize.h - y - h, w, h};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
constexpr ResolvedControlLayout resolve_control_layout(
|
||||
ControlProps props, ControlLayoutSize docSize) noexcept {
|
||||
const float visualW = props.w * props.scale;
|
||||
const float visualH = props.h * props.scale;
|
||||
const ControlRect visual =
|
||||
resolve_anchored_rect(props.anchor, props.x, props.y, visualW, visualH, docSize);
|
||||
const ControlRect box = {
|
||||
visual.l + (visual.w - props.w) * 0.5f,
|
||||
visual.t + (visual.h - props.h) * 0.5f,
|
||||
props.w,
|
||||
props.h,
|
||||
};
|
||||
return {
|
||||
.visual = visual,
|
||||
.box = box,
|
||||
.scale = props.scale,
|
||||
};
|
||||
}
|
||||
|
||||
constexpr ControlProps encode_control_props(ControlRect visual, ControlLayoutSize docSize,
|
||||
ControlProps props, ControlAnchor anchor) noexcept {
|
||||
props.anchor = anchor;
|
||||
|
||||
switch (anchor) {
|
||||
case ControlAnchor::None:
|
||||
props.x = (visual.l + visual.w * 0.5f) / docSize.w;
|
||||
props.y = (visual.t + visual.h * 0.5f) / docSize.h;
|
||||
break;
|
||||
case ControlAnchor::Top:
|
||||
props.x = (visual.l + visual.w * 0.5f) / docSize.w;
|
||||
props.y = visual.t;
|
||||
break;
|
||||
case ControlAnchor::Bottom:
|
||||
props.x = (visual.l + visual.w * 0.5f) / docSize.w;
|
||||
props.y = docSize.h - visual.t - visual.h;
|
||||
break;
|
||||
case ControlAnchor::Left:
|
||||
props.x = visual.l;
|
||||
props.y = (visual.t + visual.h * 0.5f) / docSize.h;
|
||||
break;
|
||||
case ControlAnchor::Right:
|
||||
props.x = docSize.w - visual.l - visual.w;
|
||||
props.y = (visual.t + visual.h * 0.5f) / docSize.h;
|
||||
break;
|
||||
case ControlAnchor::TopLeft:
|
||||
props.x = visual.l;
|
||||
props.y = visual.t;
|
||||
break;
|
||||
case ControlAnchor::TopRight:
|
||||
props.x = docSize.w - visual.l - visual.w;
|
||||
props.y = visual.t;
|
||||
break;
|
||||
case ControlAnchor::BottomLeft:
|
||||
props.x = visual.l;
|
||||
props.y = docSize.h - visual.t - visual.h;
|
||||
break;
|
||||
case ControlAnchor::BottomRight:
|
||||
props.x = docSize.w - visual.l - visual.w;
|
||||
props.y = docSize.h - visual.t - visual.h;
|
||||
break;
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
@@ -19,11 +20,15 @@ Rml::ElementDocument* load_document(const Rml::String& source) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Document::Document(const Rml::String& source) : mDocument(load_document(source)) {
|
||||
Document::Document(const Rml::String& source, bool passive)
|
||||
: mDocument(load_document(source)), mPassive(passive) {
|
||||
// Block events while hidden (except for Menu command); play nav sounds when visible
|
||||
listen(
|
||||
Rml::EventId::Keydown,
|
||||
[this](Rml::Event& event) {
|
||||
if (mPassive) {
|
||||
return;
|
||||
}
|
||||
const auto cmd = map_nav_event(event);
|
||||
if (cmd != NavCommand::Menu && !visible()) {
|
||||
event.StopImmediatePropagation();
|
||||
@@ -40,11 +45,14 @@ Document::Document(const Rml::String& source) : mDocument(load_document(source))
|
||||
listen(Rml::EventId::Scroll, blockUnlessVisible, true);
|
||||
|
||||
listen(Rml::EventId::Keydown, [this](Rml::Event& event) {
|
||||
const auto cmd = map_nav_event(event);
|
||||
if (cmd == NavCommand::None) {
|
||||
if (mPassive) {
|
||||
auto* doc = top_document();
|
||||
if (doc != nullptr && doc->handle_nav_event(event)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (handle_nav_command(event, cmd)) {
|
||||
if (handle_nav_event(event)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
@@ -97,6 +105,18 @@ void Document::listen(Rml::Element* element, Rml::EventId event,
|
||||
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
|
||||
}
|
||||
|
||||
void Document::listen(Rml::Element* element, const Rml::String& event,
|
||||
ScopedEventListener::Callback callback, bool capture) {
|
||||
if (element == nullptr) {
|
||||
element = mDocument;
|
||||
}
|
||||
if (element == nullptr || event.empty() || !callback) {
|
||||
return;
|
||||
}
|
||||
mListeners.emplace_back(
|
||||
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
|
||||
}
|
||||
|
||||
bool Document::visible() const {
|
||||
if (mDocument == nullptr) {
|
||||
return false;
|
||||
@@ -104,6 +124,14 @@ bool Document::visible() const {
|
||||
return *mDocument->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Visible;
|
||||
}
|
||||
|
||||
bool Document::handle_nav_event(Rml::Event& event) {
|
||||
const auto cmd = map_nav_event(event);
|
||||
if (cmd == NavCommand::None) {
|
||||
return false;
|
||||
}
|
||||
return handle_nav_command(event, cmd);
|
||||
}
|
||||
|
||||
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Menu) {
|
||||
mDoAud_seStartMenu(visible() ? kSoundMenuClose : kSoundMenuOpen);
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace dusk::ui {
|
||||
|
||||
class Document {
|
||||
public:
|
||||
Document(const Rml::String& source);
|
||||
explicit Document(const Rml::String& source, bool passive = false);
|
||||
virtual ~Document();
|
||||
|
||||
Document(const Document&) = delete;
|
||||
@@ -21,9 +21,15 @@ public:
|
||||
|
||||
void listen(Rml::Element* element, Rml::EventId event, ScopedEventListener::Callback callback,
|
||||
bool capture = false);
|
||||
void listen(Rml::Element* element, const Rml::String& event,
|
||||
ScopedEventListener::Callback callback, bool capture = false);
|
||||
void listen(Rml::EventId event, ScopedEventListener::Callback callback, bool capture = false) {
|
||||
listen(mDocument, event, std::move(callback), capture);
|
||||
}
|
||||
void listen(
|
||||
const Rml::String& event, ScopedEventListener::Callback callback, bool capture = false) {
|
||||
listen(mDocument, event, std::move(callback), capture);
|
||||
}
|
||||
void toggle() {
|
||||
if (visible()) {
|
||||
hide(false);
|
||||
@@ -43,6 +49,8 @@ public:
|
||||
bool pending_close() const { return mPendingClose; }
|
||||
bool closed() const { return mClosed; }
|
||||
|
||||
bool handle_nav_event(Rml::Event& event);
|
||||
|
||||
protected:
|
||||
virtual bool handle_nav_command(Rml::Event& event, NavCommand cmd);
|
||||
|
||||
@@ -50,6 +58,7 @@ protected:
|
||||
std::vector<std::unique_ptr<ScopedEventListener> > mListeners;
|
||||
bool mPendingClose = false;
|
||||
bool mClosed = false;
|
||||
bool mPassive = false;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -0,0 +1,899 @@
|
||||
#include "icon_provider.hpp"
|
||||
|
||||
#include "d/dolzel.h" // IWYU pragma: keep
|
||||
|
||||
#ifdef AURORA_ENABLE_RMLUI
|
||||
|
||||
#include <SDL3/SDL_surface.h>
|
||||
#include <aurora/lib/gfx/texture_convert.hpp>
|
||||
#include <aurora/rmlui.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "JSystem/J2DGraph/J2DPicture.h"
|
||||
#include "JSystem/JUtility/JUTTexture.h"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "d/d_item_data.h"
|
||||
#include "d/d_meter2_info.h"
|
||||
#include "d/d_pane_class.h"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kScheme = "item";
|
||||
constexpr std::string_view kSourcePrefix = "item://";
|
||||
constexpr std::string_view kMeterScheme = "meter";
|
||||
constexpr std::string_view kMeterSourcePrefix = "meter://";
|
||||
constexpr size_t kItemTextureBufferSize = 0xC00;
|
||||
constexpr size_t kMaxCachedIcons = 128;
|
||||
constexpr uint64_t kMeterTextureSourceSlots = 8;
|
||||
constexpr uint32_t kMinRenderedPaneIconSize = 128;
|
||||
constexpr uint32_t kMaxRenderedPaneIconSize = 1024;
|
||||
|
||||
struct alignas(32) ItemTextureBuffer {
|
||||
std::array<std::byte, kItemTextureBufferSize> bytes{};
|
||||
|
||||
std::byte* data() noexcept { return bytes.data(); }
|
||||
const std::byte* data() const noexcept { return bytes.data(); }
|
||||
};
|
||||
|
||||
struct CachedIcon {
|
||||
std::vector<uint8_t> pixels;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
};
|
||||
|
||||
struct RuntimeIconState {
|
||||
CachedIcon icon;
|
||||
uint64_t revision = 0;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
struct LayerColors {
|
||||
JUtility::TColor black;
|
||||
JUtility::TColor white;
|
||||
std::array<JUtility::TColor, 4> corner;
|
||||
};
|
||||
|
||||
struct RectF {
|
||||
float left = std::numeric_limits<float>::max();
|
||||
float top = std::numeric_limits<float>::max();
|
||||
float right = std::numeric_limits<float>::lowest();
|
||||
float bottom = std::numeric_limits<float>::lowest();
|
||||
|
||||
bool valid() const noexcept { return left < right && top < bottom; }
|
||||
float width() const noexcept { return right - left; }
|
||||
float height() const noexcept { return bottom - top; }
|
||||
|
||||
void include(float x, float y) noexcept {
|
||||
if (!std::isfinite(x) || !std::isfinite(y)) {
|
||||
return;
|
||||
}
|
||||
left = std::min(left, x);
|
||||
top = std::min(top, y);
|
||||
right = std::max(right, x);
|
||||
bottom = std::max(bottom, y);
|
||||
}
|
||||
|
||||
void include(const RectF& rect) noexcept {
|
||||
if (!rect.valid()) {
|
||||
return;
|
||||
}
|
||||
include(rect.left, rect.top);
|
||||
include(rect.right, rect.bottom);
|
||||
}
|
||||
};
|
||||
|
||||
struct PictureLayer {
|
||||
J2DPicture* picture = nullptr;
|
||||
RectF rect;
|
||||
uint8_t alpha = 0;
|
||||
};
|
||||
|
||||
struct SurfaceDeleter {
|
||||
void operator()(SDL_Surface* surface) const noexcept { SDL_DestroySurface(surface); }
|
||||
};
|
||||
|
||||
using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
|
||||
|
||||
std::unordered_map<std::string, CachedIcon>& icon_cache() {
|
||||
static auto* cache = new std::unordered_map<std::string, CachedIcon>();
|
||||
return *cache;
|
||||
}
|
||||
|
||||
RuntimeIconState& midna_icon_state() {
|
||||
static auto* state = new RuntimeIconState();
|
||||
return *state;
|
||||
}
|
||||
|
||||
std::string_view strip_query(std::string_view path) noexcept {
|
||||
const auto queryPos = path.find_first_of("?#");
|
||||
if (queryPos != std::string_view::npos) {
|
||||
path = path.substr(0, queryPos);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
std::optional<u8> parse_item_no(std::string_view text) noexcept {
|
||||
if (text.starts_with("0x") || text.starts_with("0X")) {
|
||||
text.remove_prefix(2);
|
||||
}
|
||||
unsigned value = 0;
|
||||
const auto* first = text.data();
|
||||
const auto* last = text.data() + text.size();
|
||||
const auto [ptr, ec] = std::from_chars(first, last, value, 16);
|
||||
if (ec != std::errc() || ptr != last || value > 0xFF) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<u8>(value);
|
||||
}
|
||||
|
||||
bool is_valid_icon_item(u8 itemNo) noexcept {
|
||||
return itemNo != 0 && itemNo != dItemNo_NONE_e;
|
||||
}
|
||||
|
||||
u8 item_icon_texture_item(u8 itemNo) noexcept {
|
||||
if (itemNo == dItemNo_LIGHT_ARROW_e) {
|
||||
return dItemNo_BOW_e;
|
||||
}
|
||||
return itemNo;
|
||||
}
|
||||
|
||||
std::optional<u8> selected_slot_item(int slot) noexcept {
|
||||
const u8 itemNo = dComIfGp_getSelectItem(slot);
|
||||
if (!is_valid_icon_item(itemNo)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return item_icon_texture_item(itemNo);
|
||||
}
|
||||
|
||||
bool is_sword_item(u8 itemNo) noexcept {
|
||||
switch (itemNo) {
|
||||
case dItemNo_WOOD_STICK_e:
|
||||
case dItemNo_SWORD_e:
|
||||
case dItemNo_MASTER_SWORD_e:
|
||||
case dItemNo_LIGHT_SWORD_e:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<u8> b_button_item() noexcept {
|
||||
const u8 action = dComIfGp_getAStatus();
|
||||
if (action == 0x26 || action == 0x2E) {
|
||||
const u8 sword = dComIfGs_getSelectEquipSword();
|
||||
if (is_sword_item(sword)) {
|
||||
return sword;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
if (action == 0x4F) {
|
||||
return dItemNo_LURE_ROD_e;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<u8> item_for_source(std::string_view source) noexcept {
|
||||
if (!source.starts_with(kSourcePrefix)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string_view path = strip_query(source.substr(kSourcePrefix.size()));
|
||||
if (path.starts_with("item/")) {
|
||||
path.remove_prefix(5);
|
||||
const auto itemNo = parse_item_no(path);
|
||||
if (itemNo && is_valid_icon_item(*itemNo)) {
|
||||
return item_icon_texture_item(*itemNo);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
if (path == "slot/x") {
|
||||
return selected_slot_item(0);
|
||||
}
|
||||
if (path == "slot/y") {
|
||||
return selected_slot_item(1);
|
||||
}
|
||||
if (path == "button/b") {
|
||||
return b_button_item();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
uint32_t item_revision(u8 itemNo) noexcept {
|
||||
uint32_t revision = itemNo;
|
||||
revision = revision * 131u + g_meter2_info.getItemType(itemNo);
|
||||
|
||||
if (itemNo == dItemNo_KANTERA_e || itemNo == dItemNo_KANTERA2_e) {
|
||||
revision = revision * 131u + (dComIfGs_getOil() == 0 ? 0u : 1u);
|
||||
}
|
||||
if (itemNo == dItemNo_COPY_ROD_e) {
|
||||
auto* player = daPy_getPlayerActorClass();
|
||||
revision = revision * 131u + (player != nullptr && player->checkCopyRodTopUse() ? 1u : 0u);
|
||||
}
|
||||
return revision;
|
||||
}
|
||||
|
||||
std::string item_source_for_item(u8 itemNo) {
|
||||
itemNo = item_icon_texture_item(itemNo);
|
||||
return fmt::format("{}://item/{:02x}?rev={:08x}", kScheme, itemNo, item_revision(itemNo));
|
||||
}
|
||||
|
||||
std::optional<int> selected_slot_count(int slot) noexcept {
|
||||
const u8 itemNo = dComIfGp_getSelectItem(slot);
|
||||
if (!is_valid_icon_item(itemNo)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (item_icon_texture_item(itemNo) == dItemNo_KANTERA_e ||
|
||||
item_icon_texture_item(itemNo) == dItemNo_KANTERA2_e)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
int max = 0;
|
||||
switch (itemNo) {
|
||||
case dItemNo_BOW_e:
|
||||
case dItemNo_LIGHT_ARROW_e:
|
||||
case dItemNo_ARROW_LV1_e:
|
||||
case dItemNo_ARROW_LV2_e:
|
||||
case dItemNo_ARROW_LV3_e:
|
||||
case dItemNo_HAWK_ARROW_e:
|
||||
count = dComIfGs_getArrowNum();
|
||||
max = dComIfGs_getArrowMax();
|
||||
break;
|
||||
case dItemNo_BOMB_ARROW_e:
|
||||
count = std::min<int>(dComIfGp_getSelectItemNum(slot), dComIfGs_getArrowNum());
|
||||
max = std::max<int>(dComIfGp_getSelectItemMaxNum(slot), dComIfGs_getArrowMax());
|
||||
break;
|
||||
default:
|
||||
count = dComIfGp_getSelectItemNum(slot);
|
||||
max = dComIfGp_getSelectItemMaxNum(slot);
|
||||
break;
|
||||
}
|
||||
if (max <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::clamp(count, 0, max);
|
||||
}
|
||||
|
||||
aurora::gfx::ConvertedTexture decode_timg(const ResTIMG* image) {
|
||||
if (image == nullptr || image->width.host() == 0 || image->height.host() == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto* base = reinterpret_cast<const uint8_t*>(image);
|
||||
const auto width = image->width.host();
|
||||
const auto height = image->height.host();
|
||||
const uint32_t textureSize = GXGetTexBufferSize(width, height, image->format, GX_FALSE, 0);
|
||||
const auto* textureData = base + static_cast<int32_t>(image->imageOffset);
|
||||
|
||||
if (image->indexTexture != 0) {
|
||||
const auto* paletteData = base + static_cast<int32_t>(image->paletteOffset);
|
||||
return aurora::gfx::convert_texture_palette(image->format, width, height, 1,
|
||||
aurora::ArrayRef{textureData, textureSize}, static_cast<GXTlutFmt>(image->colorFormat),
|
||||
image->numColors,
|
||||
aurora::ArrayRef{paletteData, static_cast<size_t>(image->numColors) * 2});
|
||||
}
|
||||
|
||||
return aurora::gfx::convert_texture(
|
||||
image->format, width, height, 1, aurora::ArrayRef{textureData, textureSize});
|
||||
}
|
||||
|
||||
uint8_t lerp_u8(uint8_t a, uint8_t b, uint32_t t) noexcept {
|
||||
return static_cast<uint8_t>(
|
||||
(static_cast<uint32_t>(a) * (255u - t) + static_cast<uint32_t>(b) * t) / 255u);
|
||||
}
|
||||
|
||||
JUtility::TColor lerp_color(
|
||||
const JUtility::TColor& a, const JUtility::TColor& b, uint32_t t) noexcept {
|
||||
return {
|
||||
lerp_u8(a.r, b.r, t),
|
||||
lerp_u8(a.g, b.g, t),
|
||||
lerp_u8(a.b, b.b, t),
|
||||
lerp_u8(a.a, b.a, t),
|
||||
};
|
||||
}
|
||||
|
||||
JUtility::TColor bilerp_corner(
|
||||
const LayerColors& colors, uint32_t x, uint32_t y, uint32_t width, uint32_t height) noexcept {
|
||||
const uint32_t u = width > 1 ? (x * 255u) / (width - 1u) : 0u;
|
||||
const uint32_t v = height > 1 ? (y * 255u) / (height - 1u) : 0u;
|
||||
const JUtility::TColor top = lerp_color(colors.corner[0], colors.corner[1], u);
|
||||
const JUtility::TColor bottom = lerp_color(colors.corner[2], colors.corner[3], u);
|
||||
return lerp_color(top, bottom, v);
|
||||
}
|
||||
|
||||
std::array<uint8_t, 4> apply_layer_colors(std::span<const uint8_t, 4> src,
|
||||
const LayerColors& colors, uint32_t x, uint32_t y, uint32_t width, uint32_t height) noexcept {
|
||||
std::array out{
|
||||
lerp_u8(colors.black.r, colors.white.r, src[0]),
|
||||
lerp_u8(colors.black.g, colors.white.g, src[1]),
|
||||
lerp_u8(colors.black.b, colors.white.b, src[2]),
|
||||
src[3],
|
||||
};
|
||||
|
||||
const auto corner = bilerp_corner(colors, x, y, width, height);
|
||||
out[0] = static_cast<uint8_t>((static_cast<uint32_t>(out[0]) * corner.r) / 255u);
|
||||
out[1] = static_cast<uint8_t>((static_cast<uint32_t>(out[1]) * corner.g) / 255u);
|
||||
out[2] = static_cast<uint8_t>((static_cast<uint32_t>(out[2]) * corner.b) / 255u);
|
||||
out[3] = static_cast<uint8_t>((static_cast<uint32_t>(out[3]) * corner.a) / 255u);
|
||||
return out;
|
||||
}
|
||||
|
||||
void blend_premultiplied(uint8_t* dst, const std::array<uint8_t, 4>& src) noexcept {
|
||||
const uint32_t srcAlpha = src[3];
|
||||
const uint32_t invAlpha = 255u - srcAlpha;
|
||||
const uint32_t srcR = (static_cast<uint32_t>(src[0]) * srcAlpha) / 255u;
|
||||
const uint32_t srcG = (static_cast<uint32_t>(src[1]) * srcAlpha) / 255u;
|
||||
const uint32_t srcB = (static_cast<uint32_t>(src[2]) * srcAlpha) / 255u;
|
||||
|
||||
dst[0] = static_cast<uint8_t>(
|
||||
std::min(255u, srcR + (static_cast<uint32_t>(dst[0]) * invAlpha) / 255u));
|
||||
dst[1] = static_cast<uint8_t>(
|
||||
std::min(255u, srcG + (static_cast<uint32_t>(dst[1]) * invAlpha) / 255u));
|
||||
dst[2] = static_cast<uint8_t>(
|
||||
std::min(255u, srcB + (static_cast<uint32_t>(dst[2]) * invAlpha) / 255u));
|
||||
dst[3] = static_cast<uint8_t>(
|
||||
std::min(255u, srcAlpha + (static_cast<uint32_t>(dst[3]) * invAlpha) / 255u));
|
||||
}
|
||||
|
||||
LayerColors layer_colors(const J2DPicture& picture) noexcept {
|
||||
return {
|
||||
.black = picture.getBlack(),
|
||||
.white = picture.getWhite(),
|
||||
.corner = {picture.corner(0), picture.corner(1), picture.corner(2), picture.corner(3)},
|
||||
};
|
||||
}
|
||||
|
||||
LayerColors layer_colors(J2DPicture& picture, uint8_t alpha) noexcept {
|
||||
std::array<JUtility::TColor, 4> corners{};
|
||||
picture.getNewColor(corners.data());
|
||||
for (auto& corner : corners) {
|
||||
corner.a = static_cast<uint8_t>((static_cast<uint32_t>(corner.a) * alpha) / 255u);
|
||||
}
|
||||
return {
|
||||
.black = picture.getBlack(),
|
||||
.white = picture.getWhite(),
|
||||
.corner = corners,
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<CachedIcon> render_item_icon(u8 itemNo) {
|
||||
std::array<ItemTextureBuffer, 4> buffers{};
|
||||
std::array<J2DPicture, 4> pictures{};
|
||||
|
||||
const int textureCount =
|
||||
dMeter2Info_readItemTexture(itemNo, buffers[0].data(), &pictures[0], buffers[1].data(),
|
||||
&pictures[1], buffers[2].data(), &pictures[2], buffers[3].data(), &pictures[3], -1);
|
||||
if (textureCount <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::array<aurora::gfx::ConvertedTexture, 4> decodedLayers{};
|
||||
std::array<LayerColors, 4> colors{};
|
||||
int decodedCount = 0;
|
||||
for (int i = 0; i < textureCount && i < static_cast<int>(decodedLayers.size()); ++i) {
|
||||
auto decoded = decode_timg(reinterpret_cast<const ResTIMG*>(buffers[i].data()));
|
||||
if (decoded.data.empty()) {
|
||||
continue;
|
||||
}
|
||||
colors[decodedCount] = layer_colors(pictures[i]);
|
||||
decodedLayers[decodedCount] = std::move(decoded);
|
||||
++decodedCount;
|
||||
}
|
||||
if (decodedCount == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CachedIcon icon{
|
||||
.width = decodedLayers[0].width,
|
||||
.height = decodedLayers[0].height,
|
||||
};
|
||||
icon.pixels.assign(static_cast<size_t>(icon.width) * static_cast<size_t>(icon.height) * 4, 0);
|
||||
|
||||
for (int layer = 0; layer < decodedCount; ++layer) {
|
||||
const auto& decoded = decodedLayers[layer];
|
||||
for (uint32_t y = 0; y < icon.height; ++y) {
|
||||
const uint32_t sourceY = decoded.height > 0 ? (y * decoded.height) / icon.height : 0;
|
||||
for (uint32_t x = 0; x < icon.width; ++x) {
|
||||
const uint32_t sourceX = decoded.width > 0 ? (x * decoded.width) / icon.width : 0;
|
||||
const size_t sourceOffset =
|
||||
(static_cast<size_t>(sourceY) * decoded.width + static_cast<size_t>(sourceX)) *
|
||||
4;
|
||||
if (sourceOffset + 3 >= decoded.data.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::span<const uint8_t, 4> sourcePixel(
|
||||
decoded.data.data() + sourceOffset, 4);
|
||||
const auto pixel =
|
||||
apply_layer_colors(sourcePixel, colors[layer], x, y, icon.width, icon.height);
|
||||
uint8_t* destination =
|
||||
icon.pixels.data() +
|
||||
(static_cast<size_t>(y) * icon.width + static_cast<size_t>(x)) * 4;
|
||||
blend_premultiplied(destination, pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
SurfacePtr create_rgba_surface(uint32_t width, uint32_t height) {
|
||||
if (width == 0 || height == 0 ||
|
||||
width > static_cast<uint32_t>(std::numeric_limits<int>::max()) ||
|
||||
height > static_cast<uint32_t>(std::numeric_limits<int>::max()))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return SurfacePtr{SDL_CreateSurface(
|
||||
static_cast<int>(width), static_cast<int>(height), SDL_PIXELFORMAT_RGBA32)};
|
||||
}
|
||||
|
||||
bool lock_surface(SDL_Surface* surface) noexcept {
|
||||
return surface != nullptr && (!SDL_MUSTLOCK(surface) || SDL_LockSurface(surface));
|
||||
}
|
||||
|
||||
void unlock_surface(SDL_Surface* surface) noexcept {
|
||||
if (surface != nullptr && SDL_MUSTLOCK(surface)) {
|
||||
SDL_UnlockSurface(surface);
|
||||
}
|
||||
}
|
||||
|
||||
SurfacePtr create_layer_surface(
|
||||
const aurora::gfx::ConvertedTexture& decoded, const LayerColors& colors) {
|
||||
if (decoded.width == 0 || decoded.height == 0 || decoded.data.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto surface = create_rgba_surface(decoded.width, decoded.height);
|
||||
if (!surface || !lock_surface(surface.get())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (uint32_t y = 0; y < decoded.height; ++y) {
|
||||
auto* destination = static_cast<uint8_t*>(surface->pixels) +
|
||||
static_cast<size_t>(y) * static_cast<size_t>(surface->pitch);
|
||||
for (uint32_t x = 0; x < decoded.width; ++x) {
|
||||
const size_t sourceOffset =
|
||||
(static_cast<size_t>(y) * decoded.width + static_cast<size_t>(x)) * 4;
|
||||
if (sourceOffset + 3 >= decoded.data.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::span<const uint8_t, 4> sourcePixel(decoded.data.data() + sourceOffset, 4);
|
||||
const auto pixel =
|
||||
apply_layer_colors(sourcePixel, colors, x, y, decoded.width, decoded.height);
|
||||
std::memcpy(destination + static_cast<size_t>(x) * 4, pixel.data(), pixel.size());
|
||||
}
|
||||
}
|
||||
|
||||
unlock_surface(surface.get());
|
||||
SDL_SetSurfaceBlendMode(surface.get(), SDL_BLENDMODE_BLEND);
|
||||
return surface;
|
||||
}
|
||||
|
||||
std::optional<CachedIcon> icon_from_surface(SDL_Surface* surface) {
|
||||
if (surface == nullptr || surface->w <= 0 || surface->h <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CachedIcon icon{
|
||||
.width = static_cast<uint32_t>(surface->w),
|
||||
.height = static_cast<uint32_t>(surface->h),
|
||||
};
|
||||
const size_t rowSize = static_cast<size_t>(icon.width) * 4u;
|
||||
icon.pixels.resize(rowSize * static_cast<size_t>(icon.height));
|
||||
|
||||
if (!lock_surface(surface)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (uint32_t y = 0; y < icon.height; ++y) {
|
||||
const auto* source = static_cast<const uint8_t*>(surface->pixels) +
|
||||
static_cast<size_t>(y) * static_cast<size_t>(surface->pitch);
|
||||
auto* destination = icon.pixels.data() + static_cast<size_t>(y) * rowSize;
|
||||
std::memcpy(destination, source, rowSize);
|
||||
}
|
||||
|
||||
unlock_surface(surface);
|
||||
return icon;
|
||||
}
|
||||
|
||||
RectF pane_global_rect(J2DPane* pane) noexcept {
|
||||
RectF rect;
|
||||
CPaneMgr paneMgr;
|
||||
Mtx m;
|
||||
for (u8 i = 0; i < 4; ++i) {
|
||||
const Vec vertex = paneMgr.getGlobalVtx(pane, &m, i, false, 0);
|
||||
rect.include(vertex.x, vertex.y);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
uint8_t effective_pane_alpha(J2DPane& pane, uint8_t parentAlpha) noexcept {
|
||||
uint32_t alpha = pane.getAlpha();
|
||||
if (pane.isInfluencedAlpha()) {
|
||||
alpha = alpha * parentAlpha / 255u;
|
||||
}
|
||||
return static_cast<uint8_t>(alpha);
|
||||
}
|
||||
|
||||
void collect_picture_layers(
|
||||
J2DPane* pane, std::vector<PictureLayer>& layers, uint8_t parentAlpha = 255) noexcept {
|
||||
if (pane == nullptr || !pane->isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t paneAlpha = effective_pane_alpha(*pane, parentAlpha);
|
||||
if (paneAlpha == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pane->getKind() == MULTI_CHAR('PIC1') || pane->getKind() == MULTI_CHAR('PIC2')) {
|
||||
auto* picture = static_cast<J2DPicture*>(pane);
|
||||
if (picture->getTexture(0) != nullptr) {
|
||||
RectF rect = pane_global_rect(pane);
|
||||
if (rect.valid()) {
|
||||
layers.push_back({
|
||||
.picture = picture,
|
||||
.rect = rect,
|
||||
.alpha = paneAlpha,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (J2DPane* child = pane->getFirstChildPane(); child != nullptr;
|
||||
child = child->getNextChildPane())
|
||||
{
|
||||
collect_picture_layers(child, layers, paneAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint32_t> icon_dimension(float value) noexcept {
|
||||
if (!std::isfinite(value) || value <= 0.0f) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto dimension = static_cast<uint32_t>(std::ceil(value));
|
||||
if (dimension == 0 || dimension > kMaxRenderedPaneIconSize) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return dimension;
|
||||
}
|
||||
|
||||
float pane_icon_render_scale(const std::vector<PictureLayer>& layers, const RectF& canvas) {
|
||||
float scale = 1.0f;
|
||||
for (const auto& layer : layers) {
|
||||
if (layer.picture == nullptr || !layer.rect.valid() || layer.rect.width() <= 0.0f ||
|
||||
layer.rect.height() <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* texture = layer.picture->getTexture(0);
|
||||
const ResTIMG* image = texture != nullptr ? texture->getTexInfo() : nullptr;
|
||||
if (image == nullptr || image->width.host() == 0 || image->height.host() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
scale = std::max(scale, static_cast<float>(image->width) / layer.rect.width());
|
||||
scale = std::max(scale, static_cast<float>(image->height) / layer.rect.height());
|
||||
}
|
||||
|
||||
const float canvasMax = std::max(canvas.width(), canvas.height());
|
||||
if (canvasMax <= 0.0f) {
|
||||
return scale;
|
||||
}
|
||||
|
||||
const float minScale = static_cast<float>(kMinRenderedPaneIconSize) / canvasMax;
|
||||
const float maxScale = static_cast<float>(kMaxRenderedPaneIconSize) / canvasMax;
|
||||
return std::clamp(std::max(scale, minScale), 1.0f, maxScale);
|
||||
}
|
||||
|
||||
void composite_picture_layer(
|
||||
SDL_Surface& icon, const RectF& canvas, const PictureLayer& layer, float renderScale) {
|
||||
if (layer.picture == nullptr || !layer.rect.valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* texture = layer.picture->getTexture(0);
|
||||
if (texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto decoded = decode_timg(texture->getTexInfo());
|
||||
if (decoded.data.empty() || decoded.width == 0 || decoded.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto colors = layer_colors(*layer.picture, layer.alpha);
|
||||
auto layerSurface = create_layer_surface(decoded, colors);
|
||||
if (!layerSurface) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float dstLeft = (layer.rect.left - canvas.left) * renderScale;
|
||||
const float dstTop = (layer.rect.top - canvas.top) * renderScale;
|
||||
const float dstRight = (layer.rect.right - canvas.left) * renderScale;
|
||||
const float dstBottom = (layer.rect.bottom - canvas.top) * renderScale;
|
||||
const float dstWidth = dstRight - dstLeft;
|
||||
const float dstHeight = dstBottom - dstTop;
|
||||
if (dstWidth <= 0.0f || dstHeight <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int x0 = std::clamp(static_cast<int>(std::floor(dstLeft)), 0, icon.w);
|
||||
const int y0 = std::clamp(static_cast<int>(std::floor(dstTop)), 0, icon.h);
|
||||
const int x1 = std::clamp(static_cast<int>(std::ceil(dstRight)), 0, icon.w);
|
||||
const int y1 = std::clamp(static_cast<int>(std::ceil(dstBottom)), 0, icon.h);
|
||||
if (x0 >= x1 || y0 >= y1) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Rect destinationRect{
|
||||
.x = x0,
|
||||
.y = y0,
|
||||
.w = x1 - x0,
|
||||
.h = y1 - y0,
|
||||
};
|
||||
SDL_BlitSurfaceScaled(
|
||||
layerSurface.get(), nullptr, &icon, &destinationRect, SDL_SCALEMODE_LINEAR);
|
||||
}
|
||||
|
||||
std::optional<CachedIcon> render_j2d_pane_icon(J2DPane* pane) {
|
||||
std::vector<PictureLayer> layers;
|
||||
collect_picture_layers(pane, layers);
|
||||
if (layers.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
RectF canvas;
|
||||
for (const auto& layer : layers) {
|
||||
canvas.include(layer.rect);
|
||||
}
|
||||
if (!canvas.valid()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const float renderScale = pane_icon_render_scale(layers, canvas);
|
||||
auto width = icon_dimension(canvas.width() * renderScale);
|
||||
auto height = icon_dimension(canvas.height() * renderScale);
|
||||
if (!width || !height) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto surface = create_rgba_surface(*width, *height);
|
||||
if (!surface) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (const auto& layer : layers) {
|
||||
composite_picture_layer(*surface, canvas, layer, renderScale);
|
||||
}
|
||||
|
||||
return icon_from_surface(surface.get());
|
||||
}
|
||||
|
||||
std::optional<aurora::rmlui::RuntimeTexture> icon_provider(std::string_view source) {
|
||||
const auto itemNo = item_for_source(source);
|
||||
if (!itemNo) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto& cache = icon_cache();
|
||||
const std::string key(source);
|
||||
auto it = cache.find(key);
|
||||
if (it == cache.end()) {
|
||||
auto icon = render_item_icon(*itemNo);
|
||||
if (!icon) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (cache.size() >= kMaxCachedIcons) {
|
||||
cache.erase(cache.begin());
|
||||
}
|
||||
it = cache.emplace(key, std::move(*icon)).first;
|
||||
}
|
||||
|
||||
const auto& icon = it->second;
|
||||
return aurora::rmlui::RuntimeTexture{
|
||||
.width = icon.width,
|
||||
.height = icon.height,
|
||||
.rgba8 =
|
||||
std::span(reinterpret_cast<const std::byte*>(icon.pixels.data()), icon.pixels.size()),
|
||||
.premultipliedAlpha = true,
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<aurora::rmlui::RuntimeTexture> meter_texture_provider(std::string_view source) {
|
||||
if (!source.starts_with(kMeterSourcePrefix)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::string name(strip_query(source.substr(kMeterSourcePrefix.size())));
|
||||
if (name != "midna") {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto& state = midna_icon_state();
|
||||
if (!state.valid) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return aurora::rmlui::RuntimeTexture{
|
||||
.width = state.icon.width,
|
||||
.height = state.icon.height,
|
||||
.rgba8 = std::span(
|
||||
reinterpret_cast<const std::byte*>(state.icon.pixels.data()), state.icon.pixels.size()),
|
||||
.premultipliedAlpha = true,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_icon_texture_provider() noexcept {
|
||||
aurora::rmlui::register_texture_provider(std::string(kScheme), icon_provider);
|
||||
aurora::rmlui::register_texture_provider(std::string(kMeterScheme), meter_texture_provider);
|
||||
}
|
||||
|
||||
void unregister_icon_texture_provider() noexcept {
|
||||
aurora::rmlui::unregister_texture_provider(kScheme);
|
||||
aurora::rmlui::unregister_texture_provider(kMeterScheme);
|
||||
icon_cache().clear();
|
||||
midna_icon_state() = {};
|
||||
}
|
||||
|
||||
void update_midna_icon_texture(J2DPane* pane) noexcept {
|
||||
auto& state = midna_icon_state();
|
||||
if (pane == nullptr || !pane->isVisible()) {
|
||||
if (state.valid) {
|
||||
state.valid = false;
|
||||
state.icon = {};
|
||||
state.revision++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto icon = render_j2d_pane_icon(pane);
|
||||
if (!icon) {
|
||||
if (state.valid) {
|
||||
state.valid = false;
|
||||
state.icon = {};
|
||||
state.revision++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.valid || state.icon.width != icon->width || state.icon.height != icon->height ||
|
||||
state.icon.pixels != icon->pixels)
|
||||
{
|
||||
state.icon = std::move(*icon);
|
||||
state.valid = true;
|
||||
state.revision++;
|
||||
}
|
||||
}
|
||||
|
||||
std::string midna_icon_source() {
|
||||
const auto& state = midna_icon_state();
|
||||
if (!state.valid) {
|
||||
return "";
|
||||
}
|
||||
return fmt::format(
|
||||
"{}://midna?slot={}", kMeterScheme, state.revision % kMeterTextureSourceSlots);
|
||||
}
|
||||
|
||||
uint64_t midna_icon_revision() noexcept {
|
||||
const auto& state = midna_icon_state();
|
||||
return state.valid ? state.revision : 0;
|
||||
}
|
||||
|
||||
std::string item_icon_source_for_button(Control control) {
|
||||
std::optional<u8> itemNo;
|
||||
switch (control) {
|
||||
case Control::X:
|
||||
itemNo = selected_slot_item(0);
|
||||
break;
|
||||
case Control::Y:
|
||||
itemNo = selected_slot_item(1);
|
||||
break;
|
||||
case Control::B:
|
||||
itemNo = b_button_item();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!itemNo) {
|
||||
return {};
|
||||
}
|
||||
return item_source_for_item(*itemNo);
|
||||
}
|
||||
|
||||
std::string item_count_label_for_button(Control control) {
|
||||
std::optional<int> count;
|
||||
switch (control) {
|
||||
case Control::X:
|
||||
count = selected_slot_count(0);
|
||||
break;
|
||||
case Control::Y:
|
||||
count = selected_slot_count(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!count) {
|
||||
return {};
|
||||
}
|
||||
return fmt::format("{}", *count);
|
||||
}
|
||||
|
||||
std::optional<float> item_oil_fill_for_button(Control control) noexcept {
|
||||
std::optional<u8> itemNo;
|
||||
switch (control) {
|
||||
case Control::X:
|
||||
itemNo = selected_slot_item(0);
|
||||
break;
|
||||
case Control::Y:
|
||||
itemNo = selected_slot_item(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!itemNo || (*itemNo != dItemNo_KANTERA_e && *itemNo != dItemNo_KANTERA2_e)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const int maxOil = dComIfGs_getMaxOil();
|
||||
if (maxOil <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::clamp(
|
||||
static_cast<float>(dComIfGs_getOil()) / static_cast<float>(maxOil), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
#else
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
void register_icon_texture_provider() noexcept {}
|
||||
void unregister_icon_texture_provider() noexcept {}
|
||||
void update_midna_icon_texture(J2DPane*) noexcept {}
|
||||
std::string midna_icon_source() {
|
||||
return {};
|
||||
}
|
||||
uint64_t midna_icon_revision() noexcept {
|
||||
return 0;
|
||||
}
|
||||
std::string item_icon_source_for_button(Control) {
|
||||
return {};
|
||||
}
|
||||
std::string item_count_label_for_button(Control) {
|
||||
return {};
|
||||
}
|
||||
std::optional<float> item_oil_fill_for_button(Control) noexcept {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "controls.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
class J2DPane;
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
void register_icon_texture_provider() noexcept;
|
||||
void unregister_icon_texture_provider() noexcept;
|
||||
|
||||
void update_midna_icon_texture(J2DPane* pane) noexcept;
|
||||
std::string midna_icon_source();
|
||||
uint64_t midna_icon_revision() noexcept;
|
||||
std::string item_icon_source_for_button(Control control);
|
||||
std::string item_count_label_for_button(Control control);
|
||||
std::optional<float> item_oil_fill_for_button(Control control) noexcept;
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "controller_config.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "fmt/format.h"
|
||||
#include "magic_enum.hpp"
|
||||
@@ -194,7 +195,7 @@ static std::string FormatTime(OSTime ticks) {
|
||||
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", t.hour, t.min, t.sec, t.msec);
|
||||
}
|
||||
|
||||
Overlay::Overlay() : Document(kDocumentSource) {
|
||||
Overlay::Overlay() : Document(kDocumentSource, true) {
|
||||
mFpsCounter = mDocument->GetElementById("fps");
|
||||
mSpeedrunTimer = mDocument->GetElementById("speedrun-timer");
|
||||
mSpeedrunRta = mDocument->GetElementById("speedrun-rta");
|
||||
@@ -317,6 +318,7 @@ void Overlay::update() {
|
||||
u32 count = 0;
|
||||
const bool showControllerWarning = PADGetIndexForPort(PAD_CHAN0) < 0 &&
|
||||
PADGetKeyButtonBindings(PAD_CHAN0, &count) == nullptr &&
|
||||
!getSettings().game.enableTouchControls &&
|
||||
dynamic_cast<Window*>(top_document()) == nullptr &&
|
||||
dynamic_cast<WindowSmall*>(top_document()) == nullptr;
|
||||
if (showControllerWarning && mControllerWarning == nullptr) {
|
||||
|
||||
@@ -21,6 +21,7 @@ void applyPresetClassic() {
|
||||
s.game.shadowResolutionMultiplier.setValue(1);
|
||||
s.game.hideTvSettingsScreen.setValue(false);
|
||||
s.game.menuScalingMode.setValue(MenuScaling::GameCube);
|
||||
s.game.enableMenuPointer.setValue(false);
|
||||
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
|
||||
}
|
||||
|
||||
@@ -53,6 +54,7 @@ void applyPresetDusk() {
|
||||
s.game.autoSave.setValue(true);
|
||||
s.game.menuScalingMode.setValue(MenuScaling::Dusklight);
|
||||
s.game.enhancedMapMenus.setValue(true);
|
||||
s.game.enableMenuPointer.setValue(true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "menu_bar.hpp"
|
||||
#include "pane.hpp"
|
||||
#include "prelaunch.hpp"
|
||||
#include "touch_controls_editor.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <aurora/lib/window.hpp>
|
||||
@@ -35,6 +36,17 @@
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_ANDROID) || defined(__ANDROID__) || \
|
||||
(defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST)
|
||||
#define TOUCH_CONTROLS_AVAILABLE true
|
||||
#else
|
||||
#define TOUCH_CONTROLS_AVAILABLE false
|
||||
#endif
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
@@ -968,6 +980,31 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.onChange = [](bool value) { aurora_set_background_input(value); },
|
||||
});
|
||||
|
||||
#if TOUCH_CONTROLS_AVAILABLE
|
||||
leftPane.add_section("Touch");
|
||||
addOption("Touch Controls", getSettings().game.enableTouchControls,
|
||||
"Enables controls overlay for touch screens.<br/><br/>Press and drag on the left side "
|
||||
"of the screen to move, and on the right side of the screen to control the camera.");
|
||||
auto& customizeTouchLayout = leftPane.add_button(ControlledButton::Props{
|
||||
.text = "Customize Layout",
|
||||
.isDisabled = [] { return !getSettings().game.enableTouchControls; },
|
||||
});
|
||||
leftPane.register_control(customizeTouchLayout.on_pressed(
|
||||
[this] { push(std::make_unique<TouchControlsEditor>()); }),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text("Open the touch controls layout editor.");
|
||||
});
|
||||
config_percent_select(leftPane, rightPane, getSettings().game.touchCameraXSensitivity,
|
||||
"Touch Camera X Sensitivity",
|
||||
"Adjusts touch camera horizontal sensitivity.<br/><br/>Applies to touch input only.",
|
||||
25, 400, 5, [] { return !getSettings().game.enableTouchControls; });
|
||||
config_percent_select(leftPane, rightPane, getSettings().game.touchCameraYSensitivity,
|
||||
"Touch Camera Y Sensitivity",
|
||||
"Adjusts touch camera vertical sensitivity.<br/><br/>Applies to touch input only.", 25,
|
||||
400, 5, [] { return !getSettings().game.enableTouchControls; });
|
||||
#endif
|
||||
|
||||
leftPane.add_section("Camera");
|
||||
addOption("Free Camera", getSettings().game.freeCamera,
|
||||
"Enables free camera control, letting you control the camera fully with the C-Stick.");
|
||||
@@ -1035,6 +1072,8 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
[] { return !getSettings().game.enableMouseAim || !getSettings().game.enableMouseCamera; });
|
||||
|
||||
leftPane.add_section("Gameplay");
|
||||
addOption("Mouse/Touch in Menus", getSettings().game.enableMenuPointer,
|
||||
"Enables mouse and touch input for supported in-game menus.");
|
||||
addOption("Invert Air/Swim X Axis", getSettings().game.invertAirSwimX,
|
||||
"Invert horizontal movement while flying or swimming.");
|
||||
addOption("Invert Air/Swim Y Axis", getSettings().game.invertAirSwimY,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include "controls.hpp"
|
||||
#include "document.hpp"
|
||||
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/ui/controls.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
enum class ControlOverride {
|
||||
Default,
|
||||
Action,
|
||||
};
|
||||
|
||||
bool get_equip_target(int slot, EquipTarget& target) noexcept;
|
||||
void set_control_override(Control control, ControlOverride override) noexcept;
|
||||
void sync_virtual_input() noexcept;
|
||||
|
||||
class TouchControls final : public Document {
|
||||
public:
|
||||
TouchControls();
|
||||
~TouchControls() override;
|
||||
|
||||
void show() override;
|
||||
void hide(bool close) override;
|
||||
void update() override;
|
||||
void sync_virtual_input() noexcept;
|
||||
|
||||
private:
|
||||
struct StickTouch {
|
||||
SDL_FingerID id = 0;
|
||||
Rml::Vector2f start;
|
||||
Rml::Vector2f current;
|
||||
bool active = false;
|
||||
};
|
||||
struct ControlTouch {
|
||||
SDL_FingerID id = 0;
|
||||
clock::time_point startTime{};
|
||||
bool active = false;
|
||||
bool longPressFired = false;
|
||||
};
|
||||
struct LayoutState {
|
||||
std::optional<ControlRect> visualRect;
|
||||
std::optional<ControlRect> appliedBox;
|
||||
float layoutScale = 1.0f;
|
||||
std::optional<float> appliedTransform;
|
||||
};
|
||||
struct ControlElements {
|
||||
Rml::Element* root = nullptr;
|
||||
Rml::Element* icon = nullptr;
|
||||
Rml::Element* oil = nullptr;
|
||||
Rml::Element* oilFill = nullptr;
|
||||
Rml::Element* count = nullptr;
|
||||
LayoutState layout;
|
||||
};
|
||||
enum class ControlAction {
|
||||
Tap,
|
||||
Hold,
|
||||
};
|
||||
|
||||
void set_control_pressed(Control control, bool pressed);
|
||||
void release_control(Control control) noexcept;
|
||||
void sync_control_button_mask() noexcept;
|
||||
bool fire_control_action(Control control, ControlAction action) noexcept;
|
||||
bool start_control_touch(SDL_FingerID id, Control control) noexcept;
|
||||
void set_control_visual(Control control, bool pressed) noexcept;
|
||||
void sync_l_lock_state() noexcept;
|
||||
void clear_motion_touch_input() noexcept;
|
||||
void clear_control_input() noexcept;
|
||||
void clear_virtual_input() noexcept;
|
||||
void sync_touch_state() noexcept;
|
||||
void sync_visibility() noexcept;
|
||||
void sync_safe_area() noexcept;
|
||||
void sync_control_layouts() noexcept;
|
||||
void sync_visual_state() noexcept;
|
||||
void sync_action_bar_state() noexcept;
|
||||
void sync_control_displays() noexcept;
|
||||
void apply_control_transform(Control control) noexcept;
|
||||
void handle_touch_down(Rml::Event& event) noexcept;
|
||||
void handle_touch_motion(Rml::Event& event) noexcept;
|
||||
void handle_touch_up(Rml::Event& event) noexcept;
|
||||
void handle_touch_cancel(Rml::Event& event) noexcept;
|
||||
void handle_mouse_move(Rml::Event& event) noexcept;
|
||||
void handle_mouse_down(Rml::Event& event) noexcept;
|
||||
void handle_mouse_up(Rml::Event& event) noexcept;
|
||||
void sync_control_long_presses() noexcept;
|
||||
bool release_control_touch(SDL_FingerID id, bool cancelled) noexcept;
|
||||
bool handle_menu_event(Rml::Event& event, menu_pointer::Phase phase) noexcept;
|
||||
|
||||
Rml::Element* mRoot = nullptr;
|
||||
Rml::Element* mControlStick = nullptr;
|
||||
Rml::Element* mControlKnob = nullptr;
|
||||
Rml::Element* mActionBar = nullptr;
|
||||
std::array<ControlElements, static_cast<std::size_t>(Control::COUNT)> mControlElements{};
|
||||
std::string mButtonBIconSource;
|
||||
std::string mButtonXIconSource;
|
||||
std::string mButtonYIconSource;
|
||||
std::string mZTriggerIconSource;
|
||||
uint64_t mZTriggerIconRevision = 0;
|
||||
std::string mButtonXCountLabel;
|
||||
std::string mButtonYCountLabel;
|
||||
StickTouch mMoveTouch;
|
||||
StickTouch mCameraTouch;
|
||||
SDL_FingerID mMenuPointerTouch = 0;
|
||||
int mMenuPointerMouseSuppressions = 0;
|
||||
std::array<ControlTouch, static_cast<std::size_t>(Control::COUNT)> mControlTouches{};
|
||||
std::array<bool, static_cast<std::size_t>(Control::COUNT)> mControlVisualPressed{};
|
||||
std::bitset<static_cast<std::size_t>(ActionBinds::COUNT)> mQueuedActions;
|
||||
LayoutState mActionBarLayout;
|
||||
Insets mSafeInsets;
|
||||
u16 mButtonMask = 0;
|
||||
bool mLPressed = false;
|
||||
bool mLLatched = false;
|
||||
bool mManualLLatched = false;
|
||||
bool mLReleasePending = false;
|
||||
bool mRTriggerHeld = false;
|
||||
bool mWantsVirtualPad = false;
|
||||
bool mWasSuppressed = true;
|
||||
bool mMenuPointerTouchActive = false;
|
||||
clock::time_point mLPressStartTime{};
|
||||
clock::time_point mLastLTapTime{};
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,359 @@
|
||||
#include "touch_controls_common.hpp"
|
||||
|
||||
#include <aurora/rmlui.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
constexpr std::array<TouchLayoutControlInfo, kTouchLayoutControlCount> kLayoutControls = {{
|
||||
{
|
||||
.layoutId = "triggerL",
|
||||
.elementId = "trigger-l",
|
||||
.props =
|
||||
{
|
||||
.x = 24.f,
|
||||
.y = 18.f,
|
||||
.w = 78.f,
|
||||
.h = 46.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::TopLeft,
|
||||
},
|
||||
.control = Control::L,
|
||||
.hasControl = true,
|
||||
},
|
||||
{
|
||||
.layoutId = "triggerR",
|
||||
.elementId = "trigger-r",
|
||||
.props =
|
||||
{
|
||||
.x = 24.f,
|
||||
.y = 18.f,
|
||||
.w = 78.f,
|
||||
.h = 46.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::TopRight,
|
||||
},
|
||||
.control = Control::R,
|
||||
.hasControl = true,
|
||||
},
|
||||
{
|
||||
.layoutId = "buttonZ",
|
||||
.elementId = "button-z",
|
||||
.props =
|
||||
{
|
||||
.x = 24.f,
|
||||
.y = 72.f,
|
||||
.w = 78.f,
|
||||
.h = 46.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::TopRight,
|
||||
},
|
||||
.control = Control::Z,
|
||||
.hasControl = true,
|
||||
},
|
||||
{
|
||||
.layoutId = "actionBar",
|
||||
.elementId = "action-bar",
|
||||
.props =
|
||||
{
|
||||
.x = 56.f,
|
||||
.y = 0.f,
|
||||
.w = 230.f,
|
||||
.h = 46.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::BottomLeft,
|
||||
},
|
||||
},
|
||||
{
|
||||
.layoutId = "skip",
|
||||
.elementId = "skip",
|
||||
.props =
|
||||
{
|
||||
.x = 24.f,
|
||||
.y = 18.f,
|
||||
.w = 64.f,
|
||||
.h = 46.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::TopRight,
|
||||
},
|
||||
.control = Control::SKIP,
|
||||
.hasControl = true,
|
||||
},
|
||||
{
|
||||
.layoutId = "buttonY",
|
||||
.elementId = "button-y",
|
||||
.props =
|
||||
{
|
||||
.x = 124.f,
|
||||
.y = 138.f,
|
||||
.w = 58.f,
|
||||
.h = 58.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::BottomRight,
|
||||
},
|
||||
.control = Control::Y,
|
||||
.hasControl = true,
|
||||
},
|
||||
{
|
||||
.layoutId = "buttonX",
|
||||
.elementId = "button-x",
|
||||
.props =
|
||||
{
|
||||
.x = 28.f,
|
||||
.y = 144.f,
|
||||
.w = 58.f,
|
||||
.h = 58.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::BottomRight,
|
||||
},
|
||||
.control = Control::X,
|
||||
.hasControl = true,
|
||||
},
|
||||
{
|
||||
.layoutId = "buttonB",
|
||||
.elementId = "button-b",
|
||||
.props =
|
||||
{
|
||||
.x = 158.f,
|
||||
.y = 48.f,
|
||||
.w = 58.f,
|
||||
.h = 58.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::BottomRight,
|
||||
},
|
||||
.control = Control::B,
|
||||
.hasControl = true,
|
||||
},
|
||||
{
|
||||
.layoutId = "buttonA",
|
||||
.elementId = "button-a",
|
||||
.props =
|
||||
{
|
||||
.x = 62.f,
|
||||
.y = 64.f,
|
||||
.w = 74.f,
|
||||
.h = 74.f,
|
||||
.scale = 1.f,
|
||||
.anchor = ControlAnchor::BottomRight,
|
||||
},
|
||||
.control = Control::A,
|
||||
.hasControl = true,
|
||||
},
|
||||
}};
|
||||
|
||||
constexpr std::string_view kTouchControlsRmlFragment = R"RML(
|
||||
<button id="trigger-l" class="control trigger trigger-l"><span>L</span></button>
|
||||
<action-bar id="action-bar" class="control">
|
||||
<button id="items" class="utility items"><icon><glyph></glyph></icon></button>
|
||||
<separator />
|
||||
<button id="first-person" class="utility first-person"><icon><glyph></glyph></icon></button>
|
||||
<separator />
|
||||
<button id="map" class="utility map"><icon><glyph></glyph></icon></button>
|
||||
<separator />
|
||||
<button id="collections" class="utility collections"><icon><glyph></glyph></icon></button>
|
||||
</action-bar>
|
||||
<button id="skip" class="control skip"><icon><glyph></glyph></icon></button>
|
||||
|
||||
<button id="trigger-r" class="control trigger trigger-r"><span>R</span></button>
|
||||
<button id="button-z" class="control trigger button-z midna"><img id="z-midna-icon" class="midna-icon" /><span>Z</span></button>
|
||||
|
||||
<button id="button-y" class="control face y"><img id="button-y-icon" class="item-icon" /><oil-meter id="button-y-oil" class="oil-meter"><oil-fill id="button-y-oil-fill" /></oil-meter><count id="button-y-count" class="item-count"></count><span>Y</span></button>
|
||||
<button id="button-x" class="control face x"><img id="button-x-icon" class="item-icon" /><oil-meter id="button-x-oil" class="oil-meter"><oil-fill id="button-x-oil-fill" /></oil-meter><count id="button-x-count" class="item-count"></count><span>X</span></button>
|
||||
<button id="button-b" class="control face b"><img id="button-b-icon" class="item-icon" /><span>B</span></button>
|
||||
<button id="button-a" class="control face a"><span>A</span></button>
|
||||
)RML";
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string_view touch_controls_rml_fragment() noexcept {
|
||||
return kTouchControlsRmlFragment;
|
||||
}
|
||||
|
||||
std::span<const TouchLayoutControlInfo> touch_layout_controls() noexcept {
|
||||
return kLayoutControls;
|
||||
}
|
||||
|
||||
const TouchLayoutControlInfo* find_touch_layout_control(std::string_view layoutId) noexcept {
|
||||
for (const auto& info : kLayoutControls) {
|
||||
if (info.layoutId == layoutId) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const TouchLayoutControlInfo* find_touch_layout_control(Control control) noexcept {
|
||||
for (const auto& info : kLayoutControls) {
|
||||
if (info.hasControl && info.control == control) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SDL_FingerID touch_event_id(const Rml::Event& event) noexcept {
|
||||
return event.GetParameter<SDL_FingerID>("finger_id", 0);
|
||||
}
|
||||
|
||||
Rml::Vector2f touch_event_position(const Rml::Event& event) noexcept {
|
||||
return {
|
||||
event.GetParameter("x", 0.f),
|
||||
event.GetParameter("y", 0.f),
|
||||
};
|
||||
}
|
||||
|
||||
Rml::Vector2f mouse_event_position(const Rml::Event& event) noexcept {
|
||||
return {
|
||||
event.GetParameter("mouse_x", 0.f),
|
||||
event.GetParameter("mouse_y", 0.f),
|
||||
};
|
||||
}
|
||||
|
||||
float touch_dp_scale(Rml::Context* context) noexcept {
|
||||
if (context == nullptr) {
|
||||
context = aurora::rmlui::get_context();
|
||||
}
|
||||
if (context == nullptr) {
|
||||
return 1.f;
|
||||
}
|
||||
return std::max(context->GetDensityIndependentPixelRatio(), 1.f);
|
||||
}
|
||||
|
||||
ControlLayoutSize touch_document_size_dp(Rml::Context* context) noexcept {
|
||||
if (context == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto dimensions = context->GetDimensions();
|
||||
const float scale = touch_dp_scale(context);
|
||||
return {
|
||||
.w = static_cast<float>(dimensions.x) / scale,
|
||||
.h = static_cast<float>(dimensions.y) / scale,
|
||||
};
|
||||
}
|
||||
|
||||
ControlAnchor touch_control_dock_anchor(ControlRect visual, ControlLayoutSize docSize) noexcept {
|
||||
if (docSize.w <= 0.f || docSize.h <= 0.f || visual.w <= 0.f || visual.h <= 0.f) {
|
||||
return ControlAnchor::None;
|
||||
}
|
||||
|
||||
const bool top = control_float_near(visual.t, 0.f);
|
||||
const bool bottom = control_float_near(visual.t + visual.h, docSize.h);
|
||||
const bool left = control_float_near(visual.l, 0.f);
|
||||
const bool right = control_float_near(visual.l + visual.w, docSize.w);
|
||||
|
||||
if (top && left && !right) {
|
||||
return ControlAnchor::TopLeft;
|
||||
}
|
||||
if (top && right && !left) {
|
||||
return ControlAnchor::TopRight;
|
||||
}
|
||||
if (bottom && left && !right) {
|
||||
return ControlAnchor::BottomLeft;
|
||||
}
|
||||
if (bottom && right && !left) {
|
||||
return ControlAnchor::BottomRight;
|
||||
}
|
||||
if (top) {
|
||||
return ControlAnchor::Top;
|
||||
}
|
||||
if (bottom) {
|
||||
return ControlAnchor::Bottom;
|
||||
}
|
||||
if (left) {
|
||||
return ControlAnchor::Left;
|
||||
}
|
||||
if (right) {
|
||||
return ControlAnchor::Right;
|
||||
}
|
||||
return ControlAnchor::None;
|
||||
}
|
||||
|
||||
bool control_float_near(float a, float b) noexcept {
|
||||
return std::abs(a - b) <= 0.01f;
|
||||
}
|
||||
|
||||
bool control_rect_near(ControlRect a, ControlRect b) noexcept {
|
||||
return control_float_near(a.l, b.l) && control_float_near(a.t, b.t) &&
|
||||
control_float_near(a.w, b.w) && control_float_near(a.h, b.h);
|
||||
}
|
||||
|
||||
void apply_control_box_if_changed(
|
||||
Rml::Element* element, std::optional<ControlRect>& appliedBox, ControlRect box) noexcept {
|
||||
if (element == nullptr || (appliedBox && control_rect_near(*appliedBox, box))) {
|
||||
return;
|
||||
}
|
||||
|
||||
element->SetProperty(Rml::PropertyId::Left, Rml::Property(box.l, Rml::Unit::DP));
|
||||
element->SetProperty(Rml::PropertyId::Top, Rml::Property(box.t, Rml::Unit::DP));
|
||||
element->SetProperty(Rml::PropertyId::Width, Rml::Property(box.w, Rml::Unit::DP));
|
||||
element->SetProperty(Rml::PropertyId::Height, Rml::Property(box.h, Rml::Unit::DP));
|
||||
appliedBox = box;
|
||||
}
|
||||
|
||||
void apply_control_transform_if_changed(
|
||||
Rml::Element* element, std::optional<float>& appliedTransform, float scale) noexcept {
|
||||
if (element == nullptr || (appliedTransform && control_float_near(*appliedTransform, scale))) {
|
||||
return;
|
||||
}
|
||||
|
||||
element->SetProperty(Rml::PropertyId::Transform,
|
||||
Rml::Transform::MakeProperty({Rml::Transforms::Scale2D{scale}}));
|
||||
appliedTransform = scale;
|
||||
}
|
||||
|
||||
void apply_control_dock_classes(Rml::Element* element, ControlAnchor anchor) noexcept {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool top = false;
|
||||
bool bottom = false;
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
|
||||
switch (anchor) {
|
||||
case ControlAnchor::Top:
|
||||
top = true;
|
||||
break;
|
||||
case ControlAnchor::Bottom:
|
||||
bottom = true;
|
||||
break;
|
||||
case ControlAnchor::Left:
|
||||
left = true;
|
||||
break;
|
||||
case ControlAnchor::Right:
|
||||
right = true;
|
||||
break;
|
||||
case ControlAnchor::TopLeft:
|
||||
top = true;
|
||||
left = true;
|
||||
break;
|
||||
case ControlAnchor::TopRight:
|
||||
top = true;
|
||||
right = true;
|
||||
break;
|
||||
case ControlAnchor::BottomLeft:
|
||||
bottom = true;
|
||||
left = true;
|
||||
break;
|
||||
case ControlAnchor::BottomRight:
|
||||
bottom = true;
|
||||
right = true;
|
||||
break;
|
||||
case ControlAnchor::None:
|
||||
break;
|
||||
}
|
||||
|
||||
element->SetClass("docked", top || bottom || left || right);
|
||||
element->SetClass("docked-top", top);
|
||||
element->SetClass("docked-bottom", bottom);
|
||||
element->SetClass("docked-left", left);
|
||||
element->SetClass("docked-right", right);
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "controls.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <SDL3/SDL_touch.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
constexpr std::size_t kTouchLayoutControlCount = 9;
|
||||
|
||||
struct TouchLayoutControlInfo {
|
||||
std::string_view layoutId;
|
||||
const char* elementId = nullptr;
|
||||
ControlProps props;
|
||||
Control control = Control::COUNT;
|
||||
bool hasControl = false;
|
||||
};
|
||||
|
||||
std::string_view touch_controls_rml_fragment() noexcept;
|
||||
std::span<const TouchLayoutControlInfo> touch_layout_controls() noexcept;
|
||||
const TouchLayoutControlInfo* find_touch_layout_control(std::string_view layoutId) noexcept;
|
||||
const TouchLayoutControlInfo* find_touch_layout_control(Control control) noexcept;
|
||||
|
||||
SDL_FingerID touch_event_id(const Rml::Event& event) noexcept;
|
||||
Rml::Vector2f touch_event_position(const Rml::Event& event) noexcept;
|
||||
Rml::Vector2f mouse_event_position(const Rml::Event& event) noexcept;
|
||||
float touch_dp_scale(Rml::Context* context = nullptr) noexcept;
|
||||
ControlLayoutSize touch_document_size_dp(Rml::Context* context) noexcept;
|
||||
ControlAnchor touch_control_dock_anchor(ControlRect visual, ControlLayoutSize docSize) noexcept;
|
||||
|
||||
bool control_float_near(float a, float b) noexcept;
|
||||
bool control_rect_near(ControlRect a, ControlRect b) noexcept;
|
||||
void apply_control_box_if_changed(
|
||||
Rml::Element* element, std::optional<ControlRect>& appliedBox, ControlRect box) noexcept;
|
||||
void apply_control_transform_if_changed(
|
||||
Rml::Element* element, std::optional<float>& appliedTransform, float scale) noexcept;
|
||||
void apply_control_dock_classes(Rml::Element* element, ControlAnchor anchor) noexcept;
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,630 @@
|
||||
#include "touch_controls_editor.hpp"
|
||||
|
||||
#include "modal.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
#include <aurora/rmlui.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
constexpr float kDragThresholdDp = 6.f;
|
||||
constexpr float kMinControlDp = 36.f;
|
||||
constexpr float kMinTriggerWidthDp = 44.f;
|
||||
constexpr float kMinTriggerHeightDp = 32.f;
|
||||
constexpr float kMinActionBarWidthDp = 112.f;
|
||||
constexpr float kMinActionBarHeightDp = 36.f;
|
||||
constexpr float kMinScale = 0.25f;
|
||||
|
||||
struct HandleBinding {
|
||||
const char* id = nullptr;
|
||||
TouchControlsEditor::EditHandle handle = TouchControlsEditor::EditHandle::Move;
|
||||
};
|
||||
|
||||
constexpr std::array kHandleBindings = {
|
||||
HandleBinding{"editor-handle-left", TouchControlsEditor::EditHandle::Left},
|
||||
HandleBinding{"editor-handle-right", TouchControlsEditor::EditHandle::Right},
|
||||
HandleBinding{"editor-handle-top", TouchControlsEditor::EditHandle::Top},
|
||||
HandleBinding{"editor-handle-bottom", TouchControlsEditor::EditHandle::Bottom},
|
||||
HandleBinding{"editor-handle-top-left", TouchControlsEditor::EditHandle::TopLeft},
|
||||
HandleBinding{"editor-handle-top-right", TouchControlsEditor::EditHandle::TopRight},
|
||||
HandleBinding{"editor-handle-bottom-left", TouchControlsEditor::EditHandle::BottomLeft},
|
||||
HandleBinding{"editor-handle-bottom-right", TouchControlsEditor::EditHandle::BottomRight},
|
||||
};
|
||||
|
||||
Rml::String touch_controls_editor_document_source() {
|
||||
const auto fragment = touch_controls_rml_fragment();
|
||||
return Rml::String{R"RML(
|
||||
<rml>
|
||||
<head>
|
||||
<link type="text/rcss" href="res/rml/touch_controls.rcss" />
|
||||
<link type="text/rcss" href="res/rml/touch_controls_editor.rcss" />
|
||||
</head>
|
||||
<body id="root" class="touch-editor">
|
||||
)RML"} + Rml::String{fragment.data(), fragment.size()} + Rml::String{R"RML(
|
||||
<selection-frame id="editor-selection-frame">
|
||||
<resize-handle id="editor-handle-left" class="edge horizontal left" />
|
||||
<resize-handle id="editor-handle-right" class="edge horizontal right" />
|
||||
<resize-handle id="editor-handle-top" class="edge vertical top" />
|
||||
<resize-handle id="editor-handle-bottom" class="edge vertical bottom" />
|
||||
<resize-handle id="editor-handle-top-left" class="corner top left" />
|
||||
<resize-handle id="editor-handle-top-right" class="corner top right" />
|
||||
<resize-handle id="editor-handle-bottom-left" class="corner bottom left" />
|
||||
<resize-handle id="editor-handle-bottom-right" class="corner bottom right" />
|
||||
</selection-frame>
|
||||
<editor-toolbar id="editor-toolbar">
|
||||
<button id="editor-save" class="editor-command primary"><span>Save</span></button>
|
||||
<button id="editor-reset" class="editor-command"><span>Reset</span></button>
|
||||
<button id="editor-cancel" class="editor-command"><span>Cancel</span></button>
|
||||
</editor-toolbar>
|
||||
</body>
|
||||
</rml>
|
||||
)RML"};
|
||||
}
|
||||
|
||||
bool is_corner(TouchControlsEditor::EditHandle handle) noexcept {
|
||||
using EditHandle = TouchControlsEditor::EditHandle;
|
||||
return handle == EditHandle::TopLeft || handle == EditHandle::TopRight ||
|
||||
handle == EditHandle::BottomLeft || handle == EditHandle::BottomRight;
|
||||
}
|
||||
|
||||
bool is_horizontal_edge(TouchControlsEditor::EditHandle handle) noexcept {
|
||||
using EditHandle = TouchControlsEditor::EditHandle;
|
||||
return handle == EditHandle::Left || handle == EditHandle::Right;
|
||||
}
|
||||
|
||||
bool is_vertical_edge(TouchControlsEditor::EditHandle handle) noexcept {
|
||||
using EditHandle = TouchControlsEditor::EditHandle;
|
||||
return handle == EditHandle::Top || handle == EditHandle::Bottom;
|
||||
}
|
||||
|
||||
bool control_valid(std::size_t index) noexcept {
|
||||
return index < touch_layout_controls().size();
|
||||
}
|
||||
|
||||
float squared_distance(Rml::Vector2f a, Rml::Vector2f b) noexcept {
|
||||
const auto delta = a - b;
|
||||
return delta.x * delta.x + delta.y * delta.y;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TouchControlsEditor::TouchControlsEditor()
|
||||
: Document(touch_controls_editor_document_source()),
|
||||
mRoot(mDocument != nullptr ? mDocument->GetElementById("root") : nullptr),
|
||||
mSelectionFrame(
|
||||
mDocument != nullptr ? mDocument->GetElementById("editor-selection-frame") : nullptr),
|
||||
mSaveButton(mDocument != nullptr ? mDocument->GetElementById("editor-save") : nullptr),
|
||||
mResetButton(mDocument != nullptr ? mDocument->GetElementById("editor-reset") : nullptr),
|
||||
mCancelButton(mDocument != nullptr ? mDocument->GetElementById("editor-cancel") : nullptr),
|
||||
mWorkingLayout(getSettings().game.touchControlsLayout.getValue()) {
|
||||
mWorkingLayout.version = ControlLayout::Version;
|
||||
|
||||
const auto controls = touch_layout_controls();
|
||||
for (std::size_t i = 0; i < controls.size() && i < mElements.size(); ++i) {
|
||||
mElements[i].root =
|
||||
mDocument != nullptr ? mDocument->GetElementById(controls[i].elementId) : nullptr;
|
||||
}
|
||||
|
||||
bind_control_events();
|
||||
bind_handle_events();
|
||||
bind_toolbar_events();
|
||||
|
||||
listen(mRoot, aurora::rmlui::TouchStartEvent, [this](Rml::Event& event) {
|
||||
if (event.GetTargetElement() != mRoot) {
|
||||
return;
|
||||
}
|
||||
clear_selected_control();
|
||||
event.StopPropagation();
|
||||
});
|
||||
listen(mRoot, Rml::EventId::Mousedown, [this](Rml::Event& event) {
|
||||
const s32 button = event.GetParameter("button", -1);
|
||||
if (button != 0 || event.GetTargetElement() != mRoot) {
|
||||
return;
|
||||
}
|
||||
clear_selected_control();
|
||||
event.StopPropagation();
|
||||
});
|
||||
listen(mRoot, aurora::rmlui::TouchMoveEvent, [this](Rml::Event& event) {
|
||||
if (continue_edit(touch_event_position(event))) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
listen(mRoot, aurora::rmlui::TouchEndEvent, [this](Rml::Event& event) {
|
||||
if (end_edit(true, touch_event_id(event), false)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
listen(mRoot, aurora::rmlui::TouchCancelEvent, [this](Rml::Event& event) {
|
||||
if (end_edit(true, touch_event_id(event), true)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
listen(mRoot, Rml::EventId::Mousemove, [this](Rml::Event& event) {
|
||||
if (continue_edit(mouse_event_position(event))) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
listen(mRoot, Rml::EventId::Mouseup, [this](Rml::Event& event) {
|
||||
if (end_edit(false, 0, false)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
|
||||
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
|
||||
Document::visible())
|
||||
{
|
||||
Document::hide(mPendingClose);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void TouchControlsEditor::show() {
|
||||
Document::show();
|
||||
if (mRoot != nullptr) {
|
||||
mRoot->SetAttribute("open", "");
|
||||
}
|
||||
}
|
||||
|
||||
void TouchControlsEditor::hide(bool close) {
|
||||
if (mRoot != nullptr) {
|
||||
mRoot->RemoveAttribute("open");
|
||||
mPendingClose = close;
|
||||
} else {
|
||||
Document::hide(close);
|
||||
}
|
||||
}
|
||||
|
||||
void TouchControlsEditor::update() {
|
||||
sync_control_layouts();
|
||||
sync_selection_frame();
|
||||
Document::update();
|
||||
}
|
||||
|
||||
bool TouchControlsEditor::focus() {
|
||||
return mSaveButton != nullptr && mSaveButton->Focus(true);
|
||||
}
|
||||
|
||||
void TouchControlsEditor::bind_control_events() noexcept {
|
||||
const auto controls = touch_layout_controls();
|
||||
for (std::size_t i = 0; i < controls.size() && i < mElements.size(); ++i) {
|
||||
auto* element = mElements[i].root;
|
||||
if (element == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
listen(element, aurora::rmlui::TouchStartEvent, [this, i](Rml::Event& event) {
|
||||
if (begin_edit(i, EditHandle::Move, touch_event_position(event), true,
|
||||
touch_event_id(event)))
|
||||
{
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
listen(element, Rml::EventId::Mousedown, [this, i](Rml::Event& event) {
|
||||
const s32 button = event.GetParameter("button", -1);
|
||||
if (button != 0) {
|
||||
return;
|
||||
}
|
||||
if (begin_edit(i, EditHandle::Move, mouse_event_position(event), false)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TouchControlsEditor::bind_handle_events() noexcept {
|
||||
for (const auto& binding : kHandleBindings) {
|
||||
auto* element = mDocument != nullptr ? mDocument->GetElementById(binding.id) : nullptr;
|
||||
if (element == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
listen(element, aurora::rmlui::TouchStartEvent, [this, handle = binding.handle](
|
||||
Rml::Event& event) {
|
||||
if (!control_valid(mSelectedIndex)) {
|
||||
return;
|
||||
}
|
||||
if (begin_edit(mSelectedIndex, handle, touch_event_position(event), true,
|
||||
touch_event_id(event)))
|
||||
{
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
listen(element, Rml::EventId::Mousedown, [this, handle = binding.handle](Rml::Event& event) {
|
||||
const s32 button = event.GetParameter("button", -1);
|
||||
if (button != 0 || !control_valid(mSelectedIndex)) {
|
||||
return;
|
||||
}
|
||||
if (begin_edit(mSelectedIndex, handle, mouse_event_position(event), false)) {
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TouchControlsEditor::bind_toolbar_events() noexcept {
|
||||
bind_button_command(mSaveButton, &TouchControlsEditor::save_layout);
|
||||
bind_button_command(mResetButton, &TouchControlsEditor::request_reset);
|
||||
bind_button_command(mCancelButton, &TouchControlsEditor::cancel_edit);
|
||||
}
|
||||
|
||||
void TouchControlsEditor::bind_button_command(
|
||||
Rml::Element* element, void (TouchControlsEditor::*callback)()) noexcept {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
listen(element, Rml::EventId::Click, [this, callback](Rml::Event& event) {
|
||||
(this->*callback)();
|
||||
event.StopPropagation();
|
||||
});
|
||||
listen(element, Rml::EventId::Keydown, [this, callback](Rml::Event& event) {
|
||||
if (map_nav_event(event) != NavCommand::Confirm) {
|
||||
return;
|
||||
}
|
||||
(this->*callback)();
|
||||
event.StopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
void TouchControlsEditor::sync_control_layouts() noexcept {
|
||||
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
|
||||
const auto docSize = touch_document_size_dp(context);
|
||||
if (docSize.w <= 0.f || docSize.h <= 0.f || context == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto controls = touch_layout_controls();
|
||||
for (std::size_t i = 0; i < controls.size() && i < mElements.size(); ++i) {
|
||||
const auto layout = resolve_control_layout(props_for(i), docSize);
|
||||
auto& element = mElements[i];
|
||||
element.layout.visualRect = layout.visual;
|
||||
element.layout.layoutScale = layout.scale;
|
||||
if (element.root != nullptr) {
|
||||
element.root->SetPseudoClass("hidden", false);
|
||||
}
|
||||
apply_control_box_if_changed(element.root, element.layout.appliedBox, layout.box);
|
||||
apply_control_dock_classes(
|
||||
element.root, touch_control_dock_anchor(layout.visual, docSize));
|
||||
apply_control_transform_if_changed(
|
||||
element.root, element.layout.appliedTransform, element.layout.layoutScale);
|
||||
}
|
||||
}
|
||||
|
||||
void TouchControlsEditor::sync_selection_frame() noexcept {
|
||||
const bool hasSelection =
|
||||
control_valid(mSelectedIndex) && mElements[mSelectedIndex].layout.visualRect;
|
||||
if (mSelectionFrame == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSelectionFrame->SetClass("visible", hasSelection);
|
||||
for (std::size_t i = 0; i < mElements.size(); ++i) {
|
||||
if (mElements[i].root != nullptr) {
|
||||
mElements[i].root->SetClass("editor-selected", hasSelection && i == mSelectedIndex);
|
||||
}
|
||||
}
|
||||
if (!hasSelection) {
|
||||
mAppliedSelectionFrame = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
apply_control_box_if_changed(
|
||||
mSelectionFrame, mAppliedSelectionFrame, *mElements[mSelectedIndex].layout.visualRect);
|
||||
}
|
||||
|
||||
void TouchControlsEditor::set_selected_control(std::size_t index) noexcept {
|
||||
if (!control_valid(index)) {
|
||||
clear_selected_control();
|
||||
return;
|
||||
}
|
||||
mSelectedIndex = index;
|
||||
sync_selection_frame();
|
||||
}
|
||||
|
||||
void TouchControlsEditor::clear_selected_control() noexcept {
|
||||
mSelectedIndex = kTouchLayoutControlCount;
|
||||
sync_selection_frame();
|
||||
}
|
||||
|
||||
ControlProps TouchControlsEditor::props_for(std::size_t index) const {
|
||||
const auto controls = touch_layout_controls();
|
||||
if (!control_valid(index)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& info = controls[index];
|
||||
if (const auto iter = mWorkingLayout.controls.find(info.layoutId);
|
||||
iter != mWorkingLayout.controls.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
return info.props;
|
||||
}
|
||||
|
||||
void TouchControlsEditor::store_props(
|
||||
std::size_t index, ControlRect visual, ControlProps props) noexcept {
|
||||
if (!control_valid(index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
|
||||
const auto docSize = touch_document_size_dp(context);
|
||||
if (docSize.w <= 0.f || docSize.h <= 0.f) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.w = std::max(props.w, 1.f);
|
||||
props.h = std::max(props.h, 1.f);
|
||||
props.scale = std::max(props.scale, kMinScale);
|
||||
props = encode_control_props(visual, docSize, props, touch_control_dock_anchor(visual, docSize));
|
||||
mWorkingLayout.version = ControlLayout::Version;
|
||||
mWorkingLayout.controls[std::string{touch_layout_controls()[index].layoutId}] = props;
|
||||
sync_control_layouts();
|
||||
sync_selection_frame();
|
||||
}
|
||||
|
||||
void TouchControlsEditor::restore_active_control() noexcept {
|
||||
if (!control_valid(mPointerEdit.index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& controls = mWorkingLayout.controls;
|
||||
const auto key = std::string{touch_layout_controls()[mPointerEdit.index].layoutId};
|
||||
if (mPointerEdit.storedProps) {
|
||||
controls[key] = *mPointerEdit.storedProps;
|
||||
} else {
|
||||
controls.erase(key);
|
||||
}
|
||||
sync_control_layouts();
|
||||
sync_selection_frame();
|
||||
}
|
||||
|
||||
bool TouchControlsEditor::begin_edit(
|
||||
std::size_t index, EditHandle handle, Rml::Vector2f positionPx, bool touch,
|
||||
SDL_FingerID touchId) noexcept {
|
||||
if (!control_valid(index) || mPointerEdit.active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
|
||||
const auto docSize = touch_document_size_dp(context);
|
||||
if (docSize.w <= 0.f || docSize.h <= 0.f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto props = props_for(index);
|
||||
const auto layout = resolve_control_layout(props, docSize);
|
||||
std::optional<ControlProps> storedProps;
|
||||
if (const auto iter = mWorkingLayout.controls.find(touch_layout_controls()[index].layoutId);
|
||||
iter != mWorkingLayout.controls.end())
|
||||
{
|
||||
storedProps = iter->second;
|
||||
}
|
||||
|
||||
mPointerEdit = {
|
||||
.index = index,
|
||||
.touchId = touchId,
|
||||
.startPointerDp = pointer_position_dp(positionPx),
|
||||
.startVisual = layout.visual,
|
||||
.startProps = props,
|
||||
.storedProps = storedProps,
|
||||
.handle = handle,
|
||||
.active = true,
|
||||
.touch = touch,
|
||||
};
|
||||
set_selected_control(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TouchControlsEditor::continue_edit(Rml::Vector2f positionPx) noexcept {
|
||||
if (!mPointerEdit.active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto pointerDp = pointer_position_dp(positionPx);
|
||||
if (!mPointerEdit.dragging) {
|
||||
if (squared_distance(pointerDp, mPointerEdit.startPointerDp) <
|
||||
kDragThresholdDp * kDragThresholdDp)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
mPointerEdit.dragging = true;
|
||||
}
|
||||
|
||||
auto props = mPointerEdit.startProps;
|
||||
auto rect = rect_for_edit(pointerDp, props);
|
||||
rect = clamp_visual_rect(mPointerEdit.index, rect);
|
||||
if (is_corner(mPointerEdit.handle)) {
|
||||
props.scale = std::max(rect.w / std::max(mPointerEdit.startProps.w, 1.f), kMinScale);
|
||||
} else if (is_horizontal_edge(mPointerEdit.handle)) {
|
||||
props.w = rect.w / std::max(props.scale, kMinScale);
|
||||
} else if (is_vertical_edge(mPointerEdit.handle)) {
|
||||
props.h = rect.h / std::max(props.scale, kMinScale);
|
||||
}
|
||||
store_props(mPointerEdit.index, rect, props);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TouchControlsEditor::end_edit(bool touch, SDL_FingerID touchId, bool cancelled) noexcept {
|
||||
if (!mPointerEdit.active || mPointerEdit.touch != touch ||
|
||||
(touch && mPointerEdit.touchId != touchId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cancelled && mPointerEdit.dragging) {
|
||||
restore_active_control();
|
||||
}
|
||||
mPointerEdit = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
Rml::Vector2f TouchControlsEditor::pointer_position_dp(Rml::Vector2f positionPx) const noexcept {
|
||||
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
|
||||
return positionPx / touch_dp_scale(context);
|
||||
}
|
||||
|
||||
ControlRect TouchControlsEditor::rect_for_edit(
|
||||
Rml::Vector2f pointerDp, ControlProps& props) const noexcept {
|
||||
const auto& edit = mPointerEdit;
|
||||
auto rect = edit.startVisual;
|
||||
const auto delta = pointerDp - edit.startPointerDp;
|
||||
|
||||
switch (edit.handle) {
|
||||
case EditHandle::Move:
|
||||
rect.l += delta.x;
|
||||
rect.t += delta.y;
|
||||
return rect;
|
||||
case EditHandle::Left: {
|
||||
const float right = edit.startVisual.l + edit.startVisual.w;
|
||||
rect.l = pointerDp.x;
|
||||
rect.w = right - rect.l;
|
||||
return rect;
|
||||
}
|
||||
case EditHandle::Right:
|
||||
rect.w = pointerDp.x - edit.startVisual.l;
|
||||
return rect;
|
||||
case EditHandle::Top: {
|
||||
const float bottom = edit.startVisual.t + edit.startVisual.h;
|
||||
rect.t = pointerDp.y;
|
||||
rect.h = bottom - rect.t;
|
||||
return rect;
|
||||
}
|
||||
case EditHandle::Bottom:
|
||||
rect.h = pointerDp.y - edit.startVisual.t;
|
||||
return rect;
|
||||
case EditHandle::TopLeft:
|
||||
case EditHandle::TopRight:
|
||||
case EditHandle::BottomLeft:
|
||||
case EditHandle::BottomRight:
|
||||
break;
|
||||
}
|
||||
|
||||
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
|
||||
const auto docSize = touch_document_size_dp(context);
|
||||
const bool left = edit.handle == EditHandle::TopLeft || edit.handle == EditHandle::BottomLeft;
|
||||
const bool top = edit.handle == EditHandle::TopLeft || edit.handle == EditHandle::TopRight;
|
||||
const Rml::Vector2f fixed{
|
||||
left ? edit.startVisual.l + edit.startVisual.w : edit.startVisual.l,
|
||||
top ? edit.startVisual.t + edit.startVisual.h : edit.startVisual.t,
|
||||
};
|
||||
const float desiredW = left ? fixed.x - pointerDp.x : pointerDp.x - fixed.x;
|
||||
const float desiredH = top ? fixed.y - pointerDp.y : pointerDp.y - fixed.y;
|
||||
const auto minSize = min_visual_size(edit.index);
|
||||
const float minRatio =
|
||||
std::max(minSize.x / std::max(edit.startVisual.w, 1.f),
|
||||
minSize.y / std::max(edit.startVisual.h, 1.f));
|
||||
const float maxW = left ? fixed.x : docSize.w - fixed.x;
|
||||
const float maxH = top ? fixed.y : docSize.h - fixed.y;
|
||||
const float maxRatio =
|
||||
std::max(minRatio, std::min(maxW / std::max(edit.startVisual.w, 1.f),
|
||||
maxH / std::max(edit.startVisual.h, 1.f)));
|
||||
const float ratio =
|
||||
std::clamp(std::max(desiredW / std::max(edit.startVisual.w, 1.f),
|
||||
desiredH / std::max(edit.startVisual.h, 1.f)),
|
||||
minRatio, maxRatio);
|
||||
|
||||
rect.w = edit.startVisual.w * ratio;
|
||||
rect.h = edit.startVisual.h * ratio;
|
||||
rect.l = left ? fixed.x - rect.w : fixed.x;
|
||||
rect.t = top ? fixed.y - rect.h : fixed.y;
|
||||
props.scale = std::max(edit.startProps.scale * ratio, kMinScale);
|
||||
return rect;
|
||||
}
|
||||
|
||||
ControlRect TouchControlsEditor::clamp_visual_rect(std::size_t index, ControlRect rect) const noexcept {
|
||||
auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr;
|
||||
const auto docSize = touch_document_size_dp(context);
|
||||
if (docSize.w <= 0.f || docSize.h <= 0.f || !control_valid(index)) {
|
||||
return rect;
|
||||
}
|
||||
|
||||
const auto minSize = min_visual_size(index);
|
||||
const float minW = std::min(minSize.x, docSize.w);
|
||||
const float minH = std::min(minSize.y, docSize.h);
|
||||
rect.w = std::clamp(rect.w, minW, docSize.w);
|
||||
rect.h = std::clamp(rect.h, minH, docSize.h);
|
||||
rect.l = std::clamp(rect.l, 0.f, std::max(0.f, docSize.w - rect.w));
|
||||
rect.t = std::clamp(rect.t, 0.f, std::max(0.f, docSize.h - rect.h));
|
||||
return rect;
|
||||
}
|
||||
|
||||
Rml::Vector2f TouchControlsEditor::min_visual_size(std::size_t index) const noexcept {
|
||||
if (!control_valid(index)) {
|
||||
return {kMinControlDp, kMinControlDp};
|
||||
}
|
||||
|
||||
const auto id = touch_layout_controls()[index].layoutId;
|
||||
if (id == "actionBar") {
|
||||
return {kMinActionBarWidthDp, kMinActionBarHeightDp};
|
||||
}
|
||||
if (id == "triggerL" || id == "triggerR" || id == "buttonZ" || id == "skip") {
|
||||
return {kMinTriggerWidthDp, kMinTriggerHeightDp};
|
||||
}
|
||||
return {kMinControlDp, kMinControlDp};
|
||||
}
|
||||
|
||||
bool TouchControlsEditor::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Cancel || cmd == NavCommand::Menu) {
|
||||
cancel_edit();
|
||||
return true;
|
||||
}
|
||||
return Document::handle_nav_command(event, cmd);
|
||||
}
|
||||
|
||||
void TouchControlsEditor::save_layout() {
|
||||
mWorkingLayout.version = ControlLayout::Version;
|
||||
getSettings().game.touchControlsLayout.setValue(mWorkingLayout);
|
||||
config::Save();
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
pop();
|
||||
}
|
||||
|
||||
void TouchControlsEditor::request_reset() {
|
||||
auto dismiss = [](Modal& modal) { modal.pop(); };
|
||||
push(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Reset Touch Layout?",
|
||||
.bodyRml = "Reset controls to their default layout. This will not be saved until you press Save.",
|
||||
.actions =
|
||||
{
|
||||
ModalAction{
|
||||
.label = "Reset",
|
||||
.onPressed =
|
||||
[this, dismiss](Modal& modal) {
|
||||
reset_working_layout();
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
dismiss(modal);
|
||||
},
|
||||
},
|
||||
ModalAction{
|
||||
.label = "Cancel",
|
||||
.onPressed = dismiss,
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
void TouchControlsEditor::reset_working_layout() noexcept {
|
||||
mWorkingLayout = ControlLayout{};
|
||||
mWorkingLayout.version = ControlLayout::Version;
|
||||
mPointerEdit = {};
|
||||
sync_control_layouts();
|
||||
sync_selection_frame();
|
||||
}
|
||||
|
||||
void TouchControlsEditor::cancel_edit() {
|
||||
mDoAud_seStartMenu(kSoundWindowClose);
|
||||
pop();
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "controls.hpp"
|
||||
#include "document.hpp"
|
||||
#include "touch_controls_common.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class TouchControlsEditor final : public Document {
|
||||
public:
|
||||
TouchControlsEditor();
|
||||
|
||||
void show() override;
|
||||
void hide(bool close) override;
|
||||
void update() override;
|
||||
bool focus() override;
|
||||
|
||||
enum class EditHandle {
|
||||
Move,
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
};
|
||||
|
||||
private:
|
||||
struct LayoutState {
|
||||
std::optional<ControlRect> visualRect;
|
||||
std::optional<ControlRect> appliedBox;
|
||||
float layoutScale = 1.0f;
|
||||
std::optional<float> appliedTransform;
|
||||
};
|
||||
|
||||
struct EditElement {
|
||||
Rml::Element* root = nullptr;
|
||||
LayoutState layout;
|
||||
};
|
||||
|
||||
struct PointerEdit {
|
||||
std::size_t index = kTouchLayoutControlCount;
|
||||
SDL_FingerID touchId = 0;
|
||||
Rml::Vector2f startPointerDp;
|
||||
ControlRect startVisual;
|
||||
ControlProps startProps;
|
||||
std::optional<ControlProps> storedProps;
|
||||
EditHandle handle = EditHandle::Move;
|
||||
bool active = false;
|
||||
bool touch = false;
|
||||
bool dragging = false;
|
||||
};
|
||||
|
||||
void bind_control_events() noexcept;
|
||||
void bind_handle_events() noexcept;
|
||||
void bind_toolbar_events() noexcept;
|
||||
void bind_button_command(
|
||||
Rml::Element* element, void (TouchControlsEditor::*callback)()) noexcept;
|
||||
void sync_control_layouts() noexcept;
|
||||
void sync_selection_frame() noexcept;
|
||||
void set_selected_control(std::size_t index) noexcept;
|
||||
void clear_selected_control() noexcept;
|
||||
ControlProps props_for(std::size_t index) const;
|
||||
void store_props(std::size_t index, ControlRect visual, ControlProps props) noexcept;
|
||||
void restore_active_control() noexcept;
|
||||
bool begin_edit(std::size_t index, EditHandle handle, Rml::Vector2f positionPx, bool touch,
|
||||
SDL_FingerID touchId = 0) noexcept;
|
||||
bool continue_edit(Rml::Vector2f positionPx) noexcept;
|
||||
bool end_edit(bool touch, SDL_FingerID touchId, bool cancelled) noexcept;
|
||||
Rml::Vector2f pointer_position_dp(Rml::Vector2f positionPx) const noexcept;
|
||||
ControlRect rect_for_edit(Rml::Vector2f pointerDp, ControlProps& props) const noexcept;
|
||||
ControlRect clamp_visual_rect(std::size_t index, ControlRect rect) const noexcept;
|
||||
Rml::Vector2f min_visual_size(std::size_t index) const noexcept;
|
||||
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
|
||||
void save_layout();
|
||||
void request_reset();
|
||||
void reset_working_layout() noexcept;
|
||||
void cancel_edit();
|
||||
|
||||
Rml::Element* mRoot = nullptr;
|
||||
Rml::Element* mSelectionFrame = nullptr;
|
||||
Rml::Element* mSaveButton = nullptr;
|
||||
Rml::Element* mResetButton = nullptr;
|
||||
Rml::Element* mCancelButton = nullptr;
|
||||
std::array<EditElement, kTouchLayoutControlCount> mElements{};
|
||||
ControlLayout mWorkingLayout;
|
||||
PointerEdit mPointerEdit;
|
||||
std::optional<ControlRect> mAppliedSelectionFrame;
|
||||
std::size_t mSelectedIndex = kTouchLayoutControlCount;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
+9
-2
@@ -1,7 +1,11 @@
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
#include <SDL3/SDL_joystick.h>
|
||||
#include <SDL3/SDL_power.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <aurora/rmlui.hpp>
|
||||
#include <fmt/format.h>
|
||||
@@ -11,11 +15,12 @@
|
||||
#include <ranges>
|
||||
|
||||
#include "aurora/lib/window.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/io.hpp"
|
||||
#include "input.hpp"
|
||||
#include "icon_provider.hpp"
|
||||
#include "prelaunch.hpp"
|
||||
#include "window.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
@@ -56,11 +61,13 @@ bool initialize() noexcept {
|
||||
load_font("MaterialSymbolsRounded-Regular.ttf");
|
||||
load_font("NotoMono-Regular.ttf");
|
||||
|
||||
register_icon_texture_provider();
|
||||
sInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() noexcept {
|
||||
unregister_icon_texture_provider();
|
||||
sDocumentStack.clear();
|
||||
sPassiveDocuments.clear();
|
||||
sConnectedGamepads.clear();
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include <dusk/gamepad_color.h>
|
||||
#include <dusk/autosave.h>
|
||||
#include "dusk/menu_pointer.h"
|
||||
#endif
|
||||
|
||||
fapGm_HIO_c::fapGm_HIO_c() {
|
||||
@@ -743,6 +744,7 @@ static void fapGm_AfterRecord() {
|
||||
BOOL isRecording = false;
|
||||
|
||||
static void duskExecute() {
|
||||
dusk::menu_pointer::begin_game_frame();
|
||||
dusk::input::handleGamepadColor();
|
||||
updateAutoSave();
|
||||
|
||||
@@ -842,6 +844,7 @@ void fapGm_Execute() {
|
||||
#ifdef TARGET_PC
|
||||
dusk::speedrun::onGameFrame();
|
||||
dusk::AchievementSystem::get().tick();
|
||||
dusk::menu_pointer::end_game_frame();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/ui/touch_controls.hpp"
|
||||
#endif
|
||||
|
||||
JUTGamePad* mDoCPd_c::m_gamePad[4];
|
||||
|
||||
interface_of_controller_pad mDoCPd_c::m_cpadInfo[4];
|
||||
@@ -58,6 +63,9 @@ void mDoCPd_c::create() {
|
||||
|
||||
void mDoCPd_c::read() {
|
||||
ZoneScoped;
|
||||
#if TARGET_PC
|
||||
dusk::ui::sync_virtual_input();
|
||||
#endif
|
||||
JUTGamePad::read();
|
||||
|
||||
if (!mDoRst::isReset() && mDoRst::is3ButtonReset()) {
|
||||
@@ -88,6 +96,12 @@ void mDoCPd_c::read() {
|
||||
cLib_memSet(interface, 0, sizeof(interface_of_controller_pad));
|
||||
} else {
|
||||
convert(interface, *pad);
|
||||
#if TARGET_PC
|
||||
const u32 suppressedButtons = dusk::menu_pointer::suppressed_pad_buttons(i);
|
||||
interface->mButtonFlags &= ~suppressedButtons;
|
||||
interface->mPressedButtonFlags &= ~suppressedButtons;
|
||||
dusk::menu_pointer::finish_pad_suppression_read(i);
|
||||
#endif
|
||||
LRlockCheck(interface);
|
||||
}
|
||||
#if DEBUG
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
#include "dusk/ui/overlay.hpp"
|
||||
#include "dusk/ui/prelaunch.hpp"
|
||||
#include "dusk/ui/preset.hpp"
|
||||
#include "dusk/ui/touch_controls.hpp"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "version.h"
|
||||
|
||||
@@ -663,6 +664,7 @@ int game_main(int argc, char* argv[]) {
|
||||
dusk::texture_replacements::reload();
|
||||
dusk::ui::initialize();
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::Overlay>(), true, true);
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::TouchControls>(), false, true);
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), false);
|
||||
|
||||
// Invalidate a bad saved isoPath so that Dusklight can't get blocked from starting up.
|
||||
|
||||
Reference in New Issue
Block a user