From 6e819f283f3402f5c59a1a71c06fcc51a2c97cd6 Mon Sep 17 00:00:00 2001 From: Stardust Date: Mon, 25 May 2026 12:35:17 +0930 Subject: [PATCH] UI: make display notification/query use sciter --- external/sciterui | 2 +- lang/english/css/notification_dialog.css | 147 ++++++++++++++ lang/english/html/notification_dialog.html | 25 +++ src/nxemu/user_interface/notification.cpp | 180 +++++++++++++++++- src/nxemu/user_interface/notification.h | 8 + .../user_interface/sciter_main_window.cpp | 3 + 6 files changed, 358 insertions(+), 7 deletions(-) create mode 100644 lang/english/css/notification_dialog.css create mode 100644 lang/english/html/notification_dialog.html diff --git a/external/sciterui b/external/sciterui index 7b5a7e6d..10c9708a 160000 --- a/external/sciterui +++ b/external/sciterui @@ -1 +1 @@ -Subproject commit 7b5a7e6deb92648ac05548a815f96ca17baea7b2 +Subproject commit 10c9708a7554d0e3fbe7d5dc995f1a58ae4cb953 diff --git a/lang/english/css/notification_dialog.css b/lang/english/css/notification_dialog.css new file mode 100644 index 00000000..efc23c14 --- /dev/null +++ b/lang/english/css/notification_dialog.css @@ -0,0 +1,147 @@ +:root { + --color-notification-background: #181818; +} + +html { + margin: 0; + padding: 0 7px 14px 7px; + border: 0; + overflow: visible; + width: max-content; + height: max-content; + background: transparent; + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + background: transparent; +} + +.notification-panel { + border-radius: 8px; + border: none; + background-color: var(--color-notification-background); + box-shadow: + 0 2px 8px rgba(0, 0, 0, 1), + 0 12px 12px rgba(0, 0, 0, 0.55); + overflow: hidden; + color: #eff0f1; + font-family: 'Segoe UI', Arial, sans-serif; + font-size: 0.9rem; +} + +.notification-panel > header { + width: *; + flow: horizontal; + background-color: var(--color-titlebar-background); + height: window-caption-height; + margin: 0; + color: var(--color-titlebar-text); +} + +html[data-active="false"] .notification-panel > header { + background-color: var(--color-titlebar-background-inactive); +} + +.notification-panel > header > window-caption { + display: block; + line-height: window-caption-height; + width: *; + height: *; + padding: 0 0.75em 0 1.1em; + font-family: var(--font-ui); + font-size: 1rem; + font-weight: 400; +} + +.notification-panel > header > window-caption span { + vertical-align: middle; +} + +.notification-panel > header > window-caption img { + vertical-align: middle; + margin-right: 0.5em; + width: 1.5em; + height: 1.5em; +} + +.notification-panel > header > window-buttons { + display: block; + flow: horizontal; + width: max-content; + height: *; + vertical-align: middle; +} + +.notification-panel > header > window-buttons > window-button { + behavior: clickable; + display: block; + height: *; + width: window-button-width; + foreground-size: 2em 1.5em; + foreground-repeat: no-repeat; + foreground-position: 50% 50%; + stroke-width: 0; + fill: var(--color-titlebar-icon); + transition: background-color linear 100ms; + padding: 0.25em; +} + +html[data-active="false"] .notification-panel > header > window-buttons > window-button { + fill: var(--color-titlebar-icon-inactive); +} + +.notification-panel > header > window-buttons > window-button:hover { + background: var(--color-titlebar-icon-hover); +} + +.notification-panel > header > window-buttons > window-button[role="window-close"] { + foreground-image: url(icon:window-close); +} + +.notification-panel > header > window-buttons > window-button[role="window-close"]:hover { + background: rgb(232, 17, 35); + fill: #fff; +} + +.notification-body { + min-width: 22em; + max-width: 32em; + margin: 0; + padding: 1.35em 1.2em 0.75em 1.2em; +} + +#NotificationMessage { + white-space: pre-wrap; + line-height: 1.4; +} + +.footer { + padding: 0.25em 1.2em 1.1em 1.2em; + text-align: right; +} + +.notification-panel button { + color: #eff0f1; + background: #232728; + border: 1px solid #55575a; + font-family: 'Segoe UI', Arial, sans-serif; + font-size: 0.85rem; +} + +.notification-panel button:hover { + border: 1px solid #3cafe9; +} + +.notification-panel button:active { + background: #3cafe9; +} + +.footer button { + margin-left: 0.5em; + min-width: 5.5rem; + height: 2rem; + padding: 0.4em 1.4em; +} diff --git a/lang/english/html/notification_dialog.html b/lang/english/html/notification_dialog.html new file mode 100644 index 00000000..829fd9eb --- /dev/null +++ b/lang/english/html/notification_dialog.html @@ -0,0 +1,25 @@ + + + + + Notification + + +
+
+ Notification + + + +
+
+
+
+ +
+ + diff --git a/src/nxemu/user_interface/notification.cpp b/src/nxemu/user_interface/notification.cpp index 64cbfd5a..0e3c37af 100644 --- a/src/nxemu/user_interface/notification.cpp +++ b/src/nxemu/user_interface/notification.cpp @@ -1,23 +1,191 @@ #include "notification.h" #include "settings/ui_settings.h" -#include +#include +#include +#include #include +#include + +namespace +{ +enum class NotificationDialogMode +{ + Alert, + Query, +}; + +class NotificationWindow : + public IClickSink, + public IWindowDestroySink +{ +public: + NotificationWindow(ISciterUI & sciterUI) : + m_sciterUI(sciterUI), + m_window(nullptr), + m_mode(NotificationDialogMode::Alert), + m_response(NotificationResponse::No) + { + } + + NotificationResponse Show(void * parentWindow, NotificationDialogMode mode, const char * title, const char * message) + { + m_mode = mode; + m_response = mode == NotificationDialogMode::Query ? NotificationResponse::No : NotificationResponse::Yes; + m_window = nullptr; + + if (!m_sciterUI.WindowCreate(parentWindow, "notification_dialog.html", 0, 0, 0, 0, SUIW_CHILD, m_window)) + { + return m_response; + } + + SciterElement root(m_window->GetRootElement()); + if (root.IsValid()) + { + SciterElement titleEl(root.GetElementByID("NotificationTitle")); + if (titleEl.IsValid() && title != nullptr) + { + titleEl.SetText(title); + } + + SciterElement messageEl(root.GetElementByID("NotificationMessage")); + if (messageEl.IsValid() && message != nullptr) + { + messageEl.SetText(message); + } + + ConfigureButtons(); + + m_sciterUI.AttachHandler(root.FindFirst("button[role=\"window-ok\"]"), IID_ICLICKSINK, (IClickSink *)this); + m_sciterUI.AttachHandler(root.FindFirst("button[role=\"window-close\"]"), IID_ICLICKSINK, (IClickSink *)this); + m_sciterUI.AttachHandler(root.GetElementByID("NotificationYes"), IID_ICLICKSINK, (IClickSink *)this); + m_sciterUI.AttachHandler(root.GetElementByID("NotificationNo"), IID_ICLICKSINK, (IClickSink *)this); + m_sciterUI.AttachHandler(root.GetElementByID("NotificationOk"), IID_ICLICKSINK, (IClickSink *)this); + } + + m_window->OnDestroySinkAdd(this); + m_window->FixMinSize(); + m_window->CenterWindow(); + m_window->RunModal(); + + return m_response; + } + +private: + NotificationWindow(const NotificationWindow &) = delete; + NotificationWindow & operator=(const NotificationWindow &) = delete; + + void ConfigureButtons() + { + SciterElement root(m_window->GetRootElement()); + if (!root.IsValid()) + { + return; + } + + const bool query = m_mode == NotificationDialogMode::Query; + SciterElement yesBtn(root.GetElementByID("NotificationYes")); + SciterElement noBtn(root.GetElementByID("NotificationNo")); + SciterElement okBtn(root.GetElementByID("NotificationOk")); + + if (yesBtn.IsValid()) + { + yesBtn.SetStyleAttribute("display", query ? "inline-block" : "none"); + } + if (noBtn.IsValid()) + { + noBtn.SetStyleAttribute("display", query ? "inline-block" : "none"); + } + if (okBtn.IsValid()) + { + okBtn.SetStyleAttribute("display", query ? "none" : "inline-block"); + } + } + + void Close(NotificationResponse response) + { + if (m_window == nullptr || m_window->IsClosed()) + { + return; + } + m_response = response; + m_window->Destroy(); + } + + bool OnClick(SCITER_ELEMENT element, SCITER_ELEMENT /*source*/, uint32_t /*reason*/) override + { + SciterElement clickElem(element); + const std::string role = clickElem.GetAttribute("role"); + const std::string id = clickElem.GetAttribute("id"); + + if (id == "NotificationYes" || role == "window-ok") + { + Close(NotificationResponse::Yes); + return true; + } + if (id == "NotificationNo" || role == "window-close") + { + Close(NotificationResponse::No); + return true; + } + if (id == "NotificationOk") + { + Close(NotificationResponse::Yes); + return true; + } + return false; + } + + void OnWindowDestroy(HWINDOW /*hWnd*/) override + { + m_window = nullptr; + } + + ISciterUI & m_sciterUI; + ISciterWindow * m_window; + NotificationDialogMode m_mode; + NotificationResponse m_response; +}; + +} // namespace std::unique_ptr Notification::s_instance; -Notification::Notification() +Notification::Notification() : + m_sciterUI(nullptr), + m_parentWindow(nullptr) { } +void Notification::SetSciterContext(ISciterUI * sciterUI, void * parentWindow) +{ + m_sciterUI = sciterUI; + m_parentWindow = parentWindow; +} + +void Notification::ClearSciterContext() +{ + m_sciterUI = nullptr; + m_parentWindow = nullptr; +} + void Notification::DisplayError(const char * message, const char * title) const { - MessageBoxW(nullptr, stdstr(message).ToUTF16().c_str(), stdstr(title).ToUTF16().c_str(), MB_OK | MB_ICONERROR | MB_SETFOREGROUND); + if (m_sciterUI == nullptr || m_parentWindow == nullptr) + { + return; + } + NotificationWindow dialog(*m_sciterUI); + dialog.Show(m_parentWindow, NotificationDialogMode::Alert, title, message); } NotificationResponse Notification::Query(const char * message, const char * title) const { - const int result = MessageBoxW(nullptr, stdstr(message).ToUTF16().c_str(), stdstr(title).ToUTF16().c_str(), MB_YESNO | MB_ICONQUESTION | MB_SETFOREGROUND); - return result == IDYES ? NotificationResponse::Yes : NotificationResponse::No; + if (m_sciterUI == nullptr || m_parentWindow == nullptr) + { + return NotificationResponse::No; + } + NotificationWindow dialog(*m_sciterUI); + return dialog.Show(m_parentWindow, NotificationDialogMode::Query, title, message); } void Notification::BreakPoint(const char * fileName, uint32_t lineNumber) @@ -46,4 +214,4 @@ Notification & Notification::GetInstance() void Notification::CleanUp() { s_instance.reset(); -} \ No newline at end of file +} diff --git a/src/nxemu/user_interface/notification.h b/src/nxemu/user_interface/notification.h index 712f90d9..ec10bc58 100644 --- a/src/nxemu/user_interface/notification.h +++ b/src/nxemu/user_interface/notification.h @@ -2,6 +2,8 @@ #include #include +__interface ISciterUI; + class Notification : public INotification { @@ -14,6 +16,9 @@ public: void BreakPoint(const char * fileName, uint32_t lineNumber) override; void AppInitDone(void) override; + void SetSciterContext(ISciterUI * sciterUI, void * parentWindow); + void ClearSciterContext(); + static Notification & GetInstance(); static void CleanUp(); @@ -22,4 +27,7 @@ private: Notification & operator=(const Notification &) = delete; static std::unique_ptr s_instance; + + ISciterUI * m_sciterUI; + void * m_parentWindow; }; \ No newline at end of file diff --git a/src/nxemu/user_interface/sciter_main_window.cpp b/src/nxemu/user_interface/sciter_main_window.cpp index 3f5751ba..a0c55ad1 100644 --- a/src/nxemu/user_interface/sciter_main_window.cpp +++ b/src/nxemu/user_interface/sciter_main_window.cpp @@ -1,4 +1,5 @@ #include "sciter_main_window.h" +#include "user_interface/notification.h" #include "settings/input_config.h" #include "settings/system_config.h" #include "settings/ui_settings.h" @@ -298,6 +299,7 @@ const std::string * SciterMainWindow::MenuIconSvg(GuiAction action) SciterMainWindow::~SciterMainWindow() { + Notification::GetInstance().ClearSciterContext(); m_WebBrowser.DetachWindow(); SettingsStore & settings = SettingsStore::GetInstance(); @@ -491,6 +493,7 @@ bool SciterMainWindow::Show() { PopulateVulkanRecords(m_vkDeviceRecords, RenderSurface()); } + Notification::GetInstance().SetSciterContext(&m_sciterUI, (void *)m_window->GetHandle()); m_window->Show(); return true; }