diff --git a/.gitignore b/.gitignore
index 43b2e4a1c3..c43499954e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# IDE folders
.idea/
.vs/
+.zed/
# Caches
__pycache__
diff --git a/docs/building.md b/docs/building.md
index 1001c42788..9f7879ab48 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -1,50 +1,164 @@
-### Building
-#### Prerequisites
+# Building Dusklight
+
+## Dependencies
+
+The following dependencies are required:
+
* [CMake 3.25+](https://cmake.org)
- * Windows: Install `CMake Tools` in Visual Studio
- * macOS: `brew install cmake`
* [Python 3+](https://python.org)
- * Windows: [Microsoft Store](https://go.microsoft.com/fwlink?linkID=2082640)
- * Verify it's added to `%PATH%` by typing `python` in `cmd`.
- * macOS: `brew install python@3`
-* **[Windows]** [Visual Studio 2026 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx)
- * Select `C++ Development` and verify the following packages are included:
- * `Windows 11 SDK`
- * `CMake Tools`
- * `C++ Clang Compiler`
- * `C++ Clang-cl`
-* **[macOS]** [Xcode 16.4+](https://developer.apple.com/xcode/download/)
-* **[Linux]** Actively tested on Ubuntu 24.04, Arch Linux & derivatives.
- * Ubuntu 24.04+ packages
- ```
- build-essential curl git ninja-build clang lld zlib1g-dev libcurl4-openssl-dev \
- libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev libpulse-dev \
- libudev-dev libpng-dev libncurses5-dev cmake libx11-xcb-dev python3 python-is-python3 \
- libclang-dev libfreetype-dev libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev \
- libxss-dev libxtst-dev
- ```
- * Arch Linux packages
- ```
- base-devel cmake ninja llvm vulkan-headers python python-markupsafe clang lld alsa-lib libpulse libxrandr freetype2
- ```
- * Fedora packages
- ```
- cmake vulkan-headers ninja-build clang-devel llvm-devel libpng-devel
- ```
- * It's also important that you install the developer tools and libraries
- ```
- sudo dnf groupinstall "Development Tools" "Development Libraries"
- ```
-#### Setup
-Clone and initialize the Dusklight repository
+
+### Windows
+
+* Install [CMake 3.25+](https://cmake.org) by searching `CMake Tools` in Visual Studio
+* Install Python 3 from the [Microsoft Store](https://go.microsoft.com/fwlink?linkID=2082640) and verify it's added to `%PATH%` by typing `python` in `cmd`.
+
+Recommended IDEs:
+
+* [Visual Studio 2026 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx). During installation:
+ * Select `C++ Development` and verify the following packages are included:
+ * `Windows 11 SDK`
+ * `CMake Tools`
+ * `C++ Clang Compiler`
+ * `C++ Clang-cl`
+
+### macOS
+
+* Make sure [Homebrew](https://brew.sh) is installed
+* Install [CMake 3.25+](https://cmake.org)
+
```sh
-git clone --recursive https://github.com/TwilitRealm/dusklight.git
-cd dusklight
-git pull
-git submodule update --init --recursive
+brew install cmake
```
-#### Building
+* Install Python 3
+
+```sh
+brew install python@3
+```
+
+Recommended IDEs:
+
+* [Xcode 16.4 or later](https://developer.apple.com/xcode/)
+* [Visual Studio Code](https://code.visualstudio.com/download/)
+* [CLion](https://www.jetbrains.com/clion/)
+
+### Linux
+
+Actively tested on Ubuntu 24.04, Arch Linux & derivatives.
+
+**Ubuntu 24.04+ packages**
+
+
+Click to expand
+
+* Run the following command to install the required dependencies:
+
+```sh
+sudo apt update && sudo apt install -y \
+ build-essential \
+ clang \
+ cmake \
+ curl \
+ git \
+ libasound2-dev \
+ libclang-dev \
+ libcurl4-openssl-dev \
+ libdbus-1-dev \
+ libfreetype-dev \
+ libglu1-mesa-dev \
+ libgtk-3-dev \
+ libncurses5-dev \
+ libpng-dev \
+ libpulse-dev \
+ libudev-dev \
+ libvulkan-dev \
+ libx11-xcb-dev \
+ libxcursor-dev \
+ libxi-dev \
+ libxinerama-dev \
+ libxrandr-dev \
+ libxss-dev \
+ libxtst-dev \
+ lld \
+ ninja-build \
+ python-is-python3 \
+ python3 \
+ python3-markupsafe \
+ zlib1g-dev
+```
+
+
+
+
+**Arch Linux packages**
+
+
+Click to expand
+
+* Run the following command to install the required dependencies:
+
+```sh
+sudo pacman -S --needed \
+ alsa-lib \
+ base-devel \
+ clang \
+ cmake \
+ freetype2 \
+ libpulse \
+ libxrandr \
+ lld \
+ llvm \
+ ninja \
+ python \
+ python-markupsafe \
+ vulkan-headers
+```
+
+
+
+
+**Fedora packages**
+
+
+Click to expand
+
+* Run the following command to install the required dependencies:
+
+```sh
+sudo dnf install -y \
+ clang-devel \
+ cmake \
+ libpng-devel \
+ llvm-devel \
+ ninja-build \
+ vulkan-headers
+```
+
+* It's also important that you install the developer tools and libraries
+
+```sh
+sudo dnf groupinstall \
+ "Development Libraries" "Development Tools"
+```
+
+
+
+
+Recommended IDEs:
+
+* [CLion](https://www.jetbrains.com/clion/)
+* [Visual Studio Code](https://code.visualstudio.com/download/)
+
+## Building
+
+* Clone and initialize the Dusklight repository:
+
+```sh
+git clone --recursive https://github.com/TwilitRealm/dusklight.git
+git pull
+cd dusklight
+git submodule update --init --recursive
+```
**CLion (Windows / macOS / Linux)**
@@ -64,7 +178,8 @@ cmake --build --preset macos-default-relwithdebinfo
```
Alternate presets available:
-- `macos-default-debug`: Clang, Debug
+
+* `macos-default-debug`: Clang, Debug
**ninja (Linux)**
@@ -74,9 +189,10 @@ cmake --build --preset linux-default-relwithdebinfo
```
Alternate presets available:
-- `linux-default-debug`: GCC, Debug
-- `linux-clang-relwithdebinfo`: Clang, RelWithDebInfo
-- `linux-clang-debug`: Clang, Debug
+
+* `linux-default-debug`: GCC, Debug
+* `linux-clang-relwithdebinfo`: Clang, RelWithDebInfo
+* `linux-clang-debug`: Clang, Debug
**ninja (Windows)**
@@ -86,13 +202,27 @@ cmake --build --preset windows-msvc-relwithdebinfo
```
Alternate presets available:
-- `windows-msvc-debug`: MSVC, Debug
-- `windows-clang-relwithdebinfo`: Clang-cl, RelWithDebInfo
-- `windows-clang-debug`: Clang-cl, Debug
-#### Running
-Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
+* `windows-msvc-debug`: MSVC, Debug
+* `windows-clang-relwithdebinfo`: Clang-cl, RelWithDebInfo
+* `windows-clang-debug`: Clang-cl, Debug
+
+## Running
+
+**Windows / Linux**
+
+* Pass the disc image as a positional argument using the `--dvd` flag. Supported formats are: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
+
```sh
-build/{preset}/dusklight/path/to/game.rvz
+build/{preset}/dusklight --dvd /path/to/game.iso
+```
+
+**macOS**
+
+macOS builds an `.app` bundle which contains the executable and all necessary resources.
+
+* Pass the disc image as a positional argument using the `--dvd` flag. Supported formats are: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ
+
+```sh
+build/{preset}/Dusklight.app/Contents/MacOS/Dusklight --dvd /path/to/game.iso
```
-If no path is specified, Dusklight defaults to `game.iso` in the current working directory.
diff --git a/extern/aurora b/extern/aurora
index 40913d532e..f93b9e5bc2 160000
--- a/extern/aurora
+++ b/extern/aurora
@@ -1 +1 @@
-Subproject commit 40913d532e5859a68e56b88d8aaec6bff1b88a2e
+Subproject commit f93b9e5bc20850198538ccd3abc69ab2b128ecf7
diff --git a/flake.nix b/flake.nix
index edba703a3d..29c99b10a5 100644
--- a/flake.nix
+++ b/flake.nix
@@ -15,12 +15,6 @@
# Dependencies that are not packaged in nixpkgs (used by the Linux package build):
buildSources = pkgs: {
- aurora-src = pkgs.fetchFromGitHub {
- owner = "encounter";
- repo = "aurora";
- rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
- hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
- };
dawn-src = pkgs.fetchzip {
url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
@@ -59,9 +53,6 @@
name = "dusklight";
src = ./.;
postUnpack = ''
- mkdir -p $sourceRoot/extern/aurora
- cp -r ${srcs.aurora-src}/. $sourceRoot/extern/aurora/
- chmod -R u+w $sourceRoot/extern/aurora
sed -i '/add_subdirectory(tests)/d' $sourceRoot/extern/aurora/CMakeLists.txt
'';
# Remove last line to re-enable tests
@@ -225,4 +216,4 @@
default = mkDevShell (pkgsFor system);
});
};
-}
\ No newline at end of file
+}
diff --git a/include/dusk/settings.h b/include/dusk/settings.h
index bf8fbe5d08..0a9d392d7c 100644
--- a/include/dusk/settings.h
+++ b/include/dusk/settings.h
@@ -72,6 +72,10 @@ struct UserSettings {
ConfigVar lockAspectRatio;
ConfigVar enableFpsOverlay;
ConfigVar fpsOverlayCorner;
+ ConfigVar windowPositionX;
+ ConfigVar windowPositionY;
+ ConfigVar windowWidth;
+ ConfigVar windowHeight;
} video;
struct {
@@ -123,6 +127,7 @@ struct UserSettings {
ConfigVar bloomMode;
ConfigVar bloomMultiplier;
ConfigVar disableWaterRefraction;
+ ConfigVar enableTextureReplacements;
ConfigVar enableFrameInterpolation;
ConfigVar internalResolutionScale;
ConfigVar shadowResolutionMultiplier;
diff --git a/src/d/d_meter_button.cpp b/src/d/d_meter_button.cpp
index e55b42bc1a..b39a234056 100644
--- a/src/d/d_meter_button.cpp
+++ b/src/d/d_meter_button.cpp
@@ -18,6 +18,9 @@
#include "d/d_pane_class.h"
#include "dusk/frame_interpolation.h"
#include
+#if TARGET_PC
+#include "dusk/string.hpp"
+#endif
#if VERSION == VERSION_GCN_JPN
#define STR_BUF_LEN 528
@@ -2927,6 +2930,12 @@ bool dMeterButton_c::isClose() {
}
void dMeterButton_c::setString(char* i_string, u8 i_button, u8 param_2, u8 param_3) {
+#if TARGET_PC
+ char* i_string_full = i_string;
+ char i_string_buf[sizeof(mButtonText[0])];
+ dusk::SafeStringCopyTruncate(i_string_buf, i_string);
+ i_string = i_string_buf;
+#endif
if (strcmp(mButtonText[param_2], i_string) != 0 || field_0x4be[param_2] != i_button) {
if (param_2 == 0 && strcmp(mButtonText[1], i_string) == 0 &&
((i_button == BUTTON_A_e && field_0x4be[1] == BUTTON_A_e) ||
@@ -3022,6 +3031,10 @@ void dMeterButton_c::setString(char* i_string, u8 i_button, u8 param_2, u8 param
strcpy(mButtonText[param_2], i_string);
+#if TARGET_PC
+ i_string = i_string_full;
+#endif
+
if (param_2 == 0) {
if (param_3 != 0) {
field_0x4d9 = param_2;
diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp
index c6a92f3b75..9b247996e6 100644
--- a/src/dusk/settings.cpp
+++ b/src/dusk/settings.cpp
@@ -1,5 +1,8 @@
#include "dusk/settings.h"
#include "dusk/config.hpp"
+#include "dusk/dusk.h"
+
+#include
namespace dusk {
@@ -10,6 +13,10 @@ UserSettings g_userSettings = {
.lockAspectRatio {"video.lockAspectRatio", false},
.enableFpsOverlay {"game.enableFpsOverlay", false},
.fpsOverlayCorner {"game.fpsOverlayCorner", 0},
+ .windowPositionX {"video.windowPositionX", SDL_WINDOWPOS_UNDEFINED},
+ .windowPositionY {"video.windowPositionY", SDL_WINDOWPOS_UNDEFINED},
+ .windowWidth {"video.windowWidth", defaultWindowWidth * 2},
+ .windowHeight {"video.windowHeight", defaultWindowHeight * 2},
},
.audio = {
@@ -58,6 +65,7 @@ UserSettings g_userSettings = {
.bloomMode {"game.bloomMode", BloomMode::Dusk},
.bloomMultiplier {"game.bloomMultiplier", 1.0f},
.disableWaterRefraction {"game.disableWaterRefraction", false},
+ .enableTextureReplacements {"game.enableTextureReplacements", true},
.enableFrameInterpolation {"game.enableFrameInterpolation", false},
.internalResolutionScale {"game.internalResolutionScale", 0},
.shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1},
@@ -178,6 +186,10 @@ void registerSettings() {
Register(g_userSettings.video.lockAspectRatio);
Register(g_userSettings.video.enableFpsOverlay);
Register(g_userSettings.video.fpsOverlayCorner);
+ Register(g_userSettings.video.windowPositionX);
+ Register(g_userSettings.video.windowPositionY);
+ Register(g_userSettings.video.windowWidth);
+ Register(g_userSettings.video.windowHeight);
// Audio
Register(g_userSettings.audio.masterVolume);
@@ -219,6 +231,7 @@ void registerSettings() {
Register(g_userSettings.game.bloomMode);
Register(g_userSettings.game.bloomMultiplier);
Register(g_userSettings.game.disableWaterRefraction);
+ Register(g_userSettings.game.enableTextureReplacements);
Register(g_userSettings.game.internalResolutionScale);
Register(g_userSettings.game.shadowResolutionMultiplier);
Register(g_userSettings.game.enableDepthOfField);
diff --git a/src/dusk/ui/controller_config.cpp b/src/dusk/ui/controller_config.cpp
index aef911f996..a0b2b6baa9 100644
--- a/src/dusk/ui/controller_config.cpp
+++ b/src/dusk/ui/controller_config.cpp
@@ -37,7 +37,7 @@ Rml::String current_controller_name(int port) {
Rml::String controller_index_name(u32 index) {
const char* name = PADGetNameForControllerIndex(index);
if (name == nullptr) {
- return fmt::format("Controller {}", index + 1);
+ return fmt::format("Device {}", index + 1);
}
return name;
}
@@ -124,7 +124,7 @@ Rml::String native_axis_name(const PADAxisMapping& mapping, SDL_Gamepad* gamepad
return native_button_name(gamepad, static_cast(mapping.nativeButton));
}
- return "Not bound";
+ return "Not Bound";
}
bool is_dpad_button(PADButton button) {
@@ -162,7 +162,7 @@ bool keyboard_escape_pressed() {
Rml::String keyboard_key_name(s32 scancode) {
if (scancode == PAD_KEY_INVALID) {
- return "Not bound";
+ return "Not Bound";
}
switch (scancode) {
case PAD_KEY_MOUSE_LEFT:
@@ -303,7 +303,7 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
});
};
- addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); }, [] { return false; });
+ addPageButton(Page::Controller, "Device", [port] { return current_controller_name(port); }, [] { return false; });
addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); }, [] { return false; });
@@ -349,7 +349,14 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
rightPane, [](Pane& pane) {
pane.add_text("Treat analog trigger movement as digital L and R button input.");
});
-
+ leftPane.register_control(leftPane.add_button("Restore Default Controls").on_pressed([this, port] {
+ mDoAud_seStartMenu(kSoundClick);
+ PADRestoreDefaultMapping(port);
+ }),
+ rightPane, [](Pane& pane) {
+ pane.clear();
+ pane.add_text("Restores all binding configurations for the currently selected device to their defaults.");
+ });
render_page(rightPane, port, mPage);
}
@@ -365,7 +372,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
[port] { return PADGetIndexForPort(port) < 0 && !keyboard_active(port); },
})
.on_pressed([this, port] {
- mDoAud_seStartMenu(kSoundItemChange);
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
PADClearPort(port);
PADSetKeyboardActive(static_cast(port), FALSE);
@@ -378,7 +385,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
.isSelected = [port] { return keyboard_active(port); },
})
.on_pressed([this, port] {
- mDoAud_seStartMenu(kSoundItemChange);
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
PADClearPort(port);
PADSetKeyboardActive(static_cast(port), TRUE);
@@ -388,7 +395,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
const u32 controllerCount = PADCount();
if (controllerCount == 0) {
- pane.add_text("No controllers detected");
+ pane.add_text("No Device Detected");
break;
}
@@ -400,7 +407,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
[port, i] { return PADGetIndexForPort(port) == static_cast(i); },
})
.on_pressed([this, port, i] {
- mDoAud_seStartMenu(kSoundItemChange);
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
PADSetKeyboardActive(static_cast(port), FALSE);
PADSetPortForIndex(i, port);
@@ -425,17 +432,18 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADKeyButtonBinding* bindings =
PADGetKeyButtonBindings(static_cast(port), &count);
if (bindings == nullptr) {
- return Rml::String("Not bound");
+ return Rml::String("Not Bound");
}
for (u32 i = 0; i < PAD_BUTTON_COUNT; ++i) {
if (bindings[i].padButton == button) {
return keyboard_key_name(bindings[i].scancode);
}
}
- return Rml::String("Not bound");
+ return Rml::String("Not Bound");
},
})
.on_pressed([this, port, button] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -462,7 +470,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
u32 buttonCount = 0;
PADButtonMapping* mappings = PADGetButtonMappings(port, &buttonCount);
if (mappings == nullptr) {
- pane.add_text("No controller selected");
+ pane.add_text("No Device Selected");
break;
}
@@ -486,6 +494,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
},
})
.on_pressed([this, port, &mapping] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -512,6 +521,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
},
})
.on_pressed([this, port, &mapping] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -535,17 +545,18 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADKeyButtonBinding* bindings =
PADGetKeyButtonBindings(static_cast(port), &count);
if (bindings == nullptr) {
- return Rml::String("Not bound");
+ return Rml::String("Not Bound");
}
for (u32 i = 0; i < PAD_BUTTON_COUNT; ++i) {
if (bindings[i].padButton == button) {
return keyboard_key_name(bindings[i].scancode);
}
}
- return Rml::String("Not bound");
+ return Rml::String("Not Bound");
},
})
.on_pressed([this, port, button] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -566,17 +577,18 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADKeyAxisBinding* bindings =
PADGetKeyAxisBindings(static_cast(port), &count);
if (bindings == nullptr) {
- return Rml::String("Not bound");
+ return Rml::String("Not Bound");
}
for (u32 i = 0; i < PAD_AXIS_COUNT; ++i) {
if (bindings[i].padAxis == axis) {
return keyboard_key_name(bindings[i].scancode);
}
}
- return Rml::String("Not bound");
+ return Rml::String("Not Bound");
},
})
.on_pressed([this, port, axis] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -599,7 +611,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
u32 buttonCount = 0;
PADButtonMapping* buttons = PADGetButtonMappings(port, &buttonCount);
if (axes == nullptr && buttons == nullptr) {
- pane.add_text("No controller selected");
+ pane.add_text("No Device Selected");
break;
}
@@ -623,6 +635,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
},
})
.on_pressed([this, port, &mapping] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -631,30 +644,33 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
}
}
- pane.add_section("Digital");
- if (buttons != nullptr) {
- for (u32 i = 0; i < buttonCount; ++i) {
- PADButtonMapping& mapping = buttons[i];
- if (mapping.padButton != PAD_TRIGGER_L && mapping.padButton != PAD_TRIGGER_R) {
- continue;
+ if (getSettings().backend.enableAdvancedSettings) {
+ pane.add_section("Digital");
+ if (buttons != nullptr) {
+ for (u32 i = 0; i < buttonCount; ++i) {
+ PADButtonMapping& mapping = buttons[i];
+ if (mapping.padButton != PAD_TRIGGER_L && mapping.padButton != PAD_TRIGGER_R) {
+ continue;
+ }
+ pane.add_select_button({
+ .key = PADGetButtonName(mapping.padButton),
+ .getValue =
+ [this, &mapping, gamepad] {
+ if (mPendingButtonMapping == &mapping) {
+ return pending_button_label();
+ }
+ return native_button_name(
+ gamepad, mapping.nativeButton);
+ },
+ })
+ .on_pressed([this, port, &mapping] {
+ mDoAud_seStartMenu(kSoundClick);
+ cancel_pending_binding();
+ mPendingPort = port;
+ mPendingBindingArmed = false;
+ mPendingButtonMapping = &mapping;
+ });
}
- pane.add_select_button({
- .key = PADGetButtonName(mapping.padButton),
- .getValue =
- [this, &mapping, gamepad] {
- if (mPendingButtonMapping == &mapping) {
- return pending_button_label();
- }
- return native_button_name(
- gamepad, mapping.nativeButton);
- },
- })
- .on_pressed([this, port, &mapping] {
- cancel_pending_binding();
- mPendingPort = port;
- mPendingBindingArmed = false;
- mPendingButtonMapping = &mapping;
- });
}
}
@@ -706,17 +722,18 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADKeyAxisBinding* bindings =
PADGetKeyAxisBindings(static_cast(port), &count);
if (bindings == nullptr) {
- return Rml::String("Not bound");
+ return Rml::String("Not Bound");
}
for (u32 i = 0; i < PAD_AXIS_COUNT; ++i) {
if (bindings[i].padAxis == axis) {
return keyboard_key_name(bindings[i].scancode);
}
}
- return Rml::String("Not bound");
+ return Rml::String("Not Bound");
},
})
.on_pressed([this, port, axis] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -741,7 +758,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
if (axes == nullptr) {
- pane.add_text("No controller selected");
+ pane.add_text("No Device Selected");
break;
}
@@ -762,6 +779,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
},
})
.on_pressed([this, port, &mapping] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -907,6 +925,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
},
})
.on_pressed([this, port, actionBind] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -926,7 +945,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
u32 buttonCount = 0;
PADButtonMapping* mappings = PADGetButtonMappings(port, &buttonCount);
if (mappings == nullptr) {
- pane.add_text("No controller selected");
+ pane.add_text("No Device Selected");
break;
}
@@ -950,6 +969,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
},
})
.on_pressed([this, port, actionBind] {
+ mDoAud_seStartMenu(kSoundClick);
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
@@ -1013,6 +1033,12 @@ void ControllerConfigWindow::poll_pending_binding() {
const s32 nativeButton = PADGetNativeButtonPressed(mPendingPort);
if (nativeButton != -1) {
const int completedPort = mPendingPort;
+ if (mPendingButtonMapping->nativeButton == static_cast(nativeButton) &&
+ (mPendingButtonMapping->padButton != PAD_BUTTON_A &&
+ mPendingButtonMapping->padButton != PAD_BUTTON_B)) {
+ unmap_pending_binding();
+ return;
+ }
mPendingButtonMapping->nativeButton = static_cast(nativeButton);
finish_pending_binding(completedPort);
}
@@ -1023,6 +1049,10 @@ void ControllerConfigWindow::poll_pending_binding() {
const PADSignedNativeAxis nativeAxis = PADGetNativeAxisPulled(mPendingPort);
if (nativeAxis.nativeAxis != -1) {
const int completedPort = mPendingPort;
+ if (mPendingAxisMapping->nativeAxis.nativeAxis == nativeAxis.nativeAxis) {
+ unmap_pending_binding();
+ return;
+ }
mPendingAxisMapping->nativeAxis = nativeAxis;
mPendingAxisMapping->nativeButton = -1;
finish_pending_binding(completedPort);
@@ -1049,6 +1079,10 @@ void ControllerConfigWindow::poll_pending_binding() {
if (button != -1) {
const int completedPort = mPendingPort;
+ if (mPendingActionBinding->getValue() == button) {
+ unmap_pending_binding();
+ return;
+ }
mPendingActionBinding->setValue(button);
config::Save();
finish_pending_binding(completedPort);
@@ -1058,6 +1092,7 @@ void ControllerConfigWindow::poll_pending_binding() {
}
void ControllerConfigWindow::finish_pending_binding(int completedPort) {
+ mDoAud_seStartMenu(kSoundBindingChanged);
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingActionBinding = nullptr;
@@ -1110,11 +1145,11 @@ bool ControllerConfigWindow::pending_input_neutral() const {
}
Rml::String ControllerConfigWindow::pending_button_label() const {
- return mPendingBindingArmed ? "Press a button..." : "Waiting...";
+ return mPendingBindingArmed ? "Press a Key or Button..." : "Waiting...";
}
Rml::String ControllerConfigWindow::pending_axis_label() const {
- return mPendingBindingArmed ? "Move axis or press a button..." : "Waiting...";
+ return mPendingBindingArmed ? "Move Axis or press a Key or Button..." : "Waiting...";
}
void ControllerConfigWindow::cancel_pending_binding() {
@@ -1143,7 +1178,7 @@ void ControllerConfigWindow::finish_pending_key_binding() {
}
Rml::String ControllerConfigWindow::pending_key_label() const {
- return mPendingBindingArmed ? "Press a key or mouse button..." : "Waiting...";
+ return mPendingBindingArmed ? "Press a Key or Mouse Button..." : "Waiting...";
}
void ControllerConfigWindow::stop_rumble_test() {
@@ -1159,7 +1194,7 @@ void ControllerConfigWindow::stop_rumble_test() {
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
- return "Not bound";
+ return "Not Bound";
}
auto button = static_cast(buttonUntyped);
diff --git a/src/dusk/ui/graphics_tuner.cpp b/src/dusk/ui/graphics_tuner.cpp
index 440d9f312a..4b70bab592 100644
--- a/src/dusk/ui/graphics_tuner.cpp
+++ b/src/dusk/ui/graphics_tuner.cpp
@@ -211,7 +211,7 @@ GraphicsTuner::GraphicsTuner(GraphicsTunerProps props, bool prelaunch)
SteppedCarousel::Props{
.min = mValueMin,
.max = mValueMax,
- .step = 1,
+ .step = props.step,
.getValue = [this] { return get_value(mOption); },
.onChange = [this](int value) { set_value(mOption, value); },
.formatValue =
diff --git a/src/dusk/ui/graphics_tuner.hpp b/src/dusk/ui/graphics_tuner.hpp
index 254aada22b..36932bcbbb 100644
--- a/src/dusk/ui/graphics_tuner.hpp
+++ b/src/dusk/ui/graphics_tuner.hpp
@@ -55,6 +55,7 @@ struct GraphicsTunerProps {
int valueMin = 0;
int valueMax = 0;
int defaultValue = 0;
+ int step = 1;
};
class GraphicsTuner : public Document {
diff --git a/src/dusk/ui/input.cpp b/src/dusk/ui/input.cpp
index 02a15e9a0f..2628aeb682 100644
--- a/src/dusk/ui/input.cpp
+++ b/src/dusk/ui/input.cpp
@@ -454,10 +454,18 @@ bool touch_moved_too_far(
return delta.SquaredMagnitude() > threshold * threshold;
}
-void dispatch_menu_key(Rml::Context& context) noexcept {
+void emit_key_press(Rml::Context& context, Rml::Input::KeyIdentifier key) noexcept {
context.ProcessMouseLeave();
- context.ProcessKeyDown(Rml::Input::KI_F1, 0);
- context.ProcessKeyUp(Rml::Input::KI_F1, 0);
+ context.ProcessKeyDown(key, 0);
+}
+
+void emit_key_tap(Rml::Context& context, Rml::Input::KeyIdentifier key) noexcept {
+ emit_key_press(context, key);
+ context.ProcessKeyUp(key, 0);
+}
+
+void dispatch_menu_key(Rml::Context& context) noexcept {
+ emit_key_tap(context, Rml::Input::KI_F1);
}
bool handle_touch_menu_tap(Rml::Context& context, const SDL_Event& event) noexcept {
@@ -627,7 +635,9 @@ void process_axis_direction(
if (repeat->held) {
if (released) {
- if (!repeat->pending) {
+ if (repeat->pending) {
+ emit_key_tap(context, repeat->key);
+ } else {
context.ProcessKeyUp(repeat->key, 0);
}
set_pad_button_held(port, heldPadButton, false);
@@ -658,8 +668,7 @@ void process_axis_direction(
}
begin_gamepad_key(*repeat, key);
- context.ProcessMouseLeave();
- context.ProcessKeyDown(key, 0);
+ emit_key_press(context, key);
}
} // namespace
@@ -747,8 +756,7 @@ void handle_event(const SDL_Event& event) noexcept {
}
}
if (!deferred) {
- context->ProcessMouseLeave();
- context->ProcessKeyDown(key, 0);
+ emit_key_press(*context, key);
}
}
} else {
@@ -760,7 +768,9 @@ void handle_event(const SDL_Event& event) noexcept {
if (repeat != nullptr) {
*repeat = {};
}
- if (!wasPending) {
+ if (wasPending) {
+ emit_key_tap(*context, key);
+ } else {
context->ProcessKeyUp(key, 0);
}
}
@@ -787,8 +797,7 @@ void update_input() noexcept {
repeat.pressedAt = now;
repeat.nextRepeatAt =
repeat.repeatable ? now + kGamepadRepeatInitialDelay : 0.0;
- context->ProcessMouseLeave();
- context->ProcessKeyDown(repeat.key, 0);
+ emit_key_press(*context, repeat.key);
continue;
}
diff --git a/src/dusk/ui/overlay.cpp b/src/dusk/ui/overlay.cpp
index 2ec47e6215..69c262720e 100644
--- a/src/dusk/ui/overlay.cpp
+++ b/src/dusk/ui/overlay.cpp
@@ -103,13 +103,13 @@ Rml::Element* create_controller_warning(Rml::Element* parent) {
auto* heading = append(elem, "heading");
auto* title = append(heading, "span");
- title->SetInnerRML("No controller assigned");
+ title->SetInnerRML("No Device Assigned");
auto* icon = append(heading, "icon");
icon->SetClass("warning", true);
auto* message = append(elem, "message");
auto* content = append(message, "span");
- content->SetInnerRML("Configure controller port 1 in Settings.");
+ content->SetInnerRML("Configure Port 1 in Settings.");
return elem;
}
diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp
index f12cb11ade..79082f75ef 100644
--- a/src/dusk/ui/settings.cpp
+++ b/src/dusk/ui/settings.cpp
@@ -793,9 +793,16 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.valueMin = 0,
.valueMax = 100,
.defaultValue = 100,
+ .step = 10,
}, mPrelaunch);
leftPane.add_section("Rendering");
+ config_bool_select(leftPane, rightPane, getSettings().game.enableTextureReplacements,
+ {
+ .key = "Use Texture Pack",
+ .helpText = "Enable installed texture replacements.",
+ .onChange = [](bool value) { aurora_set_texture_replacements_enabled(value); },
+ });
config_bool_select(leftPane, rightPane, getSettings().game.enableFrameInterpolation,
{
.key = "Unlock Framerate",
@@ -829,18 +836,18 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
});
};
- leftPane.add_section("Controller");
- leftPane.register_control(leftPane.add_button("Configure Controller").on_pressed([this] {
+ leftPane.add_section("Inputs");
+ leftPane.register_control(leftPane.add_button("Configure Inputs").on_pressed([this] {
push(std::make_unique(mPrelaunch));
}),
rightPane, [](Pane& pane) {
pane.clear();
- pane.add_text("Open controller binding configuration.");
+ pane.add_text("Open input binding configuration.");
});
config_bool_select(leftPane, rightPane, getSettings().game.allowBackgroundInput,
{
- .key = "Allow Background Input",
- .helpText = "Allow controller input even when the game window is not focused.",
+ .key = "Allow Background Inputs",
+ .helpText = "Allow inputs even when the game window is not focused.",
.onChange = [](bool value) { aurora_set_background_input(value); },
});
@@ -1247,7 +1254,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
});
pane.add_button(
{
- .text = "Controller",
+ .text = "Missing Device",
.isSelected =
[] { return getSettings().game.enableControllerToasts.getValue(); },
})
diff --git a/src/dusk/ui/ui.cpp b/src/dusk/ui/ui.cpp
index fc2d5d6691..90c1453948 100644
--- a/src/dusk/ui/ui.cpp
+++ b/src/dusk/ui/ui.cpp
@@ -11,7 +11,9 @@
#include
#include "aurora/lib/window.hpp"
+#include "dusk/dusk.h"
#include "dusk/io.hpp"
+#include "dusk/config.hpp"
#include "input.hpp"
#include "prelaunch.hpp"
#include "window.hpp"
@@ -130,7 +132,7 @@ void handle_event(const SDL_Event& event) noexcept {
if (getSettings().game.enableControllerToasts) {
const char* name = SDL_GetGamepadName(gamepad);
Rml::String content = fmt::format("{}", name ? name : "[Unknown]");
- Rml::String title = "Controller connected";
+ Rml::String title = "Device Connected";
if (const char* icon = connection_state_icon(SDL_GetGamepadConnectionState(gamepad))) {
title = fmt::format(
"{} {};
", title,
@@ -163,12 +165,24 @@ void handle_event(const SDL_Event& event) noexcept {
const char* name = SDL_GetGamepadNameForID(event.gdevice.which);
push_toast({
.type = "controller",
- .title = "Controller disconnected",
+ .title = "Device Disconnected",
.content = name ? name : "[Unknown]",
.duration = std::chrono::seconds(4),
});
}
sConnectedGamepads.erase(event.gdevice.which);
+ } else if (event.type == SDL_EVENT_WINDOW_MOVED || event.type == SDL_EVENT_WINDOW_RESIZED) {
+ int x, y;
+ if (SDL_GetWindowPosition(aurora::window::get_sdl_window(), &x, &y)) {
+ getSettings().video.windowPositionX.setValue(x);
+ getSettings().video.windowPositionY.setValue(y);
+ }
+ int width, height;
+ if (SDL_GetWindowSize(aurora::window::get_sdl_window(), &width, &height)) {
+ getSettings().video.windowWidth.setValue(width);
+ getSettings().video.windowHeight.setValue(height);
+ }
+ config::Save();
}
input::handle_event(event);
}
diff --git a/src/dusk/ui/ui.hpp b/src/dusk/ui/ui.hpp
index 4a27ac7aac..cbfe3dcc9d 100644
--- a/src/dusk/ui/ui.hpp
+++ b/src/dusk/ui/ui.hpp
@@ -26,6 +26,8 @@ struct Toast {
constexpr u32 kSoundClick = Z2SE_SY_CURSOR_OK;
// "Play" button clicked/pressed
constexpr u32 kSoundPlay = Z2SE_SY_ITEM_COMBINE_ON;
+// Input binding changed
+constexpr u32 kSoundBindingChanged = Z2SE_SY_ITEM_SET_X;
// Menu button pressed (open/close menu bar or hide/show the active window)
constexpr u32 kSoundMenuOpen = Z2SE_SY_MENU_SUB_IN;
@@ -49,6 +51,8 @@ constexpr u32 kSoundItemDisable = Z2SE_SUBJ_VIEW_OUT;
// Achievement unlocked
constexpr u32 kSoundAchievementUnlock = Z2SE_NAVI_FLY;
+// Warning prompt
+constexpr u32 kSoundWarning = Z2SE_SY_COW_GET_IN;
struct Insets {
float top = 0.0f;
diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp
index 36e0921b78..85252a68bf 100644
--- a/src/m_Do/m_Do_main.cpp
+++ b/src/m_Do/m_Do_main.cpp
@@ -567,10 +567,10 @@ int game_main(int argc, char* argv[]) {
config.cachePath = reinterpret_cast(cachePathString.c_str());
config.vsync = dusk::getSettings().video.enableVsync;
config.startFullscreen = dusk::getSettings().video.enableFullscreen;
- config.windowPosX = -1;
- config.windowPosY = -1;
- config.windowWidth = defaultWindowWidth * 2;
- config.windowHeight = defaultWindowHeight * 2;
+ config.windowPosX = dusk::getSettings().video.windowPositionX;
+ config.windowPosY = dusk::getSettings().video.windowPositionY;
+ config.windowWidth = dusk::getSettings().video.windowWidth;
+ config.windowHeight = dusk::getSettings().video.windowHeight;
config.desiredBackend = ResolveDesiredBackend(parsed_arg_options);
config.logCallback = &aurora_log_callback;
config.logLevel = startupLogLevel;
@@ -579,7 +579,7 @@ int game_main(int argc, char* argv[]) {
config.allowJoystickBackgroundEvents = dusk::getSettings().game.allowBackgroundInput;
config.pauseOnFocusLost = dusk::getSettings().game.pauseOnFocusLost;
config.imGuiInitCallback = &aurora_imgui_init_callback;
- config.allowTextureReplacements = true;
+ config.allowTextureReplacements = dusk::getSettings().game.enableTextureReplacements;
config.allowTextureDumps = false;
auroraInfo = aurora_initialize(argc, argv, &config);
}