mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Add Engine.openRequest
Pages can replace themselfe by another (continuation) page.
This commit is contained in:
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>
|
||||
13
binaries/data/mods/_test.gui/gui/OpenRequest/Root/Script.js
Normal file
13
binaries/data/mods/_test.gui/gui/OpenRequest/Root/Script.js
Normal file
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ namespace
|
|||
const CStr EVENT_NAME_GAME_LOAD_PROGRESS = "GameLoadProgress";
|
||||
const CStr EVENT_NAME_WINDOW_RESIZED = "WindowResized";
|
||||
constexpr const char* START_ATLAS{"startAtlas"};
|
||||
constexpr const char* OPEN_REQUEST{"openRequest"};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
|
@ -152,7 +153,7 @@ JS::Value CGUIManager::OpenChildPage(const CStrW& pageName, Script::StructuredCl
|
|||
CGUI& currentPage = *m_PageStack.back().gui;
|
||||
// Make sure we unfocus anything on the current page.
|
||||
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
|
||||
|
|
@ -185,11 +186,12 @@ void CGUIManager::SGUIPage::LoadPage(ScriptContext& scriptContext)
|
|||
gui.reset(new CGUI(scriptContext));
|
||||
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 nativeScope{rq.cx, JS::ObjectValue(*rq.nativeScope)};
|
||||
Script::SetProperty(rq, nativeScope, START_ATLAS, symbol, true);
|
||||
Script::SetProperty(rq, nativeScope, name, symbol, true);
|
||||
}
|
||||
gui->AddObjectTypes();
|
||||
|
||||
|
|
@ -267,16 +269,19 @@ void CGUIManager::SGUIPage::LoadPage(ScriptContext& scriptContext)
|
|||
*localPromise = returnObject ? returnObject : JS::NewPromiseObject(rq.cx, nullptr);
|
||||
}
|
||||
|
||||
JS::Value CGUIManager::SGUIPage::ReplacePromise(ScriptInterface& scriptInterface)
|
||||
JS::Value CGUIManager::SGUIPage::GetPromise()
|
||||
{
|
||||
const ScriptRequest rq{scriptInterface};
|
||||
receivingPromise = std::make_shared<JS::PersistentRootedObject>(rq.cx,
|
||||
const ScriptRequest rq{gui->GetScriptInterface()};
|
||||
if (receivingPromise == nullptr)
|
||||
{
|
||||
receivingPromise = std::make_shared<JS::PersistentRootedObject>(rq.cx,
|
||||
JS::NewPromiseObject(rq.cx, nullptr));
|
||||
}
|
||||
|
||||
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)
|
||||
return std::nullopt;
|
||||
|
|
@ -287,9 +292,29 @@ std::optional<CGUIManager::SGUIPage::CloseResult> CGUIManager::SGUIPage::MaybeCl
|
|||
const ScriptRequest rq{gui->GetScriptInterface()};
|
||||
JS::RootedValue arg{rq.cx, JS::GetPromiseResult(*sendingPromise)};
|
||||
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};
|
||||
Script::GetProperty(rq, nativeScope, START_ATLAS, &symbol);
|
||||
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."};
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -432,10 +457,15 @@ std::optional<bool> CGUIManager::TickObjects()
|
|||
break;
|
||||
ENSURE(m_PageStack.size() == stackSize);
|
||||
m_PageStack.pop_back();
|
||||
if (m_PageStack.empty())
|
||||
return !result.value().arg;
|
||||
if (const SGUIPage::OpenRequest* request{std::get_if<SGUIPage::OpenRequest>(&result.value())})
|
||||
OpenChildPage(request->path, request->arg);
|
||||
else if (const SGUIPage::Close& ret{std::get<SGUIPage::Close>(result.value())};
|
||||
m_PageStack.empty())
|
||||
{
|
||||
return !ret.arg;
|
||||
}
|
||||
else
|
||||
m_PageStack.back().Refocus(result.value());
|
||||
m_PageStack.back().Refocus(ret);
|
||||
|
||||
m_ScriptContext.RunJobs();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
|
||||
class CCanvas2D;
|
||||
class CGUI;
|
||||
|
|
@ -155,29 +156,36 @@ private:
|
|||
void LoadPage(ScriptContext& context);
|
||||
|
||||
/**
|
||||
* A new promise gets set. A reference to that promise is returned. The promise will settle when
|
||||
* the page is closed.
|
||||
* A reference to the promise is returned. The promise will settle when the page is closed.
|
||||
*/
|
||||
JS::Value ReplacePromise(ScriptInterface& scriptInterface);
|
||||
JS::Value GetPromise();
|
||||
|
||||
struct CloseResult
|
||||
struct Close
|
||||
{
|
||||
Script::StructuredClone arg;
|
||||
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
|
||||
* returns the result of the @c init function.
|
||||
* 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
|
||||
* result of the closed page should be the argument of this
|
||||
* function. This function resolves the @c receivingPromise.
|
||||
*/
|
||||
void Refocus(const CloseResult& result);
|
||||
void Refocus(const Close& result);
|
||||
|
||||
std::wstring m_Name;
|
||||
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.
|
||||
*
|
||||
* 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",
|
||||
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");
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue