From 8aa08c94431e35d79f36509f607f7acc401a73e4 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 30 Apr 2026 20:55:14 -0600 Subject: [PATCH] Window animations & tags instead of classes --- extern/aurora | 2 +- res/rml/popup.rcss | 20 ++++----- res/rml/popup.rml | 9 ----- res/rml/window.rcss | 76 +++++++++++++++++++---------------- res/rml/window.rml | 9 ----- src/dusk/ui/button.cpp | 9 ++--- src/dusk/ui/button.hpp | 2 +- src/dusk/ui/document.cpp | 6 +-- src/dusk/ui/document.hpp | 2 +- src/dusk/ui/pane.cpp | 12 +++--- src/dusk/ui/pane.hpp | 2 +- src/dusk/ui/popup.cpp | 20 +++++++-- src/dusk/ui/select_button.cpp | 9 ++--- src/dusk/ui/tab_bar.cpp | 3 +- src/dusk/ui/ui.cpp | 4 +- src/dusk/ui/window.cpp | 51 +++++++++++++++++++++-- src/dusk/ui/window.hpp | 3 ++ 17 files changed, 141 insertions(+), 98 deletions(-) delete mode 100644 res/rml/popup.rml delete mode 100644 res/rml/window.rml diff --git a/extern/aurora b/extern/aurora index 1bd972429e..285c11a13c 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 1bd972429ebba4e486012b1acd08d4d7580eb917 +Subproject commit 285c11a13c9200be196a3c407e31a642824658a9 diff --git a/res/rml/popup.rcss b/res/rml/popup.rcss index 852ab2ce39..0ebe05f640 100644 --- a/res/rml/popup.rcss +++ b/res/rml/popup.rcss @@ -19,7 +19,7 @@ button { focus: auto; } -.popup { +popup { width: 100%; display: flex; align-items: stretch; @@ -28,15 +28,15 @@ button { background-color: rgba(21, 22, 16, 80%); border-bottom: 2dp #92875B; backdrop-filter: blur(5dp); - transform: translateY(0); + transform: translateY(-64dp); transition: transform 0.2s cubic-in-out; } -.popup.popup-hidden { - transform: translateY(-64dp); +popup[open] { + transform: translateY(0); } -.popup .tab-bar { +popup tab-bar { display: flex; flex: 1 1 0; min-width: 0; @@ -44,7 +44,7 @@ button { text-transform: uppercase; } -.popup .tab-bar .tab { +popup tab-bar tab { flex: 0 0 auto; padding: 0 24dp; line-height: 64dp; @@ -55,19 +55,19 @@ button { transition: decorator 0.1s linear-in-out, opacity 0.1s linear-in-out; } -.popup .tab-bar .tab.selected { +popup tab-bar tab.selected { opacity: 1; border-bottom: 4dp #C2A42D; font-effect: glow(0dp 4dp 0dp 4dp black); } -.popup .tab-bar .tab:focus-visible, -.popup .tab-bar .tab:hover { +popup tab-bar tab:focus-visible, +popup tab-bar tab:hover { opacity: 1; font-effect: glow(0dp 4dp 0dp 4dp black); decorator: vertical-gradient(#c2a42d00 #c2a42d26); } -.popup .tab-bar .tab:active { +popup tab-bar tab:active { decorator: vertical-gradient(#c2a42d10 #c2a42d40); } diff --git a/res/rml/popup.rml b/res/rml/popup.rml deleted file mode 100644 index 05be1a5c2d..0000000000 --- a/res/rml/popup.rml +++ /dev/null @@ -1,9 +0,0 @@ - - - Popup - - - - - - diff --git a/res/rml/window.rcss b/res/rml/window.rcss index 7a14f55096..93e891f01d 100644 --- a/res/rml/window.rcss +++ b/res/rml/window.rcss @@ -13,12 +13,7 @@ body { color: #E0DBC8; } -button { - cursor: pointer; - focus: auto; -} - -.window { +window { display: flex; flex-flow: column; height: 100%; @@ -31,18 +26,27 @@ button { 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 { + window { box-shadow: none; } } -.window .tab-bar { +window tab-bar { display: flex; flex: 0 0 64dp; height: 64dp; @@ -56,7 +60,7 @@ button { border-bottom: 2dp #92875B; } -.window .tab-bar .tab { +window tab-bar tab { flex: 0 0 auto; padding: 0 24dp; line-height: 64dp; @@ -66,24 +70,24 @@ button { transition: decorator 0.1s linear-in-out, opacity 0.1s linear-in-out; } -.window .tab-bar .tab.selected { +window tab-bar tab.selected { opacity: 1; border-bottom: 4dp #C2A42D; font-effect: glow(0dp 4dp 0dp 4dp black); } -.window .tab-bar .tab:focus-visible, -.window .tab-bar .tab:hover { +window tab-bar tab:focus-visible, +window tab-bar tab:hover { opacity: 1; font-effect: glow(0dp 4dp 0dp 4dp black); decorator: vertical-gradient(#c2a42d00 #c2a42d26); } -.window .tab-bar .tab:active { +window tab-bar tab:active { decorator: vertical-gradient(#c2a42d10 #c2a42d40); } -.window .content { +window content { display: flex; flex: 1 1 0; min-width: 0; @@ -91,7 +95,7 @@ button { overflow: hidden; } -.window .content .pane { +window content pane { display: flex; flex-flow: column; flex: 1 1 0; @@ -104,15 +108,15 @@ button { overflow: hidden auto; } -.window .content .pane:not(:last-of-type) { +window content pane:not(:last-of-type) { border-right: 1dp #92875B; } -.window .content .pane > * { +window content pane > * { flex: 0 0 auto; } -.window .content .pane > .spacer { +window content pane > spacer { display: block; /* Completes the 24dp bottom inset after the pane's 8dp gap. */ flex: 0 0 16dp; @@ -176,7 +180,7 @@ scrollbarhorizontal sliderbar { padding-top: 12dp; } -.button { +button { text-align: center; background-color: rgba(17, 16, 10, 20%); opacity: 0.9; @@ -185,23 +189,25 @@ scrollbarhorizontal sliderbar { 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).active, -.button:not(:disabled):hover, -.button:not(:disabled):focus-visible { +button:not(:disabled).active, +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, -.button:not(:disabled):active { +button:not(:disabled).selected, +button:not(:disabled):active { opacity: 1; background-color: rgba(204, 184, 119, 40%); box-shadow: #C2A42D 0 0 0 2dp; } -.select-button { +select-button { display: flex; align-items: center; gap: 8dp; @@ -211,28 +217,30 @@ scrollbarhorizontal sliderbar { 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).active, -.select-button:not(:disabled):hover, -.select-button:not(:disabled):focus-visible { +select-button:not(:disabled).active, +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, -.select-button:not(:disabled):active { +select-button:not(:disabled).selected, +select-button:not(:disabled):active { opacity: 1; background-color: rgba(204, 184, 119, 40%); box-shadow: #C2A42D 0 0 0 2dp; } -.select-button:disabled { +select-button:disabled { opacity: 0.35; cursor: default; } -.select-button .key { +select-button key { font-family: "Fira Sans Condensed"; font-weight: bold; font-size: 18dp; @@ -240,12 +248,12 @@ scrollbarhorizontal sliderbar { flex: 1 0 auto; } -.select-button .value { +select-button value { margin-left: auto; font-size: 20dp; } -.select-button input { +select-button input { text-align: right; font-size: 20dp; } diff --git a/res/rml/window.rml b/res/rml/window.rml deleted file mode 100644 index 4947c281c7..0000000000 --- a/res/rml/window.rml +++ /dev/null @@ -1,9 +0,0 @@ - - - Window - - - -
- -
diff --git a/src/dusk/ui/button.cpp b/src/dusk/ui/button.cpp index 832c2a9876..b315de1291 100644 --- a/src/dusk/ui/button.cpp +++ b/src/dusk/ui/button.cpp @@ -7,17 +7,16 @@ namespace dusk::ui { namespace { -Rml::Element* createRoot(Rml::Element* parent, const Rml::String& className) { +Rml::Element* createRoot(Rml::Element* parent, const Rml::String& tagName) { auto* doc = parent->GetOwnerDocument(); - auto elem = doc->CreateElement("button"); - elem->SetClass(className, true); + auto elem = doc->CreateElement(tagName); return parent->AppendChild(std::move(elem)); } } // namespace -Button::Button(Rml::Element* parent, ButtonProps props, const Rml::String& className) - : Component(createRoot(parent, className)) { +Button::Button(Rml::Element* parent, ButtonProps props, const Rml::String& tagName) + : Component(createRoot(parent, tagName)) { update_props(std::move(props)); listen(mRoot, Rml::EventId::Click, [this](Rml::Event& event) { if (mProps.onPressed) { diff --git a/src/dusk/ui/button.hpp b/src/dusk/ui/button.hpp index deaadde12b..902a82a312 100644 --- a/src/dusk/ui/button.hpp +++ b/src/dusk/ui/button.hpp @@ -14,7 +14,7 @@ class Button : public Component { public: using Props = ButtonProps; - Button(Rml::Element* parent, ButtonProps props, const Rml::String& className = "button"); + Button(Rml::Element* parent, ButtonProps props, const Rml::String& tagName = "button"); void set_text(const Rml::String& text); void set_selected(bool selected); diff --git a/src/dusk/ui/document.cpp b/src/dusk/ui/document.cpp index f6a73f8c42..b3eefc95ad 100644 --- a/src/dusk/ui/document.cpp +++ b/src/dusk/ui/document.cpp @@ -6,17 +6,17 @@ namespace dusk::ui { namespace { -Rml::ElementDocument* load_document(const Rml::String& path) { +Rml::ElementDocument* load_document(const Rml::String& source) { auto* context = aurora::rmlui::get_context(); if (context == nullptr) { return nullptr; } - return context->LoadDocument(path); + return context->LoadDocumentFromMemory(source); } } // namespace -Document::Document(const Rml::String& path) : mDocument(load_document(path)) { +Document::Document(const Rml::String& source) : mDocument(load_document(source)) { listen(Rml::EventId::Keydown, [this](Rml::Event& event) { const auto cmd = map_nav_event(event); if (cmd != NavCommand::None && handle_nav_command(event, cmd)) { diff --git a/src/dusk/ui/document.hpp b/src/dusk/ui/document.hpp index 3827a147a1..d27d20c59d 100644 --- a/src/dusk/ui/document.hpp +++ b/src/dusk/ui/document.hpp @@ -7,7 +7,7 @@ namespace dusk::ui { class Document { public: - Document(const Rml::String& path); + Document(const Rml::String& source); virtual ~Document(); Document(const Document&) = delete; diff --git a/src/dusk/ui/pane.cpp b/src/dusk/ui/pane.cpp index 8fe05763d6..dd6ad9f27f 100644 --- a/src/dusk/ui/pane.cpp +++ b/src/dusk/ui/pane.cpp @@ -5,17 +5,16 @@ namespace dusk::ui { namespace { -Rml::Element* createRoot(Rml::Element* parent, const Rml::String& className) { +Rml::Element* createRoot(Rml::Element* parent) { auto* doc = parent->GetOwnerDocument(); - auto elem = doc->CreateElement("div"); - elem->SetClass(className, true); + auto elem = doc->CreateElement("pane"); return parent->AppendChild(std::move(elem)); } } // namespace -Pane::Pane(Rml::Element* parent, Direction direction, const Rml::String& className) - : Component(createRoot(parent, className)), mDirection(direction) { +Pane::Pane(Rml::Element* parent, Direction direction) + : Component(createRoot(parent)), mDirection(direction) { listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) { const auto cmd = map_nav_event(event); int direction = 0; @@ -95,8 +94,7 @@ void Pane::finalize() { // padding-bottom or margin-bottom on a scrollable flex container, so // we need to create a fake spacer with an actual layout height to get // padding at the bottom of a scrollable container. - auto* elem = append(mRoot, "div"); - elem->SetClass("spacer", true); + append(mRoot, "spacer"); } void Pane::clear() { diff --git a/src/dusk/ui/pane.hpp b/src/dusk/ui/pane.hpp index f354d6f106..ed9f0e6733 100644 --- a/src/dusk/ui/pane.hpp +++ b/src/dusk/ui/pane.hpp @@ -13,7 +13,7 @@ public: Horizontal, }; - explicit Pane(Rml::Element* parent, Direction direction, const Rml::String& className = "pane"); + explicit Pane(Rml::Element* parent, Direction direction); bool focus() override; void update() override; diff --git a/src/dusk/ui/popup.cpp b/src/dusk/ui/popup.cpp index 000025b4c2..c95387b213 100644 --- a/src/dusk/ui/popup.cpp +++ b/src/dusk/ui/popup.cpp @@ -11,8 +11,22 @@ #include namespace dusk::ui { +namespace { -Popup::Popup() : Document("res/rml/popup.rml"), mRoot(mDocument->GetElementById("popup")) { +const Rml::String kDocumentSource = R"RML( + + + + + + + + +)RML"; + +} + +Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("popup")) { mTabBar = std::make_unique(mRoot, TabBar::Props{.autoSelect = false}); mTabBar->add_tab("Settings", [] { push_document(std::make_unique()); }); mTabBar->add_tab("Warp", [] { @@ -48,7 +62,7 @@ void Popup::show() { } Document::show(); - mRoot->SetClass("popup-hidden", false); + mRoot->SetAttribute("open", ""); mTabBar->set_active_tab(-1); mVisible = true; } @@ -62,7 +76,7 @@ void Popup::hide() { return; } - mRoot->SetClass("popup-hidden", true); + mRoot->RemoveAttribute("open"); mVisible = false; } diff --git a/src/dusk/ui/select_button.cpp b/src/dusk/ui/select_button.cpp index 707f605e82..13cfaba37f 100644 --- a/src/dusk/ui/select_button.cpp +++ b/src/dusk/ui/select_button.cpp @@ -9,18 +9,15 @@ namespace { Rml::Element* createRoot(Rml::Element* parent) { auto* doc = parent->GetOwnerDocument(); - auto elem = doc->CreateElement("button"); - elem->SetClass("select-button", true); + auto elem = doc->CreateElement("select-button"); return parent->AppendChild(std::move(elem)); } } // namespace SelectButton::SelectButton(Rml::Element* parent, Props props) : Component(createRoot(parent)) { - mKeyElem = append(mRoot, "div"); - mKeyElem->SetClass("key", true); - mValueElem = append(mRoot, "div"); - mValueElem->SetClass("value", true); + mKeyElem = append(mRoot, "key"); + mValueElem = append(mRoot, "value"); update_props(std::move(props)); listen(mRoot, Rml::EventId::Click, [this](Rml::Event& event) { if (mProps.disabled) { diff --git a/src/dusk/ui/tab_bar.cpp b/src/dusk/ui/tab_bar.cpp index 85bfecc803..5ae696a973 100644 --- a/src/dusk/ui/tab_bar.cpp +++ b/src/dusk/ui/tab_bar.cpp @@ -5,8 +5,7 @@ namespace { Rml::Element* createRoot(Rml::Element* parent) { auto* doc = parent->GetOwnerDocument(); - auto elem = doc->CreateElement("div"); - elem->SetClass("tab-bar", true); + auto elem = doc->CreateElement("tab-bar"); return parent->AppendChild(std::move(elem)); } diff --git a/src/dusk/ui/ui.cpp b/src/dusk/ui/ui.cpp index bbd69558a2..f531a8674b 100644 --- a/src/dusk/ui/ui.cpp +++ b/src/dusk/ui/ui.cpp @@ -87,8 +87,8 @@ Document* top_document() noexcept { } void update() noexcept { - for (size_t i = 0; i < sDocuments.size(); ++i) { - sDocuments[i].doc->update(); + for (const auto& doc : sDocuments) { + doc.doc->update(); } sDocuments.erase( std::remove_if(sDocuments.begin(), sDocuments.end(), diff --git a/src/dusk/ui/window.cpp b/src/dusk/ui/window.cpp index 3ad4cd559b..45459517b2 100644 --- a/src/dusk/ui/window.cpp +++ b/src/dusk/ui/window.cpp @@ -23,17 +23,27 @@ float base_body_padding(Rml::Context* context) noexcept { return 64.0f * dpRatio; } +const Rml::String kDocumentSource = R"RML( + + + + + + + + +)RML"; + } // namespace -Window::Window() : Document("res/rml/window.rml"), mRoot(mDocument->GetElementById("window")) { +Window::Window() : Document(kDocumentSource), mRoot(mDocument->GetElementById("window")) { mTabBar = std::make_unique(mRoot, TabBar::Props{ .selectedTabIndex = 0, .autoSelect = true, }); - auto elem = mDocument->CreateElement("div"); + auto elem = mDocument->CreateElement("content"); elem->SetAttribute("id", "content"); - elem->SetClass("content", true); mContentRoot = mRoot->AppendChild(std::move(elem)); listen(Rml::EventId::Keydown, [this](Rml::Event& event) { @@ -49,6 +59,39 @@ Window::Window() : Document("res/rml/window.rml"), mRoot(mDocument->GetElementBy } } }); + + // Hide document after transition completion + listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) { + if (event.GetTargetElement() == mRoot && + *mRoot->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Visible && + !mVisible) + { + Document::hide(); + } + }); +} + +void Window::show() { + if (mVisible) { + return; + } + + Document::show(); + mRoot->SetAttribute("open", ""); + mVisible = true; +} + +void Window::hide() { + if (mDocument == nullptr) { + mVisible = false; + return; + } + if (!mVisible) { + return; + } + + mRoot->RemoveAttribute("open"); + mVisible = false; } void Window::update() { @@ -114,7 +157,7 @@ bool Window::focus() { bool Window::handle_nav_command(Rml::Event& event, NavCommand cmd) { auto* target = event.GetTargetElement(); - if (cmd != NavCommand::Next && cmd != NavCommand::Previous && target->Closest(".content")) { + if (cmd != NavCommand::Next && cmd != NavCommand::Previous && target->Closest("content")) { if (handle_content_nav(event, cmd)) { return true; } diff --git a/src/dusk/ui/window.hpp b/src/dusk/ui/window.hpp index 4576740182..ffc69f2022 100644 --- a/src/dusk/ui/window.hpp +++ b/src/dusk/ui/window.hpp @@ -23,6 +23,8 @@ public: Window(const Window&) = delete; Window& operator=(const Window&) = delete; + void show() override; + void hide() override; void update() override; bool focus() override; bool set_active_tab(int index); @@ -47,6 +49,7 @@ protected: std::unique_ptr mTabBar; std::vector > mContentComponents; Insets mBodyPadding; + bool mVisible = false; }; } // namespace dusk::ui