mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-09-01 00:13:05 -04:00
UiService: Dialog controls (#2332)
This commit is contained in:
@@ -808,7 +808,8 @@ ModResult ui_window_close(LoadedMod& mod, uint64_t handle) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult ui_dialog_push(LoadedMod& mod, const UiDialogDesc& desc, uint64_t& outHandle) {
|
||||
ModResult ui_dialog_push(
|
||||
LoadedMod& mod, const UiDialogDesc& desc, UiPaneBuildFn build, uint64_t& outHandle) {
|
||||
outHandle = 0;
|
||||
if (!aurora::rmlui::is_initialized()) {
|
||||
return MOD_UNAVAILABLE;
|
||||
@@ -851,6 +852,16 @@ ModResult ui_dialog_push(LoadedMod& mod, const UiDialogDesc& desc, uint64_t& out
|
||||
|
||||
auto dialog = std::make_unique<ModDialog>(
|
||||
std::move(props), [handle] { on_mod_dialog_destroyed(handle); });
|
||||
if (build != nullptr) {
|
||||
auto& pane = dialog->content_pane();
|
||||
const uint64_t paneHandle = wrap_pane(mod, pane, nullptr);
|
||||
invoke_mod_ui_callback(mod, "mod UI dialog build", [&](ModError* error) {
|
||||
return build(mod.context.get(), paneHandle, desc.user_data, error);
|
||||
});
|
||||
if (!mod.active) {
|
||||
return MOD_ERROR;
|
||||
}
|
||||
}
|
||||
if (auto* slot = slot_from_handle(handle)) {
|
||||
slot->document = dialog.get();
|
||||
}
|
||||
@@ -1338,7 +1349,8 @@ ModResult ui_dialog_push(ModContext* context, const UiDialogDesc* desc, UiDialog
|
||||
*outDialog = 0;
|
||||
}
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(UiDialogDesc) ||
|
||||
constexpr size_t kLegacyDescSize = offsetof(UiDialogDesc, build);
|
||||
if (mod == nullptr || desc == nullptr || desc->struct_size < kLegacyDescSize ||
|
||||
desc->title == nullptr || desc->body_rml == nullptr || desc->actions == nullptr ||
|
||||
desc->action_count == 0 || desc->variant > UI_DIALOG_DANGER)
|
||||
{
|
||||
@@ -1349,8 +1361,9 @@ ModResult ui_dialog_push(ModContext* context, const UiDialogDesc* desc, UiDialog
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
const UiPaneBuildFn build = desc->struct_size >= sizeof(UiDialogDesc) ? desc->build : nullptr;
|
||||
uint64_t handle = 0;
|
||||
const auto result = ui_impl::ui_dialog_push(*mod, *desc, handle);
|
||||
const auto result = ui_impl::ui_dialog_push(*mod, *desc, build, handle);
|
||||
if (result == MOD_OK && outDialog != nullptr) {
|
||||
*outDialog = handle;
|
||||
}
|
||||
|
||||
+49
-2
@@ -1,5 +1,7 @@
|
||||
#include "modal.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::move(props)) {
|
||||
@@ -23,6 +25,9 @@ Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::mo
|
||||
body->SetClass("modal-body", true);
|
||||
body->SetInnerRML(mProps.bodyRml);
|
||||
|
||||
mContentRoot = append(mDialog, "div");
|
||||
mContentRoot->SetClass("modal-content", true);
|
||||
|
||||
auto* actions = append(mDialog, "div");
|
||||
actions->SetClass("modal-actions", true);
|
||||
if (props.isVertical) {
|
||||
@@ -36,12 +41,36 @@ Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::mo
|
||||
mDoAud_seStartMenu(kSoundWindowOpen);
|
||||
}
|
||||
|
||||
void Modal::update() {
|
||||
if (mContentPane != nullptr) {
|
||||
mContentPane->update();
|
||||
}
|
||||
if (mPendingAction) {
|
||||
auto action = std::move(mPendingAction);
|
||||
action(*this);
|
||||
}
|
||||
WindowSmall::update();
|
||||
}
|
||||
|
||||
Pane& Modal::content_pane() {
|
||||
if (mContentPane == nullptr) {
|
||||
mContentRoot->SetClass("active", true);
|
||||
mContentPane = std::make_unique<Pane>(mContentRoot, Pane::Type::Uncontrolled);
|
||||
}
|
||||
return *mContentPane;
|
||||
}
|
||||
|
||||
void Modal::add_action(ModalAction action) {
|
||||
auto* actions = mDialog->QuerySelector(".modal-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) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
if (mContentPane != nullptr) {
|
||||
mPendingAction = callback;
|
||||
} else {
|
||||
callback(*this);
|
||||
}
|
||||
});
|
||||
@@ -68,6 +97,9 @@ void Modal::set_icon(const Rml::String& icon) {
|
||||
}
|
||||
|
||||
bool Modal::focus() {
|
||||
if (mContentPane != nullptr && mContentPane->focus()) {
|
||||
return true;
|
||||
}
|
||||
if (!mButtons.empty()) {
|
||||
return mButtons.front()->focus();
|
||||
}
|
||||
@@ -89,6 +121,22 @@ bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto* target = event.GetTargetElement();
|
||||
if (mContentPane != nullptr && mContentPane->contains(target) && cmd == NavCommand::Down &&
|
||||
!mButtons.empty() && mButtons.front()->focus())
|
||||
{
|
||||
mDoAud_seStartMenu(kSoundItemFocus);
|
||||
return true;
|
||||
}
|
||||
if (mContentPane != nullptr && cmd == NavCommand::Up &&
|
||||
std::ranges::any_of(
|
||||
mButtons, [target](const auto& button) { return button->contains(target); }) &&
|
||||
mContentPane->focus_last())
|
||||
{
|
||||
mDoAud_seStartMenu(kSoundItemFocus);
|
||||
return true;
|
||||
}
|
||||
|
||||
int direction = 0;
|
||||
NavCommand prevCommand = mProps.isVertical ? NavCommand::Up : NavCommand::Left;
|
||||
NavCommand nextCommand = mProps.isVertical ? NavCommand::Down : NavCommand::Right;
|
||||
@@ -100,7 +148,6 @@ bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "button.hpp"
|
||||
#include "pane.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
@@ -20,13 +21,15 @@ public:
|
||||
std::function<void(Modal&)> onDismiss;
|
||||
Rml::String variant;
|
||||
Rml::String icon = "";
|
||||
bool isVertical;
|
||||
bool isVertical = false;
|
||||
};
|
||||
|
||||
explicit Modal(Props props);
|
||||
|
||||
void update() override;
|
||||
bool focus() override;
|
||||
|
||||
Pane& content_pane();
|
||||
void add_action(ModalAction action);
|
||||
void set_body(const Rml::String& bodyRml);
|
||||
void set_icon(const Rml::String& icon);
|
||||
@@ -38,7 +41,10 @@ private:
|
||||
void dismiss();
|
||||
|
||||
Props mProps;
|
||||
std::vector<std::unique_ptr<Button> > mButtons;
|
||||
Rml::Element* mContentRoot = nullptr;
|
||||
std::unique_ptr<Pane> mContentPane;
|
||||
std::function<void(Modal&)> mPendingAction;
|
||||
std::vector<std::unique_ptr<Button>> mButtons;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -163,6 +163,15 @@ bool Pane::focus() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Pane::focus_last() {
|
||||
for (auto child = mChildren.rbegin(); child != mChildren.rend(); ++child) {
|
||||
if ((*child)->focus()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Rml::Element* Pane::add_section(const Rml::String& text) {
|
||||
auto* elem = append(mRoot, "div");
|
||||
elem->SetClass("section-heading", true);
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
explicit Pane(Rml::Element* parent, Type type);
|
||||
|
||||
bool focus() override;
|
||||
bool focus_last();
|
||||
void update() override;
|
||||
|
||||
void set_selected_item(int index);
|
||||
|
||||
@@ -52,6 +52,10 @@ Popover::Popover(Rml::Element* anchor, Side side, const Rml::String& windowClass
|
||||
true);
|
||||
}
|
||||
|
||||
Popover::~Popover() {
|
||||
notify_close(false);
|
||||
}
|
||||
|
||||
void Popover::show() {
|
||||
Document::show();
|
||||
reposition();
|
||||
@@ -60,7 +64,7 @@ void Popover::show() {
|
||||
}
|
||||
|
||||
void Popover::hide(bool close) {
|
||||
notify_close();
|
||||
notify_close(true);
|
||||
mBody->RemoveAttribute("open");
|
||||
mPendingClose = close;
|
||||
}
|
||||
@@ -136,13 +140,13 @@ void Popover::reposition() {
|
||||
mBody->SetProperty(Rml::PropertyId::Top, Rml::Property{pos.y, Rml::Unit::PX});
|
||||
}
|
||||
|
||||
void Popover::notify_close() {
|
||||
void Popover::notify_close(bool restoreFocus) {
|
||||
if (mOnClose) {
|
||||
auto callback = std::move(mOnClose);
|
||||
mOnClose = nullptr;
|
||||
callback();
|
||||
}
|
||||
if (mAnchor != nullptr && mAnchor->IsVisible(true)) {
|
||||
if (restoreFocus && mAnchor != nullptr && mAnchor->IsVisible(true)) {
|
||||
mAnchor->Focus(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public:
|
||||
};
|
||||
|
||||
Popover(Rml::Element* anchor, Side side, const Rml::String& windowClass = "");
|
||||
~Popover() override;
|
||||
|
||||
void show() override;
|
||||
void hide(bool close) override;
|
||||
@@ -32,7 +33,7 @@ protected:
|
||||
|
||||
private:
|
||||
void reposition();
|
||||
void notify_close();
|
||||
void notify_close(bool restoreFocus);
|
||||
|
||||
Rml::Element* mAnchor = nullptr;
|
||||
Side mSide;
|
||||
|
||||
Reference in New Issue
Block a user