1
0
forked from mirrors/0ad

Add Engine.openRequest

Pages can replace themselfe by another (continuation) page.
This commit is contained in:
phosit
2025-04-23 13:01:54 +02:00
parent 456e2a0b56
commit 6ead0d2f92
12 changed files with 125 additions and 22 deletions
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/OpenRequest/Continuation/Script.js"/>
</objects>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>OpenRequest/Continuation/Object.xml</include>
</page>
@@ -0,0 +1,4 @@
async function init(arg)
{
return arg + " Continuation";
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/OpenRequest/Entry/Script.js"/>
</objects>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>OpenRequest/Entry/Object.xml</include>
</page>
@@ -0,0 +1,7 @@
async function init()
{
return { [Engine.openRequest]: {
"page": "OpenRequest/Continuation/Page.xml",
"argument": "Entry"
} };
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/OpenRequest/Root/Script.js"/>
</objects>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>OpenRequest/Root/Object.xml</include>
</page>
@@ -0,0 +1,13 @@
async function init()
{
const result = await Engine.OpenChildPage("OpenRequest/Entry/Page.xml");
await new Promise(closePageCallback =>
{
globalThis.closePageCallback = () =>
{
closePageCallback();
return result;
};
});
}
+45 -15
View File
@@ -65,6 +65,7 @@ namespace
const CStr EVENT_NAME_GAME_LOAD_PROGRESS = "GameLoadProgress"; const CStr EVENT_NAME_GAME_LOAD_PROGRESS = "GameLoadProgress";
const CStr EVENT_NAME_WINDOW_RESIZED = "WindowResized"; const CStr EVENT_NAME_WINDOW_RESIZED = "WindowResized";
constexpr const char* START_ATLAS{"startAtlas"}; constexpr const char* START_ATLAS{"startAtlas"};
constexpr const char* OPEN_REQUEST{"openRequest"};
} // anonymous namespace } // anonymous namespace
@@ -152,7 +153,7 @@ JS::Value CGUIManager::OpenChildPage(const CStrW& pageName, Script::StructuredCl
CGUI& currentPage = *m_PageStack.back().gui; CGUI& currentPage = *m_PageStack.back().gui;
// Make sure we unfocus anything on the current page. // Make sure we unfocus anything on the current page.
currentPage.SendFocusMessage(GUIM_LOST_FOCUS); currentPage.SendFocusMessage(GUIM_LOST_FOCUS);
return m_PageStack.back().ReplacePromise(*currentPage.GetScriptInterface()); return m_PageStack.back().GetPromise();
}()}; }()};
// Emplace the page prior to loading its contents, because that may open // Emplace the page prior to loading its contents, because that may open
@@ -185,11 +186,12 @@ void CGUIManager::SGUIPage::LoadPage(ScriptContext& scriptContext)
gui.reset(new CGUI(scriptContext)); gui.reset(new CGUI(scriptContext));
const ScriptRequest rq{gui->GetScriptInterface()}; const ScriptRequest rq{gui->GetScriptInterface()};
for (const char* name : {START_ATLAS, OPEN_REQUEST})
{ {
JS::RootedString jsName{rq.cx, JS_NewStringCopyZ(rq.cx, START_ATLAS)}; JS::RootedString jsName{rq.cx, JS_NewStringCopyZ(rq.cx, name)};
JS::RootedValue symbol{rq.cx, JS::SymbolValue(JS::NewSymbol(rq.cx, jsName))}; JS::RootedValue symbol{rq.cx, JS::SymbolValue(JS::NewSymbol(rq.cx, jsName))};
JS::RootedValue nativeScope{rq.cx, JS::ObjectValue(*rq.nativeScope)}; JS::RootedValue nativeScope{rq.cx, JS::ObjectValue(*rq.nativeScope)};
Script::SetProperty(rq, nativeScope, START_ATLAS, symbol, true); Script::SetProperty(rq, nativeScope, name, symbol, true);
} }
gui->AddObjectTypes(); gui->AddObjectTypes();
@@ -267,16 +269,19 @@ void CGUIManager::SGUIPage::LoadPage(ScriptContext& scriptContext)
*localPromise = returnObject ? returnObject : JS::NewPromiseObject(rq.cx, nullptr); *localPromise = returnObject ? returnObject : JS::NewPromiseObject(rq.cx, nullptr);
} }
JS::Value CGUIManager::SGUIPage::ReplacePromise(ScriptInterface& scriptInterface) JS::Value CGUIManager::SGUIPage::GetPromise()
{ {
const ScriptRequest rq{scriptInterface}; const ScriptRequest rq{gui->GetScriptInterface()};
receivingPromise = std::make_shared<JS::PersistentRootedObject>(rq.cx, if (receivingPromise == nullptr)
{
receivingPromise = std::make_shared<JS::PersistentRootedObject>(rq.cx,
JS::NewPromiseObject(rq.cx, nullptr)); JS::NewPromiseObject(rq.cx, nullptr));
}
return JS::ObjectValue(**receivingPromise); return JS::ObjectValue(**receivingPromise);
} }
std::optional<CGUIManager::SGUIPage::CloseResult> CGUIManager::SGUIPage::MaybeClose(const bool topmostPage) std::optional<CGUIManager::SGUIPage::CloseResult> CGUIManager::SGUIPage::MaybeClose(const bool isRootPage)
{ {
if (JS::GetPromiseState(*sendingPromise) == JS::PromiseState::Pending) if (JS::GetPromiseState(*sendingPromise) == JS::PromiseState::Pending)
return std::nullopt; return std::nullopt;
@@ -287,9 +292,29 @@ std::optional<CGUIManager::SGUIPage::CloseResult> CGUIManager::SGUIPage::MaybeCl
const ScriptRequest rq{gui->GetScriptInterface()}; const ScriptRequest rq{gui->GetScriptInterface()};
JS::RootedValue arg{rq.cx, JS::GetPromiseResult(*sendingPromise)}; JS::RootedValue arg{rq.cx, JS::GetPromiseResult(*sendingPromise)};
const bool rejected{JS::GetPromiseState(*sendingPromise) == JS::PromiseState::Rejected}; const bool rejected{JS::GetPromiseState(*sendingPromise) == JS::PromiseState::Rejected};
if (topmostPage)
JS::RootedValue nativeScope{rq.cx, JS::ObjectValue(*rq.nativeScope)};
if (arg.isObject())
{
JS::RootedObject argObject{rq.cx, &arg.toObject()};
JS::RootedValue symbol{rq.cx};
Script::GetProperty(rq, nativeScope, OPEN_REQUEST, &symbol);
JS::RootedId key{rq.cx, JS::PropertyKey::Symbol(symbol.toSymbol())};
JS::RootedValue openRequest{rq.cx};
if (JS_GetPropertyById(rq.cx, argObject, key, &openRequest) &&
Script::HasProperty(rq, openRequest, "page"))
{
std::wstring page;
Script::GetProperty(rq, openRequest, "page", page);
JS::RootedValue openArg{rq.cx};
Script::GetProperty(rq, openRequest, "argument", &openArg);
return CGUIManager::SGUIPage::OpenRequest{page,
Script::WriteStructuredClone(rq, openArg)};
}
}
if (isRootPage)
{ {
JS::RootedValue nativeScope{rq.cx, JS::ObjectValue(*rq.nativeScope)};
JS::RootedValue symbol{rq.cx}; JS::RootedValue symbol{rq.cx};
Script::GetProperty(rq, nativeScope, START_ATLAS, &symbol); Script::GetProperty(rq, nativeScope, START_ATLAS, &symbol);
bool equals; bool equals;
@@ -297,12 +322,12 @@ std::optional<CGUIManager::SGUIPage::CloseResult> CGUIManager::SGUIPage::MaybeCl
throw std::runtime_error{"Error while comparing return value to a symbol."}; throw std::runtime_error{"Error while comparing return value to a symbol."};
if (equals) if (equals)
return CGUIManager::SGUIPage::CloseResult{nullptr, rejected}; return CGUIManager::SGUIPage::Close{nullptr, rejected};
} }
return CGUIManager::SGUIPage::CloseResult{Script::WriteStructuredClone(rq, arg), rejected}; return CGUIManager::SGUIPage::Close{Script::WriteStructuredClone(rq, arg), rejected};
} }
void CGUIManager::SGUIPage::Refocus(const CloseResult& result) void CGUIManager::SGUIPage::Refocus(const Close& result)
{ {
ENSURE(receivingPromise); ENSURE(receivingPromise);
@@ -432,10 +457,15 @@ std::optional<bool> CGUIManager::TickObjects()
break; break;
ENSURE(m_PageStack.size() == stackSize); ENSURE(m_PageStack.size() == stackSize);
m_PageStack.pop_back(); m_PageStack.pop_back();
if (m_PageStack.empty()) if (const SGUIPage::OpenRequest* request{std::get_if<SGUIPage::OpenRequest>(&result.value())})
return !result.value().arg; OpenChildPage(request->path, request->arg);
else if (const SGUIPage::Close& ret{std::get<SGUIPage::Close>(result.value())};
m_PageStack.empty())
{
return !ret.arg;
}
else else
m_PageStack.back().Refocus(result.value()); m_PageStack.back().Refocus(ret);
m_ScriptContext.RunJobs(); m_ScriptContext.RunJobs();
} }
+14 -6
View File
@@ -34,6 +34,7 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <variant>
class CCanvas2D; class CCanvas2D;
class CGUI; class CGUI;
@@ -155,29 +156,36 @@ private:
void LoadPage(ScriptContext& context); void LoadPage(ScriptContext& context);
/** /**
* A new promise gets set. A reference to that promise is returned. The promise will settle when * A reference to the promise is returned. The promise will settle when the page is closed.
* the page is closed.
*/ */
JS::Value ReplacePromise(ScriptInterface& scriptInterface); JS::Value GetPromise();
struct CloseResult struct Close
{ {
Script::StructuredClone arg; Script::StructuredClone arg;
bool rejected; bool rejected;
}; };
struct OpenRequest
{
std::wstring path;
Script::StructuredClone arg;
};
using CloseResult = std::variant<Close, OpenRequest>;
/** /**
* If the page should be closed this function closes the page and * If the page should be closed this function closes the page and
* returns the result of the @c init function. * returns the result of the @c init function.
* If this page wasn't closed an empty optional is returned. * If this page wasn't closed an empty optional is returned.
*/ */
std::optional<CloseResult> MaybeClose(const bool topmostPage); std::optional<CloseResult> MaybeClose(const bool isRootPage);
/** /**
* This function should be called when a child page got closed. The * This function should be called when a child page got closed. The
* result of the closed page should be the argument of this * result of the closed page should be the argument of this
* function. This function resolves the @c receivingPromise. * function. This function resolves the @c receivingPromise.
*/ */
void Refocus(const CloseResult& result); void Refocus(const Close& result);
std::wstring m_Name; std::wstring m_Name;
std::unordered_set<VfsPath> inputs; // for hotloading std::unordered_set<VfsPath> inputs; // for hotloading
+18 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games. /* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@@ -325,4 +325,21 @@ public:
TS_ASSERT_THROWS(g_GUI->OpenChildPage(L"await/page.xml", TS_ASSERT_THROWS(g_GUI->OpenChildPage(L"await/page.xml",
Script::WriteStructuredClone(rq, JS::NullHandleValue)), const std::bad_variant_access&); Script::WriteStructuredClone(rq, JS::NullHandleValue)), const std::bad_variant_access&);
} }
void test_OpenRequest()
{
const ScriptRequest rq{g_GUI->GetScriptInterface()};
g_GUI->OpenChildPage(L"OpenRequest/Root/Page.xml",
Script::WriteStructuredClone(rq, JS::UndefinedHandleValue));
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 2);
g_GUI->TickObjects();
const ScriptRequest pageRq{g_GUI->GetActiveGUI()->GetScriptInterface()};
JS::RootedValue global{pageRq.cx, pageRq.globalValue()};
std::string result;
TS_ASSERT(ScriptFunction::Call(pageRq, global, "closePageCallback", result));
TS_ASSERT_STR_EQUALS(result, "Entry Continuation");
}
}; };