diff --git a/.clang-format b/.clang-format index 1428d3c71e..8ffd4ebe96 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ Language: Cpp Standard: C++03 AccessModifierOffset: -4 -AlignAfterOpenBracket: Align +AlignAfterOpenBracket: DontAlign AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignOperands: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 46f0b1a411..3e9c30fdad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL Linux) endif () set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE) set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE) +set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE) add_subdirectory(extern/aurora EXCLUDE_FROM_ALL) add_subdirectory(libs/freeverb) diff --git a/README.md b/README.md index a66a423c5e..85c899aeb0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![DuskLogo](res/logo-mascot.webp) +![DuskLogo](res/logo-mascot.png) - ### **[Official Website](https://twilitrealm.dev)** - ### **[Discord](https://discord.gg/QACynxeyna)** diff --git a/extern/aurora b/extern/aurora index 1bd972429e..1119435dd1 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 1bd972429ebba4e486012b1acd08d4d7580eb917 +Subproject commit 1119435dd1bbd28c43941f4b5f9b51e24a8a1146 diff --git a/files.cmake b/files.cmake index 6cec4672fb..cb2200d59e 100644 --- a/files.cmake +++ b/files.cmake @@ -1462,6 +1462,45 @@ set(DUSK_FILES src/dusk/imgui/ImGuiStateShare.cpp src/dusk/imgui/ImGuiAchievements.hpp src/dusk/imgui/ImGuiAchievements.cpp + src/dusk/ui/bool_button.cpp + src/dusk/ui/bool_button.hpp + src/dusk/ui/button.cpp + src/dusk/ui/button.hpp + src/dusk/ui/component.cpp + src/dusk/ui/component.hpp + src/dusk/ui/document.cpp + src/dusk/ui/document.hpp + src/dusk/ui/editor.cpp + src/dusk/ui/editor.hpp + src/dusk/ui/event.cpp + src/dusk/ui/event.hpp + src/dusk/ui/input.cpp + src/dusk/ui/input.hpp + src/dusk/ui/nav_types.hpp + src/dusk/ui/number_button.cpp + src/dusk/ui/number_button.hpp + src/dusk/ui/overlay.cpp + 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/prelaunch.cpp + src/dusk/ui/prelaunch.hpp + src/dusk/ui/prelaunch_options.cpp + src/dusk/ui/prelaunch_options.hpp + src/dusk/ui/select_button.cpp + src/dusk/ui/select_button.hpp + src/dusk/ui/settings.cpp + src/dusk/ui/settings.hpp + src/dusk/ui/string_button.cpp + src/dusk/ui/string_button.hpp + src/dusk/ui/tab_bar.cpp + src/dusk/ui/tab_bar.hpp + src/dusk/ui/ui.cpp + src/dusk/ui/ui.hpp + src/dusk/ui/window.cpp + src/dusk/ui/window.hpp src/dusk/achievements.cpp src/dusk/iso_validate.cpp src/dusk/livesplit.cpp diff --git a/res/AlegreyaSC-Bold.ttf b/res/AlegreyaSC-Bold.ttf new file mode 100644 index 0000000000..dc73beb916 Binary files /dev/null and b/res/AlegreyaSC-Bold.ttf differ diff --git a/res/AlegreyaSC-Regular.ttf b/res/AlegreyaSC-Regular.ttf new file mode 100644 index 0000000000..31cae6c8ad Binary files /dev/null and b/res/AlegreyaSC-Regular.ttf differ diff --git a/res/FiraSans-Regular.ttf b/res/FiraSans-Regular.ttf new file mode 100644 index 0000000000..6f80647494 Binary files /dev/null and b/res/FiraSans-Regular.ttf differ diff --git a/res/FiraSansCondensed-Bold.ttf b/res/FiraSansCondensed-Bold.ttf new file mode 100644 index 0000000000..ec7e841549 Binary files /dev/null and b/res/FiraSansCondensed-Bold.ttf differ diff --git a/res/FiraSansCondensed-Regular.ttf b/res/FiraSansCondensed-Regular.ttf new file mode 100644 index 0000000000..6e1a192127 Binary files /dev/null and b/res/FiraSansCondensed-Regular.ttf differ diff --git a/res/logo-mascot.png b/res/logo-mascot.png new file mode 100644 index 0000000000..9f9a5d1ace Binary files /dev/null and b/res/logo-mascot.png differ diff --git a/res/logo-mascot.webp b/res/logo-mascot.webp deleted file mode 100644 index c22f3bc90f..0000000000 Binary files a/res/logo-mascot.webp and /dev/null differ diff --git a/res/prelaunch-bg.png b/res/prelaunch-bg.png new file mode 100644 index 0000000000..045dcbe655 Binary files /dev/null and b/res/prelaunch-bg.png differ diff --git a/res/rml/overlay.rcss b/res/rml/overlay.rcss new file mode 100644 index 0000000000..a8323bb7b2 --- /dev/null +++ b/res/rml/overlay.rcss @@ -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; +} + +.overlay-root { + width: 100%; + min-height: 45%; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: stretch; + decorator: vertical-gradient(#00000000 #151610F2); + padding: 48dp 0 40dp 0; + 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: 0 32dp; +} + +@media (max-height: 800dp) { + .overlay-root { + min-height: 38%; + padding: 32dp 0 28dp 0; + } + + .overlay { + gap: 16dp; + padding: 0 24dp; + } +} + +.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; +} diff --git a/res/rml/popup.rcss b/res/rml/popup.rcss new file mode 100644 index 0000000000..9a6ddbad42 --- /dev/null +++ b/res/rml/popup.rcss @@ -0,0 +1,46 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + overflow: visible; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 18dp; + color: #E0DBC8; +} + +button { + cursor: pointer; + focus: auto; +} + +popup { + width: 100%; + display: flex; + align-items: stretch; + width: 100%; + height: 64dp; + background-color: rgba(21, 22, 16, 80%); + border-bottom: 2dp #92875B; + backdrop-filter: blur(5dp); + transform: translateY(-64dp); + transition: transform 0.2s cubic-in-out; +} + +popup[open] { + transform: translateY(0); +} + +popup tab-bar { + flex: 1 1 0; +} + +popup tab-bar tab { + opacity: 0.35; + color: #E0DBC8; +} diff --git a/res/rml/prelaunch.rcss b/res/rml/prelaunch.rcss new file mode 100644 index 0000000000..f846591497 --- /dev/null +++ b/res/rml/prelaunch.rcss @@ -0,0 +1,169 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + width: 100%; + height: 100%; + font-family: "Fira Sans"; + font-weight: normal; + font-size: 20dp; + color: #FFFFFF; + background-color: #000000; + decorator: image(../prelaunch-bg.png cover left center); +} + +.menu { + position: absolute; + left: 96dp; + top: 50%; + transform: translateY(-50%); + /* Scale based on a reference screen width, 428/1216 */ + width: 35.230264vw; + min-width: 428dp; + max-width: 856dp; + height: auto; + display: flex; + flex-direction: column; + gap: 48dp; +} + +.hero { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 8dp; +} + +.hero img { + width: 100%; +} + +.eyebrow { + font-family: "Alegreya SC"; + font-size: 32dp; +} + +@media (min-width: 1216dp) { + .eyebrow { + /* Same logic as .menu, 32/1216 */ + font-size: 2.631579vw; + } +} + +.eyebrow span { + font-weight: bold; +} + +#menu-list { + display: flex; + flex-direction: column; + gap: 12dp; +} + +#menu-list button { + width: 428dp; + height: 54dp; + padding: 8dp 16dp; + border-radius: 8dp; + text-transform: uppercase; + font-family: "Fira Sans Condensed"; + font-size: 32dp; + font-weight: normal; + cursor: pointer; + /* Define a fully transparent gradient as the default state, otherwise a white flash occurs */ + decorator: horizontal-gradient(#00000000 #00000000); +} + +#menu-list button[anim-done] { + transition: decorator color 0.1s linear-in-out; +} + +#menu-list button:hover, +#menu-list button:focus-visible { + color: black; + decorator: horizontal-gradient(#FEE685FF #FEE68500); +} + +.disk-status { + position: absolute; + left: 96dp; + bottom: 72dp; + display: flex; + flex-direction: column; + gap: 8dp; +} + +.version-info { + position: absolute; + right: 96dp; + bottom: 72dp; + display: flex; + flex-direction: column; + gap: 8dp; + text-align: right; +} + +.status, +.version { + font-size: 24dp; +} + +.status, +.update { + color: #D8F999; +} + +.status[bad] { + color: #FFC9C9; +} + +/* TODO: Hidden until an actual update checker is introduced */ +.update { + display: none; + font-size: 16dp; + font-weight: bold; + cursor: pointer; +} + +.detail, +.update span { + color: #A6A09B; +} + +/* Startup animation */ +.intro-item { + opacity: 0; + transform: translateY(10dp); + transition: opacity transform 0.3s 0.1s cubic-in-out; +} + +body.animate-in .intro-item { + opacity: 1; + transform: translateY(0dp); +} + +.delay-0 { + transition: opacity transform 0.3s 0.1s cubic-in-out; +} + +.delay-1 { + transition: opacity transform 0.3s 0.2s cubic-in-out; +} + +.delay-2 { + transition: opacity transform 0.3s 0.3s cubic-in-out; +} + +.delay-3 { + transition: opacity transform 0.3s 0.4s cubic-in-out; +} + +.delay-4 { + transition: opacity transform 0.3s 0.5s cubic-in-out; +} + +.delay-5 { + transition: opacity transform 0.3s 0.6s cubic-in-out; +} diff --git a/res/rml/tabbing.rcss b/res/rml/tabbing.rcss new file mode 100644 index 0000000000..e223a4cfad --- /dev/null +++ b/res/rml/tabbing.rcss @@ -0,0 +1,33 @@ +tab-bar { + display: flex; + min-width: 0; + overflow: auto hidden; + text-transform: uppercase; +} + +tab-bar tab { + flex: 0 0 auto; + padding: 0 24dp; + line-height: 64dp; + white-space: nowrap; + decorator: vertical-gradient(#c2a42d00 #c2a42d00); + transition: decorator 0.1s linear-in-out, opacity 0.1s linear-in-out; + cursor: pointer; +} + +tab-bar tab:selected { + opacity: 1; + border-bottom: 4dp #C2A42D; + font-effect: glow(0dp 4dp 0dp 4dp black); +} + +tab-bar tab:focus-visible, +tab-bar tab:hover { + opacity: 1; + font-effect: glow(0dp 4dp 0dp 4dp black); + decorator: vertical-gradient(#c2a42d00 #c2a42d26); +} + +tab-bar tab:active { + decorator: vertical-gradient(#c2a42d10 #c2a42d40); +} diff --git a/res/rml/window.rcss b/res/rml/window.rcss new file mode 100644 index 0000000000..e1569b3fed --- /dev/null +++ b/res/rml/window.rcss @@ -0,0 +1,238 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + width: 100%; + height: 100%; + padding: 64dp; + font-family: "Fira Sans"; + font-weight: normal; + font-style: normal; + font-size: 15dp; + color: #E0DBC8; +} + +window { + display: flex; + flex-flow: column; + height: 100%; + max-width: 1088dp; + max-height: 768dp; + margin: auto; + border-radius: 14dp; + overflow: hidden; + border: 2dp #92875B; + backdrop-filter: blur(5dp); + box-shadow: 0 0 25dp 5dp; + background-color: rgba(21, 22, 16, 90%); + filter: opacity(0); + transform: scale(0.9); + transform-origin: center; + transition: filter transform 0.2s cubic-in-out; +} + +window[open] { + filter: opacity(1); + transform: scale(1); +} + +@media (max-height: 640dp) { + body { + padding: 16dp; + } + window { + box-shadow: none; + } +} + +window tab-bar { + flex: 0 0 64dp; + height: 64dp; + background-color: rgba(217, 217, 217, 10%); + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 18dp; + border-bottom: 2dp #92875B; +} + +window tab-bar tab { + opacity: 0.25; +} + +window content { + display: flex; + flex: 1 1 0; + min-width: 0; + min-height: 0; + overflow: hidden; +} + +window content pane { + display: flex; + flex-flow: column; + flex: 1 1 0; + height: 100%; + min-width: 0; + min-height: 0; + padding: 24dp; + padding-bottom: 0dp; + gap: 8dp; + overflow: hidden auto; +} + +window content pane:not(:last-of-type) { + border-right: 1dp #92875B; +} + +window content pane > * { + flex: 0 0 auto; +} + +window content pane > spacer { + display: block; + /* Completes the 24dp bottom inset after the pane's 8dp gap. */ + flex: 0 0 16dp; + height: 16dp; + pointer-events: none; +} + +scrollbarvertical { + width: 8dp; + margin: 4dp 4dp 4dp 0; +} + +scrollbarvertical sliderarrowdec, +scrollbarvertical sliderarrowinc { + width: 0; + height: 0; +} + +scrollbarvertical slidertrack { + width: 8dp; +} + +scrollbarvertical sliderbar { + width: 8dp; + min-height: 24dp; + background-color: rgba(224, 219, 200, 45%); + border-radius: 2dp; + transition: background-color 0.2s cubic-in-out; +} + +scrollbarvertical sliderbar:hover, +scrollbarvertical sliderbar:active { + background-color: rgba(194, 164, 45, 80%); +} + +scrollbarhorizontal { + height: 0; +} + +scrollbarhorizontal sliderarrowdec, +scrollbarhorizontal sliderarrowinc { + width: 0; + height: 0; +} + +scrollbarhorizontal slidertrack, +scrollbarhorizontal sliderbar { + width: 0; + height: 0; +} + +.section-heading { + font-family: "Fira Sans Condensed"; + font-weight: bold; + text-transform: uppercase; + font-size: 22dp; + opacity: 0.25; +} + +.section-heading:not(:first-of-type) { + padding-top: 12dp; +} + +button { + text-align: center; + background-color: rgba(17, 16, 10, 20%); + opacity: 0.9; + padding: 8dp 16dp; + border-radius: 14dp; + box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp; + font-size: 20dp; + transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out; + cursor: pointer; + focus: auto; +} + +button:not(:disabled):hover, +button:not(:disabled):focus-visible { + background-color: rgba(204, 184, 119, 20%); + box-shadow: #C2A42D 0 0 0 2dp; +} + +button:not(:disabled):selected { + opacity: 1; + background-color: rgba(204, 184, 119, 40%); +} + +button:not(:disabled):active { + opacity: 1; + background-color: rgba(204, 184, 119, 40%); + box-shadow: #C2A42D 0 0 0 2dp; +} + +select-button { + display: flex; + align-items: center; + gap: 8dp; + background-color: rgba(17, 16, 10, 20%); + opacity: 0.9; + padding: 8dp 16dp; + border-radius: 14dp; + box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp; + transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out; + cursor: pointer; + focus: auto; +} + +select-button:not(:disabled):hover, +select-button:not(:disabled):focus-visible { + background-color: rgba(204, 184, 119, 20%); + box-shadow: #C2A42D 0 0 0 2dp; +} + +select-button:not(:disabled):selected { + opacity: 1; + background-color: rgba(204, 184, 119, 40%); +} + +select-button:not(:disabled):active { + opacity: 1; + background-color: rgba(204, 184, 119, 40%); + box-shadow: #C2A42D 0 0 0 2dp; +} + +select-button:disabled { + opacity: 0.35; + cursor: default; +} + +select-button key { + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 18dp; + text-transform: uppercase; + flex: 1 0 auto; +} + +select-button value { + margin-left: auto; + font-size: 20dp; +} + +select-button input { + text-align: right; + font-size: 20dp; +} diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 6bca83482e..80e0e97b16 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -324,18 +324,18 @@ namespace dusk { ImGuiMenuGame::ToggleFullscreen(); } - if (!dusk::IsGameLaunched) { - m_preLaunchWindow.draw(); - } + // if (!dusk::IsGameLaunched) { + // m_preLaunchWindow.draw(); + // } m_isHidden = !getSettings().backend.duskMenuOpen; - bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); - if (dusk::IsGameLaunched) { - const bool menuOpen = !m_isHidden; - if (getSettings().backend.duskMenuOpen != menuOpen) { - getSettings().backend.duskMenuOpen.setValue(menuOpen); - Save(); - } + if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) { + m_isHidden = !m_isHidden; + } + bool showMenu = !m_isHidden; + if (getSettings().backend.duskMenuOpen != showMenu) { + getSettings().backend.duskMenuOpen.setValue(showMenu); + Save(); } // The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg, diff --git a/src/dusk/iso_validate.cpp b/src/dusk/iso_validate.cpp index e83b3fd34e..a4e64c5b70 100644 --- a/src/dusk/iso_validate.cpp +++ b/src/dusk/iso_validate.cpp @@ -101,6 +101,10 @@ ValidationError validate(const char* path) { NodHandleWrapper disc; const auto sdlStream = SDL_IOFromFile(path, "rb"); + if (sdlStream == nullptr) { + return ValidationError::IOError; + } + const NodDiscStream nod_stream { .user_data = sdlStream, .read_at = StreamReadAt, diff --git a/src/dusk/ui/bool_button.cpp b/src/dusk/ui/bool_button.cpp new file mode 100644 index 0000000000..cc43bb8b65 --- /dev/null +++ b/src/dusk/ui/bool_button.cpp @@ -0,0 +1,29 @@ +#include "bool_button.hpp" + +namespace dusk::ui { + +BoolButton::BoolButton(Rml::Element* parent, Props props) + : BaseControlledSelectButton(parent, {std::move(props.key)}), + mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)), + mIsDisabled(std::move(props.isDisabled)) {} + +bool BoolButton::disabled() const { + if (mIsDisabled) { + return mIsDisabled(); + } + return BaseControlledSelectButton::disabled(); +} + +Rml::String BoolButton::format_value() { + return mGetValue() ? "On" : "Off"; +} + +bool BoolButton::handle_nav_command(NavCommand cmd) { + if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) { + mSetValue(!mGetValue()); + return true; + } + return false; +} + +} // namespace dusk::ui \ No newline at end of file diff --git a/src/dusk/ui/bool_button.hpp b/src/dusk/ui/bool_button.hpp new file mode 100644 index 0000000000..750c7e5cdb --- /dev/null +++ b/src/dusk/ui/bool_button.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "select_button.hpp" + +namespace dusk::ui { + +class BoolButton : public BaseControlledSelectButton { +public: + struct Props { + Rml::String key; + std::function getValue; + std::function setValue; + std::function isDisabled; + }; + + BoolButton(Rml::Element* parent, Props props); + + bool disabled() const override; + +protected: + Rml::String format_value() override; + bool handle_nav_command(NavCommand cmd) override; + +private: + std::function mGetValue; + std::function mSetValue; + std::function mIsDisabled; +}; + +} // namespace dusk::ui diff --git a/src/dusk/ui/button.cpp b/src/dusk/ui/button.cpp new file mode 100644 index 0000000000..a51918004e --- /dev/null +++ b/src/dusk/ui/button.cpp @@ -0,0 +1,64 @@ +#include "button.hpp" + +#include "ui.hpp" + +#include + +namespace dusk::ui { +namespace { + +Rml::Element* createRoot(Rml::Element* parent, const Rml::String& tagName) { + auto* doc = parent->GetOwnerDocument(); + auto elem = doc->CreateElement(tagName); + return parent->AppendChild(std::move(elem)); +} + +} // namespace + +Button::Button(Rml::Element* parent, Props props, const Rml::String& tagName) + : FluentComponent(createRoot(parent, tagName)) { + update_props(std::move(props)); +} + +void Button::set_text(const Rml::String& text) { + if (mProps.text != text) { + mRoot->SetInnerRML(escape(text)); + mProps.text = text; + } +} + +Button& Button::on_pressed(ButtonCallback callback) { + if (!callback) { + return *this; + } + // TODO: convert this to a FluentComponent method? + on_nav_command([callback = std::move(callback)](Rml::Event&, NavCommand cmd) { + if (cmd == NavCommand::Confirm) { + callback(); + return true; + } + return false; + }); + return *this; +} + +void Button::update_props(Props props) { + set_text(props.text); + mProps = std::move(props); +} + +void ControlledButton::update() { + if (mIsSelected) { + set_selected(mIsSelected()); + } + Button::update(); +} + +bool ControlledButton::selected() const { + if (mIsSelected) { + return mIsSelected(); + } + return Button::selected(); +} + +} // namespace dusk::ui \ No newline at end of file diff --git a/src/dusk/ui/button.hpp b/src/dusk/ui/button.hpp new file mode 100644 index 0000000000..43e349e3fa --- /dev/null +++ b/src/dusk/ui/button.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "component.hpp" + +namespace dusk::ui { + +using ButtonCallback = std::function; + +class Button : public FluentComponent