forked from mirrors/0ad
Add Engine.openRequest
Pages can replace themselfe by another (continuation) page.
This commit is contained in:
@@ -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;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
+44
-14
@@ -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()};
|
||||||
|
if (receivingPromise == nullptr)
|
||||||
|
{
|
||||||
receivingPromise = std::make_shared<JS::PersistentRootedObject>(rq.cx,
|
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)};
|
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 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
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user