mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-23 14:02:20 -04:00
UiService v2: UiDialogAction is_disabled
This commit is contained in:
+7
-3
@@ -529,7 +529,11 @@ ModResult build_dialog(ModContext*, UiElementHandle pane, void*, ModError*) {
|
||||
return svc_ui->pane_add_control(mod_ctx, pane, &input, nullptr);
|
||||
}
|
||||
|
||||
UiDialogAction action = {"Save", save, nullptr, false};
|
||||
UiDialogAction action = UI_DIALOG_ACTION_INIT;
|
||||
action.label = "Save";
|
||||
action.on_pressed = save;
|
||||
action.is_disabled = is_save_disabled;
|
||||
|
||||
UiDialogDesc dialog = UI_DIALOG_DESC_INIT;
|
||||
dialog.title = "New Preset";
|
||||
dialog.body_rml = "Choose a name for the preset.";
|
||||
@@ -540,8 +544,8 @@ svc_ui->dialog_push(mod_ctx, &dialog, nullptr);
|
||||
```
|
||||
|
||||
After an action's `on_pressed`, the dialog closes unless the action sets `keep_open`. It can then be closed later
|
||||
(or immediately) with `dialog_close`. Cancel fires `on_dismiss` and always closes. `dialog_set_body`, `dialog_set_icon`,
|
||||
and `dialog_add_action` mutate a live dialog.
|
||||
(or immediately) with `dialog_close`. Cancel fires `on_dismiss` and always closes. `dialog_set_body` and
|
||||
`dialog_set_icon` mutate a live dialog.
|
||||
|
||||
**Toasts:** `push_toast` enqueues a notification. Titles and bodies accept RML. The optional `type` is applied as an
|
||||
RCSS class; `warning` uses the built-in warning appearance, and mods can define their own types. A duration of 0 uses
|
||||
|
||||
+14
-22
@@ -8,8 +8,8 @@
|
||||
#endif
|
||||
|
||||
#define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui"
|
||||
#define UI_SERVICE_MAJOR 1u
|
||||
#define UI_SERVICE_MINOR 5u
|
||||
#define UI_SERVICE_MAJOR 2u
|
||||
#define UI_SERVICE_MINOR 0u
|
||||
|
||||
/*
|
||||
* UI primitives: a panel inside the host Mods window, mod-owned windows, dialogs, toasts,
|
||||
@@ -120,9 +120,8 @@ typedef struct UiControlDesc {
|
||||
/* COLOR: optional RRGGBB/RRGGBBAA values for presets. "rainbow" is a special value. */
|
||||
const char* const* color_presets;
|
||||
size_t color_preset_count;
|
||||
bool color_alpha; /* COLOR: use RRGGBBAA values instead of RRGGBB */
|
||||
/* Optional selected state for BUTTON/GROUP. Added in UiService minor version 5. */
|
||||
UiPredicateFn is_selected;
|
||||
bool color_alpha; /* COLOR: use RRGGBBAA values instead of RRGGBB */
|
||||
UiPredicateFn is_selected; /* BUTTON/GROUP: pptional selected state */
|
||||
} UiControlDesc;
|
||||
|
||||
#define UI_CONTROL_DESC_INIT \
|
||||
@@ -198,15 +197,17 @@ typedef struct UiWindowDesc {
|
||||
|
||||
typedef void (*UiDialogActionFn)(ModContext* ctx, UiDialogHandle dialog, void* user_data);
|
||||
|
||||
/* Note: array element without struct_size; a future change requires appending
|
||||
* a v2 desc struct rather than growing this one. */
|
||||
typedef struct UiDialogAction {
|
||||
uint32_t struct_size;
|
||||
const char* label; /* required */
|
||||
UiDialogActionFn on_pressed;
|
||||
void* user_data;
|
||||
bool keep_open; /* false = the dialog closes after on_pressed returns */
|
||||
bool keep_open; /* false = the dialog closes after on_pressed returns */
|
||||
UiPredicateFn is_disabled; /* optional; polled every frame while the dialog is visible */
|
||||
} UiDialogAction;
|
||||
|
||||
#define UI_DIALOG_ACTION_INIT {sizeof(UiDialogAction), NULL, NULL, NULL, false, NULL}
|
||||
|
||||
typedef struct UiDialogDesc {
|
||||
uint32_t struct_size;
|
||||
const char* title; /* plain text; required */
|
||||
@@ -222,8 +223,7 @@ typedef struct UiDialogDesc {
|
||||
UiDialogActionFn on_dismiss;
|
||||
void* user_data; /* passed to build and on_dismiss */
|
||||
/* Optional content builder. The pane is rendered below body_rml and above the actions. Its
|
||||
* handle and all child element handles remain valid until the dialog closes.
|
||||
* Added in UiService minor version 4. */
|
||||
* handle and all child element handles remain valid until the dialog closes. */
|
||||
UiPaneBuildFn build;
|
||||
} UiDialogDesc;
|
||||
|
||||
@@ -270,6 +270,10 @@ typedef struct UiService {
|
||||
ModContext* ctx, UiElementHandle pane, float value, UiElementHandle* out_elem);
|
||||
ModResult (*pane_add_control)(ModContext* ctx, UiElementHandle pane, const UiControlDesc* desc,
|
||||
UiElementHandle* out_elem);
|
||||
/* Add a group button to one pane that builds controls in its paired pane.
|
||||
* The panes must be the left and right handles from the same UiTabBuildFn call. */
|
||||
ModResult (*pane_add_group)(ModContext* ctx, UiElementHandle group_pane,
|
||||
UiElementHandle target_pane, const UiGroupDesc* desc, UiElementHandle* out_elem);
|
||||
|
||||
/* Element updates. The handle kind must match the setter (text/rml on text
|
||||
* rows, progress on progress bars). */
|
||||
@@ -291,9 +295,6 @@ typedef struct UiService {
|
||||
ModResult (*dialog_set_body)(ModContext* ctx, UiDialogHandle dialog, const char* body_rml);
|
||||
/* Replace the dialog icon ("" removes it; names as in UiDialogDesc.icon). */
|
||||
ModResult (*dialog_set_icon)(ModContext* ctx, UiDialogHandle dialog, const char* icon);
|
||||
/* Append one action button (same callback rules as at push). */
|
||||
ModResult (*dialog_add_action)(
|
||||
ModContext* ctx, UiDialogHandle dialog, const UiDialogAction* action);
|
||||
|
||||
/* Whether any focus-stack document is currently visible (visible documents block gamepad
|
||||
* input). */
|
||||
@@ -320,18 +321,9 @@ typedef struct UiService {
|
||||
/* Enqueue a toast notification. */
|
||||
ModResult (*push_toast)(ModContext* ctx, const UiToastDesc* desc);
|
||||
|
||||
/* Minor version 2 */
|
||||
|
||||
ModResult (*get_clipboard_text)(
|
||||
ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength);
|
||||
ModResult (*set_clipboard_text)(ModContext* ctx, const char* text);
|
||||
|
||||
/* Minor version 3 */
|
||||
|
||||
/* Add a group button to one pane that builds controls in its paired pane.
|
||||
* The panes must be the left and right handles from the same UiTabBuildFn call. */
|
||||
ModResult (*pane_add_group)(ModContext* ctx, UiElementHandle group_pane,
|
||||
UiElementHandle target_pane, const UiGroupDesc* desc, UiElementHandle* out_elem);
|
||||
} UiService;
|
||||
|
||||
MOD_DECLARE_SERVICE(UiService, svc_ui, UI_SERVICE_ID, UI_SERVICE_MAJOR, UI_SERVICE_MINOR);
|
||||
|
||||
@@ -214,6 +214,7 @@ void ModLoader::init_services() {
|
||||
&svc::g_overlayModule,
|
||||
&svc::g_textureModule,
|
||||
&svc::g_configModule,
|
||||
&svc::g_uiModule_v1,
|
||||
&svc::g_uiModule,
|
||||
&svc::g_gameModule,
|
||||
&svc::g_cameraModule,
|
||||
|
||||
@@ -72,6 +72,7 @@ extern const ServiceModule g_hookModule;
|
||||
extern const ServiceModule g_overlayModule;
|
||||
extern const ServiceModule g_textureModule;
|
||||
extern const ServiceModule g_configModule;
|
||||
extern const ServiceModule g_uiModule_v1;
|
||||
extern const ServiceModule g_uiModule;
|
||||
extern const ServiceModule g_gameModule;
|
||||
extern const ServiceModule g_cameraModule;
|
||||
|
||||
+117
-35
@@ -3,10 +3,12 @@
|
||||
#include "config.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "slot_map.hpp"
|
||||
#include "ui_v1.hpp"
|
||||
|
||||
#include <borealis/log.hpp>
|
||||
#include "dusk/mod_loader.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "dusk/mods/log_buffer.hpp"
|
||||
#include "dusk/ui/menu_bar.hpp"
|
||||
#include "dusk/ui/mod_window.hpp"
|
||||
#include "dusk/ui/modal.hpp"
|
||||
@@ -440,10 +442,8 @@ void push_stacked_document(std::unique_ptr<ui::Document> document) {
|
||||
}
|
||||
}
|
||||
|
||||
// Shared by dialog_push and dialog_add_action; the guard handle keeps a
|
||||
// pressed callback from calling into a torn-down mod.
|
||||
ui::ModalAction make_dialog_action(LoadedMod& mod, uint64_t handle, const UiDialogAction& action) {
|
||||
return {
|
||||
ui::ModalAction result{
|
||||
.label = action.label,
|
||||
.onPressed =
|
||||
[modPtr = &mod, handle, fn = action.on_pressed, userData = action.user_data,
|
||||
@@ -461,6 +461,17 @@ ui::ModalAction make_dialog_action(LoadedMod& mod, uint64_t handle, const UiDial
|
||||
}
|
||||
},
|
||||
};
|
||||
if (action.is_disabled != nullptr) {
|
||||
result.isDisabled = [modPtr = &mod, handle, fn = action.is_disabled,
|
||||
userData = action.user_data] {
|
||||
if (!dialog_open(handle)) {
|
||||
return false;
|
||||
}
|
||||
return guarded_call(*modPtr, "dialog action is_disabled callback", false,
|
||||
[&] { return fn(modPtr->context.get(), userData); });
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -811,8 +822,7 @@ ModResult ui_window_close(LoadedMod& mod, uint64_t handle) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult ui_dialog_push(
|
||||
LoadedMod& mod, const UiDialogDesc& desc, UiPaneBuildFn build, uint64_t& outHandle) {
|
||||
ModResult ui_dialog_push(LoadedMod& mod, const UiDialogDesc& desc, uint64_t& outHandle) {
|
||||
outHandle = 0;
|
||||
if (!aurora::rmlui::is_initialized()) {
|
||||
return MOD_UNAVAILABLE;
|
||||
@@ -849,17 +859,20 @@ ModResult ui_dialog_push(
|
||||
static_cast<ModDialog&>(modal).close();
|
||||
}
|
||||
};
|
||||
auto* actionData = reinterpret_cast<const std::byte*>(desc.actions);
|
||||
for (size_t i = 0; i < desc.action_count; ++i) {
|
||||
props.actions.push_back(make_dialog_action(mod, handle, desc.actions[i]));
|
||||
const auto& action = *reinterpret_cast<const UiDialogAction*>(actionData);
|
||||
props.actions.push_back(make_dialog_action(mod, handle, action));
|
||||
actionData += action.struct_size;
|
||||
}
|
||||
|
||||
auto dialog = std::make_unique<ModDialog>(
|
||||
std::move(props), [handle] { on_mod_dialog_destroyed(handle); });
|
||||
if (build != nullptr) {
|
||||
if (desc.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);
|
||||
return desc.build(mod.context.get(), paneHandle, desc.user_data, error);
|
||||
});
|
||||
if (!mod.active) {
|
||||
return MOD_ERROR;
|
||||
@@ -901,15 +914,6 @@ ModResult ui_dialog_set_icon(LoadedMod& mod, uint64_t handle, const char* icon)
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult ui_dialog_add_action(LoadedMod& mod, uint64_t handle, const UiDialogAction& action) {
|
||||
auto* slot = resolve(mod, handle, UiSlotKind::Dialog, "dialog_add_action");
|
||||
if (slot == nullptr || slot->document == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
static_cast<ModDialog*>(slot->document)->add_action(make_dialog_action(mod, handle, action));
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult ui_register_menu_tab(LoadedMod& mod, const UiMenuTabDesc& desc, uint64_t& outHandle) {
|
||||
outHandle = 0;
|
||||
for (const auto& [owner, tabs] : s_modMenuTabs) {
|
||||
@@ -1353,21 +1357,24 @@ ModResult ui_dialog_push(ModContext* context, const UiDialogDesc* desc, UiDialog
|
||||
*outDialog = 0;
|
||||
}
|
||||
auto* mod = mod_from_context(context);
|
||||
constexpr size_t kLegacyDescSize = offsetof(UiDialogDesc, build);
|
||||
if (mod == nullptr || desc == nullptr || desc->struct_size < kLegacyDescSize ||
|
||||
if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(UiDialogDesc) ||
|
||||
desc->title == nullptr || desc->body_rml == nullptr || desc->actions == nullptr ||
|
||||
desc->action_count == 0 || desc->variant > UI_DIALOG_DANGER)
|
||||
{
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
auto* actionData = reinterpret_cast<const std::byte*>(desc->actions);
|
||||
for (size_t i = 0; i < desc->action_count; ++i) {
|
||||
if (desc->actions[i].label == nullptr) {
|
||||
const auto* action = reinterpret_cast<const UiDialogAction*>(actionData);
|
||||
if (action->struct_size < sizeof(UiDialogAction) ||
|
||||
action->struct_size % alignof(UiDialogAction) != 0 || action->label == nullptr)
|
||||
{
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
actionData += action->struct_size;
|
||||
}
|
||||
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, build, handle);
|
||||
const auto result = ui_impl::ui_dialog_push(*mod, *desc, handle);
|
||||
if (result == MOD_OK && outDialog != nullptr) {
|
||||
*outDialog = handle;
|
||||
}
|
||||
@@ -1499,15 +1506,6 @@ ModResult ui_dialog_set_icon(ModContext* context, UiDialogHandle dialog, const c
|
||||
return ui_impl::ui_dialog_set_icon(*mod, dialog, icon);
|
||||
}
|
||||
|
||||
ModResult ui_dialog_add_action(
|
||||
ModContext* context, UiDialogHandle dialog, const UiDialogAction* action) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || dialog == 0 || action == nullptr || action->label == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return ui_impl::ui_dialog_add_action(*mod, dialog, *action);
|
||||
}
|
||||
|
||||
ModResult ui_get_clipboard_text(
|
||||
ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength) {
|
||||
auto* mod = mod_from_context(ctx);
|
||||
@@ -1526,8 +1524,55 @@ ModResult ui_set_clipboard_text(ModContext* ctx, const char* text) {
|
||||
return ui_impl::ui_set_clipboard_text(*mod, text);
|
||||
}
|
||||
|
||||
constexpr UiService s_uiService{
|
||||
.header = SERVICE_HEADER(UiService, UI_SERVICE_MAJOR, UI_SERVICE_MINOR),
|
||||
ModResult ui_v1_dialog_push(
|
||||
ModContext* context, const ui_v1::UiDialogDesc* desc, UiDialogHandle* outDialog) {
|
||||
if (outDialog != nullptr) {
|
||||
*outDialog = 0;
|
||||
}
|
||||
constexpr size_t kLegacyDescSize = offsetof(ui_v1::UiDialogDesc, build);
|
||||
if (desc == nullptr || desc->struct_size < kLegacyDescSize || desc->actions == nullptr ||
|
||||
desc->action_count == 0)
|
||||
{
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
std::vector<UiDialogAction> actions;
|
||||
actions.reserve(desc->action_count);
|
||||
for (size_t i = 0; i < desc->action_count; ++i) {
|
||||
UiDialogAction action = UI_DIALOG_ACTION_INIT;
|
||||
action.label = desc->actions[i].label;
|
||||
action.on_pressed = desc->actions[i].on_pressed;
|
||||
action.user_data = desc->actions[i].user_data;
|
||||
action.keep_open = desc->actions[i].keep_open;
|
||||
actions.push_back(action);
|
||||
}
|
||||
|
||||
UiDialogDesc translated = UI_DIALOG_DESC_INIT;
|
||||
translated.title = desc->title;
|
||||
translated.body_rml = desc->body_rml;
|
||||
translated.variant = desc->variant;
|
||||
translated.icon = desc->icon;
|
||||
translated.actions = actions.data();
|
||||
translated.action_count = actions.size();
|
||||
translated.on_dismiss = desc->on_dismiss;
|
||||
translated.user_data = desc->user_data;
|
||||
translated.build = desc->struct_size >= sizeof(ui_v1::UiDialogDesc) ? desc->build : nullptr;
|
||||
return ui_dialog_push(context, &translated, outDialog);
|
||||
}
|
||||
|
||||
ModResult ui_v1_dialog_add_action(
|
||||
ModContext* context, UiDialogHandle, const ui_v1::UiDialogAction*) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
log::write(
|
||||
mod->metadata.id, LOG_LEVEL_WARN, "UiService v1 dialog_add_action is no longer supported");
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
|
||||
constexpr ui_v1::UiService s_uiService_v1{
|
||||
.header = SERVICE_HEADER(ui_v1::UiService, ui_v1::kMajorVersion, ui_v1::kMinorVersion),
|
||||
.register_mods_panel = ui_register_mods_panel,
|
||||
.pane_add_section = ui_pane_add_section,
|
||||
.pane_add_text = ui_pane_add_text,
|
||||
@@ -1540,11 +1585,11 @@ constexpr UiService s_uiService{
|
||||
.elem_set_class = ui_elem_set_class,
|
||||
.window_push = ui_window_push,
|
||||
.window_close = ui_window_close,
|
||||
.dialog_push = ui_dialog_push,
|
||||
.dialog_push = ui_v1_dialog_push,
|
||||
.dialog_close = ui_dialog_close,
|
||||
.dialog_set_body = ui_dialog_set_body,
|
||||
.dialog_set_icon = ui_dialog_set_icon,
|
||||
.dialog_add_action = ui_dialog_add_action,
|
||||
.dialog_add_action = ui_v1_dialog_add_action,
|
||||
.is_any_document_visible = ui_is_any_document_visible,
|
||||
.register_styles = ui_register_styles,
|
||||
.register_styles_file = ui_register_styles_file,
|
||||
@@ -1557,6 +1602,36 @@ constexpr UiService s_uiService{
|
||||
.pane_add_group = ui_pane_add_group,
|
||||
};
|
||||
|
||||
constexpr UiService s_uiService{
|
||||
.header = SERVICE_HEADER(UiService, UI_SERVICE_MAJOR, UI_SERVICE_MINOR),
|
||||
.register_mods_panel = ui_register_mods_panel,
|
||||
.pane_add_section = ui_pane_add_section,
|
||||
.pane_add_text = ui_pane_add_text,
|
||||
.pane_add_rml = ui_pane_add_rml,
|
||||
.pane_add_progress = ui_pane_add_progress,
|
||||
.pane_add_control = ui_pane_add_control,
|
||||
.pane_add_group = ui_pane_add_group,
|
||||
.elem_set_text = ui_elem_set_text,
|
||||
.elem_set_rml = ui_elem_set_rml,
|
||||
.elem_set_progress = ui_elem_set_progress,
|
||||
.elem_set_class = ui_elem_set_class,
|
||||
.window_push = ui_window_push,
|
||||
.window_close = ui_window_close,
|
||||
.dialog_push = ui_dialog_push,
|
||||
.dialog_close = ui_dialog_close,
|
||||
.dialog_set_body = ui_dialog_set_body,
|
||||
.dialog_set_icon = ui_dialog_set_icon,
|
||||
.is_any_document_visible = ui_is_any_document_visible,
|
||||
.register_styles = ui_register_styles,
|
||||
.register_styles_file = ui_register_styles_file,
|
||||
.unregister_styles = ui_unregister_styles,
|
||||
.register_menu_tab = ui_register_menu_tab,
|
||||
.unregister_menu_tab = ui_unregister_menu_tab,
|
||||
.push_toast = ui_push_toast,
|
||||
.get_clipboard_text = ui_get_clipboard_text,
|
||||
.set_clipboard_text = ui_set_clipboard_text,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void ui_build_mods_panels(LoadedMod& mod, ui::Pane& pane) {
|
||||
@@ -1571,6 +1646,13 @@ std::vector<ModMenuTabEntry> ui_mod_menu_tabs() {
|
||||
return ui_impl::ui_mod_menu_tabs();
|
||||
}
|
||||
|
||||
constinit const ServiceModule g_uiModule_v1{
|
||||
.id = UI_SERVICE_ID,
|
||||
.majorVersion = ui_v1::kMajorVersion,
|
||||
.minorVersion = ui_v1::kMinorVersion,
|
||||
.service = &s_uiService_v1,
|
||||
};
|
||||
|
||||
constinit const ServiceModule g_uiModule{
|
||||
.id = UI_SERVICE_ID,
|
||||
.majorVersion = UI_SERVICE_MAJOR,
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "mods/svc/ui.h"
|
||||
|
||||
namespace dusk::mods::svc::ui_v1 {
|
||||
|
||||
constexpr uint16_t kMajorVersion = 1;
|
||||
constexpr uint16_t kMinorVersion = 5;
|
||||
|
||||
struct UiDialogAction {
|
||||
const char* label;
|
||||
UiDialogActionFn on_pressed;
|
||||
void* user_data;
|
||||
bool keep_open;
|
||||
};
|
||||
|
||||
struct UiDialogDesc {
|
||||
uint32_t struct_size;
|
||||
const char* title;
|
||||
const char* body_rml;
|
||||
UiDialogVariant variant;
|
||||
const char* icon;
|
||||
const UiDialogAction* actions;
|
||||
size_t action_count;
|
||||
UiDialogActionFn on_dismiss;
|
||||
void* user_data;
|
||||
UiPaneBuildFn build;
|
||||
};
|
||||
|
||||
struct UiService {
|
||||
ServiceHeader header;
|
||||
|
||||
ModResult (*register_mods_panel)(ModContext* ctx, const UiModsPanelDesc* desc);
|
||||
ModResult (*pane_add_section)(ModContext* ctx, UiElementHandle pane, const char* title);
|
||||
ModResult (*pane_add_text)(
|
||||
ModContext* ctx, UiElementHandle pane, const char* text, UiElementHandle* out_elem);
|
||||
ModResult (*pane_add_rml)(
|
||||
ModContext* ctx, UiElementHandle pane, const char* rml, UiElementHandle* out_elem);
|
||||
ModResult (*pane_add_progress)(
|
||||
ModContext* ctx, UiElementHandle pane, float value, UiElementHandle* out_elem);
|
||||
ModResult (*pane_add_control)(ModContext* ctx, UiElementHandle pane, const UiControlDesc* desc,
|
||||
UiElementHandle* out_elem);
|
||||
|
||||
ModResult (*elem_set_text)(ModContext* ctx, UiElementHandle elem, const char* text);
|
||||
ModResult (*elem_set_rml)(ModContext* ctx, UiElementHandle elem, const char* rml);
|
||||
ModResult (*elem_set_progress)(ModContext* ctx, UiElementHandle elem, float value);
|
||||
ModResult (*elem_set_class)(
|
||||
ModContext* ctx, UiElementHandle elem, const char* name, bool active);
|
||||
|
||||
ModResult (*window_push)(ModContext* ctx, const UiWindowDesc* desc, UiWindowHandle* out_window);
|
||||
ModResult (*window_close)(ModContext* ctx, UiWindowHandle window);
|
||||
|
||||
ModResult (*dialog_push)(ModContext* ctx, const UiDialogDesc* desc, UiDialogHandle* out_dialog);
|
||||
ModResult (*dialog_close)(ModContext* ctx, UiDialogHandle dialog);
|
||||
ModResult (*dialog_set_body)(ModContext* ctx, UiDialogHandle dialog, const char* body_rml);
|
||||
ModResult (*dialog_set_icon)(ModContext* ctx, UiDialogHandle dialog, const char* icon);
|
||||
ModResult (*dialog_add_action)(
|
||||
ModContext* ctx, UiDialogHandle dialog, const UiDialogAction* action);
|
||||
|
||||
ModResult (*is_any_document_visible)(ModContext* ctx, bool* out_visible);
|
||||
|
||||
ModResult (*register_styles)(
|
||||
ModContext* ctx, UiStyleScope scope, const char* rcss, UiStyleHandle* out_style);
|
||||
ModResult (*register_styles_file)(
|
||||
ModContext* ctx, UiStyleScope scope, const char* path, UiStyleHandle* out_style);
|
||||
ModResult (*unregister_styles)(ModContext* ctx, UiStyleHandle style);
|
||||
|
||||
ModResult (*register_menu_tab)(
|
||||
ModContext* ctx, const UiMenuTabDesc* desc, UiMenuTabHandle* out_tab);
|
||||
ModResult (*unregister_menu_tab)(ModContext* ctx, UiMenuTabHandle tab);
|
||||
|
||||
ModResult (*push_toast)(ModContext* ctx, const UiToastDesc* desc);
|
||||
|
||||
ModResult (*get_clipboard_text)(
|
||||
ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength);
|
||||
ModResult (*set_clipboard_text)(ModContext* ctx, const char* text);
|
||||
|
||||
ModResult (*pane_add_group)(ModContext* ctx, UiElementHandle group_pane,
|
||||
UiElementHandle target_pane, const UiGroupDesc* desc, UiElementHandle* out_elem);
|
||||
};
|
||||
|
||||
} // namespace dusk::mods::svc::ui_v1
|
||||
+26
-12
@@ -45,6 +45,9 @@ void Modal::update() {
|
||||
if (mContentPane != nullptr) {
|
||||
mContentPane->update();
|
||||
}
|
||||
for (const auto& button : mButtons) {
|
||||
button->update();
|
||||
}
|
||||
if (mPendingAction) {
|
||||
auto action = std::move(mPendingAction);
|
||||
action(*this);
|
||||
@@ -62,7 +65,11 @@ Pane& Modal::content_pane() {
|
||||
|
||||
void Modal::add_action(ModalAction action) {
|
||||
auto* actions = mDialog->QuerySelector(".modal-actions");
|
||||
auto btn = std::make_unique<Button>(actions, action.label);
|
||||
auto btn =
|
||||
std::make_unique<ControlledButton>(actions, ControlledButton::Props{
|
||||
.text = std::move(action.label),
|
||||
.isDisabled = std::move(action.isDisabled),
|
||||
});
|
||||
btn->root()->SetClass("modal-btn", true);
|
||||
btn->on_pressed([this, callback = std::move(action.onPressed)] {
|
||||
if (!callback) {
|
||||
@@ -100,8 +107,10 @@ bool Modal::focus() {
|
||||
if (mContentPane != nullptr && mContentPane->focus()) {
|
||||
return true;
|
||||
}
|
||||
if (!mButtons.empty()) {
|
||||
return mButtons.front()->focus();
|
||||
for (const auto& button : mButtons) {
|
||||
if (button->focus()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -122,11 +131,13 @@ bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
}
|
||||
|
||||
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 && mContentPane->contains(target) && cmd == NavCommand::Down) {
|
||||
for (const auto& button : mButtons) {
|
||||
if (button->focus()) {
|
||||
mDoAud_seStartMenu(kSoundItemFocus);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mContentPane != nullptr && cmd == NavCommand::Up &&
|
||||
std::ranges::any_of(
|
||||
@@ -150,10 +161,13 @@ bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
|
||||
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;
|
||||
for (int next = i + direction; next >= 0 && next < static_cast<int>(mButtons.size());
|
||||
next += direction)
|
||||
{
|
||||
if (mButtons[next]->focus()) {
|
||||
mDoAud_seStartMenu(kSoundItemFocus);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ class Modal;
|
||||
struct ModalAction {
|
||||
Rml::String label;
|
||||
std::function<void(Modal&)> onPressed;
|
||||
std::function<bool()> isDisabled;
|
||||
};
|
||||
|
||||
class Modal : public WindowSmall {
|
||||
@@ -30,7 +31,6 @@ public:
|
||||
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,6 +38,7 @@ protected:
|
||||
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
|
||||
|
||||
private:
|
||||
void add_action(ModalAction action);
|
||||
void dismiss();
|
||||
|
||||
Props mProps;
|
||||
|
||||
Reference in New Issue
Block a user