Merge branch 'main' of ssh://github.com/TwilitRealm/dusklight into rando-mod

This commit is contained in:
TakaRikka
2026-08-20 18:15:25 -07:00
72 changed files with 5020 additions and 258 deletions
+2 -2
View File
@@ -5874,8 +5874,8 @@ static int dmg_rod_Execute(dmg_rod_class* i_this) {
#if TARGET_PC
if (dusk::getSettings().game.buttonFishing) {
if ((item_any_fishing_rod(dComIfGp_getSelectItem(0)) && mDoCPd_c::getHoldX(PAD_1)) ||
(item_any_fishing_rod(dComIfGp_getSelectItem(1)) && mDoCPd_c::getHoldY(PAD_1)))
{
(item_any_fishing_rod(dComIfGp_getSelectItem(1)) && mDoCPd_c::getHoldY(PAD_1)) ||
(i_this->action == ACTION_LURE_STANDBY && mDoCPd_c::getTrigB(PAD_1))) {
i_this->rod_stick_y = -1.0f;
i_this->rod_substick_y = -1.0f;
}
+40 -7
View File
@@ -23,6 +23,7 @@
#include "m_Do/m_Do_graphic.h"
#include <cstring>
#include "dusk/utilities.hpp"
#include "dusk/version.hpp"
#if TARGET_PC
@@ -4153,11 +4154,43 @@ bool dFile_select_c::yesnoWakuAlpahAnm(u8 param_1) {
}
#if TARGET_PC
static dusk::utils::PaneCache mSelDtPanes[] = {
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
{MULTI_CHAR('gray_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('b_base'), 0.0f, 0.0f, false},
{MULTI_CHAR('b_base1'), 0.0f, 0.0f, false},
};
static dusk::utils::PaneCache fileSelPanes[] = {
{MULTI_CHAR('w_uzu00'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu01'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu02'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu03'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu04'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu05'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu06'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu07'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu08'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu09'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_er_msg'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_er_msE'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_er_msR'), 0.0f, 0.0f, false},
{MULTI_CHAR('er_for0'), 0.0f, 0.0f, false},
{MULTI_CHAR('er_for1'), 0.0f, 0.0f, false},
};
void dFile_select_c::fileSelectWide() {
static bool cachedPanes = false;
// Get pre-scale values for each pane
if (!cachedPanes) {
for (PaneCache& entry : mSelDtPanes) {
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -4165,7 +4198,7 @@ void dFile_select_c::fileSelectWide() {
entry.cached = true;
}
}
for (PaneCache& entry : fileSelPanes) {
for (dusk::utils::PaneCache& entry : fileSelPanes) {
J2DPane* pane = fileSel.Scr->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -4179,13 +4212,13 @@ void dFile_select_c::fileSelectWide() {
// Reset all panes
mSelDt.ScrDt->scale(1.0f, 1.0f);
mSelDt.ScrDt->translate(0.0f, 0.0f);
for (PaneCache& entry : mSelDtPanes) {
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
pane->translate(entry.origTransX, entry.origTransY);
}
for (PaneCache& entry : fileSelPanes) {
for (dusk::utils::PaneCache& entry : fileSelPanes) {
J2DPane* pane = fileSel.Scr->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
@@ -4257,7 +4290,7 @@ void dFile_select_c::fileSelectWide() {
mSelDt.ScrDt->search(MULTI_CHAR('fuku_n2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
// Spirals & Memory Card Text
for (PaneCache& entry : fileSelPanes) {
for (dusk::utils::PaneCache& entry : fileSelPanes) {
J2DPane* pane = fileSel.Scr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
@@ -4283,7 +4316,7 @@ void dFile_select_c::fileSelectWide() {
const f32 wideShiftFactor = mSelDt.ScrDt->search(MULTI_CHAR('gray_n'))->getTranslateX() * (wideScaleFactor - mDoGph_gInf_c::hudAspectScaleDown);
const f32 ultraShiftFactor = mSelDt.ScrDt->search(MULTI_CHAR('gray_n'))->getTranslateX() * (ultraScaleFactor - mDoGph_gInf_c::hudAspectScaleDown);
for (PaneCache& entry : mSelDtPanes) {
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
const size_t index = &entry - mSelDtPanes;
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
pane->setBasePosition(J2DBasePosition_0);
@@ -4351,7 +4384,7 @@ void dFile_select_c::fileSelectWide() {
}
// Spirals & Memory Card Text
for (PaneCache& entry : fileSelPanes) {
for (dusk::utils::PaneCache& entry : fileSelPanes) {
J2DPane* pane = fileSel.Scr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
+5
View File
@@ -311,6 +311,11 @@ u32 dLib_getExpandSizeFromAramArchive(JKRAramArchive* i_aramArchive, char const*
JUT_ASSERT(1260, readAddress == header);
JKRArchive::SDIFileEntry* entry = i_aramArchive->findFsResource(param_2, 0);
JUT_ASSERT(1263, entry != NULL);
#if TARGET_PC
if (u32 size; i_aramArchive->getOverlayFileSize(entry, &size)) {
return ALIGN_NEXT(size, 32);
}
#endif
u32 uVar1 = ALIGN_NEXT(JKRDecompExpandSize(header), 32);
u32 uVar5 = ALIGN_NEXT(entry->data_size, 32);
return uVar1 > uVar5 ? uVar1 : uVar5;
+36 -2
View File
@@ -39,6 +39,7 @@
#if TARGET_PC
#include "dusk/menu_pointer.h"
#include "dusk/utilities.hpp"
#endif
typedef void (dMenu_Collect2D_c::*initFunc)();
@@ -101,11 +102,44 @@ dMenu_Collect2D_c::~dMenu_Collect2D_c() {
}
#if TARGET_PC
static dusk::utils::PaneCache mpScreenPanes[] = {
{MULTI_CHAR('sa_tex_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('op_tex_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('heart_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('wolf_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('item_0_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('item_1_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('item_2_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('fish_3_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('lett_4_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('maki_5_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('kabu_6n'), 0.0f, 0.0f, false},
{MULTI_CHAR('t_t00'), 0.0f, 0.0f, false},
{MULTI_CHAR('f_t00'), 0.0f, 0.0f, false},
{MULTI_CHAR('itemn_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('infotxtn'), 0.0f, 0.0f, false},
{MULTI_CHAR('sa_op_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('title_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('menu_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_er_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('center_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('info_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('lavel_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('modelbgn'), 0.0f, 0.0f, false},
};
void dMenu_Collect2D_c::menuCollectWide() {
static bool cachedPanes = false;
// Get pre-scale values for each pane
if (!cachedPanes) {
for (PaneCache& entry : mpScreenPanes) {
for (dusk::utils::PaneCache& entry : mpScreenPanes) {
J2DPane* pane = mpScreen->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -119,7 +153,7 @@ void dMenu_Collect2D_c::menuCollectWide() {
// Reset all panes
mpScreen->scale(1.0f, 1.0f);
mpScreen->translate(0.0f, 0.0f);
for (PaneCache& entry : mpScreenPanes) {
for (dusk::utils::PaneCache& entry : mpScreenPanes) {
J2DPane* pane = mpScreen->search(entry.tag);
pane->scale(1.0f, 1.0f);
pane->translate(entry.origTransX, entry.origTransY);
+30 -9
View File
@@ -10,6 +10,7 @@
#include <cstring>
#include "JSystem/J2DGraph/J2DAnmLoader.h"
#include "dusk/utilities.hpp"
#include "dusk/version.hpp"
#include "f_op/f_op_msg_mng.h"
@@ -1512,11 +1513,31 @@ void dName_c::selectCursorPosSet(int row) {
}
#if TARGET_PC
static dusk::utils::PaneCache l_tagName[] = {
{MULTI_CHAR('m_00_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_3'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_01_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_2'), 0.0f, 0.0f, false},
{MULTI_CHAR('m03_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_1'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_05_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_0'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_07_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_4'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_09_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_3'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_10_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_2'), 0.0f, 0.0f, false},
{MULTI_CHAR('m12_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_0'), 0.0f, 0.0f, false},
};
static dusk::utils::PaneCache l_nameTagName[] = {
{MULTI_CHAR('name_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_07'), 0.0f, 0.0f, false},
};
static dusk::utils::PaneCache l_nameCurTagName[] = {
{MULTI_CHAR('s__n_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_07'), 0.0f, 0.0f, false},
};
void dName_c::nameWide() {
static bool cachedPanes = false;
// Get pre-scale values for each pane
if (!cachedPanes) {
for (PaneCache& entry : l_tagName) {
for (dusk::utils::PaneCache& entry : l_tagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -1524,7 +1545,7 @@ void dName_c::nameWide() {
entry.cached = true;
}
}
for (PaneCache& entry : l_nameTagName) {
for (dusk::utils::PaneCache& entry : l_nameTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -1532,7 +1553,7 @@ void dName_c::nameWide() {
entry.cached = true;
}
}
for (PaneCache& entry : l_nameCurTagName) {
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -1546,19 +1567,19 @@ void dName_c::nameWide() {
// Reset all panes
nameIn.NameInScr->scale(1.0f, 1.0f);
nameIn.NameInScr->translate(0.0f, 0.0f);
for (PaneCache& entry : l_tagName) {
for (dusk::utils::PaneCache& entry : l_tagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
pane->translate(entry.origTransX, entry.origTransY);
}
for (PaneCache& entry : l_nameTagName) {
for (dusk::utils::PaneCache& entry : l_nameTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
pane->translate(entry.origTransX, entry.origTransY);
}
for (PaneCache& entry : l_nameCurTagName) {
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
@@ -1574,17 +1595,17 @@ void dName_c::nameWide() {
break;
default: // Wii and Dusklight
// List of Characters Box
for (PaneCache& entry : l_tagName) {
for (dusk::utils::PaneCache& entry : l_tagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
// Letters being typed
for (PaneCache& entry : l_nameTagName) {
for (dusk::utils::PaneCache& entry : l_nameTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
// Underscores when typing below letters
for (PaneCache& entry : l_nameCurTagName) {
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
+2 -2
View File
@@ -335,7 +335,7 @@ int dRes_info_c::loadResource() {
#endif
void* res = mArchive->getIdxResource(fileIndex);
#if TARGET_PC
u32 size = mArchive->findIdxResource(fileIndex)->data_size;
u32 size = mArchive->getFileSize(mArchive->findIdxResource(fileIndex));
std::string fileName = mArchive->mStringTable +
(mArchive->findIdxResource(fileIndex)->type_flags_and_name_offset & 0xFFFFFF);
DuskLog.debug("Loading Resource: {} (Size: {})", fileName, size);
@@ -369,7 +369,7 @@ int dRes_info_c::loadResource() {
parentHeap = NULL;
}
int rt = dComIfG_setObjectRes(arcName, res, entry->data_size, parentHeap);
int rt = dComIfG_setObjectRes(arcName, res, DUSK_IF_ELSE(mArchive->getFileSize(entry),entry->data_size), parentHeap);
JUT_ASSERT(788, rt);
} else if (nodeType == 'BMDP') {
#if DEBUG
+2 -2
View File
@@ -35,7 +35,7 @@ static void SDLCALL GetNewAudio(
/**
* Render an entire new frame of audio and output it to SDL3.
* Note: "audio frames" are unrelated to video frames.
* @return Amount of audio samples rendered.
* @return Amount of audio samples rendered in bytes.
*/
static int RenderNewAudioFrame();
@@ -121,7 +121,7 @@ int RenderNewAudioFrame() {
JASAudioThread::snIntCount -= 1;
}
return static_cast<u16>(countSubframes) * DSP_SUBFRAME_SIZE;
return static_cast<u16>(countSubframes) * sizeof(OutputSubframe);
}
static void InterleaveOutputData(const OutputSubframe& data, std::span<f32> target) {
+16 -14
View File
@@ -1,8 +1,9 @@
#include "registry.hpp"
#include "slot_map.hpp"
#include "aurora/dvd.h"
#include <borealis/log.hpp>
#include "JSystem/JKernel/JKRArchive.h"
#include "aurora/dvd.h"
#include "dusk/mods/loader/loader.hpp"
#include "mods/svc/overlay.h"
@@ -24,7 +25,7 @@ constexpr borealis::Log Log{"dusk::mods::overlay"};
struct OverlayFileData {
std::string bundlePath;
std::shared_ptr<ModBundle> bundle;
std::shared_ptr<const std::vector<u8> > buffer;
std::shared_ptr<const std::vector<u8>> buffer;
};
// Keyed by the id passed to Aurora as per-file userdata. Guarded by s_overlayMutex: Aurora may
@@ -98,6 +99,7 @@ void append_runtime_overlays(std::vector<AuroraOverlayFile>& files, LoadedMod& m
for (const auto* slot : slots) {
const auto id = s_nextOverlayId++;
if (slot->buffer != nullptr) {
s_overlayFiles.emplace(id, OverlayFileData{{}, nullptr, slot->buffer});
} else {
@@ -110,7 +112,7 @@ void append_runtime_overlays(std::vector<AuroraOverlayFile>& files, LoadedMod& m
struct OpenOverlayFile {
std::vector<u8> ownedData;
std::shared_ptr<const std::vector<u8> > shared;
std::shared_ptr<const std::vector<u8>> shared;
size_t pos = 0;
[[nodiscard]] const std::vector<u8>& data() const {
@@ -200,6 +202,7 @@ void overlay_sync_files() {
Log.debug("Registering {} overlay file(s).", files.size());
aurora_dvd_overlay_files(files.data(), files.size(), nullptr);
JKRArchive::notifyOverlayFilesChanged();
for (const auto& file : files) {
std::free(const_cast<char*>(file.fileName));
@@ -220,13 +223,13 @@ uint64_t overlay_add_file(
uint64_t overlay_add_buffer(LoadedMod& mod, std::string discPath, std::vector<u8> data) {
const auto size = data.size();
const auto handle = s_runtimeOverlays.emplace(mod,
RuntimeOverlaySlot{
.discPath = std::move(discPath),
.buffer = std::make_shared<const std::vector<u8>>(std::move(data)),
.size = size,
.order = s_nextRuntimeOrder++,
});
const auto handle = s_runtimeOverlays.emplace(
mod, RuntimeOverlaySlot{
.discPath = std::move(discPath),
.buffer = std::make_shared<const std::vector<u8>>(std::move(data)),
.size = size,
.order = s_nextRuntimeOrder++,
});
s_overlaysDirty = true;
return handle;
}
@@ -275,13 +278,12 @@ ModResult overlay_add_file(
try {
size = mod->bundle->getFileSize(bundlePath);
} catch (const std::exception& e) {
Log.error(
"[{}] overlay add_file '{}' failed: {}", mod->metadata.id, bundlePath, e.what());
Log.error("[{}] overlay add_file '{}' failed: {}", mod->metadata.id, bundlePath, e.what());
return MOD_UNAVAILABLE;
}
if (size > kMaxOverlayFileSize) {
Log.error("[{}] overlay add_file '{}' failed: file too large ({} bytes)",
mod->metadata.id, bundlePath, size);
Log.error("[{}] overlay add_file '{}' failed: file too large ({} bytes)", mod->metadata.id,
bundlePath, size);
return MOD_INVALID_ARGUMENT;
}
+119 -32
View File
@@ -20,11 +20,13 @@
#include <algorithm>
#include <chrono>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -248,18 +250,16 @@ void wire_callback_binding(
if (!slot_live(guardHandle)) {
return value;
}
guarded_call(*modPtr, "control getter", [&] {
get(modPtr->context.get(), userData, &value);
});
guarded_call(
*modPtr, "control getter", [&] { get(modPtr->context.get(), userData, &value); });
return value;
};
const auto setValue = [modPtr, set, userData, guardHandle](const UiControlValue& value) {
if (!slot_live(guardHandle)) {
return;
}
guarded_call(*modPtr, "control setter", [&] {
set(modPtr->context.get(), userData, &value);
});
guarded_call(
*modPtr, "control setter", [&] { set(modPtr->context.get(), userData, &value); });
};
switch (desc.kind) {
case UI_CONTROL_TOGGLE:
@@ -280,6 +280,7 @@ void wire_callback_binding(
};
break;
case UI_CONTROL_STRING:
case UI_CONTROL_COLOR:
spec.getString = [getValue]() -> Rml::String {
const UiControlValue value = getValue();
return value.string_value != nullptr ? value.string_value : "";
@@ -358,7 +359,8 @@ bool wire_config_var_binding(LoadedMod& mod, const UiControlDesc& desc, ui::ModC
}
return true;
}
case UI_CONTROL_STRING: {
case UI_CONTROL_STRING:
case UI_CONTROL_COLOR: {
const auto find = [modPtr, varHandle] {
return static_cast<ConfigVar<std::string>*>(
config_find_var(*modPtr, varHandle, CONFIG_VAR_STRING));
@@ -401,9 +403,8 @@ void on_mod_window_destroyed(uint64_t handle) {
const UiWindowClosedFn onClosed = released->value.onClosed;
void* userData = released->value.onClosedUserData;
if (mod != nullptr && onClosed != nullptr) {
guarded_call(*mod, "window on_closed callback", [&] {
onClosed(mod->context.get(), handle, userData);
});
guarded_call(*mod, "window on_closed callback",
[&] { onClosed(mod->context.get(), handle, userData); });
}
}
@@ -451,9 +452,8 @@ ui::ModalAction make_dialog_action(LoadedMod& mod, uint64_t handle, const UiDial
return; // already being torn down
}
if (fn != nullptr) {
guarded_call(*modPtr, "dialog action callback", [&] {
fn(modPtr->context.get(), handle, userData);
});
guarded_call(*modPtr, "dialog action callback",
[&] { fn(modPtr->context.get(), handle, userData); });
}
// The callback may have closed the dialog already
if (!keepOpen && dialog_open(handle)) {
@@ -494,9 +494,8 @@ void ui_update_mods_panels(LoadedMod& mod) {
if (!mod.active || panel.update == nullptr) {
return;
}
invoke_mod_ui_callback(mod, "mod UI panel update", [&](ModError* error) {
return panel.update(mod.context.get(), panel.userData, error);
});
invoke_mod_ui_callback(mod, "mod UI panel update",
[&](ModError* error) { return panel.update(mod.context.get(), panel.userData, error); });
}
ModResult ui_pane_add_section(LoadedMod& mod, uint64_t pane, const char* title) {
@@ -568,15 +567,16 @@ ModResult ui_pane_add_control(
spec.isModified = wrap_predicate(mod, desc.is_modified, desc.user_data, pane);
switch (desc.kind) {
case UI_CONTROL_BUTTON:
spec.kind = ui::ModControlSpec::Kind::Button;
case UI_CONTROL_GROUP:
spec.kind = desc.kind == UI_CONTROL_BUTTON ? ui::ModControlSpec::Kind::Button :
ui::ModControlSpec::Kind::Group;
spec.onPressed = [modPtr = &mod, fn = desc.on_pressed, userData = desc.user_data,
guardHandle = pane] {
if (!slot_live(guardHandle)) {
return;
}
guarded_call(*modPtr, "control on_pressed callback", [&] {
fn(modPtr->context.get(), userData);
});
guarded_call(*modPtr, "control on_pressed callback",
[&] { fn(modPtr->context.get(), userData); });
};
break;
case UI_CONTROL_TOGGLE:
@@ -599,6 +599,13 @@ ModResult ui_pane_add_control(
spec.kind = ui::ModControlSpec::Kind::String;
spec.maxLength = desc.max_length < 1 ? -1 : desc.max_length;
break;
case UI_CONTROL_COLOR:
spec.kind = ui::ModControlSpec::Kind::Color;
spec.colorAlpha = desc.color_alpha;
for (size_t i = 0; i < desc.color_preset_count; ++i) {
spec.colorPresets.emplace_back(desc.color_presets[i]);
}
break;
case UI_CONTROL_SELECT:
spec.kind = ui::ModControlSpec::Kind::Select;
if (slot->helpPane == nullptr) {
@@ -614,7 +621,7 @@ ModResult ui_pane_add_control(
return MOD_INVALID_ARGUMENT;
}
if (desc.kind != UI_CONTROL_BUTTON) {
if (desc.kind != UI_CONTROL_BUTTON && desc.kind != UI_CONTROL_GROUP) {
if (desc.binding == UI_BINDING_CONFIG_VAR) {
if (!wire_config_var_binding(mod, desc, spec)) {
Log.error("[{}] pane_add_control: config var handle {:#x} is unknown or its type "
@@ -641,6 +648,39 @@ ModResult ui_pane_add_control(
return MOD_OK;
}
ModResult ui_pane_add_group(LoadedMod& mod, uint64_t groupPaneHandle, uint64_t targetPaneHandle,
const UiGroupDesc& desc, uint64_t* outElem) {
auto* groupSlot = resolve(mod, groupPaneHandle, UiSlotKind::Pane, "pane_add_group");
auto* targetSlot = resolve(mod, targetPaneHandle, UiSlotKind::Pane, "pane_add_group");
if (groupSlot == nullptr || targetSlot == nullptr) {
return MOD_INVALID_ARGUMENT;
}
if (groupSlot->helpPane != targetSlot->pane) {
Log.error("[{}] pane_add_group: panes are not a paired tab layout", mod.metadata.id);
return MOD_UNSUPPORTED;
}
auto* groupPane = groupSlot->pane;
auto* targetPane = targetSlot->pane;
auto& button = groupPane->add_group_button(ui::GroupButton::Props{.text = desc.label});
groupPane->register_control(button, *targetPane,
[modPtr = &mod, groupPaneHandle, targetPaneHandle, build = desc.build,
userData = desc.user_data](ui::Pane&) {
if (!slot_live(groupPaneHandle) || !slot_live(targetPaneHandle) || !modPtr->active) {
return;
}
invoke_mod_ui_callback(*modPtr, "mod UI group build", [&](ModError* error) {
return build(modPtr->context.get(), targetPaneHandle, userData, error);
});
});
if (outElem != nullptr) {
auto& elemSlot = alloc_slot(mod, UiSlotKind::Control, *outElem);
track_element(*outElem, elemSlot, *button.root());
}
return MOD_OK;
}
ModResult ui_elem_set_text(LoadedMod& mod, uint64_t elem, const char* text) {
auto* slot = resolve(mod, elem, UiSlotKind::Text, "elem_set_text");
if (slot == nullptr) {
@@ -798,9 +838,8 @@ ModResult ui_dialog_push(LoadedMod& mod, const UiDialogDesc& desc, uint64_t& out
return; // already being torn down
}
if (fn != nullptr) {
guarded_call(*modPtr, "dialog on_dismiss callback", [&] {
fn(modPtr->context.get(), handle, userData);
});
guarded_call(*modPtr, "dialog on_dismiss callback",
[&] { fn(modPtr->context.get(), handle, userData); });
}
if (dialog_open(handle)) {
static_cast<ModDialog&>(modal).close();
@@ -810,8 +849,8 @@ ModResult ui_dialog_push(LoadedMod& mod, const UiDialogDesc& desc, uint64_t& out
props.actions.push_back(make_dialog_action(mod, handle, desc.actions[i]));
}
auto dialog =
std::make_unique<ModDialog>(std::move(props), [handle] { on_mod_dialog_destroyed(handle); });
auto dialog = std::make_unique<ModDialog>(
std::move(props), [handle] { on_mod_dialog_destroyed(handle); });
if (auto* slot = slot_from_handle(handle)) {
slot->document = dialog.get();
}
@@ -915,9 +954,8 @@ std::vector<ModMenuTabEntry> ui_mod_menu_tabs() {
if (!slot_live(handle) || !modPtr->active) {
return; // registered by a since-unloaded mod image
}
guarded_call(*modPtr, "menu tab on_selected callback", [&] {
fn(modPtr->context.get(), userData);
});
guarded_call(*modPtr, "menu tab on_selected callback",
[&] { fn(modPtr->context.get(), userData); });
}});
}
}
@@ -1038,7 +1076,8 @@ void ui_remove_mod(LoadedMod& mod) {
}
}
ModResult ui_get_clipboard_text(LoadedMod& mod, char* buffer, size_t bufferSize, size_t* outLength) {
ModResult ui_get_clipboard_text(
LoadedMod& mod, char* buffer, size_t bufferSize, size_t* outLength) {
if (outLength != nullptr) {
*outLength = 0;
}
@@ -1082,18 +1121,38 @@ namespace {
// Validation of the tagged control descriptor: required fields per kind/binding. Value
// translation and cvar wiring live in loader/ui.cpp.
bool valid_color_preset(const char* value, bool alpha) {
const std::string_view text{value};
if (text == "rainbow") {
return true;
}
if (text.size() != 6 && (!alpha || text.size() != 8)) {
return false;
}
return std::ranges::all_of(text, [](char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
});
}
bool valid_control_desc(const UiControlDesc& desc) {
if (desc.struct_size < sizeof(UiControlDesc) || desc.label == nullptr) {
constexpr size_t kLegacyDescSize = offsetof(UiControlDesc, color_presets);
if (desc.struct_size < kLegacyDescSize || desc.label == nullptr) {
return false;
}
switch (desc.kind) {
case UI_CONTROL_BUTTON:
case UI_CONTROL_GROUP:
return desc.on_pressed != nullptr;
case UI_CONTROL_TOGGLE:
case UI_CONTROL_NUMBER:
case UI_CONTROL_STRING:
case UI_CONTROL_SELECT:
break;
case UI_CONTROL_COLOR:
if (desc.struct_size < sizeof(UiControlDesc)) {
return false;
}
break;
default:
return false;
}
@@ -1107,6 +1166,18 @@ bool valid_control_desc(const UiControlDesc& desc) {
}
}
}
if (desc.kind == UI_CONTROL_COLOR && desc.color_preset_count != 0) {
if (desc.color_presets == nullptr) {
return false;
}
for (size_t i = 0; i < desc.color_preset_count; ++i) {
if (desc.color_presets[i] == nullptr ||
!valid_color_preset(desc.color_presets[i], desc.color_alpha))
{
return false;
}
}
}
switch (desc.binding) {
case UI_BINDING_CALLBACKS:
return desc.get != nullptr && desc.set != nullptr;
@@ -1183,6 +1254,20 @@ ModResult ui_pane_add_control(ModContext* context, UiElementHandle pane, const U
return ui_impl::ui_pane_add_control(*mod, pane, *desc, outElem);
}
ModResult ui_pane_add_group(ModContext* context, UiElementHandle groupPane,
UiElementHandle targetPane, const UiGroupDesc* desc, UiElementHandle* outElem) {
if (outElem != nullptr) {
*outElem = 0;
}
auto* mod = mod_from_context(context);
if (mod == nullptr || groupPane == 0 || targetPane == 0 || desc == nullptr ||
desc->struct_size < sizeof(UiGroupDesc) || desc->label == nullptr || desc->build == nullptr)
{
return MOD_INVALID_ARGUMENT;
}
return ui_impl::ui_pane_add_group(*mod, groupPane, targetPane, *desc, outElem);
}
ModResult ui_elem_set_text(ModContext* context, UiElementHandle elem, const char* text) {
auto* mod = mod_from_context(context);
if (mod == nullptr || elem == 0 || text == nullptr) {
@@ -1406,7 +1491,8 @@ ModResult ui_dialog_add_action(
return ui_impl::ui_dialog_add_action(*mod, dialog, *action);
}
ModResult ui_get_clipboard_text(ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength) {
ModResult ui_get_clipboard_text(
ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength) {
auto* mod = mod_from_context(ctx);
if (mod == nullptr || (buffer == nullptr && bufferSize != 0)) {
return MOD_INVALID_ARGUMENT;
@@ -1451,6 +1537,7 @@ constexpr UiService s_uiService{
.push_toast = ui_push_toast,
.get_clipboard_text = ui_get_clipboard_text,
.set_clipboard_text = ui_set_clipboard_text,
.pane_add_group = ui_pane_add_group,
};
} // namespace
+744
View File
@@ -0,0 +1,744 @@
#include "color_input.hpp"
#include "button.hpp"
#include "input.hpp"
#include "nav_group.hpp"
#include "m_Do/m_Do_audio.h"
#include <SDL3/SDL_clipboard.h>
#include <fmt/format.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <optional>
#include <string_view>
namespace dusk::ui {
namespace {
// These dimensions must match the controls in res/rml/popover.rcss.
constexpr float kSvWidthDp = 240.0f;
constexpr float kSvHeightDp = 150.0f;
constexpr float kBarHeightDp = 14.0f;
constexpr float kCursorDp = 14.0f;
constexpr size_t kHistoryLimit = 8;
constexpr int kSwatchColumns = 8;
constexpr float kSvAnalogRate = 0.7f;
constexpr float kHueAnalogRate = 240.0f;
constexpr float kAlphaAnalogRate = 0.7f;
constexpr float kSvNudge = 1.0f / 255.0f;
constexpr float kHueNudge = 1.0f;
constexpr float kAlphaNudge = 1.0f / 255.0f;
std::vector<Rml::String> sColorHistory;
bool directional_command(NavCommand command) {
return command == NavCommand::Up || command == NavCommand::Down ||
command == NavCommand::Left || command == NavCommand::Right;
}
float analog_response(float value) {
return value * std::abs(value);
}
class AdjustmentRegion : public Component {
public:
struct Props {
std::function<void()> begin;
std::function<void()> cancel;
std::function<void(NavCommand)> nudge;
std::function<void(float, float, float)> adjust;
};
AdjustmentRegion(Rml::Element* root, Props props) : Component{root}, mProps{std::move(props)} {
listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto command = map_nav_event(event);
if (command == NavCommand::Confirm) {
if (mActive) {
finish();
} else {
if (mProps.begin) {
mProps.begin();
}
mActive = true;
mAnalogReady = mAxis.x == 0.0f && mAxis.y == 0.0f;
mRoot->SetClass("adjusting", true);
}
event.StopPropagation();
return;
}
if (command == NavCommand::Cancel && mActive) {
if (mProps.cancel) {
mProps.cancel();
}
finish();
event.StopPropagation();
return;
}
if (directional_command(command) && (mActive || mAwaitingNeutral)) {
if (mActive && mAxis.x == 0.0f && mAxis.y == 0.0f && mProps.nudge) {
mProps.nudge(command);
}
event.StopPropagation();
}
});
listen(mRoot, Rml::String{input::kNavAxisEvent}, [this](Rml::Event& event) {
mAxis = {
event.GetParameter<float>("x", 0.0f),
event.GetParameter<float>("y", 0.0f),
};
if (mAxis.x == 0.0f && mAxis.y == 0.0f) {
mAwaitingNeutral = false;
mAnalogReady = true;
}
const float deltaSeconds = event.GetParameter<float>("dt", 0.0f);
if (mActive && mAnalogReady && deltaSeconds > 0.0f && mProps.adjust) {
mProps.adjust(mAxis.x, mAxis.y, deltaSeconds);
}
});
listen(mRoot, Rml::EventId::Blur, [this](Rml::Event&) {
if (mActive) {
finish();
}
});
}
private:
void finish() {
mActive = false;
mAnalogReady = false;
mAwaitingNeutral = mAxis.x != 0.0f || mAxis.y != 0.0f;
mRoot->SetClass("adjusting", false);
}
Props mProps;
Rml::Vector2f mAxis;
bool mActive = false;
bool mAnalogReady = false;
bool mAwaitingNeutral = false;
};
const std::vector<Rml::String>& default_presets() {
static const std::vector<Rml::String> presets = {
"ff4d4d",
"ff8a3d",
"ffd43b",
"61c454",
"3dc7c2",
"4d88ff",
"8b5cf6",
"e56aa6",
"8b6a45",
"ffffff",
"888888",
"202020",
};
return presets;
}
int hex_digit(char value) {
if (value >= '0' && value <= '9') {
return value - '0';
}
if (value >= 'a' && value <= 'f') {
return value - 'a' + 10;
}
if (value >= 'A' && value <= 'F') {
return value - 'A' + 10;
}
return -1;
}
std::optional<Rml::Colourb> parse_color(std::string_view value, bool alpha) {
if (value.starts_with('#')) {
value.remove_prefix(1);
}
if (value.size() != 6 && (!alpha || value.size() != 8)) {
return std::nullopt;
}
uint8_t channels[4] = {0, 0, 0, 255};
for (size_t i = 0; i < value.size() / 2; ++i) {
const int high = hex_digit(value[i * 2]);
const int low = hex_digit(value[i * 2 + 1]);
if (high < 0 || low < 0) {
return std::nullopt;
}
channels[i] = static_cast<uint8_t>((high << 4) | low);
}
return Rml::Colourb{channels[0], channels[1], channels[2], channels[3]};
}
Rml::String normalize_value(Rml::String value, bool alpha) {
if (value == "rainbow") {
return value;
}
const auto color = parse_color(value, alpha);
if (!color.has_value()) {
return value;
}
if (alpha && color->alpha != 255) {
return fmt::format(
"{:02x}{:02x}{:02x}{:02x}", color->red, color->green, color->blue, color->alpha);
}
return fmt::format("{:02x}{:02x}{:02x}", color->red, color->green, color->blue);
}
Rml::String css_color(Rml::Colourb color) {
return fmt::format("rgba({},{},{},{})", color.red, color.green, color.blue, color.alpha);
}
Rml::String format_color(Rml::Colourb color, bool hex, bool alpha) {
if (hex) {
if (alpha && color.alpha != 255) {
return fmt::format(
"#{:02X}{:02X}{:02X}{:02X}", color.red, color.green, color.blue, color.alpha);
}
return fmt::format("#{:02X}{:02X}{:02X}", color.red, color.green, color.blue);
}
if (alpha) {
return fmt::format("rgba({}, {}, {}, {:.0f}%)", color.red, color.green, color.blue,
static_cast<float>(color.alpha) / 255.0f * 100.0f);
}
return fmt::format("rgb({}, {}, {})", color.red, color.green, color.blue);
}
Rml::Colourb hsv_to_rgb(float hue, float sat, float val, float alpha) {
hue -= 360.0f * std::floor(hue / 360.0f);
const float chroma = val * sat;
const float intermediate = chroma * (1.0f - std::abs(std::fmod(hue / 60.0f, 2.0f) - 1.0f));
const float offset = val - chroma;
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
switch (static_cast<int>(hue / 60.0f) % 6) {
case 0:
red = chroma;
green = intermediate;
break;
case 1:
red = intermediate;
green = chroma;
break;
case 2:
green = chroma;
blue = intermediate;
break;
case 3:
green = intermediate;
blue = chroma;
break;
case 4:
red = intermediate;
blue = chroma;
break;
case 5:
default:
red = chroma;
blue = intermediate;
break;
}
const auto toByte = [](float value) {
return static_cast<Rml::byte>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
};
return {toByte(red + offset), toByte(green + offset), toByte(blue + offset), toByte(alpha)};
}
void rgb_to_hsv(Rml::Colourb color, float& hue, float& sat, float& val) {
const float red = static_cast<float>(color.red) / 255.0f;
const float green = static_cast<float>(color.green) / 255.0f;
const float blue = static_cast<float>(color.blue) / 255.0f;
const float maxChannel = std::max({red, green, blue});
const float minChannel = std::min({red, green, blue});
const float delta = maxChannel - minChannel;
val = maxChannel;
sat = maxChannel > 0.0f ? delta / maxChannel : 0.0f;
if (delta <= 0.0f) {
hue = 0.0f;
return;
}
if (maxChannel == red) {
hue = 60.0f * std::fmod((green - blue) / delta + 6.0f, 6.0f);
} else if (maxChannel == green) {
hue = 60.0f * ((blue - red) / delta + 2.0f);
} else {
hue = 60.0f * ((red - green) / delta + 4.0f);
}
}
void apply_swatch(Rml::Element* element, const Rml::String& value, bool alpha) {
element->RemoveProperty("background-color");
element->RemoveProperty("decorator");
element->SetClass("empty", false);
element->SetClass("rainbow", value == "rainbow");
if (value == "rainbow") {
element->SetProperty("decorator", "linear-gradient(90deg, #ff4d4d, #ffd43b, #61c454, "
"#3dc7c2, #4d88ff, #8b5cf6, #e56aa6)");
return;
}
const auto color = parse_color(value, alpha);
if (!color.has_value()) {
element->SetClass("empty", true);
return;
}
element->SetProperty("background-color", css_color(*color));
}
void remember_color(Rml::String value, bool alpha) {
value = normalize_value(std::move(value), alpha);
if (value != "rainbow" && !parse_color(value, alpha).has_value()) {
return;
}
std::erase(sColorHistory, value);
sColorHistory.insert(sColorHistory.begin(), std::move(value));
if (sColorHistory.size() > kHistoryLimit) {
sColorHistory.resize(kHistoryLimit);
}
}
} // namespace
ColorInput::ColorInput(Rml::Element* parent, Props props)
: BaseControlledSelectButton{parent, {.key = props.key, .submit = false}},
mProps{std::move(props)} {
mRoot->SetClass("color-input", true);
mSwatch = append(mRoot, "color-swatch");
refresh_swatch();
}
ColorInput::~ColorInput() {
if (mPicker != nullptr) {
mPicker->on_close(nullptr);
mPicker->on_focus(nullptr);
mPicker->dismiss();
}
}
void ColorInput::update() {
refresh_swatch();
if (mPickerNavigation != nullptr) {
mPickerNavigation->update();
}
BaseControlledSelectButton::update();
}
bool ColorInput::modified() const {
return mProps.isModified ? mProps.isModified() : BaseControlledSelectButton::modified();
}
bool ColorInput::disabled() const {
return mProps.isDisabled ? mProps.isDisabled() : BaseControlledSelectButton::disabled();
}
Rml::String ColorInput::format_value() {
const Rml::String value = mProps.getValue ? mProps.getValue() : "";
if (value == "rainbow") {
return "Rainbow";
}
const auto color = parse_color(value, mProps.alpha);
if (color.has_value()) {
return format_color(*color, mHexFormat, mProps.alpha);
}
return value.empty() ? "Default" : value;
}
bool ColorInput::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
toggle_picker();
return true;
}
return false;
}
void ColorInput::toggle_picker() {
if (mPicker != nullptr) {
mPicker->dismiss();
return;
}
mInitialValue = mProps.getValue ? mProps.getValue() : "";
mInitialValueRemembered = false;
const auto initialColor = parse_color(mInitialValue, mProps.alpha);
const Rml::Colourb color = initialColor.value_or(Rml::Colourb{128, 128, 128, 255});
rgb_to_hsv(color, mHue, mSat, mVal);
mAlpha = static_cast<float>(color.alpha) / 255.0f;
auto picker = std::make_unique<Popover>(mRoot, mProps.side, "color-picker");
mPicker = picker.get();
mPicker->on_close([this] {
if (mInitialValueRemembered && mProps.getValue) {
remember_color(mProps.getValue(), mProps.alpha);
}
mPicker = nullptr;
mPickerNavigation.reset();
mPickerListeners.clear();
mSvArea = nullptr;
mSvCursor = nullptr;
mHueBar = nullptr;
mHueCursor = nullptr;
mAlphaBar = nullptr;
mAlphaCursor = nullptr;
mPickerValue = nullptr;
});
build_picker();
refresh_picker();
mPicker->on_focus(
[this] { return mPickerNavigation != nullptr && mPickerNavigation->focus(); });
push_document(std::move(picker));
}
void ColorInput::build_picker() {
auto* body = mPicker->body();
mPickerNavigation = std::make_unique<NavGroup>(
body, NavGroup::Props{
.layout = NavGroup::Layout::Vertical,
.horizontalBoundary = NavGroup::Boundary::Stop,
.verticalBoundary = NavGroup::Boundary::Stop,
});
mSvArea = append(body, "color-sv");
mSvCursor = append(mSvArea, "color-cursor");
mPickerNavigation->add_existing_item<AdjustmentRegion>(
mSvArea, AdjustmentRegion::Props{
.begin = [this] { begin_adjustment(); },
.cancel = [this] { cancel_adjustment(); },
.nudge = [this](NavCommand direction) { nudge_sv(direction); },
.adjust = [this](float x, float y,
float deltaSeconds) { adjust_sv(x, y, deltaSeconds); },
});
mHueBar = append(body, "color-hue");
mHueCursor = append(mHueBar, "color-cursor");
mPickerNavigation->add_existing_item<AdjustmentRegion>(mHueBar,
AdjustmentRegion::Props{
.begin = [this] { begin_adjustment(); },
.cancel = [this] { cancel_adjustment(); },
.nudge = [this](NavCommand direction) { nudge_hue(direction); },
.adjust = [this](float x, float, float deltaSeconds) { adjust_hue(x, deltaSeconds); },
});
if (mProps.alpha) {
mAlphaBar = append(body, "color-alpha");
mAlphaCursor = append(mAlphaBar, "color-cursor");
mPickerNavigation->add_existing_item<AdjustmentRegion>(
mAlphaBar, AdjustmentRegion::Props{
.begin = [this] { begin_adjustment(); },
.cancel = [this] { cancel_adjustment(); },
.nudge = [this](NavCommand direction) { nudge_alpha(direction); },
.adjust = [this](float x, float,
float deltaSeconds) { adjust_alpha(x, deltaSeconds); },
});
}
add_presets(*mPickerNavigation);
add_history(*mPickerNavigation);
auto* footer = append(body, "color-footer");
auto& footerNavigation = mPickerNavigation->add_existing_item<NavGroup>(
footer, NavGroup::Props{
.layout = NavGroup::Layout::Horizontal,
.horizontalBoundary = NavGroup::Boundary::Stop,
.verticalBoundary = NavGroup::Boundary::Bubble,
});
const auto formatLabel = [this] {
return mHexFormat ? "Hex" : (mProps.alpha ? "RGBA" : "RGB");
};
auto& formatButton = footerNavigation.add_item<Button>(formatLabel());
formatButton.on_pressed([this, formatButtonPtr = &formatButton, formatLabel] {
mHexFormat = !mHexFormat;
formatButtonPtr->set_text(formatLabel());
refresh_picker();
});
footerNavigation.add_item<Button>("Default").on_pressed([this] { commit_value(""); });
auto& valueButton = footerNavigation.add_item<Button>("", "color-value");
mPickerValue = valueButton.root();
mPickerValue->SetAttribute("title", "Copy color");
valueButton.on_pressed([this] {
SDL_SetClipboardText(format_color(current_color(), mHexFormat, mProps.alpha).c_str());
push_toast({
.type = "info",
.title = "Color",
.content = "Copied to clipboard",
.duration = std::chrono::seconds{2},
});
});
const auto track = [this](Rml::Element* element, float widthDp, float heightDp,
std::function<void(float, float)> apply) {
const auto handler = [this, element, widthDp, heightDp, apply = std::move(apply)](
Rml::Event& event) {
const float dpRatio = element->GetContext()->GetDensityIndependentPixelRatio();
const auto origin = element->GetAbsoluteOffset(Rml::BoxArea::Border);
const float x =
(event.GetParameter<float>("mouse_x", 0.0f) - origin.x) / (widthDp * dpRatio);
const float y =
(event.GetParameter<float>("mouse_y", 0.0f) - origin.y) / (heightDp * dpRatio);
element->Focus(true);
apply(std::clamp(x, 0.0f, 1.0f), std::clamp(y, 0.0f, 1.0f));
commit_color();
};
mPickerListeners.push_back(
std::make_unique<ScopedEventListener>(element, Rml::EventId::Mousedown, handler));
mPickerListeners.push_back(
std::make_unique<ScopedEventListener>(element, Rml::EventId::Drag, handler));
};
track(mSvArea, kSvWidthDp, kSvHeightDp, [this](float x, float y) {
mSat = x;
mVal = 1.0f - y;
});
track(mHueBar, kSvWidthDp, kBarHeightDp,
[this](float x, float) { mHue = std::min(x * 360.0f, 359.9f); });
if (mAlphaBar != nullptr) {
track(mAlphaBar, kSvWidthDp, kBarHeightDp, [this](float x, float) { mAlpha = x; });
}
}
void ColorInput::add_presets(NavGroup& navigation) {
append_text(append(navigation.root(), "color-heading"), "Presets");
auto* gridElement = append(navigation.root(), "color-presets");
auto& grid = navigation.add_existing_item<NavGroup>(
gridElement, NavGroup::Props{
.layout = NavGroup::Layout::Grid,
.columns = kSwatchColumns,
.horizontalBoundary = NavGroup::Boundary::Stop,
.verticalBoundary = NavGroup::Boundary::Bubble,
});
const auto& presets = mProps.presets.empty() ? default_presets() : mProps.presets;
for (const auto& preset : presets) {
add_swatch_button(grid, preset);
}
}
void ColorInput::add_history(NavGroup& navigation) {
const bool supportsRainbow =
std::ranges::any_of(mProps.presets, [](const auto& preset) { return preset == "rainbow"; });
std::vector<Rml::String> compatibleHistory;
for (const auto& value : sColorHistory) {
if (parse_color(value, mProps.alpha).has_value() || (value == "rainbow" && supportsRainbow))
{
compatibleHistory.push_back(value);
}
}
if (compatibleHistory.empty()) {
return;
}
append_text(append(navigation.root(), "color-heading"), "Recent");
auto* gridElement = append(navigation.root(), "color-history");
auto& grid = navigation.add_existing_item<NavGroup>(
gridElement, NavGroup::Props{
.layout = NavGroup::Layout::Grid,
.columns = kSwatchColumns,
.horizontalBoundary = NavGroup::Boundary::Stop,
.verticalBoundary = NavGroup::Boundary::Bubble,
});
for (const auto& value : compatibleHistory) {
add_swatch_button(grid, value);
}
}
void ColorInput::add_swatch_button(NavGroup& navigation, const Rml::String& value) {
auto& button = navigation.add_item<Button>("");
button.root()->SetClass("color-swatch-button", true);
button.root()->SetInnerRML("");
auto* chip = append(button.root(), "color-chip");
apply_swatch(chip, value, mProps.alpha);
Rml::String title = value == "rainbow" ? "Rainbow" : value;
if (const auto color = parse_color(value, mProps.alpha)) {
title = format_color(*color, true, mProps.alpha);
}
button.root()->SetAttribute("title", title);
button.on_pressed([this, value] {
mDoAud_seStartMenu(kSoundItemChange);
commit_value(value);
});
}
void ColorInput::begin_adjustment() {
mAdjustmentStartValue = mProps.getValue ? mProps.getValue() : "";
mAdjustmentStartHue = mHue;
mAdjustmentStartSat = mSat;
mAdjustmentStartVal = mVal;
mAdjustmentStartAlpha = mAlpha;
}
void ColorInput::cancel_adjustment() {
mHue = mAdjustmentStartHue;
mSat = mAdjustmentStartSat;
mVal = mAdjustmentStartVal;
mAlpha = mAdjustmentStartAlpha;
commit_value(mAdjustmentStartValue);
}
void ColorInput::adjust_sv(float x, float y, float deltaSeconds) {
const float sat =
std::clamp(mSat + analog_response(x) * kSvAnalogRate * deltaSeconds, 0.0f, 1.0f);
const float val =
std::clamp(mVal - analog_response(y) * kSvAnalogRate * deltaSeconds, 0.0f, 1.0f);
if (sat == mSat && val == mVal) {
return;
}
mSat = sat;
mVal = val;
commit_color();
}
void ColorInput::adjust_hue(float x, float deltaSeconds) {
const float hue =
std::clamp(mHue + analog_response(x) * kHueAnalogRate * deltaSeconds, 0.0f, 359.9f);
if (hue == mHue) {
return;
}
mHue = hue;
commit_color();
}
void ColorInput::adjust_alpha(float x, float deltaSeconds) {
const float alpha =
std::clamp(mAlpha + analog_response(x) * kAlphaAnalogRate * deltaSeconds, 0.0f, 1.0f);
if (alpha == mAlpha) {
return;
}
mAlpha = alpha;
commit_color();
}
void ColorInput::nudge_sv(NavCommand direction) {
const float sat = std::clamp(mSat + (direction == NavCommand::Right ? kSvNudge :
direction == NavCommand::Left ? -kSvNudge :
0.0f),
0.0f, 1.0f);
const float val = std::clamp(mVal + (direction == NavCommand::Up ? kSvNudge :
direction == NavCommand::Down ? -kSvNudge :
0.0f),
0.0f, 1.0f);
if (sat == mSat && val == mVal) {
return;
}
mSat = sat;
mVal = val;
mDoAud_seStartMenu(kSoundItemChange);
commit_color();
}
void ColorInput::nudge_hue(NavCommand direction) {
const float hue = std::clamp(mHue + (direction == NavCommand::Right ? kHueNudge :
direction == NavCommand::Left ? -kHueNudge :
0.0f),
0.0f, 359.9f);
if (hue == mHue) {
return;
}
mHue = hue;
mDoAud_seStartMenu(kSoundItemChange);
commit_color();
}
void ColorInput::nudge_alpha(NavCommand direction) {
const float alpha = std::clamp(mAlpha + (direction == NavCommand::Right ? kAlphaNudge :
direction == NavCommand::Left ? -kAlphaNudge :
0.0f),
0.0f, 1.0f);
if (alpha == mAlpha) {
return;
}
mAlpha = alpha;
mDoAud_seStartMenu(kSoundItemChange);
commit_color();
}
void ColorInput::commit_color() {
const auto color = current_color();
Rml::String value;
if (mProps.alpha && color.alpha != 255) {
value = fmt::format(
"{:02x}{:02x}{:02x}{:02x}", color.red, color.green, color.blue, color.alpha);
} else {
value = fmt::format("{:02x}{:02x}{:02x}", color.red, color.green, color.blue);
}
const bool changed = !mProps.getValue || mProps.getValue() != value;
if (changed) {
remember_initial_value();
if (mProps.setValue) {
mProps.setValue(std::move(value));
}
refresh_swatch();
}
refresh_picker();
}
void ColorInput::commit_value(Rml::String value) {
remember_initial_value();
value = normalize_value(std::move(value), mProps.alpha);
if (const auto color = parse_color(value, mProps.alpha)) {
rgb_to_hsv(*color, mHue, mSat, mVal);
mAlpha = static_cast<float>(color->alpha) / 255.0f;
}
if (mProps.setValue) {
mProps.setValue(std::move(value));
}
refresh_swatch();
refresh_picker();
}
void ColorInput::remember_initial_value() {
if (mInitialValueRemembered) {
return;
}
remember_color(mInitialValue, mProps.alpha);
mInitialValueRemembered = true;
}
void ColorInput::refresh_swatch() {
const Rml::String value = mProps.getValue ? mProps.getValue() : "";
if (mHasDisplayedValue && value == mDisplayedValue) {
return;
}
mHasDisplayedValue = true;
mDisplayedValue = value;
apply_swatch(mSwatch, value, mProps.alpha);
}
void ColorInput::refresh_picker() {
if (mPicker == nullptr) {
return;
}
const float dpRatio = mSvArea->GetContext()->GetDensityIndependentPixelRatio();
const auto place = [dpRatio](Rml::Element* cursor, float xDp, float yDp) {
cursor->SetProperty(Rml::PropertyId::Left,
Rml::Property{(xDp - kCursorDp / 2.0f) * dpRatio, Rml::Unit::PX});
cursor->SetProperty(
Rml::PropertyId::Top, Rml::Property{(yDp - kCursorDp / 2.0f) * dpRatio, Rml::Unit::PX});
};
const Rml::Colourb hueColor = hsv_to_rgb(mHue, 1.0f, 1.0f, 1.0f);
const Rml::Colourb color = current_color();
const Rml::Colourb opaque{color.red, color.green, color.blue, 255};
mSvArea->SetProperty(
"decorator", fmt::format("linear-gradient(180deg, rgba(0,0,0,0), #000000), "
"linear-gradient(90deg, #ffffff, {})",
css_color(hueColor)));
place(mSvCursor, mSat * kSvWidthDp, (1.0f - mVal) * kSvHeightDp);
place(mHueCursor, mHue / 360.0f * kSvWidthDp, kBarHeightDp / 2.0f);
if (mAlphaBar != nullptr) {
mAlphaBar->SetProperty(
"decorator", fmt::format("linear-gradient(90deg, rgba({0},{1},{2},0), {3})", opaque.red,
opaque.green, opaque.blue, css_color(opaque)));
place(mAlphaCursor, mAlpha * kSvWidthDp, kBarHeightDp / 2.0f);
}
mPickerValue->SetInnerRML(format_color(color, mHexFormat, mProps.alpha));
}
Rml::Colourb ColorInput::current_color() const {
return hsv_to_rgb(mHue, mSat, mVal, mAlpha);
}
} // namespace dusk::ui
+85
View File
@@ -0,0 +1,85 @@
#pragma once
#include "popover.hpp"
#include "select_button.hpp"
namespace dusk::ui {
class NavGroup;
class ColorInput : public BaseControlledSelectButton {
public:
struct Props {
Rml::String key;
Popover::Side side = Popover::Side::Left;
std::function<Rml::String()> getValue;
std::function<void(Rml::String)> setValue;
std::function<bool()> isDisabled;
std::function<bool()> isModified;
std::vector<Rml::String> presets;
bool alpha = false;
};
ColorInput(Rml::Element* parent, Props props);
~ColorInput() override;
void update() override;
bool modified() const override;
bool disabled() const override;
protected:
Rml::String format_value() override;
bool handle_nav_command(NavCommand cmd) override;
private:
void toggle_picker();
void build_picker();
void add_presets(NavGroup& navigation);
void add_history(NavGroup& navigation);
void add_swatch_button(NavGroup& navigation, const Rml::String& value);
void begin_adjustment();
void cancel_adjustment();
void adjust_sv(float x, float y, float deltaSeconds);
void adjust_hue(float x, float deltaSeconds);
void adjust_alpha(float x, float deltaSeconds);
void nudge_sv(NavCommand direction);
void nudge_hue(NavCommand direction);
void nudge_alpha(NavCommand direction);
void commit_color();
void commit_value(Rml::String value);
void remember_initial_value();
void refresh_swatch();
void refresh_picker();
Rml::Colourb current_color() const;
Props mProps;
Rml::Element* mSwatch = nullptr;
Rml::String mDisplayedValue;
Rml::String mInitialValue;
Rml::String mAdjustmentStartValue;
bool mHasDisplayedValue = false;
Popover* mPicker = nullptr;
Rml::Element* mSvArea = nullptr;
Rml::Element* mSvCursor = nullptr;
Rml::Element* mHueBar = nullptr;
Rml::Element* mHueCursor = nullptr;
Rml::Element* mAlphaBar = nullptr;
Rml::Element* mAlphaCursor = nullptr;
Rml::Element* mPickerValue = nullptr;
std::unique_ptr<NavGroup> mPickerNavigation;
std::vector<std::unique_ptr<ScopedEventListener>> mPickerListeners;
float mHue = 0.0f;
float mSat = 0.0f;
float mVal = 0.0f;
float mAlpha = 1.0f;
float mAdjustmentStartHue = 0.0f;
float mAdjustmentStartSat = 0.0f;
float mAdjustmentStartVal = 0.0f;
float mAdjustmentStartAlpha = 1.0f;
bool mHexFormat = true;
bool mInitialValueRemembered = false;
};
} // namespace dusk::ui
+10 -1
View File
@@ -68,6 +68,15 @@ void Component::listen(Rml::Element* element, Rml::EventId event,
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
}
void Component::listen(Rml::Element* element, const Rml::String& event,
ScopedEventListener::Callback callback, bool capture) {
if (element == nullptr) {
element = mRoot;
}
mListeners.emplace_back(
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
}
bool Component::contains(Rml::Element* element) const {
for (const auto* node = element; node != nullptr; node = node->GetParentNode()) {
if (node == mRoot) {
@@ -84,4 +93,4 @@ void Component::clear_children() {
}
}
} // namespace dusk::ui
} // namespace dusk::ui
+7 -3
View File
@@ -26,6 +26,7 @@ public:
virtual void update();
virtual bool focus();
virtual bool focus_from(NavCommand direction) { return focus(); }
virtual bool selected() const { return mRoot->IsPseudoClassSet("selected"); }
virtual void set_selected(bool selected);
@@ -34,10 +35,13 @@ 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);
bool contains(Rml::Element* element) const;
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T> T& add_child(Args&&... args) {
requires std::is_base_of_v<Component, T>
T& add_child(Args&&... args) {
auto child = std::make_unique<T>(mRoot, std::forward<Args>(args)...);
T& ref = *child;
mChildren.emplace_back(std::move(child));
@@ -50,8 +54,8 @@ protected:
void clear_children();
Rml::Element* mRoot = nullptr;
std::vector<std::unique_ptr<Component> > mChildren;
std::vector<std::unique_ptr<ScopedEventListener> > mListeners;
std::vector<std::unique_ptr<Component>> mChildren;
std::vector<std::unique_ptr<ScopedEventListener>> mListeners;
};
template <class Derived>
+17 -10
View File
@@ -291,10 +291,9 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
mActivePort = port;
auto addPageButton = [this, &leftPane, &rightPane, port](
Page page, Rml::String key, auto getValue, auto isDisabled) {
leftPane.register_control(leftPane.add_select_button({
.key = std::move(key),
.getValue = std::move(getValue),
Page page, Rml::String text, std::function<bool()> isDisabled = {}) {
leftPane.register_control(leftPane.add_group_button({
.text = std::move(text),
.isDisabled = std::move(isDisabled),
}),
rightPane, [this, port, page](Pane& pane) {
@@ -303,12 +302,20 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
});
};
addPageButton(Page::Controller, "Device", [port] { return current_controller_name(port); }, [] { return false; });
addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Rumble, "Rumble", [] { return Rml::String(">"); }, [port] { return !PADSupportsRumbleIntensity(static_cast<u32>(port)); });
addPageButton(Page::Actions, "Custom Action Bindings", [] {return Rml::String(">"); }, [] { return false; });
leftPane.register_control(leftPane.add_select_button({
.key = "Device",
.getValue = [port] { return current_controller_name(port); },
}),
rightPane, [this, port](Pane& pane) {
mPage = Page::Controller;
render_page(pane, port, Page::Controller);
});
addPageButton(Page::Buttons, "Buttons");
addPageButton(Page::Triggers, "Triggers");
addPageButton(Page::Sticks, "Sticks");
addPageButton(Page::Rumble, "Rumble",
[port] { return !PADSupportsRumbleIntensity(static_cast<u32>(port)); });
addPageButton(Page::Actions, "Custom Action Bindings");
leftPane.add_section("Options");
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
+20
View File
@@ -0,0 +1,20 @@
#include "group_button.hpp"
namespace dusk::ui {
GroupButton::GroupButton(Rml::Element* parent, Props props)
: SelectButton{parent, {.key = std::move(props.text)}},
mIsDisabled{std::move(props.isDisabled)} {
mRoot->SetClass("group-button", true);
}
void GroupButton::update() {
set_disabled(disabled());
SelectButton::update();
}
bool GroupButton::disabled() const {
return mIsDisabled ? mIsDisabled() : SelectButton::disabled();
}
} // namespace dusk::ui
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "select_button.hpp"
namespace dusk::ui {
class GroupButton : public SelectButton {
public:
struct Props {
Rml::String text;
std::function<bool()> isDisabled;
};
GroupButton(Rml::Element* parent, Props props);
void update() override;
bool disabled() const override;
private:
std::function<bool()> mIsDisabled;
};
} // namespace dusk::ui
+161 -4
View File
@@ -11,6 +11,7 @@
#include <algorithm>
#include <array>
#include <cmath>
#include "dusk/action_bindings.h"
@@ -24,6 +25,8 @@ constexpr double kGamepadRepeatRampDuration = 1.0;
constexpr double kGamepadMenuChordGraceDuration = 0.12;
constexpr Sint16 kGamepadAxisPressThreshold = 16384;
constexpr Sint16 kGamepadAxisReleaseThreshold = 12000;
constexpr float kNavAxisDeadZone = 0.12f;
constexpr double kMaxNavAxisDt = 0.05;
constexpr int kGamepadAxisDirectionCount = SDL_GAMEPAD_AXIS_COUNT * 2;
constexpr int kMenuTapFingerCount = 3;
constexpr float kMenuTapMoveThreshold = 12.0f;
@@ -59,6 +62,10 @@ std::array<GamepadRepeatState, kGamepadAxisDirectionCount> sGamepadAxisRepeats;
std::array<u32, PAD_MAX_CONTROLLERS> sPadHoldMasks;
std::array<bool, PAD_MAX_CONTROLLERS> sMenuChordConsumed;
TouchTapState sTouchMenuTap;
Rml::Vector2f sNavAxis;
std::array<Rml::Vector2f, SDL_GAMEPAD_AXIS_COUNT> sNavAxisContributions;
SDL_JoystickID sNavAxisSource = 0;
double sLastInputUpdateAt = 0.0;
double now_seconds() noexcept {
return static_cast<double>(SDL_GetTicksNS()) / 1000000000.0;
@@ -335,7 +342,9 @@ Rml::Input::KeyIdentifier map_gamepad_button(const SDL_GamepadButtonEvent& event
}
}
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port)) {
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK &&
!isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port))
{
return Rml::Input::KI_F1;
}
@@ -367,6 +376,138 @@ Rml::Input::KeyIdentifier map_gamepad_axis(
return Rml::Input::KI_UNKNOWN;
}
struct LogicalNavAxis {
enum class Component {
None,
X,
Y,
};
Component component = Component::None;
float direction = 0.0f;
};
LogicalNavAxis logical_nav_axis(PADAxis axis) noexcept {
switch (axis) {
case PAD_AXIS_LEFT_X_POS:
return {LogicalNavAxis::Component::X, 1.0f};
case PAD_AXIS_LEFT_X_NEG:
return {LogicalNavAxis::Component::X, -1.0f};
case PAD_AXIS_LEFT_Y_POS:
return {LogicalNavAxis::Component::Y, -1.0f};
case PAD_AXIS_LEFT_Y_NEG:
return {LogicalNavAxis::Component::Y, 1.0f};
default:
return {};
}
}
struct LogicalNavAxisMapping {
LogicalNavAxis active;
bool participates = false;
};
LogicalNavAxisMapping mapped_logical_nav_axis(const SDL_GamepadAxisEvent& event) noexcept {
u32 port = 0;
if (find_event_port(event.which, port)) {
LogicalNavAxisMapping result;
const PADAxisSign activeSign = event.value < 0 ? AXIS_SIGN_NEGATIVE : AXIS_SIGN_POSITIVE;
for (const PADAxisSign sign : {AXIS_SIGN_NEGATIVE, AXIS_SIGN_POSITIVE}) {
PADAxis axis = 0;
if (!find_mapped_pad_axis(port, static_cast<SDL_GamepadAxis>(event.axis), sign, axis)) {
continue;
}
const auto logical = logical_nav_axis(axis);
if (logical.component == LogicalNavAxis::Component::None) {
continue;
}
result.participates = true;
if (event.value != 0 && sign == activeSign) {
result.active = logical;
}
}
return result;
}
switch (static_cast<SDL_GamepadAxis>(event.axis)) {
case SDL_GAMEPAD_AXIS_LEFTX:
return {
.active = {LogicalNavAxis::Component::X, event.value < 0 ? -1.0f : 1.0f},
.participates = true,
};
case SDL_GAMEPAD_AXIS_LEFTY:
return {
.active = {LogicalNavAxis::Component::Y, event.value < 0 ? -1.0f : 1.0f},
.participates = true,
};
default:
return {};
}
}
float normalize_nav_axis(Sint16 value) noexcept {
const float normalized =
value < 0 ? -static_cast<float>(value) / 32768.0f : static_cast<float>(value) / 32767.0f;
if (normalized <= kNavAxisDeadZone) {
return 0.0f;
}
return (normalized - kNavAxisDeadZone) / (1.0f - kNavAxisDeadZone);
}
bool update_nav_axis(const SDL_GamepadAxisEvent& event) noexcept {
const auto mapping = mapped_logical_nav_axis(event);
const int axisIndex = static_cast<int>(event.axis);
if (!mapping.participates || axisIndex < 0 || axisIndex >= SDL_GAMEPAD_AXIS_COUNT) {
return false;
}
const float magnitude = normalize_nav_axis(event.value);
if (magnitude != 0.0f && mapping.active.component != LogicalNavAxis::Component::None &&
sNavAxisSource != event.which)
{
sNavAxis = {};
sNavAxisContributions = {};
sNavAxisSource = event.which;
} else if (sNavAxisSource != 0 && sNavAxisSource != event.which) {
return false;
}
auto& contribution = sNavAxisContributions[axisIndex];
contribution = {};
if (mapping.active.component == LogicalNavAxis::Component::X) {
contribution.x = mapping.active.direction * magnitude;
} else if (mapping.active.component == LogicalNavAxis::Component::Y) {
contribution.y = mapping.active.direction * magnitude;
}
const Rml::Vector2f previous = sNavAxis;
sNavAxis = {};
for (const auto value : sNavAxisContributions) {
if (std::abs(value.x) > std::abs(sNavAxis.x)) {
sNavAxis.x = value.x;
}
if (std::abs(value.y) > std::abs(sNavAxis.y)) {
sNavAxis.y = value.y;
}
}
if (sNavAxis.x == 0.0f && sNavAxis.y == 0.0f) {
sNavAxisSource = 0;
}
return sNavAxis != previous;
}
void dispatch_nav_axis(Rml::Context& context, double dtSeconds) noexcept {
auto* target = context.GetFocusElement();
if (target == nullptr) {
return;
}
target->DispatchEvent(kNavAxisEvent, {
{"x", Rml::Variant{sNavAxis.x}},
{"y", Rml::Variant{sNavAxis.y}},
{"dt", Rml::Variant{static_cast<float>(dtSeconds)}},
});
}
bool is_repeatable_key(Rml::Input::KeyIdentifier key) noexcept {
switch (key) {
case Rml::Input::KI_UP:
@@ -657,7 +798,9 @@ void process_axis_direction(
if (chorded) {
consume_menu_chord(port, context);
}
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ?
Rml::Input::KI_F1 :
map_gamepad_axis(event, sign);
if (key == Rml::Input::KI_UNKNOWN) {
return;
}
@@ -695,6 +838,9 @@ void release_input_block() noexcept {
void reset_input_state() noexcept {
clear_gamepad_repeats();
reset_touch_menu_tap();
sNavAxis = {};
sNavAxisContributions = {};
sNavAxisSource = 0;
}
void handle_event(const SDL_Event& event) noexcept {
@@ -728,6 +874,9 @@ void handle_event(const SDL_Event& event) noexcept {
}
if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
if (update_nav_axis(event.gaxis)) {
dispatch_nav_axis(*context, 0.0);
}
process_axis_direction(*context, event.gaxis, AXIS_SIGN_POSITIVE);
process_axis_direction(*context, event.gaxis, AXIS_SIGN_NEGATIVE);
sync_input_block();
@@ -744,7 +893,9 @@ void handle_event(const SDL_Event& event) noexcept {
if (chorded) {
consume_menu_chord(port, *context);
}
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ?
Rml::Input::KI_F1 :
map_gamepad_button(event.gbutton);
if (key != Rml::Input::KI_UNKNOWN) {
bool deferred = false;
if (repeat != nullptr) {
@@ -782,6 +933,10 @@ void update_input() noexcept {
auto* context = aurora::rmlui::get_context();
if (context != nullptr) {
const double now = now_seconds();
const double dtSeconds = sLastInputUpdateAt == 0.0 ?
0.0 :
std::clamp(now - sLastInputUpdateAt, 0.0, kMaxNavAxisDt);
sLastInputUpdateAt = now;
auto process_repeats = [context, now](auto& repeats) {
for (auto& repeat : repeats) {
if (!repeat.held) {
@@ -814,9 +969,11 @@ void update_input() noexcept {
};
process_repeats(sGamepadButtonRepeats);
process_repeats(sGamepadAxisRepeats);
dispatch_nav_axis(*context, dtSeconds);
} else {
reset_input_state();
sLastInputUpdateAt = 0.0;
}
}
} // namespace dusk::ui
} // namespace dusk::ui::input
+2
View File
@@ -4,6 +4,8 @@ union SDL_Event;
namespace dusk::ui::input {
inline constexpr char kNavAxisEvent[] = "navaxis";
void handle_event(const SDL_Event& event) noexcept;
void update_input() noexcept;
void reset_input_state() noexcept;
+23
View File
@@ -1,6 +1,7 @@
#include "mod_window.hpp"
#include "bool_button.hpp"
#include "color_input.hpp"
#include "number_button.hpp"
#include "string_button.hpp"
@@ -24,6 +25,17 @@ Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec) {
}
});
break;
case ModControlSpec::Kind::Group:
control = &pane.add_group_button(GroupButton::Props{
.text = s.label,
.isDisabled = s.isDisabled,
})
.on_pressed([shared] {
if (shared->onPressed) {
shared->onPressed();
}
});
break;
case ModControlSpec::Kind::Toggle:
control = &pane.add_child<BoolButton>(BoolButton::Props{
.key = s.label,
@@ -57,6 +69,17 @@ Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec) {
.maxLength = s.maxLength,
});
break;
case ModControlSpec::Kind::Color:
control = &pane.add_child<ColorInput>(ColorInput::Props{
.key = s.label,
.getValue = s.getString,
.setValue = s.setString,
.isDisabled = s.isDisabled,
.isModified = s.isModified,
.presets = s.colorPresets,
.alpha = s.colorAlpha,
});
break;
case ModControlSpec::Kind::Select:
if (helpPane == nullptr || s.options.empty()) {
return nullptr;
+4
View File
@@ -10,10 +10,12 @@ namespace dusk::ui {
struct ModControlSpec {
enum class Kind : u8 {
Button,
Group,
Toggle,
Number,
String,
Select,
Color,
};
Kind kind = Kind::Button;
@@ -36,6 +38,8 @@ struct ModControlSpec {
Rml::String suffix;
std::vector<Rml::String> options;
int maxLength = -1;
std::vector<Rml::String> colorPresets;
bool colorAlpha = false;
};
Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec);
+180
View File
@@ -0,0 +1,180 @@
#include "nav_group.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <algorithm>
namespace dusk::ui {
NavGroup::NavGroup(Rml::Element* root, Props props) : Component{root}, mProps{props} {
mProps.columns = std::max(mProps.columns, 1);
listen(
mRoot, Rml::EventId::Focus,
[this](Rml::Event& event) {
const int index = item_index(event.GetTargetElement());
if (index >= 0) {
mRememberedIndex = index;
}
},
true);
listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto direction = map_nav_event(event);
const auto result = navigate(event.GetTargetElement(), direction);
if (result == MoveResult::Moved) {
mDoAud_seStartMenu(kSoundItemFocus);
}
if (result != MoveResult::Unhandled) {
event.StopPropagation();
}
});
}
bool NavGroup::focus() {
if (mRememberedIndex >= 0 && focus_index(mRememberedIndex, NavCommand::None)) {
return true;
}
return focus_linear(0, 1, NavCommand::None);
}
bool NavGroup::focus_from(NavCommand direction) {
if (mRememberedIndex >= 0 && focus_index(mRememberedIndex, direction)) {
return true;
}
const bool reverse = direction == NavCommand::Up || direction == NavCommand::Left;
return focus_linear(
reverse ? static_cast<int>(mItems.size()) - 1 : 0, reverse ? -1 : 1, direction);
}
NavGroup::MoveResult NavGroup::navigate(Rml::Element* target, NavCommand direction) {
const bool horizontal = direction == NavCommand::Left || direction == NavCommand::Right;
const bool vertical = direction == NavCommand::Up || direction == NavCommand::Down;
if (!horizontal && !vertical) {
return MoveResult::Unhandled;
}
const int current = item_index(target);
if (current < 0) {
return MoveResult::Unhandled;
}
mRememberedIndex = current;
if (mProps.layout == Layout::Grid) {
return navigate_grid(current, direction);
}
if ((mProps.layout == Layout::Horizontal && horizontal) ||
(mProps.layout == Layout::Vertical && vertical))
{
return navigate_linear(current, direction);
}
return handle_boundary(direction);
}
NavGroup::MoveResult NavGroup::navigate_linear(int current, NavCommand direction) {
const int step = direction == NavCommand::Left || direction == NavCommand::Up ? -1 : 1;
if (focus_linear(current + step, step, direction)) {
return MoveResult::Moved;
}
return handle_boundary(direction);
}
NavGroup::MoveResult NavGroup::navigate_grid(int current, NavCommand direction) {
const int columns = mProps.columns;
const int row = current / columns;
const int column = current % columns;
const int rowStart = row * columns;
const int rowEnd = std::min(rowStart + columns, static_cast<int>(mItems.size()));
if (direction == NavCommand::Left || direction == NavCommand::Right) {
const int step = direction == NavCommand::Left ? -1 : 1;
const int edge = direction == NavCommand::Left ? rowStart - 1 : rowEnd;
for (int index = current + step; index != edge; index += step) {
if (focus_index(index, direction)) {
return MoveResult::Moved;
}
}
return handle_boundary(direction);
}
const int rowCount = (static_cast<int>(mItems.size()) + columns - 1) / columns;
const int step = direction == NavCommand::Up ? -1 : 1;
for (int nextRow = row + step; nextRow >= 0 && nextRow < rowCount; nextRow += step) {
if (focus_grid_row(nextRow, column, direction)) {
return MoveResult::Moved;
}
}
return handle_boundary(direction);
}
NavGroup::MoveResult NavGroup::handle_boundary(NavCommand direction) {
switch (boundary_for(direction)) {
case Boundary::Bubble:
return MoveResult::Unhandled;
case Boundary::Stop:
return MoveResult::Consumed;
}
return MoveResult::Unhandled;
}
bool NavGroup::focus_index(int index, NavCommand direction) {
if (index < 0 || index >= static_cast<int>(mItems.size())) {
return false;
}
if (!mItems[index]->focus_from(direction)) {
return false;
}
mRememberedIndex = index;
return true;
}
bool NavGroup::focus_linear(int start, int step, NavCommand direction) {
for (int index = start; index >= 0 && index < static_cast<int>(mItems.size()); index += step) {
if (focus_index(index, direction)) {
return true;
}
}
return false;
}
bool NavGroup::focus_grid_row(int row, int preferredColumn, NavCommand direction) {
const int rowStart = row * mProps.columns;
const int rowEnd = std::min(rowStart + mProps.columns, static_cast<int>(mItems.size()));
if (rowStart >= rowEnd) {
return false;
}
const int column = std::min(preferredColumn, rowEnd - rowStart - 1);
if (focus_index(rowStart + column, direction)) {
return true;
}
for (int distance = 1; distance < mProps.columns; ++distance) {
if (column - distance >= 0 && focus_index(rowStart + column - distance, direction)) {
return true;
}
if (rowStart + column + distance < rowEnd &&
focus_index(rowStart + column + distance, direction))
{
return true;
}
}
return false;
}
int NavGroup::item_index(Rml::Element* target) const {
for (int index = 0; index < static_cast<int>(mItems.size()); ++index) {
if (mItems[index]->contains(target)) {
return index;
}
}
return -1;
}
NavGroup::Boundary NavGroup::boundary_for(NavCommand direction) const {
if (direction == NavCommand::Left || direction == NavCommand::Right) {
return mProps.horizontalBoundary;
}
return mProps.verticalBoundary;
}
} // namespace dusk::ui
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include "component.hpp"
namespace dusk::ui {
class NavGroup : public Component {
public:
enum class Layout {
Horizontal,
Vertical,
Grid,
};
enum class Boundary {
Bubble,
Stop,
};
struct Props {
Layout layout = Layout::Vertical;
int columns = 1;
Boundary horizontalBoundary = Boundary::Bubble;
Boundary verticalBoundary = Boundary::Bubble;
};
NavGroup(Rml::Element* root, Props props);
bool focus() override;
bool focus_from(NavCommand direction) override;
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T>
T& add_item(Args&&... args) {
auto child = std::make_unique<T>(mRoot, std::forward<Args>(args)...);
return add_item(std::move(child));
}
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T>
T& add_existing_item(Rml::Element* root, Args&&... args) {
auto child = std::make_unique<T>(root, std::forward<Args>(args)...);
return add_item(std::move(child));
}
private:
template <typename T>
T& add_item(std::unique_ptr<T> child) {
T& ref = *child;
mItems.push_back(&ref);
mChildren.emplace_back(std::move(child));
return ref;
}
enum class MoveResult {
Unhandled,
Consumed,
Moved,
};
MoveResult navigate(Rml::Element* target, NavCommand direction);
MoveResult navigate_linear(int current, NavCommand direction);
MoveResult navigate_grid(int current, NavCommand direction);
MoveResult handle_boundary(NavCommand direction);
bool focus_index(int index, NavCommand direction);
bool focus_linear(int start, int step, NavCommand direction);
bool focus_grid_row(int row, int preferredColumn, NavCommand direction);
int item_index(Rml::Element* target) const;
Boundary boundary_for(NavCommand direction) const;
Props mProps;
std::vector<Component*> mItems;
int mRememberedIndex = -1;
};
} // namespace dusk::ui
+4
View File
@@ -2,6 +2,7 @@
#include "button.hpp"
#include "component.hpp"
#include "group_button.hpp"
#include "select_button.hpp"
namespace dusk::ui {
@@ -26,6 +27,9 @@ public:
ControlledButton& add_button(ControlledButton::Props props) {
return add_child<ControlledButton>(std::move(props));
}
GroupButton& add_group_button(GroupButton::Props props) {
return add_child<GroupButton>(std::move(props));
}
Button& add_button(Rml::String text) { return add_child<Button>(std::move(text)); }
ControlledSelectButton& add_select_button(ControlledSelectButton::Props props) {
return add_child<ControlledSelectButton>(std::move(props));
+150
View File
@@ -0,0 +1,150 @@
#include "popover.hpp"
#include <algorithm>
namespace dusk::ui {
namespace {
const Rml::String kDocumentSource = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/popover.rcss" />
</head>
<body>
<popover id="popover"/>
</body>
</rml>
)RML";
constexpr float kAnchorGapDp = 8.0f;
constexpr float kViewportMarginDp = 8.0f;
} // namespace
Popover::Popover(Rml::Element* anchor, Side side, const Rml::String& windowClass)
: Document{kDocumentSource}, mAnchor{anchor}, mSide{side},
mBody{mDocument->GetElementById("popover")} {
if (!windowClass.empty()) {
mBody->SetClass(windowClass, true);
}
listen(mBody, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mBody && !mBody->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
listen(
mDocument->GetContext()->GetRootElement(), Rml::EventId::Mousedown,
[this](const Rml::Event& event) {
const auto within = [](const Rml::Element* element, const Rml::Element* container) {
while (element != nullptr && element != container) {
element = element->GetParentNode();
}
return element != nullptr;
};
if (visible() && !within(event.GetTargetElement(), mBody)) {
dismiss();
}
},
true);
}
void Popover::show() {
Document::show();
reposition();
mBody->SetAttribute("open", "");
focus();
}
void Popover::hide(bool close) {
notify_close();
mBody->RemoveAttribute("open");
mPendingClose = close;
}
bool Popover::focus() {
if (mOnFocus && mOnFocus()) {
return true;
}
return mBody != nullptr && mBody->Focus(true);
}
bool Popover::visible() const {
return mBody != nullptr && mBody->HasAttribute("open");
}
void Popover::update() {
Document::update();
if (!visible()) {
return;
}
if (mAnchor == nullptr || !mAnchor->IsVisible(true)) {
dismiss();
return;
}
reposition();
}
void Popover::dismiss() {
if (visible()) {
hide(true);
}
}
bool Popover::handle_nav_command(Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Cancel || cmd == NavCommand::Menu) {
dismiss();
return true;
}
return false;
}
void Popover::reposition() {
auto* context = mDocument->GetContext();
const auto dimensions = Rml::Vector2f{context->GetDimensions()};
const float dpRatio = context->GetDensityIndependentPixelRatio();
const float gap = kAnchorGapDp * dpRatio;
const float margin = kViewportMarginDp * dpRatio;
const Rml::Vector2f anchorPos = mAnchor->GetAbsoluteOffset(Rml::BoxArea::Border);
const Rml::Vector2f anchorSize = mAnchor->GetBox().GetSize(Rml::BoxArea::Border);
const Rml::Vector2f size = mBody->GetBox().GetSize(Rml::BoxArea::Border);
Rml::Vector2f pos;
switch (mSide) {
case Side::Above:
pos = {anchorPos.x + (anchorSize.x - size.x) * 0.5f, anchorPos.y - size.y - gap};
break;
case Side::Left:
pos = {anchorPos.x - size.x - gap, anchorPos.y + (anchorSize.y - size.y) * 0.5f};
break;
case Side::Right:
pos = {anchorPos.x + anchorSize.x + gap, anchorPos.y + (anchorSize.y - size.y) * 0.5f};
break;
case Side::Below:
default:
pos = {anchorPos.x + (anchorSize.x - size.x) * 0.5f, anchorPos.y + anchorSize.y + gap};
break;
}
pos.x = std::clamp(pos.x, margin, std::max(margin, dimensions.x - size.x - margin));
pos.y = std::clamp(pos.y, margin, std::max(margin, dimensions.y - size.y - margin));
mBody->SetProperty(Rml::PropertyId::Left, Rml::Property{pos.x, Rml::Unit::PX});
mBody->SetProperty(Rml::PropertyId::Top, Rml::Property{pos.y, Rml::Unit::PX});
}
void Popover::notify_close() {
if (mOnClose) {
auto callback = std::move(mOnClose);
mOnClose = nullptr;
callback();
}
if (mAnchor != nullptr && mAnchor->IsVisible(true)) {
mAnchor->Focus(true);
}
}
} // namespace dusk::ui
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "document.hpp"
namespace dusk::ui {
class Popover : public Document {
public:
enum class Side {
Below,
Above,
Left,
Right,
};
Popover(Rml::Element* anchor, Side side, const Rml::String& windowClass = "");
void show() override;
void hide(bool close) override;
bool focus() override;
bool visible() const override;
void update() override;
Rml::Element* body() const { return mBody; }
void dismiss();
void on_close(std::function<void()> callback) { mOnClose = std::move(callback); }
void on_focus(std::function<bool()> callback) { mOnFocus = std::move(callback); }
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
void reposition();
void notify_close();
Rml::Element* mAnchor = nullptr;
Side mSide;
Rml::Element* mBody = nullptr;
std::function<void()> mOnClose;
std::function<bool()> mOnFocus;
};
} // namespace dusk::ui
+20 -22
View File
@@ -887,7 +887,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.key = "Disable Cutscene Pillarboxing",
.helpText = "Disable black bars on the left and right sides of the screen "
"during some cutscenes, particularly on ultra-wide displays. "
"Visuals beyond the original intended framing may appear buggy."
"Visuals beyond the original intended framing may appear buggy.",
});
});
@@ -906,9 +906,10 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
};
leftPane.add_section("Inputs");
leftPane.register_control(leftPane.add_button("Configure Inputs").on_pressed([this] {
push(std::make_unique<ControllerConfigWindow>());
}),
leftPane.register_control(
leftPane.add_group_button({.text = "Configure Inputs"}).on_pressed([this] {
push(std::make_unique<ControllerConfigWindow>());
}),
rightPane, [](Pane& pane) {
pane.clear();
pane.add_text("Open input binding configuration.");
@@ -925,7 +926,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
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{
auto& customizeTouchLayout = leftPane.add_group_button(GroupButton::Props{
.text = "Customize Layout",
.isDisabled = [] { return !getSettings().game.enableTouchControls; },
});
@@ -935,23 +936,20 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
pane.clear();
pane.add_text("Open the touch controls layout editor.");
});
leftPane.register_control(leftPane.add_select_button({
.key = "Touch Targeting",
.getValue =
[] {
return touch_targeting_label(
getSettings().game.touchTargeting.getValue());
},
.isDisabled =
[] { return !getSettings().game.enableTouchControls; },
.isModified =
[] {
const auto& targeting =
getSettings().game.touchTargeting;
return targeting.getValue() !=
targeting.getDefaultValue();
},
}),
leftPane.register_control(
leftPane.add_select_button({
.key = "Touch Targeting",
.getValue =
[] {
return touch_targeting_label(getSettings().game.touchTargeting.getValue());
},
.isDisabled = [] { return !getSettings().game.enableTouchControls; },
.isModified =
[] {
const auto& targeting = getSettings().game.touchTargeting;
return targeting.getValue() != targeting.getDefaultValue();
},
}),
rightPane, [](Pane& pane) {
pane.clear();
for (int i = 0; i < static_cast<int>(kTouchTargetingLabels.size()); ++i) {
+7
View File
@@ -14,4 +14,11 @@ namespace dusk::utils {
std::string base64_encode(const std::vector<uint8_t>& data);
bool base64_decode(const std::string& text, std::vector<uint8_t>& out);
uint32_t crc32(const void* data, size_t size);
struct PaneCache {
uint64_t tag;
float origTransX;
float origTransY;
bool cached;
};
} // namespace dusk::utils