Merge branch 'main' of https://github.com/TwilitRealm/dusk into randomizer

This commit is contained in:
gymnast86
2026-05-05 21:33:06 -07:00
65 changed files with 2109 additions and 1393 deletions
+9 -2
View File
@@ -34,8 +34,8 @@ First, make sure your dump of the game is clean and supported by Dusk. You can d
- Extract the .zip file
- Launch Dusk
- Press **Select Disc Image**, navigate to your game dump, and select the file
- Press **Start Game** to play!
- Press **Select Disc Image** and provide the path to your supported game dump.
- Press **Play**!
# Building
@@ -46,3 +46,10 @@ Pull requests are welcomed! Note that we do not accept contributions that are pr
# Credits
Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors).
<br/>
<div align="center">
<a href="https://github.com/encounter/aurora">
<img src="assets/aurora-powered.png" alt="Powered by Aurora" width="800">
</a>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+1 -1
+10 -8
View File
@@ -1461,6 +1461,8 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiSaveEditor.cpp
src/dusk/imgui/ImGuiStateShare.hpp
src/dusk/imgui/ImGuiStateShare.cpp
src/dusk/ui/achievements.cpp
src/dusk/ui/achievements.hpp
src/dusk/ui/bool_button.cpp
src/dusk/ui/bool_button.hpp
src/dusk/ui/button.cpp
@@ -1471,16 +1473,16 @@ set(DUSK_FILES
src/dusk/ui/controller_config.hpp
src/dusk/ui/document.cpp
src/dusk/ui/document.hpp
src/dusk/ui/achievements.cpp
src/dusk/ui/achievements.hpp
src/dusk/ui/preset.cpp
src/dusk/ui/preset.hpp
src/dusk/ui/editor.cpp
src/dusk/ui/editor.hpp
src/dusk/ui/event.cpp
src/dusk/ui/event.hpp
src/dusk/ui/graphics_tuner.cpp
src/dusk/ui/graphics_tuner.hpp
src/dusk/ui/input.cpp
src/dusk/ui/input.hpp
src/dusk/ui/modal.cpp
src/dusk/ui/modal.hpp
src/dusk/ui/nav_types.hpp
src/dusk/ui/number_button.cpp
src/dusk/ui/number_button.hpp
@@ -1488,12 +1490,12 @@ set(DUSK_FILES
src/dusk/ui/overlay.hpp
src/dusk/ui/pane.cpp
src/dusk/ui/pane.hpp
src/dusk/ui/popup.cpp
src/dusk/ui/popup.hpp
src/dusk/ui/menu_bar.cpp
src/dusk/ui/menu_bar.hpp
src/dusk/ui/prelaunch.cpp
src/dusk/ui/prelaunch.hpp
src/dusk/ui/prelaunch_options.cpp
src/dusk/ui/prelaunch_options.hpp
src/dusk/ui/preset.cpp
src/dusk/ui/preset.hpp
src/dusk/ui/select_button.cpp
src/dusk/ui/select_button.hpp
src/dusk/ui/settings.cpp
+6
View File
@@ -169,6 +169,12 @@ public:
void mapBlink() {}
#if PLATFORM_WII || TARGET_PC
f32 getMirrorPosX(f32 param_0, f32 param_1) {
return (field_0x11dc * 2.0f - (param_0 + param_1)) - param_1;
}
#endif
// Unknown name
struct RegionTexData {
/* 0x00 */ float mMinX;
+10
View File
@@ -66,6 +66,16 @@ public:
_c90 = param_2;
}
#if PLATFORM_WII || TARGET_PC
f32 getMirrorCenterPosX(f32 param_0, f32 param_1) {
if (_c90) {
return (mCenterPosX * 2.0f - (param_0 + param_1)) - param_1;
}
return param_0;
}
#endif
struct Stage_c {
// Incomplete class
-3
View File
@@ -50,8 +50,6 @@ public:
bool hasSignal(const char* key) const;
std::vector<Achievement> getAchievements() const;
bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); }
std::string consumePendingUnlock();
private:
struct Entry {
@@ -68,7 +66,6 @@ private:
std::unordered_set<std::string_view> m_signals;
bool m_loaded = false;
bool m_dirty = false;
std::queue<std::string> m_pendingUnlocks;
};
} // namespace dusk
+14
View File
@@ -1,6 +1,10 @@
#ifndef DUSK_MAIN_H
#define DUSK_MAIN_H
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#include <filesystem>
namespace dusk {
@@ -8,7 +12,17 @@ namespace dusk {
extern bool IsShuttingDown;
extern bool IsGameLaunched;
extern bool IsFocusPaused;
extern bool RestartRequested;
extern std::filesystem::path ConfigPath;
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV)
inline constexpr bool SupportsProcessRestart = false;
#else
inline constexpr bool SupportsProcessRestart = true;
#endif
void RequestRestart() noexcept;
}
#endif // DUSK_MAIN_H
-1
View File
@@ -67,7 +67,6 @@ struct UserSettings {
// QoL
ConfigVar<bool> enableQuickTransform;
ConfigVar<bool> hideTvSettingsScreen;
ConfigVar<bool> skipWarningScreen;
ConfigVar<bool> biggerWallets;
ConfigVar<bool> noReturnRupees;
ConfigVar<bool> disableRupeeCutscenes;
+89 -122
View File
@@ -8,140 +8,107 @@ body {
height: 100%;
margin: 0;
padding: 0;
font-family: "Fira Sans Condensed";
font-size: 24dp;
color: #FFFFFF;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
}
.overlay-root {
width: 100%;
min-height: 45%;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
decorator: vertical-gradient(#00000000 #151610F2);
filter: opacity(0);
transition: filter 0.2s linear-in-out;
}
.overlay-root[open] {
filter: opacity(1);
}
.overlay {
width: 100%;
max-width: 1216dp;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
gap: 24dp;
padding: 48dp 64dp;
}
@media (max-height: 800dp) {
.overlay-root {
min-height: 38%;
}
.overlay {
gap: 16dp;
padding: 32dp 48dp;
}
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
.carousel-container {
flex: 1 1 auto;
display: flex;
justify-content: flex-end;
min-width: 0;
}
.description {
font-size: 18dp;
line-height: 22dp;
color: rgba(255, 255, 255, 50%);
}
.divider {
margin: 1dp 0;
border-top: 1dp rgba(217, 217, 217, 50%);
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
footer-button {
display: block;
width: 100%;
max-width: 220dp;
border: 0;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
font-family: "Fira Sans";
font-weight: normal;
font-size: 20dp;
line-height: 24dp;
text-transform: uppercase;
color: #FFFFFF;
opacity: 1;
color: #E0DBC8;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
z-index: 1;
pointer-events: none;
}
toast {
position: absolute;
top: 40dp;
right: 40dp;
display: flex;
flex-flow: column;
border-radius: 14dp;
overflow: hidden;
border: 1dp #92875B;
backdrop-filter: blur(5dp);
box-shadow: 0 0 15dp 3dp;
background-color: rgba(21, 22, 16, 80%);
filter: opacity(0);
transform: scale(0.9);
transform-origin: center;
transition: filter transform 0.2s cubic-in-out;
padding: 18dp 24dp;
gap: 8dp;
}
toast[open] {
filter: opacity(1);
transform: scale(1);
}
/*toast:hover {
cursor: pointer;
background-color: rgba(61, 59, 36, 80%);
}
footer-button.return {
text-align: left;
toast:active {
background-color: rgba(45, 43, 26, 80%);
}*/
toast heading {
display: flex;
gap: 18dp;
align-items: center;
font-family: "Fira Sans Condensed";
font-size: 18dp;
font-weight: bold;
text-transform: uppercase;
color: #92875B;
}
footer-button.reset {
text-align: right;
}
.stepped-carousel {
toast message {
display: flex;
align-items: center;
justify-content: center;
gap: 16dp;
width: auto;
min-width: 246dp;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
justify-content: start;
gap: 8dp;
}
.stepped-carousel-value {
line-height: 29dp;
min-width: 166dp;
text-align: center;
white-space: nowrap;
opacity: 0.9;
toast progress {
height: 4dp;
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
.stepped-carousel-arrow {
width: 24dp;
height: 24dp;
min-width: 24dp;
padding: 0;
border: 0;
background-color: transparent;
opacity: 1;
cursor: pointer;
toast progress fill {
background-color: rgba(194, 164, 45, 80%);
}
toast.achievement {
border: 1dp #C2A42D;
}
toast.achievement heading {
color: #C2A42D;
}
icon {
font-family: "Material Symbols Rounded";
font-weight: normal;
display: inline-block;
vertical-align: middle;
}
icon.arrow-forward {
width: 24dp;
height: 24dp;
font-size: 24dp;
decorator: text("&#xe5c8;" center center);
}
icon.trophy {
width: 24dp;
height: 24dp;
font-size: 24dp;
decorator: text("&#xe71a;" center center);
}
+72 -2
View File
@@ -9,17 +9,44 @@ body {
font-weight: normal;
font-size: 20dp;
color: #FFFFFF;
background-color: #000000;
decorator: image(../prelaunch-bg.png cover left center);
filter: opacity(0);
transition: filter 1s 0.2s linear-in-out;
z-index: -1;
}
.gradient {
position: absolute;
width: 100%;
height: 100%;
/* The color gradient from the Figma bands really badly. A fully black gradient does as well, but not as badly. */
decorator: horizontal-gradient(#000000FF #00000000);
}
body.mirrored .gradient {
decorator: horizontal-gradient(#00000000 #000000FF);
}
.background {
position: absolute;
width: 100%;
height: 100%;
decorator: image(../prelaunch-bg.png cover left center);
opacity: 0;
transition: opacity 1s linear-in-out;
}
body[open] {
filter: opacity(1);
}
body[open] .background {
opacity: 1;
}
body.disc-ready .background {
opacity: 0;
}
content {
display: block;
width: 100%;
@@ -35,6 +62,7 @@ content[open] {
menu {
position: absolute;
left: 96dp;
right: auto;
top: 50%;
transform: translateY(-50%);
/* Scale based on a reference screen width, 428/1216 */
@@ -47,6 +75,11 @@ menu {
gap: 48dp;
}
body.mirrored menu {
left: auto;
right: 96dp;
}
hero {
display: flex;
flex-direction: column;
@@ -55,6 +88,10 @@ hero {
gap: 8dp;
}
body.mirrored hero {
align-items: flex-end;
}
hero img {
width: 100%;
}
@@ -79,6 +116,7 @@ hero img {
display: flex;
flex-direction: column;
gap: 12dp;
align-items: flex-start;
}
#menu-list button {
@@ -86,6 +124,7 @@ hero img {
height: 54dp;
padding: 8dp 16dp;
border-radius: 8dp;
text-align: left;
text-transform: uppercase;
font-family: "Fira Sans Condensed";
font-size: 32dp;
@@ -105,25 +144,56 @@ hero img {
decorator: horizontal-gradient(#FEE685FF #FEE68500);
}
body.mirrored #menu-list {
align-items: flex-end;
}
body.mirrored #menu-list button {
text-align: right;
}
body.mirrored #menu-list button:hover,
body.mirrored #menu-list button:focus-visible {
decorator: horizontal-gradient(#FEE68500 #FEE685FF);
}
disc-info {
position: absolute;
left: 96dp;
right: auto;
bottom: 72dp;
display: flex;
flex-direction: column;
gap: 12dp;
font-size: 24dp;
font-effect: glow(0dp 4dp 0dp 4dp black);
text-align: left;
}
body.mirrored disc-info {
left: auto;
right: 96dp;
text-align: right;
}
version-info {
position: absolute;
right: 96dp;
left: auto;
bottom: 72dp;
display: flex;
flex-direction: column;
gap: 12dp;
text-align: right;
font-size: 24dp;
font-effect: glow(0dp 4dp 0dp 4dp black);
text-align: right;
}
body.mirrored version-info {
right: auto;
left: 96dp;
text-align: left;
}
#disc-status {
+147
View File
@@ -0,0 +1,147 @@
*, *:before, *:after {
box-sizing: border-box;
}
body {
overflow: visible;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: "Fira Sans Condensed";
font-size: 24dp;
color: #FFFFFF;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
}
.tuner-root {
width: 100%;
min-height: 45%;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: stretch;
decorator: vertical-gradient(#00000000 #151610F2);
filter: opacity(0);
transition: filter 0.2s linear-in-out;
}
.tuner-root[open] {
filter: opacity(1);
}
.tuner {
width: 100%;
max-width: 1216dp;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
gap: 24dp;
padding: 48dp 64dp;
}
@media (max-height: 800dp) {
.tuner-root {
min-height: 38%;
}
.tuner {
gap: 16dp;
padding: 32dp 48dp;
}
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
.carousel-container {
flex: 1 1 auto;
display: flex;
justify-content: flex-end;
min-width: 0;
}
.description {
font-size: 18dp;
line-height: 22dp;
color: rgba(255, 255, 255, 50%);
}
.divider {
margin: 1dp 0;
border-top: 1dp rgba(217, 217, 217, 50%);
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24dp;
}
footer-button {
display: block;
width: 100%;
max-width: 220dp;
border: 0;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
font-size: 20dp;
line-height: 24dp;
text-transform: uppercase;
color: #FFFFFF;
opacity: 1;
cursor: pointer;
}
footer-button.return {
text-align: left;
}
footer-button.reset {
text-align: right;
}
.stepped-carousel {
display: flex;
align-items: center;
justify-content: center;
gap: 16dp;
width: auto;
min-width: 246dp;
padding: 0;
background-color: transparent;
font-family: "Fira Sans Condensed";
font-weight: bold;
}
.stepped-carousel-value {
line-height: 29dp;
min-width: 166dp;
text-align: center;
white-space: nowrap;
opacity: 0.9;
}
.stepped-carousel-arrow {
width: 24dp;
height: 24dp;
min-width: 24dp;
padding: 0;
border: 0;
background-color: transparent;
opacity: 1;
cursor: pointer;
font-family: "Material Symbols Rounded";
font-weight: normal;
}
+35
View File
@@ -43,6 +43,10 @@ window.preset {
min-width: 650dp;
}
window.modal {
max-width: 816dp;
}
window[open] {
filter: opacity(1);
transform: scale(1);
@@ -108,6 +112,12 @@ window content pane > spacer {
pointer-events: none;
}
window modal {
padding: 32dp;
gap: 20dp;
flex: 0 1 auto;
}
scrollbarvertical {
width: 8dp;
margin: 4dp 4dp 4dp 0;
@@ -194,6 +204,12 @@ button:not(:disabled):active {
box-shadow: #C2A42D 0 0 0 2dp;
}
button.modal-btn {
font-size: 20dp;
padding: 16dp 10dp;
flex: 1 1 0;
}
select-button {
display: flex;
align-items: center;
@@ -399,3 +415,22 @@ button.preset-btn {
color: rgba(224, 219, 200, 65%);
text-align: center;
}
.modal-dialog {
display: flex;
flex-flow: column;
padding: 16dp;
gap: 20dp;
flex: 0 1 auto;
min-width: 0;
}
.modal-actions {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: stretch;
gap: 12dp;
padding-top: 12dp;
width: 100%;
}
+2 -2
View File
@@ -2721,7 +2721,7 @@ int daAlink_c::procHorseRun() {
}
if (mProcVar2.field_0x300c == 0) {
set3DStatus(BUTTON_STATUS_HOLD_ON, 4);
set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 1 :) 4);
}
} else {
if (mProcVar3.field_0x300e != 0) {
@@ -2731,7 +2731,7 @@ int daAlink_c::procHorseRun() {
}
if (mProcVar2.field_0x300c == 0) {
set3DStatus(BUTTON_STATUS_HOLD_ON, 1);
set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 4 :) 1);
}
}
+73
View File
@@ -11,6 +11,62 @@
#include "JSystem/JGadget/define.h"
#include <cstring>
#include "dusk/logging.h"
#if TARGET_PC
#include "dusk/ui/ui.hpp"
namespace {
static int sJaiSkip = -1;
static JSUList<JAIStream>* get_stream_list() {
return Z2GetSoundMgr()->getStreamMgr()->getStreamList();
}
static int get_stream_count(JSUList<JAIStream>* list) {
int i = 0;
for (JSULink<JAIStream>* l = list != nullptr ? list->getFirst() : nullptr; l != nullptr;
l = l->getNext()) {
i++;
}
return i;
}
static void pause_stream(int skip_first, bool paused) {
int i = 0;
JSUList<JAIStream>* list = get_stream_list();
for (JSULink<JAIStream>* l = list->getFirst(); l != nullptr; l = l->getNext(), ++i) {
if (i >= skip_first) {
l->getObject()->pause(paused);
}
}
}
static void pause_streams(int skip_first) {
if (!dusk::ui::is_prelaunch_open()) {
return;
}
JSUList<JAIStream>* list = get_stream_list();
if (list == nullptr || get_stream_count(list) <= skip_first) {
return;
}
pause_stream(skip_first, true);
sJaiSkip = skip_first;
}
static void unpause_streams(bool require_prelaunch_hidden) {
if (sJaiSkip < 0) {
return;
}
if (require_prelaunch_hidden && dusk::ui::is_prelaunch_open()) {
return;
}
pause_stream(sJaiSkip, false);
sJaiSkip = -1;
}
} // namespace
#endif
s16 dDemo_c::m_branchId = -1;
namespace {
@@ -1006,7 +1062,16 @@ int dDemo_c::start(u8 const* p_data, cXyz* p_translation, f32 rotationY) {
m_control->setSuspend(0);
}
#if TARGET_PC
const int existing_streams = get_stream_count(get_stream_list());
#endif
m_control->forward(0);
#if TARGET_PC
pause_streams(existing_streams);
#endif
m_translation = p_translation;
if (m_translation != NULL) {
@@ -1034,6 +1099,10 @@ static void dummyString2() {
void dDemo_c::end() {
JUT_ASSERT(1956, m_system != NULL);
#if TARGET_PC
unpause_streams(false);
#endif
m_control->destroyObject_all();
m_object->remove();
m_data = NULL;
@@ -1054,6 +1123,10 @@ void dDemo_c::branch() {
int dDemo_c::update() {
JUT_ASSERT(2064, m_system != NULL);
#if TARGET_PC
unpause_streams(true);
#endif
if (m_data == NULL) {
if (m_branchData == NULL) {
return 0;
+18
View File
@@ -919,9 +919,20 @@ void dMenu_Fmap_c::region_map_proc() {
}
mpDraw2DBack->regionMapMove(mpStick);
int stage_no, room_no;
#if TARGET_PC
f32 arrow_pos_x = mpDraw2DBack->getArrowPos2DX();
if (dusk::getSettings().game.enableMirrorMode) {
arrow_pos_x = mpDraw2DBack->getMirrorPosX(arrow_pos_x, 0.0f);
}
f32 pos_x = arrow_pos_x - mDoGph_gInf_c::getMinXF() - mDoGph_gInf_c::getWidthF() * 0.5f;
#else
f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF()
- mDoGph_gInf_c::getWidthF() * 0.5f;
#endif
f32 pos_y = mpDraw2DBack->getArrowPos2DY() - mDoGph_gInf_c::getHeightF() * 0.5f;
mpMenuFmapMap->getPointStagePathInnerNo(getNowFmapRegionData(), pos_x, pos_y,
mStayStageNo, &stage_no, &room_no);
if (mStageCursor != stage_no || mRoomCursor != room_no || mResetAreaName) {
@@ -2464,6 +2475,13 @@ void dMenu_Fmap_c::portalWarpMapMove(STControl* i_stick) {
f32 arrow_y = mpDraw2DBack->getArrowPos2DY();
u8 uVar6 = 0xff;
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
arrow_x = mpDraw2DBack->getMirrorPosX(arrow_x, 0.0f);
}
#endif
for (int i = 0; i < portal_dat->mCount; i++) {
if (portals[i].mRegionNo == mpDraw2DBack->getRegionCursor() + 1
&& checkDrawPortalIcon(portals[i].mStageNo, portals[i].mSwitchNo))
+24
View File
@@ -1043,6 +1043,12 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) {
calcAllMapPos2D((mArrowPos3DX + control_xpos) - mStageTransX,
(mArrowPos3DZ + control_ypos) - mStageTransZ, &sp14, &sp10);
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
sp14 = getMirrorPosX(sp14, 0.0f);
}
#endif
mSelectRegion = 0xff;
for (int i = 7; i >= 0; i--) {
int val = field_0x1230[i];
@@ -1397,6 +1403,15 @@ void dMenu_Fmap2DBack_c::regionTextureDraw() {
if (uVar10 != uVar9) {
bool b = 0;
f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10]));
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
b = true;
v = getMirrorPosX(mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10])),
mRegionMapSizeX[uVar10] * mZoom * 0.5f);
}
#endif
mpAreaTex[uVar10]->draw(
v, mTransZ + (dVar13 + (mRegionMinMapY[uVar10] + field_0xf2c[uVar10])),
mRegionMapSizeX[uVar10] * mZoom, mRegionMapSizeY[uVar10] * mZoom, b, false,
@@ -1404,6 +1419,15 @@ void dMenu_Fmap2DBack_c::regionTextureDraw() {
} else {
bool b = 0;
f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9]));
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
b = true;
v = getMirrorPosX(mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9])),
mRegionMapSizeX[uVar9] * mZoom * 0.5f);
}
#endif
mpAreaTex[uVar9]->draw(
v, mTransZ + (dVar13 + (mRegionMinMapY[uVar9] + field_0xf2c[uVar9])),
mRegionMapSizeX[uVar9] * mZoom, mRegionMapSizeY[uVar9] * mZoom, b, false,
+17
View File
@@ -343,6 +343,11 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
}
f32 pos_x = icon_pos_x + i_posX;
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorCenterPosX(pos_x, 0.0f);
}
#endif
mpDrawCursor->setPos(pos_x, icon_pos_y + i_posY);
mpDrawCursor->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalCursorScale);
mpDrawCursor->draw();
@@ -364,6 +369,12 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
}
f32 pos_x = (icon_pos_x + i_posX);
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorCenterPosX(pos_x, 0.0f);
}
#endif
mpPortalIcon->setPos(pos_x, icon_pos_y + i_posY);
mpPortalIcon->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalIconScale);
mpPortalIcon->draw();
@@ -399,6 +410,12 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
}
f32 pos_x = i_posX + (icon_pos_x - (icon_size_x / 2));
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
pos_x = getMirrorCenterPosX(i_posX + (icon_pos_x - (icon_size_x / 2)), icon_size_x / 2);
}
#endif
mPictures[mIconInfo[info_idx].icon_no]->draw(pos_x, (i_posY + (icon_pos_y - icon_size_y / 2)),
icon_size_x, icon_size_y, false, false, false);
+10
View File
@@ -431,6 +431,16 @@ static void dummyStrings() {
dMsgObject_HIO_c g_MsgObject_HIO_c;
int dMsgObject_c::_execute() {
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
// enable wii message index override
g_MsgObject_HIO_c.mMessageDisplay = 1;
} else if (!dusk::getSettings().game.enableMirrorMode && g_MsgObject_HIO_c.mMessageDisplay == 1) {
g_MsgObject_HIO_c.mMessageDisplay = 0;
}
#endif
field_0x4c7 = 0;
if (mpTalkHeap != NULL) {
field_0x148 = mDoExt_setCurrentHeap(mpTalkHeap);
+2 -16
View File
@@ -1120,26 +1120,12 @@ int dScnLogo_c::create() {
checkProgSelect();
if (field_0x20a != 0) {
mExecCommand = EXEC_PROG_IN;
#if TARGET_PC
mTimer = dusk::getSettings().game.skipWarningScreen ? 1 : 30;
#else
mTimer = 30;
#endif
field_0x218 = getProgressiveMode();
} else {
#if TARGET_PC
if (dusk::getSettings().game.skipWarningScreen) {
mTimer = 0; // Possibly unnecessary but just in case
mExecCommand = EXEC_DVD_WAIT;
} else {
if (mDoRst::getWarningDispFlag()) {
mTimer = 90;
mExecCommand = EXEC_NINTENDO_IN;
} else {
mTimer = 120;
mExecCommand = EXEC_WARNING_IN;
}
}
mTimer = 0; // Possibly unnecessary but just in case
mExecCommand = EXEC_DVD_WAIT;
#else
if (mDoRst::getWarningDispFlag()) {
mTimer = 90;
+12
View File
@@ -40,9 +40,11 @@
#include "JSystem/JKernel/JKRAramArchive.h"
#if TARGET_PC
#include "dusk/autosave.h"
#include "dusk/memory.h"
#include "dusk/randomizer/game/tools.h"
#include <dusk/autosave.h>
#include "dusk/ui/ui.hpp"
#endif
#if DEBUG
@@ -795,7 +797,17 @@ static int dScnPly_Execute(dScnPly_c* i_this) {
dJprev_c::get()->update();
#endif
#if TARGET_PC
if (!dusk::ui::is_prelaunch_open()) {
dDemo_c::update();
} else if (dusk::getSettings().audio.menuSounds) {
s8 reverb = dComIfGp_getReverb(dComIfGp_roomControl_getStayNo());
f32 fxMix = reverb / 127.0f;
g_mEnvSeMgr.field_0x144.startEnvSeDirLevel(JA_SE_ATM_WIND_1, fxMix, 1.0f);
}
#else
dDemo_c::update();
#endif
#if DEBUG
dJcame_c::get()->update();
+14 -12
View File
@@ -1,14 +1,15 @@
#include "dusk/achievements.h"
#include "dusk/io.hpp"
#include "dusk/main.h"
#include "d/d_com_inf_game.h"
#include "d/d_meter2_info.h"
#include "d/actor/d_a_alink.h"
#include "d/actor/d_a_npc4.h"
#include "d/actor/d_a_player.h"
#include "d/d_com_inf_game.h"
#include "d/d_demo.h"
#include "f_pc/f_pc_name.h"
#include "d/d_meter2_info.h"
#include "dusk/io.hpp"
#include "dusk/main.h"
#include "dusk/ui/ui.hpp"
#include "f_op/f_op_actor_mng.h"
#include "f_pc/f_pc_name.h"
#include <filesystem>
#include <algorithm>
@@ -454,12 +455,6 @@ AchievementSystem& AchievementSystem::get() {
return instance;
}
std::string AchievementSystem::consumePendingUnlock() {
std::string msg = std::move(m_pendingUnlocks.front());
m_pendingUnlocks.pop();
return msg;
}
std::vector<Achievement> AchievementSystem::getAchievements() const {
std::vector<Achievement> result;
result.reserve(m_entries.size());
@@ -559,7 +554,14 @@ void AchievementSystem::processEntry(Entry& e) {
if (nowUnlocked) {
e.achievement.progress = e.achievement.isCounter ? e.achievement.goal : 1;
e.achievement.unlocked = true;
m_pendingUnlocks.push(e.achievement.name);
if (getSettings().game.enableAchievementNotifications) {
ui::push_toast({
.type = "achievement",
.title = "Achievement Unlocked!",
.content = e.achievement.name,
.duration = std::chrono::seconds(5),
});
}
m_dirty = true;
} else if (progressChanged) {
m_dirty = true;
-16
View File
@@ -10,11 +10,9 @@
#include "fmt/format.h"
#include "ImGuiConsole.hpp"
#include "dusk/ui/preset.hpp"
#include "dusk/ui/ui.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "SDL3/SDL_mouse.h"
#include "dusk/achievements.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/config.hpp"
#include "dusk/dusk.h"
@@ -254,15 +252,6 @@ namespace dusk {
UpdateSettings();
AchievementSystem::get().tick();
while (AchievementSystem::get().hasPendingUnlock()) {
if (getSettings().game.enableAchievementNotifications) {
m_menuTools.notifyAchievement(AchievementSystem::get().consumePendingUnlock());
} else {
AchievementSystem::get().consumePendingUnlock();
}
}
if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) &&
(ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
ImGui::IsKeyPressed(ImGuiKey_R))
@@ -274,10 +263,6 @@ namespace dusk {
ImGuiMenuGame::ToggleFullscreen();
}
// if (!dusk::IsGameLaunched) {
// m_preLaunchWindow.draw();
// }
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
m_isHidden = !m_isHidden;
}
@@ -345,7 +330,6 @@ namespace dusk {
}
m_menuRandomizer.windowRandoStats();
m_menuRandomizer.windowRandoGeneration();
m_menuTools.showAchievementNotification();
DuskDebugPad(); // temporary, remove later
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
-2
View File
@@ -10,7 +10,6 @@
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuTools.hpp"
#include "ImGuiMenuRandomizer.hpp"
#include "ImGuiPreLaunchWindow.hpp"
#include "imgui.h"
union SDL_Event;
@@ -47,7 +46,6 @@ private:
ImGuiMenuGame m_menuGame;
ImGuiMenuRandomizer m_menuRandomizer;
ImGuiPreLaunchWindow m_preLaunchWindow;
// Keep always last
ImGuiMenuTools m_menuTools;
-62
View File
@@ -267,66 +267,4 @@ namespace dusk {
ImGui::End();
ImGui::PopFont();
}
void ImGuiMenuTools::notifyAchievement(std::string name) {
if (m_notifyTimer <= 0.f) {
m_notifyName = std::move(name);
m_notifyTimer = NOTIFY_DURATION;
} else {
m_notifyQueue.push(std::move(name));
}
}
void ImGuiMenuTools::showAchievementNotification() {
if (!getSettings().game.enableAchievementNotifications.getValue()) {
return;
}
if (m_notifyTimer <= 0.f) {
if (m_notifyQueue.empty()) {
return;
}
m_notifyName = std::move(m_notifyQueue.front());
m_notifyQueue.pop();
m_notifyTimer = NOTIFY_DURATION;
}
m_notifyTimer -= ImGui::GetIO().DeltaTime;
const float alpha = std::min({
m_notifyTimer / NOTIFY_FADE_TIME,
(NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME,
1.0f
});
const ImGuiViewport* viewport = ImGui::GetMainViewport();
const float padding = 12.0f;
ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding),
ImGuiCond_Always, ImVec2(1.0f, 0.0f)
);
ImGui::SetNextWindowBgAlpha(alpha * 0.92f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f));
constexpr ImGuiWindowFlags flags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs;
if (ImGui::Begin("##achievement_notify", nullptr, flags)) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha));
ImGui::TextUnformatted("Achievement Unlocked!");
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::TextUnformatted(m_notifyName.c_str());
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(3);
}
}
-8
View File
@@ -27,8 +27,6 @@ namespace dusk {
void ShowAudioDebug();
void ShowSaveEditor();
void ShowStateShare();
void notifyAchievement(std::string name);
void showAchievementNotification();
private:
bool m_showDebugOverlay = false;
@@ -68,12 +66,6 @@ namespace dusk {
bool m_showStateShare = false;
ImGuiStateShare m_stateShare;
std::string m_notifyName;
float m_notifyTimer = 0.f;
std::queue<std::string> m_notifyQueue;
static constexpr float NOTIFY_DURATION = 4.0f;
static constexpr float NOTIFY_FADE_TIME = 0.5f;
};
}
-282
View File
@@ -1,282 +0,0 @@
#include "imgui.h"
#include "ImGuiConfig.hpp"
#include "ImGuiEngine.hpp"
#include "ImGuiPreLaunchWindow.hpp"
#include "../file_select.hpp"
#include "../iso_validate.hpp"
#include "ImGuiConsole.hpp"
#include "dusk/main.h"
#include "dusk/settings.h"
#include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_filesystem.h>
#include "aurora/lib/internal.hpp"
#include "aurora/lib/window.hpp"
namespace dusk {
typedef void (ImGuiPreLaunchWindow::*drawFunc)();
drawFunc drawTable[2] = {&ImGuiPreLaunchWindow::drawMainMenu, &ImGuiPreLaunchWindow::drawOptions};
static constexpr std::array<const char*, 5> skLanguageNames = {
"English", "German", "French", "Spanish", "Italian"
};
static constexpr std::array<SDL_DialogFileFilter, 2> skGameDiscFileFilters{{
{"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"},
{"All Files", "*"},
}};
static std::string ShowIsoInvalidError(const iso::ValidationError code) {
using namespace std::literals::string_literals;
switch (code) {
case iso::ValidationError::IOError:
return "Unknown IO error occurred"s;
case iso::ValidationError::InvalidImage:
return "Unable to interpret selected file as a disc image"s;
case iso::ValidationError::WrongGame:
return "Selected disc image is for a different game"s;
case iso::ValidationError::WrongVersion:
return "Selected disc image is for an unsupported version of the game. Only North American GameCube (NTSC/GZ2E01) is supported at this time."s;
case iso::ValidationError::ExecutableMismatch:
return "Selected disc image contains modified executable files."s;
default:
return "Unknown error"s;
}
}
static std::string_view card_type_name(CARDFileType type) {
switch (type) {
case CARD_GCIFOLDER:
return "GCI Folder"sv;
case CARD_RAWIMAGE:
return "Card Image"sv;
default:
return ""sv;
}
}
void fileDialogCallback(void* userdata, const char* path, const char* error) {
auto* self = static_cast<ImGuiPreLaunchWindow*>(userdata);
if (error != nullptr) {
self->m_selectedIsoPath.clear();
self->m_errorString = fmt::format("File dialog error: {}", error);
return;
}
if (path == nullptr) {
self->m_selectedIsoPath.clear();
return;
}
self->m_selectedIsoPath = path;
self->m_isPal = iso::isPal(path);
getSettings().backend.isoPath.setValue(self->m_selectedIsoPath);
config::Save();
}
ImGuiPreLaunchWindow::ImGuiPreLaunchWindow() = default;
bool ImGuiPreLaunchWindow::isSelectedPathValid() const {
#if TARGET_ANDROID
return !m_selectedIsoPath.empty(); // unsure why SDL_GetPathInfo doesnt work here
#else
return !m_selectedIsoPath.empty() && SDL_GetPathInfo(m_selectedIsoPath.c_str(), nullptr);
#endif
}
void ImGuiPreLaunchWindow::draw() {
if (m_IsFirstDraw) {
m_selectedIsoPath = getSettings().backend.isoPath;
m_isPal = !m_selectedIsoPath.empty() && iso::isPal(m_selectedIsoPath.c_str());
m_initialGraphicsBackend = getSettings().backend.graphicsBackend;
m_IsFirstDraw = false;
}
if (isSelectedPathValid() && getSettings().backend.skipPreLaunchUI) {
dusk::IsGameLaunched = true;
return;
}
auto& io = ImGui::GetIO();
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y));
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowBgAlpha(0.65f);
ImGui::Begin("Pre Launch Window", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoBringToFrontOnFocus);
const auto& windowSize = ImGui::GetWindowSize();
for (int i = 0; i < 5; i++)
ImGui::NewLine();
float iconSize = 150.f;
ImGui::SameLine(windowSize.x / 2 - iconSize + (iconSize / 2));
if (ImGuiEngine::orgIcon != 0) {
ImGui::Image(ImGuiEngine::orgIcon, ImVec2{iconSize, iconSize});
}
ImGuiTextCenter("Twilit Realm presents");
if (ImGuiEngine::duskLogo) {
ImGui::NewLine();
float width = iconSize * 2.5f;
ImGui::SameLine(windowSize.x / 2 - width + (width / 2));
ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize});
} else {
ImGui::PushFont(ImGuiEngine::fontExtraLarge);
ImGuiTextCenter("Dusk");
ImGui::PopFont();
}
(this->*drawTable[m_CurMenu])();
ImGui::End();
}
void ImGuiPreLaunchWindow::drawMainMenu() {
const auto& windowSize = ImGui::GetWindowSize();
ImGui::SetCursorPosY(windowSize.y - 200);
ImGui::PushFont(ImGuiEngine::fontLarge);
if (!isSelectedPathValid()) {
if (!m_errorString.empty()) {
ImGuiTextCenter(m_errorString);
}
if (ImGuiButtonCenter("Select disc image...")) {
ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(),
skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr,
false);
}
} else {
if (ImGuiButtonCenter("Start game")) {
dusk::IsGameLaunched = true;
}
}
if (ImGuiButtonCenter("Options")) {
m_CurMenu = 1;
}
ImGui::PopFont();
}
void ImGuiPreLaunchWindow::drawOptions() {
const auto& windowSize = ImGui::GetWindowSize();
ImGui::NewLine();
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGuiTextCenter("Options");
ImGui::Separator();
ImGui::PopFont();
auto cursorY = ImGui::GetCursorPosY();
float endCursorY = windowSize.y - 100;
float childWidth = windowSize.x - 400;
ImGui::SetCursorPosX(windowSize.x / 2 - (childWidth / 2));
if (ImGui::BeginChild("OptionsChild", ImVec2(childWidth, endCursorY - cursorY),
ImGuiChildFlags_None, ImGuiWindowFlags_NoBackground))
{
if (!m_errorString.empty()) {
ImGuiTextCenter(m_errorString);
}
ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
if (ImGui::Button(m_selectedIsoPath == "" ? "Set" : "Change")) {
ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(),
skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr,
false);
}
if (m_isPal) {
auto selectedLanguage = getSettings().game.language.getValue();
if (ImGui::BeginCombo("Language", skLanguageNames[static_cast<u8>(selectedLanguage)])) {
for (u8 i = 0; i < skLanguageNames.size(); ++i) {
if (ImGui::Selectable(skLanguageNames[i])) {
getSettings().game.language.setValue(static_cast<GameLanguage>(i));
config::Save();
}
}
ImGui::EndCombo();
}
}
AuroraBackend configuredBackend = BACKEND_AUTO;
const std::string& configuredBackendId = getSettings().backend.graphicsBackend;
if (!try_parse_backend(configuredBackendId, configuredBackend)) {
configuredBackend = BACKEND_AUTO;
}
if (ImGui::BeginCombo("Graphics Backend", backend_name(configuredBackend).data())) {
if (ImGui::Selectable("Auto", configuredBackend == BACKEND_AUTO)) {
getSettings().backend.graphicsBackend.setValue("auto");
config::Save();
}
size_t backendCount = 0;
const AuroraBackend* availableBackends = aurora_get_available_backends(&backendCount);
for (size_t i = 0; i < backendCount; ++i) {
const AuroraBackend backend = availableBackends[i];
const bool isSelected = configuredBackend == backend;
if (ImGui::Selectable(backend_name(backend).data(), isSelected)) {
getSettings().backend.graphicsBackend.setValue(
std::string(backend_id(backend)));
config::Save();
}
if (isSelected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
if (configuredBackendId != m_initialGraphicsBackend) {
ImGui::TextDisabled("Restart Required");
}
auto curFileType = (CARDFileType)getSettings().backend.cardFileType.getValue();
if (ImGui::BeginCombo("Save File Type", card_type_name(curFileType).data())) {
if (ImGui::Selectable("GCI Folder", curFileType == CARD_GCIFOLDER)) {
getSettings().backend.cardFileType.setValue(CARD_GCIFOLDER);
config::Save();
}
if (ImGui::Selectable("Card Image", curFileType == CARD_RAWIMAGE)) {
getSettings().backend.cardFileType.setValue(CARD_RAWIMAGE);
config::Save();
}
ImGui::EndCombo();
}
ImGui::EndChild();
}
ImGui::SetCursorPosY(endCursorY);
ImGui::NewLine();
ImGui::Separator();
ImGui::PushFont(ImGuiEngine::fontLarge);
if (ImGuiButtonCenter("Back")) {
m_CurMenu = 0;
}
ImGui::PopFont();
}
} // namespace dusk
-23
View File
@@ -1,23 +0,0 @@
#pragma once
namespace dusk {
class ImGuiPreLaunchWindow {
private:
int m_CurMenu = 0;
bool m_IsFirstDraw = true;
std::string m_initialGraphicsBackend;
bool isSelectedPathValid() const;
public:
ImGuiPreLaunchWindow();
void draw();
void drawMainMenu();
void drawOptions();
std::string m_selectedIsoPath;
std::string m_errorString;
bool m_isPal = false;
};
} // namespace dusk
+110 -7
View File
@@ -5,17 +5,110 @@
#endif
#include <aurora/main.h>
#include "dusk/main.h"
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#if !defined(_WIN32)
#include <unistd.h>
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#endif
#endif
int game_main(int argc, char* argv[]);
namespace {
bool RestartProcess(int argc, char* argv[]) {
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV)
(void)argc;
(void)argv;
return false;
#elif _WIN32
std::wstring commandLine = GetCommandLineW();
STARTUPINFOW startupInfo{};
startupInfo.cb = sizeof(startupInfo);
PROCESS_INFORMATION processInfo{};
if (!CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr,
&startupInfo, &processInfo))
{
fprintf(stderr, "Failed to restart Dusk: CreateProcessW error %lu\n", GetLastError());
return false;
}
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
return true;
#else
std::filesystem::path executablePath;
#if defined(__APPLE__)
uint32_t pathSize = 0;
_NSGetExecutablePath(nullptr, &pathSize);
if (pathSize > 0) {
std::string path(pathSize, '\0');
if (_NSGetExecutablePath(path.data(), &pathSize) == 0) {
path.resize(std::strlen(path.c_str()));
std::error_code ec;
executablePath = std::filesystem::weakly_canonical(path, ec);
if (ec) {
executablePath = path;
}
}
}
#elif defined(__linux__)
std::array<char, 4096> path{};
const ssize_t len = readlink("/proc/self/exe", path.data(), path.size() - 1);
if (len > 0) {
path[static_cast<size_t>(len)] = '\0';
executablePath = path.data();
}
#endif
if (executablePath.empty() && argc > 0 && argv[0] != nullptr && argv[0][0] != '\0') {
std::error_code ec;
executablePath = std::filesystem::absolute(argv[0], ec);
if (ec) {
executablePath = argv[0];
}
}
if (executablePath.empty()) {
fprintf(stderr, "Failed to restart Dusk: unable to resolve executable path\n");
return false;
}
std::vector<std::string> args;
args.reserve(static_cast<size_t>(std::max(argc, 1)));
args.push_back(executablePath.string());
for (int i = 1; i < argc; ++i) {
args.emplace_back(argv[i] != nullptr ? argv[i] : "");
}
std::vector<char*> execArgv;
execArgv.reserve(args.size() + 1);
for (auto& arg : args) {
execArgv.push_back(arg.data());
}
execArgv.push_back(nullptr);
execv(executablePath.c_str(), execArgv.data());
fprintf(stderr, "Failed to restart Dusk: execv failed: %s\n", std::strerror(errno));
return false;
#endif
}
#if _WIN32
bool ShouldShowWindowsConsole(int argc, char* argv[]) {
if (const auto* env = std::getenv("DUSK_CONSOLE")) {
@@ -53,19 +146,25 @@ void WindowsSetupConsole(bool showConsole) {
SetConsoleOutputCP(CP_UTF8);
if (const HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr) {
stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr)
{
DWORD consoleMode = 0;
if (GetConsoleMode(stdoutHandle, &consoleMode)) {
SetConsoleMode(stdoutHandle,
consoleMode | ENABLE_PROCESSED_OUTPUT
| ENABLE_VIRTUAL_TERMINAL_PROCESSING);
consoleMode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
}
int DuskMain(int argc, char* argv[]) {
WindowsSetupConsole(ShouldShowWindowsConsole(argc, argv));
return game_main(argc, argv);
const int result = game_main(argc, argv);
if constexpr (dusk::SupportsProcessRestart) {
if (dusk::RestartRequested) {
return RestartProcess(argc, argv) ? 0 : result;
}
}
return result;
}
std::vector<std::string> WideArgsToUtf8(int argc, wchar_t** argv) {
@@ -81,8 +180,8 @@ std::vector<std::string> WideArgsToUtf8(int argc, wchar_t** argv) {
}
std::vector<char> utf8Buffer(static_cast<size_t>(requiredSize));
WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr,
nullptr);
WideCharToMultiByte(
CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr, nullptr);
utf8Args.emplace_back(utf8Buffer.data());
}
@@ -109,7 +208,11 @@ int RunWindowsGuiEntryPoint() {
}
#else
int DuskMain(int argc, char* argv[]) {
return game_main(argc, argv);
const int result = game_main(argc, argv);
if (dusk::RestartRequested && RestartProcess(argc, argv)) {
return 0;
}
return result;
}
#endif
-2
View File
@@ -27,7 +27,6 @@ UserSettings g_userSettings = {
// Quality of Life
.enableQuickTransform {"game.enableQuickTransform", false},
.hideTvSettingsScreen {"game.hideTvSettingsScreen", true},
.skipWarningScreen {"game.skipWarningScreen", true},
.biggerWallets {"game.biggerWallets", false},
.noReturnRupees {"game.noReturnRupees", false},
.disableRupeeCutscenes {"game.disableRupeeCutscenes", false},
@@ -143,7 +142,6 @@ void registerSettings() {
Register(g_userSettings.game.language);
Register(g_userSettings.game.enableQuickTransform);
Register(g_userSettings.game.hideTvSettingsScreen);
Register(g_userSettings.game.skipWarningScreen);
Register(g_userSettings.game.biggerWallets);
Register(g_userSettings.game.noReturnRupees);
Register(g_userSettings.game.disableRupeeCutscenes);
+2 -2
View File
@@ -65,7 +65,7 @@ public:
btn.on_nav_command([this, key = std::string(a.key)](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (mConfirming) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
mDoAud_seStartMenu(kSoundClick);
AchievementSystem::get().clearOne(key.c_str());
resetConfirm();
} else {
@@ -158,7 +158,7 @@ AchievementsWindow::AchievementsWindow() {
clearAllBtn.on_nav_command([clearAllPtr, confirmingAll](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (*confirmingAll) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
mDoAud_seStartMenu(kSoundClick);
AchievementSystem::get().clearAll();
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
+1 -1
View File
@@ -36,7 +36,7 @@ bool BoolButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) {
const bool newValue = !mGetValue();
mSetValue(newValue);
mDoAud_seStartMenu(newValue ? Z2SE_SY_CURSOR_OK : Z2SE_SY_CURSOR_CANCEL);
mDoAud_seStartMenu(newValue ? kSoundItemEnable : kSoundItemDisable);
return true;
}
return false;
-1
View File
@@ -37,7 +37,6 @@ Button& Button::on_pressed(ButtonCallback callback) {
// TODO: convert this to a FluentComponent method?
on_nav_command([callback = std::move(callback)](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
callback();
return true;
}
-10
View File
@@ -59,16 +59,6 @@ void Component::set_disabled(bool value) {
}
}
Rml::Element* Component::append(Rml::Element* parent, const Rml::String& tag) {
if (parent == nullptr) {
return nullptr;
}
auto* doc = parent->GetOwnerDocument();
if (doc == nullptr) {
return nullptr;
}
return parent->AppendChild(doc->CreateElement(tag));
}
void Component::listen(Rml::Element* element, Rml::EventId event,
ScopedEventListener::Callback callback, bool capture) {
if (element == nullptr) {
-1
View File
@@ -47,7 +47,6 @@ public:
Rml::Element* root() const { return mRoot; }
protected:
static Rml::Element* append(Rml::Element* parent, const Rml::String& tag);
void clear_children();
Rml::Element* mRoot = nullptr;
+2
View File
@@ -322,6 +322,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
.isSelected = [port] { return PADGetIndexForPort(port) < 0; },
})
.on_pressed([this, port] {
mDoAud_seStartMenu(kSoundItemChange);
cancel_pending_binding();
PADClearPort(port);
PADSerializeMappings();
@@ -335,6 +336,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
[port, i] { return PADGetIndexForPort(port) == static_cast<s32>(i); },
})
.on_pressed([this, port, i] {
mDoAud_seStartMenu(kSoundItemChange);
cancel_pending_binding();
PADSetPortForIndex(i, port);
PADSerializeMappings();
+1 -8
View File
@@ -44,15 +44,8 @@ Document::Document(const Rml::String& source) : mDocument(load_document(source))
if (cmd == NavCommand::None) {
return;
}
auto* prevFocused = mDocument->GetFocusLeafNode();
if (handle_nav_command(event, cmd)) {
event.StopPropagation();
return;
}
if ((cmd == NavCommand::Up || cmd == NavCommand::Down) &&
mDocument->GetFocusLeafNode() != prevFocused)
{
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
}
});
}
@@ -113,7 +106,7 @@ bool Document::visible() const {
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Menu) {
mDoAud_seStartMenu(visible() ? Z2SE_SY_MENU_OUT : Z2SE_SY_MENU_IN);
mDoAud_seStartMenu(visible() ? kSoundMenuClose : kSoundMenuOpen);
toggle();
return true;
}
+76 -17
View File
@@ -185,7 +185,10 @@ void populate_stage_picker(Pane& pane, std::function<Rml::String()> getStageFile
return getStageFile() == stageFile;
},
})
.on_pressed([setStageFile, stageFile = map.mapFile] { setStageFile(stageFile); });
.on_pressed([setStageFile, stageFile = map.mapFile] {
mDoAud_seStartMenu(kSoundItemChange);
setStageFile(stageFile);
});
}
}
}
@@ -741,11 +744,13 @@ void populate_toggle_group(Pane& pane, const std::vector<ToggleEntry>& entries)
pane.clear();
pane.add_section("Actions");
pane.add_button("Select All").on_pressed([entries] {
mDoAud_seStartMenu(kSoundItemChange);
for (const auto& entry : entries) {
entry.setSelected(true);
}
});
pane.add_button("Select None").on_pressed([entries] {
mDoAud_seStartMenu(kSoundItemChange);
for (const auto& entry : entries) {
entry.setSelected(false);
}
@@ -758,6 +763,7 @@ void populate_toggle_group(Pane& pane, const std::vector<ToggleEntry>& entries)
.isSelected = entry.isSelected,
})
.on_pressed([isSelected = entry.isSelected, setSelected = entry.setSelected] {
mDoAud_seStartMenu(kSoundItemChange);
setSelected(!isSelected());
});
}
@@ -878,7 +884,10 @@ void populate_item_slot_picker(Pane& pane, int slot) {
pane.clear();
pane.add_section("Actions");
pane.add_button(fmt::format("Default ({})", get_item_name(get_slot_default(slot))))
.on_pressed([slot] { dComIfGs_setItem(slot, get_slot_default(slot)); });
.on_pressed([slot] {
mDoAud_seStartMenu(kSoundItemChange);
dComIfGs_setItem(slot, get_slot_default(slot));
});
pane.add_section("Items");
pane.add_button(
@@ -886,7 +895,10 @@ void populate_item_slot_picker(Pane& pane, int slot) {
.text = "None",
.isSelected = [slot] { return get_player_item()->mItems[slot] == dItemNo_NONE_e; },
})
.on_pressed([slot] { dComIfGs_setItem(slot, dItemNo_NONE_e); });
.on_pressed([slot] {
mDoAud_seStartMenu(kSoundItemChange);
dComIfGs_setItem(slot, dItemNo_NONE_e);
});
for (const auto& [itemId, item] : itemMap) {
if (item.m_type != ITEMTYPE_EQUIP_e) {
continue;
@@ -896,15 +908,24 @@ void populate_item_slot_picker(Pane& pane, int slot) {
.text = item.m_name,
.isSelected = [slot, itemId] { return get_player_item()->mItems[slot] == itemId; },
})
.on_pressed([slot, itemId] { dComIfGs_setItem(slot, static_cast<u8>(itemId)); });
.on_pressed([slot, itemId] {
mDoAud_seStartMenu(kSoundItemChange);
dComIfGs_setItem(slot, static_cast<u8>(itemId));
});
}
}
void populate_item_flag_picker(Pane& pane) {
pane.clear();
pane.add_section("Actions");
pane.add_button("Select All").on_pressed([] { set_all_item_first_bits(true); });
pane.add_button("Clear None").on_pressed([] { set_all_item_first_bits(false); });
pane.add_button("Select All").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
set_all_item_first_bits(true);
});
pane.add_button("Clear None").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
set_all_item_first_bits(false);
});
pane.add_section("Items");
for (const auto& [itemId, item] : itemMap) {
@@ -916,7 +937,10 @@ void populate_item_flag_picker(Pane& pane) {
.text = item.m_name,
.isSelected = [itemId] { return dComIfGs_isItemFirstBit(static_cast<u8>(itemId)); },
})
.on_pressed([itemId] { toggle_item_first_bit(static_cast<u8>(itemId)); });
.on_pressed([itemId] {
mDoAud_seStartMenu(kSoundItemChange);
toggle_item_first_bit(static_cast<u8>(itemId));
});
}
}
@@ -927,13 +951,19 @@ void populate_select_item_picker(Pane& pane, u8& selectItemData) {
.text = "None",
.isSelected = [&selectItemData] { return selectItemData == dItemNo_NONE_e; },
})
.on_pressed([&selectItemData] { selectItemData = dItemNo_NONE_e; });
.on_pressed([&selectItemData] {
mDoAud_seStartMenu(kSoundItemChange);
selectItemData = dItemNo_NONE_e;
});
for (int i = 0; i < 24; i++) {
pane.add_button({
.text = item_label_for_slot(i),
.isSelected = [i, &selectItemData] { return selectItemData == i; },
})
.on_pressed([i, &selectItemData] { selectItemData = i; });
.on_pressed([i, &selectItemData] {
mDoAud_seStartMenu(kSoundItemChange);
selectItemData = i;
});
}
}
@@ -946,6 +976,7 @@ void populate_select_clothes_picker(Pane& pane) {
.isSelected = [id] { return get_player_status()->mSelectEquip[0] == id; },
})
.on_pressed([id] {
mDoAud_seStartMenu(kSoundItemChange);
dMeter2Info_setCloth(id, false);
daPy_getPlayerActorClass()->setClothesChange(0);
});
@@ -964,7 +995,10 @@ void populate_select_equip_picker(Pane& pane, u8& equip, const std::array<u8, Si
.text = get_item_name(id),
.isSelected = [id, &equip] { return equip == id; },
})
.on_pressed([id, &equip] { equip = id; });
.on_pressed([id, &equip] {
mDoAud_seStartMenu(kSoundItemChange);
equip = id;
});
};
addOption(dItemNo_NONE_e);
for (const auto item : entries) {
@@ -985,7 +1019,10 @@ void populate_wallet_picker(Pane& pane) {
.text = walletSizeNames[i],
.isSelected = [i] { return get_player_status()->getWalletSize() == i; },
})
.on_pressed([i] { get_player_status()->setWalletSize(i); });
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
get_player_status()->setWalletSize(i);
});
}
}
@@ -1002,7 +1039,10 @@ void populate_form_picker(Pane& pane) {
.text = formNames[i],
.isSelected = [i] { return get_player_status()->getTransformStatus() == i; },
})
.on_pressed([i] { get_player_status()->setTransformStatus(i); });
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
get_player_status()->setTransformStatus(i);
});
}
}
@@ -1013,7 +1053,10 @@ void add_toggle_button(Pane& pane, ToggleEntry entry) {
.text = entry.text,
.isSelected = isSelected,
})
.on_pressed([isSelected, setSelected] { setSelected(!isSelected()); });
.on_pressed([isSelected, setSelected] {
mDoAud_seStartMenu(kSoundItemChange);
setSelected(!isSelected());
});
}
template <size_t Size>
@@ -1126,8 +1169,14 @@ void populate_collect_clothes_picker(Pane& pane) {
void populate_poe_souls_picker(Pane& pane) {
pane.clear();
pane.add_section("Actions");
pane.add_button("All 60").on_pressed([] { dComIfGs_setPohSpiritNum(60); });
pane.add_button("Clear").on_pressed([] { dComIfGs_setPohSpiritNum(0); });
pane.add_button("All 60").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
dComIfGs_setPohSpiritNum(60);
});
pane.add_button("Clear").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
dComIfGs_setPohSpiritNum(0);
});
pane.add_section("Value");
pane.add_child<NumberButton>(NumberButton::Props{
@@ -1143,10 +1192,12 @@ void populate_max_life_picker(Pane& pane) {
pane.clear();
pane.add_section("Actions");
pane.add_button("3 Hearts").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
dComIfGs_setMaxLife(15);
dComIfGs_setLife(12);
});
pane.add_button("20 Hearts").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
dComIfGs_setMaxLife(100);
dComIfGs_setLife(80);
});
@@ -1257,7 +1308,10 @@ void populate_target_type_picker(Pane& pane) {
.text = targetTypeNames[type],
.isSelected = [type] { return get_player_config()->getAttentionType() == type; },
})
.on_pressed([type] { get_player_config()->setAttentionType(type); });
.on_pressed([type] {
mDoAud_seStartMenu(kSoundItemChange);
get_player_config()->setAttentionType(type);
});
}
}
@@ -1269,7 +1323,10 @@ void populate_sound_mode_picker(Pane& pane) {
.text = soundModeNames[mode],
.isSelected = [mode] { return get_player_config()->getSound() == mode; },
})
.on_pressed([mode] { get_player_config()->setSound(mode); });
.on_pressed([mode] {
mDoAud_seStartMenu(kSoundItemChange);
get_player_config()->setSound(mode);
});
}
}
@@ -1594,6 +1651,7 @@ EditorWindow::EditorWindow() {
leftPane.add_section("Item Wheel");
leftPane.register_control(leftPane.add_button("Default All").on_pressed([&rightPane] {
mDoAud_seStartMenu(kSoundItemChange);
for (int slot = 0; slot < 24; ++slot) {
dComIfGs_setItem(slot, get_slot_default(slot));
}
@@ -1601,6 +1659,7 @@ EditorWindow::EditorWindow() {
}),
rightPane, {});
leftPane.register_control(leftPane.add_button("Clear All").on_pressed([&rightPane] {
mDoAud_seStartMenu(kSoundItemChange);
for (int slot = 0; slot < 24; ++slot) {
dComIfGs_setItem(slot, dItemNo_NONE_e);
}
+282
View File
@@ -0,0 +1,282 @@
#include "graphics_tuner.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <dolphin/gx/GXAurora.h>
#include <dolphin/vi.h>
#include <fmt/format.h>
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include <algorithm>
#include <string>
namespace dusk::ui {
namespace {
const Rml::String kDocumentSource = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/tuner.rcss" />
</head>
<body>
<div id="root" class="tuner-root">
<div class="tuner">
<div class="header">
<div id="title"></div>
<div id="carousel-container" class="carousel-container"></div>
</div>
<div id="description" class="description"></div>
<div class="divider"></div>
<div id="footer" class="footer"></div>
</div>
</div>
</body>
</rml>
)RML";
int get_value(GraphicsOption option) {
switch (option) {
case GraphicsOption::InternalResolution:
return getSettings().game.internalResolutionScale.getValue();
case GraphicsOption::ShadowResolution:
return getSettings().game.shadowResolutionMultiplier.getValue();
case GraphicsOption::BloomMode:
return static_cast<int>(getSettings().game.bloomMode.getValue());
case GraphicsOption::BloomMultiplier:
return std::clamp(
static_cast<int>(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0,
100);
}
return 0;
}
void set_value(GraphicsOption option, int value) {
switch (option) {
case GraphicsOption::InternalResolution:
getSettings().game.internalResolutionScale.setValue(value);
VISetFrameBufferScale(static_cast<float>(value));
break;
case GraphicsOption::ShadowResolution:
getSettings().game.shadowResolutionMultiplier.setValue(value);
break;
case GraphicsOption::BloomMode:
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(std::clamp(
value, static_cast<int>(BloomMode::Off), static_cast<int>(BloomMode::Dusk))));
break;
case GraphicsOption::BloomMultiplier:
getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f);
break;
}
config::Save();
}
Rml::Element* create_stepped_carousel_root(Rml::Element* parent) {
auto* doc = parent->GetOwnerDocument();
auto root = doc->CreateElement("div");
root->SetClass("stepped-carousel", true);
root->SetAttribute("tabindex", "0");
return parent->AppendChild(std::move(root));
}
Rml::Element* create_stepped_carousel_arrow(
Rml::Element* parent, const Rml::String& className, const Rml::String& label) {
auto* doc = parent->GetOwnerDocument();
auto button = doc->CreateElement("button");
button->SetClass("stepped-carousel-arrow", true);
button->SetClass(className, true);
button->SetInnerRML(label);
return parent->AppendChild(std::move(button));
}
} // namespace
SteppedCarousel::SteppedCarousel(Rml::Element* parent, Props props)
: Component(create_stepped_carousel_root(parent)), mProps(std::move(props)) {
Rml::Element* prevElem = create_stepped_carousel_arrow(mRoot, "prev", "&#xe5cb;");
mValueElem = append(mRoot, "div");
mValueElem->SetClass("stepped-carousel-value", true);
Rml::Element* nextElem = create_stepped_carousel_arrow(mRoot, "next", "&#xe5cc;");
listen(prevElem, Rml::EventId::Click,
[this](Rml::Event&) { handle_nav_command(NavCommand::Left); });
listen(nextElem, Rml::EventId::Click,
[this](Rml::Event&) { handle_nav_command(NavCommand::Right); });
listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::None && handle_nav_command(cmd)) {
event.StopPropagation();
}
});
}
bool SteppedCarousel::focus() {
return Component::focus();
}
void SteppedCarousel::update() {
if (mValueElem == nullptr) {
return;
}
const int value = std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max);
if (mProps.formatValue) {
mValueElem->SetInnerRML(mProps.formatValue(value));
} else {
mValueElem->SetInnerRML(std::to_string(value));
}
}
bool SteppedCarousel::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Left) {
const int value = mProps.getValue ? mProps.getValue() : 0;
apply(std::clamp(value - mProps.step, mProps.min, mProps.max));
return true;
}
if (cmd == NavCommand::Right) {
const int value = mProps.getValue ? mProps.getValue() : 0;
apply(std::clamp(value + mProps.step, mProps.min, mProps.max));
return true;
}
return false;
}
void SteppedCarousel::apply(int value) {
const int nextValue = std::clamp(value, mProps.min, mProps.max);
const int currentValue =
std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max);
if (nextValue == currentValue) {
return;
}
mDoAud_seStartMenu(kSoundItemChange);
if (mProps.onChange) {
mProps.onChange(nextValue);
}
}
Rml::String format_graphics_setting_value(GraphicsOption option, int value) {
switch (option) {
case GraphicsOption::InternalResolution:
if (value <= 0) {
return "Auto";
} else {
u32 width = 0;
u32 height = 0;
AuroraGetRenderSize(&width, &height);
return fmt::format("{}× ({}×{})", value, width, height);
}
case GraphicsOption::ShadowResolution:
return fmt::format("{}×", value);
case GraphicsOption::BloomMode:
switch (static_cast<BloomMode>(value)) {
case BloomMode::Off:
return "Off";
case BloomMode::Classic:
return "Classic";
case BloomMode::Dusk:
return "Dusk";
}
break;
case GraphicsOption::BloomMultiplier:
return fmt::format("{}%", value);
}
return "";
}
GraphicsTuner::GraphicsTuner(GraphicsTunerProps props)
: Document(kDocumentSource), mOption(props.option), mValueMin(props.valueMin),
mValueMax(props.valueMax), mDefaultValue(props.defaultValue) {
if (mDocument == nullptr) {
return;
}
if (auto* title = mDocument->GetElementById("title")) {
title->SetInnerRML(escape(props.title));
}
if (auto* description = mDocument->GetElementById("description")) {
description->SetInnerRML(escape(props.helpText));
}
if (auto* carouselParent = mDocument->GetElementById("carousel-container")) {
add_component<SteppedCarousel>(carouselParent,
SteppedCarousel::Props{
.min = mValueMin,
.max = mValueMax,
.step = 1,
.getValue = [this] { return get_value(mOption); },
.onChange = [this](int value) { set_value(mOption, value); },
.formatValue =
[this](int value) { return format_graphics_setting_value(mOption, value); },
});
}
if (auto* footer = mDocument->GetElementById("footer")) {
auto& returnButton = add_component<Button>(footer, "\xE2\x86\x90 Return", "footer-button")
.on_pressed([this] { pop(); });
returnButton.root()->SetClass("return", true);
auto& resetButton =
add_component<Button>(footer, "Reset to default", "footer-button").on_pressed([this] {
mDoAud_seStartMenu(kSoundItemChange);
reset_default();
});
resetButton.root()->SetClass("reset", true);
}
// Hide document after transition completion
mRoot = mDocument->GetElementById("root");
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
}
void GraphicsTuner::show() {
Document::show();
mRoot->SetAttribute("open", "");
mDoAud_seStartMenu(kSoundWindowOpen);
}
void GraphicsTuner::hide(bool close) {
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
mDoAud_seStartMenu(kSoundWindowClose);
}
}
void GraphicsTuner::update() {
for (const auto& component : mComponents) {
component->update();
}
Document::update();
}
bool GraphicsTuner::focus() {
for (const auto& component : mComponents) {
if (component->focus()) {
return true;
}
}
return false;
}
bool GraphicsTuner::visible() const {
return mRoot->HasAttribute("open");
}
bool GraphicsTuner::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel) {
pop();
return true;
}
return Document::handle_nav_command(event, cmd);
}
void GraphicsTuner::reset_default() {
set_value(mOption, mDefaultValue);
}
} // namespace dusk::ui
+90
View File
@@ -0,0 +1,90 @@
#pragma once
#include "button.hpp"
#include "component.hpp"
#include "document.hpp"
#include "ui.hpp"
#include <functional>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
namespace dusk::ui {
class SteppedCarousel : public Component {
public:
struct Props {
int min = 0;
int max = 0;
int step = 1;
std::function<int()> getValue;
std::function<void(int)> onChange;
std::function<Rml::String(int)> formatValue;
};
SteppedCarousel(Rml::Element* parent, Props props);
bool focus() override;
void update() override;
private:
bool handle_nav_command(NavCommand cmd);
void apply(int value);
Props mProps;
Rml::Element* mValueElem = nullptr;
};
enum class GraphicsOption {
InternalResolution,
ShadowResolution,
BloomMode,
BloomMultiplier,
};
Rml::String format_graphics_setting_value(GraphicsOption option, int value);
struct GraphicsTunerProps {
GraphicsOption option;
Rml::String title;
Rml::String helpText;
int valueMin = 0;
int valueMax = 0;
int defaultValue = 0;
};
class GraphicsTuner : public Document {
public:
explicit GraphicsTuner(GraphicsTunerProps props);
void show() override;
void hide(bool close) override;
void update() override;
bool focus() override;
bool visible() const override;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T> T& add_component(Args&&... args) {
auto child = std::make_unique<T>(std::forward<Args>(args)...);
T& ref = *child;
mComponents.emplace_back(std::move(child));
return ref;
}
void reset_default();
GraphicsOption mOption;
int mValueMin = 0;
int mValueMax = 0;
int mDefaultValue = 0;
std::vector<std::unique_ptr<Component> > mComponents;
Rml::Element* mRoot;
};
} // namespace dusk::ui
@@ -1,4 +1,4 @@
#include "popup.hpp"
#include "menu_bar.hpp"
#include <RmlUi/Core.h>
@@ -30,16 +30,20 @@ const Rml::String kDocumentSource = R"RML(
<link type="text/rcss" href="res/rml/popup.rcss" />
</head>
<body>
<popup id="popup"></popup>
<popup id="popup" />
</body>
</rml>
)RML";
}
Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("popup")) {
MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById("popup")) {
mTabBar = std::make_unique<TabBar>(mRoot, TabBar::Props{
.onClose = [this] { hide(false); },
.onClose =
[this] {
mDoAud_seStartMenu(kSoundMenuClose);
hide(false);
},
.autoSelect = false,
});
mTabBar->add_tab("Settings", [this] { push(std::make_unique<SettingsWindow>()); });
@@ -68,7 +72,7 @@ Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("pop
});
}
void Popup::show() {
void MenuBar::show() {
Document::show();
mRoot->SetAttribute("open", "");
mTabBar->set_active_tab(-1);
@@ -77,7 +81,7 @@ void Popup::show() {
}
}
void Popup::hide(bool close) {
void MenuBar::hide(bool close) {
mFocusedTabIndex = mTabBar->focused_tab_index();
mRoot->RemoveAttribute("open");
if (close) {
@@ -85,12 +89,12 @@ void Popup::hide(bool close) {
}
}
void Popup::update() {
void MenuBar::update() {
update_safe_area();
Document::update();
}
void Popup::update_safe_area() noexcept {
void MenuBar::update_safe_area() noexcept {
if (mDocument == nullptr || mTabBar == nullptr) {
return;
}
@@ -128,23 +132,23 @@ void Popup::update_safe_area() noexcept {
}
}
bool Popup::visible() const {
bool MenuBar::visible() const {
return mRoot->HasAttribute("open");
}
bool Popup::handle_nav_command(Rml::Event& event, NavCommand cmd) {
bool MenuBar::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (!getSettings().backend.wasPresetChosen) {
return true;
}
if (cmd == NavCommand::Cancel && visible()) {
mDoAud_seStartMenu(Z2SE_SY_MENU_OUT);
mDoAud_seStartMenu(kSoundMenuClose);
hide(false);
return true;
}
return Document::handle_nav_command(event, cmd);
}
bool Popup::focus() {
bool MenuBar::focus() {
return mTabBar->focus();
}
@@ -8,12 +8,12 @@
namespace dusk::ui {
class Popup : public Document {
class MenuBar : public Document {
public:
Popup();
MenuBar();
Popup(const Popup&) = delete;
Popup& operator=(const Popup&) = delete;
MenuBar(const MenuBar&) = delete;
MenuBar& operator=(const MenuBar&) = delete;
void show() override;
void hide(bool close) override;
+75
View File
@@ -0,0 +1,75 @@
#include "modal.hpp"
namespace dusk::ui {
Modal::Modal(Props props)
: WindowSmall("modal", "modal-dialog"), mProps(std::move(props)) {
auto* title = append(mDialog, "div");
title->SetClass("preset-title", true);
title->SetInnerRML(mProps.title);
auto* body = append(mDialog, "div");
body->SetClass("preset-intro", true);
body->SetInnerRML(mProps.bodyRml);
auto* actions = append(mDialog, "div");
actions->SetClass("modal-actions", true);
for (auto& action : mProps.actions) {
auto btn = std::make_unique<Button>(actions, action.label);
btn->root()->SetClass("modal-btn", true);
btn->on_pressed([this, callback = std::move(action.onPressed)] {
if (callback) {
callback(*this);
}
});
mButtons.push_back(std::move(btn));
}
}
bool Modal::focus() {
if (!mButtons.empty()) {
return mButtons.front()->focus();
}
return false;
}
void Modal::dismiss() {
if (mProps.onDismiss) {
mProps.onDismiss(*this);
return;
}
pop();
}
bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel || cmd == NavCommand::Menu) {
mDoAud_seStartMenu(kSoundWindowClose);
dismiss();
return true;
}
int direction = 0;
if (cmd == NavCommand::Left) {
direction = -1;
} else if (cmd == NavCommand::Right) {
direction = 1;
} else {
return false;
}
auto* target = event.GetTargetElement();
for (int i = 0; i < static_cast<int>(mButtons.size()); ++i) {
if (mButtons[i]->contains(target)) {
const int next = i + direction;
if (next >= 0 && next < static_cast<int>(mButtons.size()) && mButtons[next]->focus()) {
mDoAud_seStartMenu(kSoundItemFocus);
return true;
}
return false;
}
}
return false;
}
} // namespace dusk::ui
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "button.hpp"
#include "window.hpp"
namespace dusk::ui {
class Modal;
struct ModalAction {
Rml::String label;
std::function<void(Modal&)> onPressed;
};
class Modal : public WindowSmall {
public:
struct Props {
Rml::String title;
Rml::String bodyRml;
std::vector<ModalAction> actions;
std::function<void(Modal&)> onDismiss;
};
explicit Modal(Props props);
bool focus() override;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
void dismiss();
Props mProps;
std::vector<std::unique_ptr<Button> > mButtons;
};
} // namespace dusk::ui
+1 -1
View File
@@ -59,7 +59,7 @@ bool NumberButton::handle_nav_command(NavCommand cmd) {
mGetValue() + (cmd == NavCommand::Right ? mStep : -mStep), mMin, mMax);
if (newValue != mGetValue()) {
mSetValue(newValue);
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
mDoAud_seStartMenu(kSoundItemChange);
}
return true;
}
+74 -237
View File
@@ -1,20 +1,15 @@
#include "overlay.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <dolphin/gx/GXAurora.h>
#include <dolphin/vi.h>
#include <fmt/format.h>
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "aurora/lib/logging.hpp"
#include "magic_enum.hpp"
#include <algorithm>
#include <string>
#include "dusk/achievements.h"
namespace dusk::ui {
namespace {
aurora::Module Log{"dusk::ui::overlay"};
const Rml::String kDocumentSource = R"RML(
<rml>
@@ -22,259 +17,101 @@ const Rml::String kDocumentSource = R"RML(
<link type="text/rcss" href="res/rml/overlay.rcss" />
</head>
<body>
<div id="root" class="overlay-root">
<div class="overlay">
<div class="header">
<div id="title"></div>
<div id="carousel-container" class="carousel-container"></div>
</div>
<div id="description" class="description"></div>
<div class="divider"></div>
<div id="footer" class="footer"></div>
</div>
</div>
</body>
</rml>
)RML";
int get_value(GraphicsOption option) {
switch (option) {
case GraphicsOption::InternalResolution:
return getSettings().game.internalResolutionScale.getValue();
case GraphicsOption::ShadowResolution:
return getSettings().game.shadowResolutionMultiplier.getValue();
case GraphicsOption::BloomMode:
return static_cast<int>(getSettings().game.bloomMode.getValue());
case GraphicsOption::BloomMultiplier:
return std::clamp(
static_cast<int>(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0,
100);
Rml::Element* create_toast(Rml::Element* parent, const Toast& toast) {
auto* elem = append(parent, "toast");
if (!toast.type.empty()) {
elem->SetClass(toast.type, true);
}
return 0;
}
void set_value(GraphicsOption option, int value) {
switch (option) {
case GraphicsOption::InternalResolution:
getSettings().game.internalResolutionScale.setValue(value);
VISetFrameBufferScale(static_cast<float>(value));
break;
case GraphicsOption::ShadowResolution:
getSettings().game.shadowResolutionMultiplier.setValue(value);
break;
case GraphicsOption::BloomMode:
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(std::clamp(
value, static_cast<int>(BloomMode::Off), static_cast<int>(BloomMode::Dusk))));
break;
case GraphicsOption::BloomMultiplier:
getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f);
break;
{
auto* heading = append(elem, "heading");
auto* span = append(heading, "span");
span->SetInnerRML(toast.title);
if (toast.type == "achievement") {
auto* icon = append(heading, "icon");
icon->SetClass("trophy", true);
mDoAud_seStartMenu(kSoundAchievementUnlock);
}
}
config::Save();
}
Rml::Element* create_stepped_carousel_root(Rml::Element* parent) {
auto* doc = parent->GetOwnerDocument();
auto root = doc->CreateElement("div");
root->SetClass("stepped-carousel", true);
root->SetAttribute("tabindex", "0");
return parent->AppendChild(std::move(root));
}
Rml::Element* create_stepped_carousel_arrow(
Rml::Element* parent, const Rml::String& className, const Rml::String& label) {
auto* doc = parent->GetOwnerDocument();
auto button = doc->CreateElement("button");
button->SetClass("stepped-carousel-arrow", true);
button->SetClass(className, true);
button->SetInnerRML(label);
return parent->AppendChild(std::move(button));
{
auto* message = append(elem, "message");
auto* span = append(message, "span");
span->SetInnerRML(toast.content);
}
{
auto* progress = append(elem, "progress");
progress->SetAttribute("value", 1.f);
}
return elem;
}
} // namespace
SteppedCarousel::SteppedCarousel(Rml::Element* parent, Props props)
: Component(create_stepped_carousel_root(parent)), mProps(std::move(props)) {
Rml::Element* prevElem = create_stepped_carousel_arrow(mRoot, "prev", "&#xe5cb;");
mValueElem = append(mRoot, "div");
mValueElem->SetClass("stepped-carousel-value", true);
Rml::Element* nextElem = create_stepped_carousel_arrow(mRoot, "next", "&#xe5cc;");
listen(prevElem, Rml::EventId::Click,
[this](Rml::Event&) { handle_nav_command(NavCommand::Left); });
listen(nextElem, Rml::EventId::Click,
[this](Rml::Event&) { handle_nav_command(NavCommand::Right); });
listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::None && handle_nav_command(cmd)) {
event.StopPropagation();
}
});
}
bool SteppedCarousel::focus() {
return Component::focus();
}
void SteppedCarousel::update() {
if (mValueElem == nullptr) {
return;
}
const int value = std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max);
if (mProps.formatValue) {
mValueElem->SetInnerRML(mProps.formatValue(value));
} else {
mValueElem->SetInnerRML(std::to_string(value));
}
}
bool SteppedCarousel::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Left) {
const int value = mProps.getValue ? mProps.getValue() : 0;
apply(std::clamp(value - mProps.step, mProps.min, mProps.max));
return true;
}
if (cmd == NavCommand::Right) {
const int value = mProps.getValue ? mProps.getValue() : 0;
apply(std::clamp(value + mProps.step, mProps.min, mProps.max));
return true;
}
return false;
}
void SteppedCarousel::apply(int value) {
const int nextValue = std::clamp(value, mProps.min, mProps.max);
const int currentValue =
std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max);
if (nextValue == currentValue) {
return;
}
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
if (mProps.onChange) {
mProps.onChange(nextValue);
}
}
Rml::String format_graphics_setting_value(GraphicsOption option, int value) {
switch (option) {
case GraphicsOption::InternalResolution:
if (value <= 0) {
return "Auto";
} else {
u32 width = 0;
u32 height = 0;
AuroraGetRenderSize(&width, &height);
return fmt::format("{}× ({}×{})", value, width, height);
}
case GraphicsOption::ShadowResolution:
return fmt::format("{}×", value);
case GraphicsOption::BloomMode:
switch (static_cast<BloomMode>(value)) {
case BloomMode::Off:
return "Off";
case BloomMode::Classic:
return "Classic";
case BloomMode::Dusk:
return "Dusk";
}
break;
case GraphicsOption::BloomMultiplier:
return fmt::format("{}%", value);
}
return "";
}
Overlay::Overlay(OverlayProps props)
: Document(kDocumentSource), mOption(props.option), mValueMin(props.valueMin),
mValueMax(props.valueMax), mDefaultValue(props.defaultValue) {
if (mDocument == nullptr) {
return;
}
if (auto* title = mDocument->GetElementById("title")) {
title->SetInnerRML(escape(props.title));
}
if (auto* description = mDocument->GetElementById("description")) {
description->SetInnerRML(escape(props.helpText));
}
if (auto* carouselParent = mDocument->GetElementById("carousel-container")) {
add_component<SteppedCarousel>(carouselParent,
SteppedCarousel::Props{
.min = mValueMin,
.max = mValueMax,
.step = 1,
.getValue = [this] { return get_value(mOption); },
.onChange = [this](int value) { set_value(mOption, value); },
.formatValue =
[this](int value) { return format_graphics_setting_value(mOption, value); },
});
}
if (auto* footer = mDocument->GetElementById("footer")) {
auto& returnButton = add_component<Button>(footer, "\xE2\x86\x90 Return", "footer-button")
.on_pressed([this] { pop(); });
returnButton.root()->SetClass("return", true);
auto& resetButton =
add_component<Button>(footer, "Reset to default", "footer-button").on_pressed([this] {
reset_default();
});
resetButton.root()->SetClass("reset", true);
}
// Hide document after transition completion
mRoot = mDocument->GetElementById("root");
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
Overlay::Overlay() : Document(kDocumentSource) {
listen(mDocument, Rml::EventId::Focus, [](Rml::Event&) { Log.warn("Overlay received focus"); });
listen(mDocument, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mCurrentToast) {
if (get_toasts().empty() ||
clock::now() >= mCurrentToastStartTime + get_toasts().front().duration)
{
mCurrentToast->SetPseudoClass("done", true);
}
}
});
}
void Overlay::show() {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
Document::show();
mRoot->SetAttribute("open", "");
}
void Overlay::hide(bool close) {
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
if (mDocument != nullptr) {
mDocument->Show(Rml::ModalFlag::None, Rml::FocusFlag::None, Rml::ScrollFlag::None);
}
}
void Overlay::update() {
for (const auto& component : mComponents) {
component->update();
}
Document::update();
}
bool Overlay::focus() {
for (const auto& component : mComponents) {
if (component->focus()) {
return true;
auto& toasts = get_toasts();
if (mCurrentToast == nullptr) {
if (!toasts.empty()) {
const auto& toast = toasts.front();
mCurrentToast = create_toast(mDocument, toast);
mCurrentToastStartTime = clock::now();
}
} else if (!toasts.empty()) {
const auto& toast = toasts.front();
const float duration = std::chrono::duration<float>(toast.duration).count();
const float elapsed =
std::chrono::duration<float>(clock::now() - mCurrentToastStartTime).count();
const float ratio = duration > 0.0f ? std::clamp(elapsed / duration, 0.0f, 1.0f) : 1.0f;
const auto remaining = 1.f - ratio;
Rml::ElementList list;
mDocument->GetElementsByTagName(list, "progress");
for (auto* elem : list) {
elem->SetAttribute("value", remaining);
}
if (remaining == 0.f) {
if (mCurrentToast->IsPseudoClassSet("done") ||
// Fallback for large gaps in time where we never actually opened it
!mCurrentToast->IsPseudoClassSet("opened"))
{
mCurrentToast->GetParentNode()->RemoveChild(mCurrentToast);
mCurrentToast = nullptr;
toasts.pop_front();
} else {
mCurrentToast->RemoveAttribute("open");
}
} else {
mCurrentToast->SetAttribute("open", "");
mCurrentToast->SetPseudoClass("opened", true);
}
}
return false;
}
bool Overlay::visible() const {
return mRoot->HasAttribute("open");
}
bool Overlay::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel) {
pop();
return true;
}
return Document::handle_nav_command(event, cmd);
}
void Overlay::reset_default() {
set_value(mOption, mDefaultValue);
Log.warn("Overlay received nav command: {}", magic_enum::enum_name(cmd));
return false;
}
} // namespace dusk::ui
+4 -71
View File
@@ -1,90 +1,23 @@
#pragma once
#include "button.hpp"
#include "component.hpp"
#include "document.hpp"
#include "ui.hpp"
#include <functional>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include <chrono>
namespace dusk::ui {
class SteppedCarousel : public Component {
public:
struct Props {
int min = 0;
int max = 0;
int step = 1;
std::function<int()> getValue;
std::function<void(int)> onChange;
std::function<Rml::String(int)> formatValue;
};
SteppedCarousel(Rml::Element* parent, Props props);
bool focus() override;
void update() override;
private:
bool handle_nav_command(NavCommand cmd);
void apply(int value);
Props mProps;
Rml::Element* mValueElem = nullptr;
};
enum class GraphicsOption {
InternalResolution,
ShadowResolution,
BloomMode,
BloomMultiplier,
};
Rml::String format_graphics_setting_value(GraphicsOption option, int value);
struct OverlayProps {
GraphicsOption option;
Rml::String title;
Rml::String helpText;
int valueMin = 0;
int valueMax = 0;
int defaultValue = 0;
};
class Overlay : public Document {
public:
explicit Overlay(OverlayProps props);
Overlay();
void show() override;
void hide(bool close) override;
void update() override;
bool focus() override;
bool visible() const override;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T> T& add_component(Args&&... args) {
auto child = std::make_unique<T>(std::forward<Args>(args)...);
T& ref = *child;
mComponents.emplace_back(std::move(child));
return ref;
}
void reset_default();
GraphicsOption mOption;
int mValueMin = 0;
int mValueMax = 0;
int mDefaultValue = 0;
std::vector<std::unique_ptr<Component> > mComponents;
Rml::Element* mRoot;
Rml::Element* mCurrentToast = nullptr;
clock::time_point mCurrentToastStartTime;
};
} // namespace dusk::ui
+1 -1
View File
@@ -58,7 +58,7 @@ Pane::Pane(Rml::Element* parent, Type type) : FluentComponent(createRoot(parent)
int i = focusedChild + direction;
while (i >= 0 && i < mChildren.size()) {
if (mChildren[i]->focus()) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
mDoAud_seStartMenu(kSoundItemFocus);
event.StopPropagation();
break;
}
+168 -34
View File
@@ -4,15 +4,18 @@
#include "dusk/file_select.hpp"
#include "dusk/iso_validate.hpp"
#include "dusk/main.h"
#include "dusk/ui/prelaunch_options.hpp"
#include "dusk/settings.h"
#include "modal.hpp"
#include "preset.hpp"
#include "settings.hpp"
#include "version.h"
#include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_filesystem.h>
#include <aurora/lib/window.hpp>
#include "m_Do/m_Do_MemCard.h"
namespace dusk::ui {
namespace {
const Rml::String kDocumentSource = R"RML(
<rml>
@@ -20,6 +23,8 @@ const Rml::String kDocumentSource = R"RML(
<link type="text/rcss" href="res/rml/prelaunch.rcss" />
</head>
<body>
<div class="gradient" />
<div class="background" />
<content id="root" open>
<menu>
<hero class="intro-item delay-0">
@@ -49,6 +54,23 @@ constexpr std::array<SDL_DialogFileFilter, 2> kDiscFileFilters{{
{"All Files", "*"},
}};
static std::string get_error_msg(iso::ValidationError error) {
switch (error) {
case iso::ValidationError::IOError:
return "Unable to read the selected file.";
case iso::ValidationError::InvalidImage:
return "The selected file is not a valid disc image.";
case iso::ValidationError::WrongGame:
return "The selected game is not supported by Dusk.";
case iso::ValidationError::WrongVersion:
return "Dusk currently supports GameCube USA and PAL disc images only.";
case iso::ValidationError::Success:
return "The selected disc image is valid.";
default:
return "The selected disc image could not be validated.";
}
}
void file_dialog_callback(void*, const char* path, const char* error) {
auto& state = prelaunch_state();
if (error != nullptr) {
@@ -58,14 +80,18 @@ void file_dialog_callback(void*, const char* path, const char* error) {
return;
}
state.selectedIsoPath = path;
state.errorString.clear();
refresh_path_state();
getSettings().backend.isoPath.setValue(state.selectedIsoPath);
config::Save();
}
const auto validation = iso::validate(path);
if (validation != iso::ValidationError::Success) {
state.errorString = escape(get_error_msg(validation));
return;
}
} // namespace
state.selectedDiscPath = path;
state.errorString.clear();
getSettings().backend.isoPath.setValue(state.selectedDiscPath);
config::Save();
refresh_state();
}
PrelaunchState sPrelaunchState;
@@ -73,9 +99,15 @@ PrelaunchState& prelaunch_state() noexcept {
return sPrelaunchState;
}
void refresh_path_state() noexcept {
void refresh_state() noexcept {
auto& state = prelaunch_state();
state.isPal = !state.selectedIsoPath.empty() && iso::isPal(state.selectedIsoPath.c_str());
const auto validation = iso::validate(state.selectedDiscPath.c_str());
if (state.selectedDiscPath.empty() || validation != iso::ValidationError::Success) {
state.selectedDiscIsValid = false;
return;
}
state.selectedDiscIsValid = true;
state.selectedDiscIsPal = iso::isPal(state.selectedDiscPath.c_str());
}
void ensure_initialized() noexcept {
@@ -84,16 +116,17 @@ void ensure_initialized() noexcept {
return;
}
state.selectedIsoPath = getSettings().backend.isoPath;
state.selectedDiscPath = getSettings().backend.isoPath;
state.initialDiscPath = state.selectedDiscPath;
if (iso::validate(state.initialDiscPath.c_str()) == iso::ValidationError::Success) {
state.initialDiscIsPal = iso::isPal(state.initialDiscPath.c_str());
}
state.initialLanguage = getSettings().game.language;
state.initialGraphicsBackend = getSettings().backend.graphicsBackend;
state.initialCardFileType = getSettings().backend.cardFileType;
state.errorString.clear();
state.initialized = true;
refresh_path_state();
}
bool is_selected_path_valid() noexcept {
return !prelaunch_state().selectedIsoPath.empty() &&
SDL_GetPathInfo(prelaunch_state().selectedIsoPath.c_str(), nullptr);
refresh_state();
}
void open_iso_picker() noexcept {
@@ -102,6 +135,20 @@ void open_iso_picker() noexcept {
kDiscFileFilters.data(), kDiscFileFilters.size(), nullptr, false);
}
bool is_restart_pending() noexcept {
const auto& state = prelaunch_state();
if (!state.initialDiscPath.empty() && state.selectedDiscPath != state.initialDiscPath) {
return true;
}
if (getSettings().backend.graphicsBackend.getValue() != state.initialGraphicsBackend) {
return true;
}
if (getSettings().game.language.getValue() != state.initialLanguage) {
return true;
}
return false;
}
void apply_intro_animation(Rml::Element* element, const char* delay_class) {
if (element == nullptr || delay_class == nullptr) {
return;
@@ -110,25 +157,53 @@ void apply_intro_animation(Rml::Element* element, const char* delay_class) {
element->SetClass(delay_class, true);
}
void try_apply_mirrored_layout(Rml::Element* body) {
if (body == nullptr) {
return;
}
body->SetClass("mirrored", getSettings().game.enableMirrorMode.getValue());
}
Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementById("root")) {
ensure_initialized();
if (auto* menuList = mDocument->GetElementById("menu-list")) {
const bool hasValidPath = is_selected_path_valid();
mMenuButtons.push_back(
std::make_unique<Button>(menuList, hasValidPath ? "Start Game" : "Select Disc Image"));
auto& state = prelaunch_state();
mMenuButtons.push_back(std::make_unique<Button>(
menuList, state.selectedDiscIsValid ? "Play" : "Select Disc Image"));
mMenuButtons.back()->on_pressed([this] {
if (!is_selected_path_valid()) {
if (!prelaunch_state().selectedDiscIsValid) {
open_iso_picker();
return;
}
mDoAud_seStartMenu(kSoundPlay);
if (getSettings().audio.menuSounds) {
JAISoundHandle* handle = g_mEnvSeMgr.field_0x144.getHandle();
if (*handle) {
(*handle)->stop(60);
(*handle)->releaseHandle();
}
}
if (g_mDoMemCd_control.mCardCommand == mDoMemCd_Ctrl_c::Command_e::COMM_NONE_e) {
mDoMemCd_ThdInit();
}
IsGameLaunched = true;
if (!getSettings().backend.wasPresetChosen) {
push_document(std::make_unique<dusk::ui::PresetWindow>());
}
hide(true);
});
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Options"));
mMenuButtons.back()->on_pressed([this] { push(std::make_unique<PrelaunchOptions>()); });
mMenuButtons.back()->on_pressed([this] {
mRestartSuppressed = false;
push(std::make_unique<SettingsWindow>(true));
});
apply_intro_animation(mMenuButtons.back()->root(), "delay-2");
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit To Desktop"));
@@ -140,6 +215,8 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
mDiscDetail = mDocument->GetElementById("disc-version");
mVersion = mDocument->GetElementById("version-text");
try_apply_mirrored_layout(mDocument);
listen(mDocument, Rml::EventId::Transitionend, [this](Rml::Event& event) {
auto* target = event.GetTargetElement();
if (target == nullptr) {
@@ -157,6 +234,40 @@ void Prelaunch::show() {
Document::show();
mDocument->SetAttribute("open", "");
mRoot->SetAttribute("open", "");
if (is_restart_pending() && !mRestartSuppressed) {
const auto dismiss = [this](Modal& modal) {
mRestartSuppressed = true;
modal.pop();
};
std::vector<ModalAction> actions;
if constexpr (dusk::SupportsProcessRestart) {
actions.push_back(ModalAction{
.label = "Restart later",
.onPressed = dismiss,
});
actions.push_back(ModalAction{
.label = "Restart now",
.onPressed = [](Modal&) { dusk::RequestRestart(); },
});
} else {
actions.push_back(ModalAction{
.label = "OK",
.onPressed = dismiss,
});
}
push(std::make_unique<Modal>(Modal::Props{
.title = "Apply Options",
.bodyRml =
dusk::SupportsProcessRestart ?
"A restart is required to apply selected options.<br/><br/>Restart now to "
"apply them immediately?" :
"A restart is required to apply selected options.<br/><br/>Close and reopen "
"Dusk to apply them.",
.actions = std::move(actions),
.onDismiss = dismiss,
}));
}
}
void Prelaunch::hide(bool close) {
@@ -164,6 +275,8 @@ void Prelaunch::hide(bool close) {
if (!mEntranceAnimationStarted) {
// Close document immediately
Document::hide(true);
} else {
mPendingClose = true;
}
mDocument->RemoveAttribute("open");
} else {
@@ -173,12 +286,34 @@ void Prelaunch::hide(bool close) {
void Prelaunch::update() {
ensure_initialized();
refresh_path_state();
try_apply_mirrored_layout(mDocument);
auto& state = prelaunch_state();
const bool hasValidPath = is_selected_path_valid();
if (hasValidPath && getSettings().backend.skipPreLaunchUI) {
hide(true);
if (!state.errorString.empty() && top_document() == this) {
auto dismiss = [](Modal& modal) {
prelaunch_state().errorString.clear();
modal.pop();
};
push(std::make_unique<Modal>(Modal::Props{
.title = "Invalid disc image",
.bodyRml = state.errorString,
.actions =
{
ModalAction{
.label = "OK",
.onPressed = dismiss,
},
},
.onDismiss = dismiss,
}));
}
const bool hasValidPath = prelaunch_state().selectedDiscIsValid;
mDocument->SetClass("disc-ready", hasValidPath);
if (hasValidPath) {
if (getSettings().backend.skipPreLaunchUI) {
hide(true);
}
IsGameLaunched = true;
}
@@ -188,7 +323,7 @@ void Prelaunch::update() {
}
if (!mMenuButtons.empty()) {
mMenuButtons[0]->set_text(hasValidPath ? "Start Game" : "Select Disc Image");
mMenuButtons[0]->set_text(hasValidPath ? "Play" : "Select Disc Image");
}
const auto discStatusLabel = mDiscStatus->GetElementById("disc-status-label");
@@ -197,15 +332,13 @@ void Prelaunch::update() {
if (hasValidPath) {
mDiscStatus->SetAttribute("status", "good");
discStatusLabel->SetInnerRML("Disc ready.");
} else {
mDiscStatus->SetAttribute("status", "bad");
discStatusLabel->SetInnerRML("Disc not found.");
}
}
if (mDiscDetail != nullptr) {
if (hasValidPath) {
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
mDiscDetail->SetInnerRML(state.isPal ? "GameCube • EUR" : "GameCube • USA");
mDiscDetail->SetInnerRML(
prelaunch_state().initialDiscIsPal ? "GameCube • EUR" : "GameCube • USA");
} else {
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
}
@@ -225,7 +358,7 @@ bool Prelaunch::focus() {
if (mMenuButtons.empty()) {
return false;
}
return mMenuButtons[0]->focus();
return mMenuButtons.front()->focus();
}
bool Prelaunch::visible() const {
@@ -253,6 +386,7 @@ bool Prelaunch::handle_nav_command(Rml::Event& event, NavCommand cmd) {
int i = ((focusedButton + direction) % n + n) % n;
while (i >= 0 && i < mMenuButtons.size()) {
if (mMenuButtons[i]->focus()) {
mDoAud_seStartMenu(kSoundItemFocus);
event.StopPropagation();
return true;
}
+12 -6
View File
@@ -24,6 +24,7 @@ protected:
private:
bool mEntranceAnimationStarted = false;
bool mRestartSuppressed = false;
std::vector<std::unique_ptr<Button>> mMenuButtons;
Rml::Element* mRoot = nullptr;
Rml::Element* mDiscStatus = nullptr;
@@ -34,17 +35,22 @@ private:
class PrelaunchOptions;
struct PrelaunchState {
std::string selectedIsoPath;
std::string errorString;
std::string initialGraphicsBackend;
bool isPal = false;
bool initialized = false;
std::string selectedDiscPath;
bool selectedDiscIsValid = false;
bool selectedDiscIsPal = false;
std::string errorString;
bool initialDiscIsPal = false;
std::string initialDiscPath;
GameLanguage initialLanguage = GameLanguage::English;
std::string initialGraphicsBackend;
int initialCardFileType = 0;
};
PrelaunchState& prelaunch_state() noexcept;
void ensure_initialized() noexcept;
void refresh_path_state() noexcept;
bool is_selected_path_valid() noexcept;
void refresh_state() noexcept;
void open_iso_picker() noexcept;
bool is_restart_pending() noexcept;
} // namespace dusk::ui
-263
View File
@@ -1,263 +0,0 @@
#include "prelaunch_options.hpp"
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "pane.hpp"
#include "prelaunch.hpp"
namespace dusk::ui {
namespace {
static constexpr std::array<const char*, 5> kLanguageNames = {
"English", "German", "French", "Spanish", "Italian",
};
// TODO: Copied from ImGui prelaunch. Needs a refactor?
bool try_parse_backend(std::string_view backend, AuroraBackend& outBackend) {
if (backend == "auto") {
outBackend = BACKEND_AUTO;
return true;
}
if (backend == "d3d11") {
outBackend = BACKEND_D3D11;
return true;
}
if (backend == "d3d12") {
outBackend = BACKEND_D3D12;
return true;
}
if (backend == "metal") {
outBackend = BACKEND_METAL;
return true;
}
if (backend == "vulkan") {
outBackend = BACKEND_VULKAN;
return true;
}
if (backend == "opengl") {
outBackend = BACKEND_OPENGL;
return true;
}
if (backend == "opengles") {
outBackend = BACKEND_OPENGLES;
return true;
}
if (backend == "webgpu") {
outBackend = BACKEND_WEBGPU;
return true;
}
if (backend == "null") {
outBackend = BACKEND_NULL;
return true;
}
return false;
}
std::string_view backend_name(AuroraBackend backend) {
switch (backend) {
default:
return "Auto";
case BACKEND_D3D12:
return "D3D12";
case BACKEND_D3D11:
return "D3D11";
case BACKEND_METAL:
return "Metal";
case BACKEND_VULKAN:
return "Vulkan";
case BACKEND_OPENGL:
return "OpenGL";
case BACKEND_OPENGLES:
return "OpenGL ES";
case BACKEND_WEBGPU:
return "WebGPU";
case BACKEND_NULL:
return "Null";
}
}
std::string_view backend_id(AuroraBackend backend) {
switch (backend) {
default:
return "auto";
case BACKEND_D3D12:
return "d3d12";
case BACKEND_D3D11:
return "d3d11";
case BACKEND_METAL:
return "metal";
case BACKEND_VULKAN:
return "vulkan";
case BACKEND_OPENGL:
return "opengl";
case BACKEND_OPENGLES:
return "opengles";
case BACKEND_WEBGPU:
return "webgpu";
case BACKEND_NULL:
return "null";
}
}
std::vector<AuroraBackend> available_backends() {
std::vector<AuroraBackend> backends;
backends.emplace_back(BACKEND_AUTO);
size_t backendCount = 0;
const AuroraBackend* raw = aurora_get_available_backends(&backendCount);
for (size_t i = 0; i < backendCount; ++i) {
// Do not expose NULL or D3D11
if (raw[i] != BACKEND_NULL && raw[i] != BACKEND_D3D11) {
backends.emplace_back(raw[i]);
}
}
return backends;
}
class LanguageSelect final : public SelectButton {
public:
explicit LanguageSelect(Rml::Element* parent) : SelectButton(parent, Props{.key = "Language"}) {}
void update() override {
ensure_initialized();
refresh_path_state();
const bool validPath = is_selected_path_valid();
const bool ntscDiscLocked = validPath && !prelaunch_state().isPal;
if (ntscDiscLocked) {
if (getSettings().game.language.getValue() != GameLanguage::English) {
getSettings().game.language.setValue(GameLanguage::English);
config::Save();
}
set_disabled(true);
} else {
set_disabled(false);
}
const auto lang = getSettings().game.language.getValue();
auto value = static_cast<u8>(lang);
if (value >= kLanguageNames.size()) {
getSettings().game.language.setValue(GameLanguage::English);
config::Save();
value = static_cast<u8>(getSettings().game.language.getValue());
}
set_value_label(kLanguageNames[value]);
SelectButton::update();
}
protected:
bool handle_nav_command(NavCommand cmd) override {
if (disabled()) {
return false;
}
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
return false;
}
constexpr int n = static_cast<int>(kLanguageNames.size());
int idx = static_cast<int>(getSettings().game.language.getValue());
const int dir = (cmd == NavCommand::Left) ? -1 : 1;
idx = ((idx + dir) % n + n) % n;
getSettings().game.language.setValue(static_cast<GameLanguage>(idx));
config::Save();
return true;
}
};
class BackendSelect final : public SelectButton {
public:
explicit BackendSelect(Rml::Element* parent) : SelectButton(parent, Props{.key = "Graphics Backend"}) {}
void update() override {
AuroraBackend configuredBackend = BACKEND_AUTO;
const auto configuredId = getSettings().backend.graphicsBackend.getValue();
if (!try_parse_backend(configuredId, configuredBackend)) {
configuredBackend = BACKEND_AUTO;
}
// Do not expose NULL or D3D11
if (configuredBackend == BACKEND_NULL || configuredBackend == BACKEND_D3D11) {
getSettings().backend.graphicsBackend.setValue("auto");
config::Save();
configuredBackend = BACKEND_AUTO;
}
const auto backend = getSettings().backend.graphicsBackend.getValue();
Rml::String value = backend_name(configuredBackend).data();
if (backend != prelaunch_state().initialGraphicsBackend) {
value += " (restart required)";
}
set_value_label(value);
SelectButton::update();
}
protected:
bool handle_nav_command(NavCommand cmd) override {
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
return false;
}
const auto backends = available_backends();
const int n = static_cast<int>(backends.size());
if (n <= 0) {
return false;
}
AuroraBackend configuredBackend = BACKEND_AUTO;
const auto configuredId = getSettings().backend.graphicsBackend.getValue();
if (!try_parse_backend(configuredId, configuredBackend)) {
configuredBackend = BACKEND_AUTO;
}
int idx = 0;
for (int i = 0; i < n; ++i) {
if (backends[static_cast<size_t>(i)] == configuredBackend) {
idx = i;
break;
}
}
const int dir = (cmd == NavCommand::Left) ? -1 : 1;
idx = ((idx + dir) % n + n) % n;
getSettings().backend.graphicsBackend.setValue(std::string(backend_id(backends[static_cast<size_t>(idx)])));
config::Save();
return true;
}
};
class SaveTypeSelect final : public SelectButton {
public:
explicit SaveTypeSelect(Rml::Element* parent) : SelectButton(parent, Props{.key = "Save File Type"}) {}
void update() override {
const CARDFileType cft = static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
set_value_label(cft == CARD_GCIFOLDER ? "GCI Folder" : "Card Image");
SelectButton::update();
}
protected:
bool handle_nav_command(NavCommand cmd) override {
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
return false;
}
CARDFileType cft = static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
const CARDFileType newValue = cft == CARD_GCIFOLDER ? CARD_RAWIMAGE : CARD_GCIFOLDER;
getSettings().backend.cardFileType.setValue(newValue);
config::Save();
return true;
}
};
} // namespace
PrelaunchOptions::PrelaunchOptions() {
add_tab("Options", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
leftPane.add_child<LanguageSelect>();
leftPane.add_child<BackendSelect>();
leftPane.add_child<SaveTypeSelect>();
});
}
} // namespace dusk::ui
-12
View File
@@ -1,12 +0,0 @@
#pragma once
#include "window.hpp"
namespace dusk::ui {
class PrelaunchOptions : public Window {
public:
PrelaunchOptions();
};
} // namespace dusk::ui
+7 -54
View File
@@ -1,10 +1,8 @@
#include "preset.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "button.hpp"
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "m_Do/m_Do_audio.h"
#include "ui.hpp"
#include <dolphin/gx/GXAurora.h>
@@ -20,14 +18,12 @@ void applyPresetClassic() {
s.game.internalResolutionScale.setValue(1);
s.game.shadowResolutionMultiplier.setValue(1);
s.game.hideTvSettingsScreen.setValue(false);
s.game.skipWarningScreen.setValue(false);
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
}
void applyPresetDusk() {
auto& s = getSettings();
s.game.hideTvSettingsScreen.setValue(true);
s.game.skipWarningScreen.setValue(true);
s.game.noReturnRupees.setValue(true);
s.game.disableRupeeCutscenes.setValue(true);
s.game.noSwordRecoil.setValue(true);
@@ -49,49 +45,20 @@ void applyPresetDusk() {
s.game.enableGyroAim.setValue(true);
}
Rml::Element* createElement(Rml::Element* parent, const Rml::String& tag) {
auto* doc = parent->GetOwnerDocument();
auto elem = doc->CreateElement(tag);
return parent->AppendChild(std::move(elem));
}
const Rml::String kDocumentSource = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/window.rcss" />
</head>
<body>
<window id="window" class="small preset">
<div id="preset-dialog" class="preset-dialog"></div>
</window>
</body>
</rml>
)RML";
} // namespace
PresetWindow::PresetWindow()
: Document(kDocumentSource), mRoot(mDocument->GetElementById("window")) {
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible()) {
Document::hide(mPendingClose);
}
});
auto* dialog = mDocument->GetElementById("preset-dialog");
auto* title = createElement(dialog, "div");
PresetWindow::PresetWindow() : WindowSmall("preset", "preset-dialog") {
auto* title = append(mDialog, "div");
title->SetClass("preset-title", true);
title->SetInnerRML("Welcome to Dusk!");
auto* intro = createElement(dialog, "div");
auto* intro = append(mDialog, "div");
intro->SetClass("preset-intro", true);
intro->SetInnerRML(
"Choose a preset to get started.<br/>"
"You can change any setting later from the Settings menu.");
auto* grid = createElement(dialog, "div");
auto* grid = append(mDialog, "div");
grid->SetClass("preset-grid", true);
struct PresetInfo {
@@ -112,7 +79,7 @@ PresetWindow::PresetWindow()
};
for (const auto& preset : kPresets) {
auto* col = createElement(grid, "div");
auto* col = append(grid, "div");
col->SetClass("preset-col", true);
auto btn = std::make_unique<Button>(col, Rml::String(preset.name));
@@ -129,26 +96,12 @@ PresetWindow::PresetWindow()
});
mButtons.push_back(std::move(btn));
auto* desc = createElement(col, "div");
auto* desc = append(col, "div");
desc->SetClass("preset-desc", true);
desc->SetInnerRML(preset.desc);
}
}
void PresetWindow::show() {
Document::show();
mRoot->SetAttribute("open", "");
}
void PresetWindow::hide(bool close) {
mRoot->RemoveAttribute("open");
mPendingClose = close;
}
bool PresetWindow::visible() const {
return mRoot->HasAttribute("open");
}
bool PresetWindow::focus() {
if (!mButtons.empty()) {
return mButtons.back()->focus();
@@ -174,7 +127,7 @@ bool PresetWindow::handle_nav_command(Rml::Event& event, NavCommand cmd) {
const int next = i + direction;
if (next >= 0 && next < static_cast<int>(mButtons.size())) {
if (mButtons[next]->focus()) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
mDoAud_seStartMenu(kSoundItemFocus);
return true;
}
}
+3 -6
View File
@@ -1,25 +1,22 @@
#pragma once
#include "component.hpp"
#include "document.hpp"
#include "window.hpp"
#include <memory>
#include <vector>
namespace dusk::ui {
class PresetWindow : public Document {
class PresetWindow : public WindowSmall {
public:
PresetWindow();
void show() override;
void hide(bool close) override;
bool visible() const override;
bool focus() override;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
Rml::Element* mRoot = nullptr;
std::vector<std::unique_ptr<Component>> mButtons;
};
+1
View File
@@ -59,6 +59,7 @@ SelectButton& SelectButton::on_pressed(SelectButtonCallback callback) {
listen(Rml::EventId::Submit, [this, callback = std::move(callback)](Rml::Event& event) {
if (!disabled() && event.GetTargetElement() == mRoot) {
callback();
event.StopPropagation();
}
});
return *this;
+320 -35
View File
@@ -8,17 +8,143 @@
#include "dusk/config.hpp"
#include "dusk/imgui/ImGuiEngine.hpp"
#include "dusk/livesplit.h"
#include "graphics_tuner.hpp"
#include "m_Do/m_Do_main.h"
#include "number_button.hpp"
#include "overlay.hpp"
#include "pane.hpp"
#include "prelaunch.hpp"
#include "ui.hpp"
#include <algorithm>
#include "modal.hpp"
namespace dusk::ui {
namespace {
constexpr std::array kLanguageNames = {
"English",
"German",
"French",
"Spanish",
"Italian",
};
constexpr std::array kCardFileTypes = {
"Card Image",
"GCI Folder",
};
bool try_parse_backend(std::string_view backend, AuroraBackend& outBackend) {
if (backend == "auto") {
outBackend = BACKEND_AUTO;
return true;
}
if (backend == "d3d11") {
outBackend = BACKEND_D3D11;
return true;
}
if (backend == "d3d12") {
outBackend = BACKEND_D3D12;
return true;
}
if (backend == "metal") {
outBackend = BACKEND_METAL;
return true;
}
if (backend == "vulkan") {
outBackend = BACKEND_VULKAN;
return true;
}
if (backend == "opengl") {
outBackend = BACKEND_OPENGL;
return true;
}
if (backend == "opengles") {
outBackend = BACKEND_OPENGLES;
return true;
}
if (backend == "webgpu") {
outBackend = BACKEND_WEBGPU;
return true;
}
if (backend == "null") {
outBackend = BACKEND_NULL;
return true;
}
return false;
}
std::string_view backend_name(AuroraBackend backend) {
switch (backend) {
default:
return "Auto";
case BACKEND_D3D12:
return "D3D12";
case BACKEND_D3D11:
return "D3D11";
case BACKEND_METAL:
return "Metal";
case BACKEND_VULKAN:
return "Vulkan";
case BACKEND_OPENGL:
return "OpenGL";
case BACKEND_OPENGLES:
return "OpenGL ES";
case BACKEND_WEBGPU:
return "WebGPU";
case BACKEND_NULL:
return "Null";
}
}
std::string_view backend_id(AuroraBackend backend) {
switch (backend) {
default:
return "auto";
case BACKEND_D3D12:
return "d3d12";
case BACKEND_D3D11:
return "d3d11";
case BACKEND_METAL:
return "metal";
case BACKEND_VULKAN:
return "vulkan";
case BACKEND_OPENGL:
return "opengl";
case BACKEND_OPENGLES:
return "opengles";
case BACKEND_WEBGPU:
return "webgpu";
case BACKEND_NULL:
return "null";
}
}
std::vector<AuroraBackend> available_backends() {
std::vector<AuroraBackend> backends;
backends.emplace_back(BACKEND_AUTO);
size_t backendCount = 0;
const AuroraBackend* raw = aurora_get_available_backends(&backendCount);
for (size_t i = 0; i < backendCount; ++i) {
// Do not expose NULL or D3D11
if (raw[i] != BACKEND_NULL && raw[i] != BACKEND_D3D11) {
backends.emplace_back(raw[i]);
}
}
return backends;
}
AuroraBackend configured_backend() {
AuroraBackend configuredBackend = BACKEND_AUTO;
const auto configuredId = getSettings().backend.graphicsBackend.getValue();
if (!try_parse_backend(configuredId, configuredBackend)) {
configuredBackend = BACKEND_AUTO;
}
return configuredBackend;
}
void reset_for_speedrun_mode() {
mDoMain::developmentMode = -1;
@@ -130,8 +256,8 @@ SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<f
}
template <typename T>
void overlay_control(
Window& window, Pane& leftPane, Pane& rightPane, ConfigVar<T>& var, const OverlayProps& props) {
void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, ConfigVar<T>& var,
const GraphicsTunerProps& props) {
leftPane.register_control(
leftPane
.add_select_button({
@@ -152,7 +278,7 @@ void overlay_control(
.on_nav_command([&window, props](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
cmd == NavCommand::Right) {
window.push(std::make_unique<Overlay>(props));
window.push(std::make_unique<GraphicsTuner>(props));
return true;
}
return false;
@@ -165,30 +291,165 @@ void overlay_control(
} // namespace
SettingsWindow::SettingsWindow() {
SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
if (prelaunch) {
mSuppressNavFallback = true;
add_tab("Prelaunch", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
leftPane.register_control(
leftPane
.add_select_button({
.key = "Disc Image",
.getValue =
[] {
const auto& path = prelaunch_state().selectedDiscPath;
std::string display;
if (path.empty()) {
display = "(none)";
} else {
display = std::filesystem::path(path).filename().string();
if (display.empty()) {
display = path;
}
}
return display;
},
.isModified =
[] {
const auto& state = prelaunch_state();
const auto& initial = state.initialDiscPath;
return !initial.empty() && state.selectedDiscPath != initial;
},
})
.on_pressed([] { open_iso_picker(); }),
rightPane, [](Pane& pane) {
pane.add_rml("Set the disc image that Dusk uses to launch the game.<br/><br/>"
"Changes require a restart.");
});
leftPane.register_control(
leftPane.add_select_button({
.key = "Language",
.getValue =
[] {
const auto& state = prelaunch_state();
if (!state.selectedDiscIsValid || !state.selectedDiscIsPal) {
return kLanguageNames[0];
}
const u8 idx = static_cast<u8>(getSettings().game.language.getValue());
return kLanguageNames[idx];
},
.isDisabled =
[] {
const auto& state = prelaunch_state();
return !state.selectedDiscIsValid || !state.selectedDiscIsPal;
},
.isModified =
[] {
return getSettings().game.language.getValue() !=
prelaunch_state().initialLanguage;
},
}),
rightPane, [](Pane& pane) {
for (int i = 0; i < kLanguageNames.size(); i++) {
pane.add_button({
.text = kLanguageNames[i],
.isSelected =
[i] {
return getSettings().game.language.getValue() ==
static_cast<GameLanguage>(i);
},
})
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().game.language.setValue(static_cast<GameLanguage>(i));
config::Save();
});
}
pane.add_rml("<br/>Changes require a restart.");
});
leftPane.register_control(
leftPane.add_select_button({
.key = "Graphics Backend",
.getValue = [] { return Rml::String{backend_name(configured_backend())}; },
.isModified =
[] {
return getSettings().backend.graphicsBackend.getValue() !=
prelaunch_state().initialGraphicsBackend;
},
}),
rightPane, [](Pane& pane) {
const auto availableBackends = available_backends();
for (const auto backend : availableBackends) {
pane
.add_button({
.text = Rml::String{backend_name(backend)},
.isSelected = [backend] { return configured_backend() == backend; },
})
.on_pressed([backend] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().backend.graphicsBackend.setValue(
std::string{backend_id(backend)});
config::Save();
});
}
pane.add_rml("<br/>Changes require a restart.");
});
leftPane.register_control(
leftPane.add_select_button({
.key = "Save File Type",
.getValue =
[] {
return kCardFileTypes[getSettings().backend.cardFileType.getValue()];
},
.isModified =
[] {
return getSettings().backend.cardFileType.getValue() !=
prelaunch_state().initialCardFileType;
},
}),
rightPane, [](Pane& pane) {
for (int i = 0; i < kCardFileTypes.size(); i++) {
pane
.add_button({
.text = kCardFileTypes[i],
.isSelected =
[i] {
return getSettings().backend.cardFileType.getValue() == i;
},
})
.on_pressed([i] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().backend.cardFileType.setValue(i);
config::Save();
});
}
});
});
}
add_tab("Graphics", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
leftPane.add_section("Display");
leftPane.register_control(
leftPane.add_button("Toggle Fullscreen").on_pressed([] {
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
VISetWindowFullscreen(getSettings().video.enableFullscreen);
config::Save();
}),
rightPane, [](Pane& pane) { pane.clear(); }
);
leftPane.register_control(
leftPane.add_button("Restore Default Window Size").on_pressed([] {
getSettings().video.enableFullscreen.setValue(false);
VISetWindowFullscreen(false);
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
VICenterWindow();
}),
rightPane, [](Pane& pane) { pane.clear(); }
);
leftPane.register_control(leftPane.add_button("Toggle Fullscreen").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
VISetWindowFullscreen(getSettings().video.enableFullscreen);
config::Save();
}),
rightPane, [](Pane& pane) { pane.clear(); });
leftPane.register_control(leftPane.add_button("Restore Default Window Size").on_pressed([] {
mDoAud_seStartMenu(kSoundItemChange);
getSettings().video.enableFullscreen.setValue(false);
VISetWindowFullscreen(false);
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
VICenterWindow();
}),
rightPane, [](Pane& pane) { pane.clear(); });
config_bool_select(leftPane, rightPane, getSettings().video.enableVsync,
{
.key = "Enable VSync",
@@ -212,8 +473,9 @@ SettingsWindow::SettingsWindow() {
});
leftPane.add_section("Resolution");
overlay_control(*this, leftPane, rightPane, getSettings().game.internalResolutionScale,
OverlayProps{
graphics_tuner_control(*this, leftPane, rightPane,
getSettings().game.internalResolutionScale,
GraphicsTunerProps{
.option = GraphicsOption::InternalResolution,
.title = "Internal Resolution",
.helpText = kInternalResolutionHelpText,
@@ -221,8 +483,9 @@ SettingsWindow::SettingsWindow() {
.valueMax = 12,
.defaultValue = 0,
});
overlay_control(*this, leftPane, rightPane, getSettings().game.shadowResolutionMultiplier,
OverlayProps{
graphics_tuner_control(*this, leftPane, rightPane,
getSettings().game.shadowResolutionMultiplier,
GraphicsTunerProps{
.option = GraphicsOption::ShadowResolution,
.title = "Shadow Resolution",
.helpText = kShadowResolutionHelpText,
@@ -232,8 +495,8 @@ SettingsWindow::SettingsWindow() {
});
leftPane.add_section("Post-Processing");
overlay_control(*this, leftPane, rightPane, getSettings().game.bloomMode,
OverlayProps{
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMode,
GraphicsTunerProps{
.option = GraphicsOption::BloomMode,
.title = "Bloom",
.helpText = kBloomHelpText,
@@ -241,8 +504,8 @@ SettingsWindow::SettingsWindow() {
.valueMax = static_cast<int>(BloomMode::Dusk),
.defaultValue = static_cast<int>(BloomMode::Classic),
});
overlay_control(*this, leftPane, rightPane, getSettings().game.bloomMultiplier,
OverlayProps{
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMultiplier,
GraphicsTunerProps{
.option = GraphicsOption::BloomMultiplier,
.title = "Bloom Brightness",
.helpText = kBloomBrightnessHelpText,
@@ -597,11 +860,6 @@ SettingsWindow::SettingsWindow() {
.key = "Skip TV Settings Screen",
.helpText = "Skips the TV calibration screen shown when loading a save.",
});
config_bool_select(leftPane, rightPane, getSettings().game.skipWarningScreen,
{
.key = "Skip Warning Screen",
.helpText = "Skips the warning screen shown when starting the game.",
});
config_bool_select(leftPane, rightPane, getSettings().backend.showPipelineCompilation,
{
.key = "Show Pipeline Compilation",
@@ -610,4 +868,31 @@ SettingsWindow::SettingsWindow() {
});
}
void SettingsWindow::update() {
// Show disc validation error message if present
if (mPrelaunch && top_document() == this) {
auto& state = prelaunch_state();
if (!state.errorString.empty()) {
auto dismissInvalidDisc = [](Modal& modal) {
prelaunch_state().errorString.clear();
modal.pop();
};
push_document(std::make_unique<Modal>(Modal::Props{
.title = "Invalid disc image",
.bodyRml = state.errorString,
.actions =
{
ModalAction{
.label = "OK",
.onPressed = dismissInvalidDisc,
},
},
.onDismiss = dismissInvalidDisc,
}));
}
}
Window::update();
}
} // namespace dusk::ui
+6 -1
View File
@@ -5,7 +5,12 @@ namespace dusk::ui {
class SettingsWindow : public Window {
public:
SettingsWindow();
SettingsWindow(bool prelaunch = false);
void update() override;
protected:
bool mPrelaunch;
};
} // namespace dusk::ui
+4 -3
View File
@@ -47,7 +47,6 @@ TabBar::TabBar(Rml::Element* parent, Props props)
add_child<Button>(Button::Props{}, "close")
.on_nav_command([this](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_CANCEL);
mProps.onClose();
return true;
}
@@ -119,7 +118,9 @@ void TabBar::add_tab(const Rml::String& title, TabCallback callback) {
auto& button = add_child<Button>(Button::Props{title}, "tab");
button.on_nav_command([this, index](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
mDoAud_seStartMenu(mProps.autoSelect ? Z2SE_SY_NAME_CURSOR : Z2SE_SY_CURSOR_OK);
if (mProps.autoSelect) {
mDoAud_seStartMenu(kSoundTabChanged);
}
set_active_tab(index);
return true;
}
@@ -229,7 +230,7 @@ bool TabBar::handle_nav_command(Rml::Event& event, NavCommand cmd) {
while (i >= 0 && i < mTabs.size()) {
const bool changed = mProps.autoSelect ? set_active_tab(i) : focus_tab(i);
if (changed) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
mDoAud_seStartMenu(kSoundTabChanged);
return true;
}
i += direction;
+58 -12
View File
@@ -10,6 +10,7 @@
#include "aurora/lib/window.hpp"
#include "input.hpp"
#include "prelaunch.hpp"
#include "window.hpp"
namespace dusk::ui {
@@ -20,7 +21,10 @@ void load_font(const char* filename, bool fallback = false) {
}
bool sInitialized = false;
std::vector<std::unique_ptr<Document> > sDocuments;
std::vector<std::unique_ptr<Document> > sDocumentStack;
// Documents that don't participate in the focus stack
std::vector<std::unique_ptr<Document> > sPassiveDocuments;
std::deque<Toast> sToasts;
} // namespace
@@ -45,15 +49,20 @@ bool initialize() noexcept {
}
void shutdown() noexcept {
sDocuments.clear();
sDocumentStack.clear();
sPassiveDocuments.clear();
reset_input_state();
release_input_block();
sInitialized = false;
}
Document& push_document(std::unique_ptr<Document> doc, bool show) noexcept {
Document& push_document(std::unique_ptr<Document> doc, bool show, bool passive) noexcept {
Document& ret = *doc;
sDocuments.push_back({std::move(doc)});
if (passive) {
sPassiveDocuments.push_back(std::move(doc));
} else {
sDocumentStack.push_back({std::move(doc)});
}
if (show) {
ret.show();
}
@@ -69,12 +78,19 @@ void show_top_document() noexcept {
}
bool any_document_visible() noexcept {
return std::any_of(sDocuments.begin(), sDocuments.end(),
return std::any_of(sDocumentStack.begin(), sDocumentStack.end(),
[](const auto& doc) { return doc && doc->visible(); });
}
bool is_prelaunch_open() noexcept {
return std::any_of(sDocumentStack.begin(), sDocumentStack.end(), [](const auto& doc) {
const auto* prelaunch = dynamic_cast<const Prelaunch*>(doc.get());
return prelaunch != nullptr && !prelaunch->pending_close() && !prelaunch->closed();
});
}
Document* top_document() noexcept {
for (auto& doc : std::views::reverse(sDocuments)) {
for (auto& doc : std::views::reverse(sDocumentStack)) {
if (!doc->closed() && !doc->pending_close()) {
return doc.get();
}
@@ -84,20 +100,31 @@ Document* top_document() noexcept {
void update() noexcept {
update_input();
for (const auto& doc : sDocuments) {
for (const auto& doc : sDocumentStack) {
doc->update();
}
for (const auto& doc : sPassiveDocuments) {
doc->update();
}
// Remove closed documents
const auto [first, last] =
std::ranges::remove_if(sDocuments, [](const auto& doc) { return doc->closed(); });
sDocuments.erase(first, last);
{
const auto [first, last] =
std::ranges::remove_if(sDocumentStack, [](const auto& doc) { return doc->closed(); });
sDocumentStack.erase(first, last);
}
{
const auto [first, last] = std::ranges::remove_if(
sPassiveDocuments, [](const auto& doc) { return doc->closed(); });
sPassiveDocuments.erase(first, last);
}
// If no documents have focus, explicitly focus the top one
if (auto* context = aurora::rmlui::get_context();
context != nullptr && context->GetFocusElement() == nullptr)
context != nullptr && (context->GetFocusElement() == nullptr ||
context->GetFocusElement() == context->GetRootElement()))
{
for (auto& doc : std::views::reverse(sDocuments)) {
for (auto& doc : std::views::reverse(sDocumentStack)) {
if (!doc->closed() && !doc->pending_close() && doc->focus()) {
break;
}
@@ -140,6 +167,17 @@ std::string escape(std::string_view str) noexcept {
return result;
}
Rml::Element* append(Rml::Element* parent, const Rml::String& tag) noexcept {
if (parent == nullptr) {
return nullptr;
}
auto* doc = parent->GetOwnerDocument();
if (doc == nullptr) {
return nullptr;
}
return parent->AppendChild(doc->CreateElement(tag));
}
NavCommand map_nav_event(const Rml::Event& event) noexcept {
const auto key = static_cast<Rml::Input::KeyIdentifier>(
event.GetParameter<int>("key_identifier", Rml::Input::KI_UNKNOWN));
@@ -201,4 +239,12 @@ Insets safe_area_insets(Rml::Context* context) noexcept {
};
}
void push_toast(Toast toast) noexcept {
sToasts.push_back(std::move(toast));
}
std::deque<Toast>& get_toasts() noexcept {
return sToasts;
}
} // namespace dusk::ui
+44 -4
View File
@@ -12,7 +12,43 @@
namespace dusk::ui {
class Document;
class Popup;
using clock = std::chrono::steady_clock;
struct Toast {
Rml::String type;
Rml::String title;
Rml::String content;
clock::duration duration;
};
// Button clicked/pressed
constexpr u32 kSoundClick = Z2SE_SY_CURSOR_OK;
// "Play" button clicked/pressed
constexpr u32 kSoundPlay = Z2SE_SY_ITEM_COMBINE_ON;
// Menu button pressed (open/close menu bar or hide/show the active window)
constexpr u32 kSoundMenuOpen = Z2SE_SY_MENU_SUB_IN;
constexpr u32 kSoundMenuClose = Z2SE_SY_MENU_SUB_OUT;
// Window opened/closed
constexpr u32 kSoundWindowOpen = Z2SE_SY_MENU_NEXT;
constexpr u32 kSoundWindowClose = Z2SE_SY_MENU_BACK;
// Window tab changed
constexpr u32 kSoundTabChanged = Z2SE_SY_MENU_CURSOR_COMMON;
// Item within menu focused
constexpr u32 kSoundItemFocus = Z2SE_SY_CURSOR_ITEM;
// Item changed (e.g. number input left/right)
constexpr u32 kSoundItemChange = Z2SE_SY_NAME_CURSOR;
// Item enabled ("On")
constexpr u32 kSoundItemEnable = Z2SE_SUBJ_VIEW_IN;
// Item disabled ("Off")
constexpr u32 kSoundItemDisable = Z2SE_SUBJ_VIEW_OUT;
// Achievement unlocked
constexpr u32 kSoundAchievementUnlock = Z2SE_NAVI_FLY;
struct Insets {
float top = 0.0f;
@@ -32,17 +68,21 @@ void shutdown() noexcept;
void handle_event(const SDL_Event& event) noexcept;
void update() noexcept;
Document& push_document(std::unique_ptr<Document> doc, bool show = true) noexcept;
Document& push_document(
std::unique_ptr<Document> doc, bool show = true, bool passive = false) noexcept;
void show_top_document() noexcept;
bool any_document_visible() noexcept;
bool is_prelaunch_open() noexcept;
Document* top_document() noexcept;
Popup& add_popup(std::unique_ptr<Popup> popup) noexcept;
std::filesystem::path resource_path(const std::filesystem::path& filename) noexcept;
std::string escape(std::string_view str) noexcept;
Rml::Element* append(Rml::Element* parent, const Rml::String& tag) noexcept;
NavCommand map_nav_event(const Rml::Event& event) noexcept;
Insets safe_area_insets(Rml::Context* context) noexcept;
void push_toast(Toast toast) noexcept;
std::deque<Toast>& get_toasts() noexcept;
} // namespace dusk::ui
+66 -8
View File
@@ -39,11 +39,24 @@ const Rml::String kDocumentSource = R"RML(
</rml>
)RML";
const Rml::String kDocumentSourceSmall = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/window.rcss" />
</head>
<body>
<window id="window" class="small">
<div id="dialog"/>
</window>
</body>
</rml>
)RML";
} // namespace
Window::Window() : Document(kDocumentSource), mRoot(mDocument->GetElementById("window")) {
mTabBar = std::make_unique<TabBar>(mRoot, TabBar::Props{
.onClose = [this] { pop(); },
.onClose = [this] { request_close(); },
.selectedTabIndex = 0,
.autoSelect = true,
});
@@ -68,7 +81,9 @@ Window::Window() : Document(kDocumentSource), mRoot(mDocument->GetElementById("w
// Hide document after transition completion
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") && Document::visible()) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
@@ -91,6 +106,10 @@ Window::Window() : Document(kDocumentSource), mRoot(mDocument->GetElementById("w
void Window::show() {
Document::show();
mRoot->SetAttribute("open", "");
if (mInitialOpen) {
mDoAud_seStartMenu(kSoundWindowOpen);
mInitialOpen = false;
}
}
void Window::hide(bool close) {
@@ -139,6 +158,17 @@ bool Window::set_active_tab(int index) {
return mTabBar->set_active_tab(index);
}
void Window::request_close() {
if (!consume_close_request()) {
mDoAud_seStartMenu(kSoundWindowClose);
pop();
}
}
bool Window::consume_close_request() {
return false;
}
void Window::refresh_active_tab() {
mTabBar->refresh_active_tab();
}
@@ -176,25 +206,24 @@ bool Window::handle_nav_command(Rml::Event& event, NavCommand cmd) {
}
if (cmd == NavCommand::Confirm || cmd == NavCommand::Down) {
if (!mContentComponents.empty() && mContentComponents.front()->focus()) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
mDoAud_seStartMenu(kSoundItemFocus);
return true;
}
}
if (cmd == NavCommand::Cancel) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_CANCEL);
pop();
request_close();
return true;
}
if (mTabBar->handle_nav_command(event, cmd)) {
return true;
}
return Document::handle_nav_command(event, cmd);
return mSuppressNavFallback ? false : Document::handle_nav_command(event, cmd);
}
bool Window::handle_content_nav(Rml::Event& event, NavCommand cmd) noexcept {
if (cmd == NavCommand::Up) {
if (focus()) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
mDoAud_seStartMenu(kSoundItemFocus);
return true;
}
return false;
@@ -250,4 +279,33 @@ bool Window::handle_content_nav(Rml::Event& event, NavCommand cmd) noexcept {
return false;
}
} // namespace dusk::ui
WindowSmall::WindowSmall(const Rml::String& windowClass, const Rml::String& dialogClass)
: Document(kDocumentSourceSmall), mRoot(mDocument->GetElementById("window")),
mDialog(mDocument->GetElementById("dialog")) {
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
mRoot->SetClass(windowClass, true);
mDialog->SetClass(dialogClass, true);
}
void WindowSmall::show() {
Document::show();
mRoot->SetAttribute("open", "");
}
void WindowSmall::hide(bool close) {
mRoot->RemoveAttribute("open");
mPendingClose = close;
}
bool WindowSmall::visible() const {
return mRoot->HasAttribute("open");
}
} // namespace dusk::ui
+18
View File
@@ -31,12 +31,15 @@ public:
bool set_active_tab(int index);
protected:
void request_close();
virtual bool consume_close_request();
void add_tab(const Rml::String& title, TabBuilder builder);
void refresh_active_tab();
void update_safe_area() noexcept;
void clear_content() noexcept;
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
bool handle_content_nav(Rml::Event& event, NavCommand cmd) noexcept;
bool mSuppressNavFallback = false;
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T> T& add_child(Args&&... args) {
@@ -51,6 +54,21 @@ protected:
std::unique_ptr<TabBar> mTabBar;
std::vector<std::unique_ptr<Component> > mContentComponents;
Insets mBodyPadding;
bool mInitialOpen = true;
};
// Shared shell for small-style windows such as Modal and PresetWindow
class WindowSmall : public Document {
public:
WindowSmall(const Rml::String& windowClass, const Rml::String& dialogClass);
void show() override;
void hide(bool close) override;
bool visible() const override;
protected:
Rml::Element* mRoot = nullptr;
Rml::Element* mDialog = nullptr;
};
} // namespace dusk::ui
+4
View File
@@ -1237,8 +1237,12 @@ static void trimming(view_class* param_0, view_port_class* param_1) {
GXEnd();
}
#ifndef TARGET_PC
// due to rounding, the scaled scissor region doesn't align with the untrimmed area
// this creates a gap when drawing the flipped image for mirror mode
GXSetScissor(param_1->scissor.x_orig, param_1->scissor.y_orig, param_1->scissor.width,
param_1->scissor.height);
#endif
}
#if !PLATFORM_WII && !TARGET_PC
+8
View File
@@ -31,6 +31,7 @@
#if TARGET_PC
#include <assert.h>
#include "dusk/ui/ui.hpp"
#endif
#if !PLATFORM_GCN
@@ -1009,7 +1010,14 @@ int mDoMch_Create() {
JKRAram::setSZSBufferSize(0x2000);
mDoDvdThd::create(OSGetThreadPriority(OSGetCurrentThread()) - 2);
mDoDvdErr_ThdInit();
#if TARGET_PC
if (!dusk::ui::is_prelaunch_open()) {
mDoMemCd_ThdInit();
}
#else
mDoMemCd_ThdInit();
#endif
return 1;
}
+49 -18
View File
@@ -46,6 +46,7 @@
#include <system_error>
#include <thread>
#include "SSystem/SComponent/c_API.h"
#include "dusk/achievements.h"
#include "dusk/app_info.hpp"
#include "dusk/crash_reporting.h"
#include "dusk/dusk.h"
@@ -54,9 +55,11 @@
#include "dusk/gyro.h"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/imgui/ImGuiEngine.hpp"
#include "dusk/iso_validate.hpp"
#include "dusk/logging.h"
#include "dusk/main.h"
#include "dusk/ui/popup.hpp"
#include "dusk/ui/menu_bar.hpp"
#include "dusk/ui/overlay.hpp"
#include "dusk/ui/prelaunch.hpp"
#include "dusk/ui/preset.hpp"
#include "dusk/ui/ui.hpp"
@@ -109,9 +112,15 @@ bool dusk::IsRunning = true;
bool dusk::IsShuttingDown = false;
bool dusk::IsGameLaunched = false;
bool dusk::IsFocusPaused = false;
bool dusk::RestartRequested = false;
std::filesystem::path dusk::ConfigPath;
#endif
void dusk::RequestRestart() noexcept {
RestartRequested = SupportsProcessRestart;
IsRunning = false;
}
s32 LOAD_COPYDATE(void*) {
char buffer[32];
memset(buffer, 0, sizeof(buffer));
@@ -280,6 +289,7 @@ void main01(void) {
dusk::gyro::read(pacing.sim_pace);
fapGm_Execute();
mDoAud_Execute();
dusk::AchievementSystem::get().tick();
dusk::game_clock::commit_sim_tick();
}
}
@@ -489,7 +499,7 @@ u8 OSGetLanguage() {
}
static void LanguageInit() {
// Keep language at 0 (English) if not on a PAL disk.
// Keep language at 0 (English) if not on a PAL disc.
// Doubt this matters, but avoid funky shit.
if (!dusk::version::isRegionPal()) {
return;
@@ -607,34 +617,51 @@ int game_main(int argc, char* argv[]) {
dusk::audio::EnableHrtf = dusk::getSettings().audio.enableHrtf;
dusk::ui::initialize();
dusk::ui::push_document(std::make_unique<dusk::ui::Overlay>(), true, true);
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), false);
// Invalidate a bad saved isoPath so that Dusk can't get blocked from starting up
const std::string p = dusk::getSettings().backend.isoPath;
if (!p.empty() && dusk::iso::validate(p.c_str()) != dusk::iso::ValidationError::Success) {
dusk::getSettings().backend.isoPath.setValue("");
}
std::string dvd_path;
bool dvd_opened = false;
if (parsed_arg_options.count("dvd")) {
dvd_path = parsed_arg_options["dvd"].as<std::string>();
DuskLog.info("Loading DVD image from command line: {}", dvd_path);
dvd_opened = aurora_dvd_open(dvd_path.c_str());
if (!dvd_opened) {
DuskLog.warn("Failed to open DVD image from command line: {}, opening prelaunch UI", dvd_path);
if (dusk::iso::validate(dvd_path.c_str()) == dusk::iso::ValidationError::Success) {
DuskLog.info("Loading DVD image from command line: {}", dvd_path);
dvd_opened = aurora_dvd_open(dvd_path.c_str());
if (!dvd_opened) {
DuskLog.warn("Failed to open DVD image from command line: {}, opening prelaunch UI", dvd_path);
} else {
dusk::getSettings().backend.isoPath.setValue(dvd_path);
dusk::config::Save();
dusk::IsGameLaunched = true;
}
} else {
dusk::getSettings().backend.isoPath.setValue(dvd_path);
dusk::config::Save();
dusk::IsGameLaunched = true;
DuskLog.warn("DVD image from command line failed verification: {}, opening prelaunch UI", dvd_path);
}
}
if (!dvd_opened) {
dusk::ui::push_document(std::make_unique<dusk::ui::Prelaunch>(), true);
if (!dusk::getSettings().backend.skipPreLaunchUI) {
dusk::ui::push_document(std::make_unique<dusk::ui::Prelaunch>(), true);
// pre game launch ui main loop
if (!launchUILoop()) {
dusk::ShutdownCrashReporting();
// pre game launch ui main loop
if (!launchUILoop()) {
dusk::ShutdownCrashReporting();
dusk::ShutdownFileLogging();
fflush(stdout);
fflush(stderr);
#ifdef DUSK_DISCORD
dusk::discord::shutdown();
dusk::discord::shutdown();
#endif
dusk::ui::shutdown();
aurora_shutdown();
return 0;
dusk::ui::shutdown();
aurora_shutdown();
return 0;
}
}
dvd_path = dusk::getSettings().backend.isoPath;
@@ -642,13 +669,17 @@ int game_main(int argc, char* argv[]) {
if (dvd_path.empty()) {
DuskLog.fatal("No DVD image specified, unable to boot!");
}
if (dusk::iso::validate(dvd_path.c_str()) != dusk::iso::ValidationError::Success) {
DuskLog.fatal("DVD image failed verification: {}", dvd_path);
}
DuskLog.info("Loading DVD image: {}", dvd_path);
if (!aurora_dvd_open(dvd_path.c_str())) {
DuskLog.fatal("Failed to open DVD image: {}", dvd_path);
}
dusk::IsGameLaunched = true;
}
dusk::ui::push_document(std::make_unique<dusk::ui::Popup>(), false);
if (!dusk::getSettings().backend.wasPresetChosen) {
dusk::ui::push_document(std::make_unique<dusk::ui::PresetWindow>());
}