Enable page-inits to return a Promise
This commit is contained in:
parent
e89d93b95f
commit
b28b2343d8
@ -2,7 +2,7 @@
|
||||
* Currently limited to at most 3 buttons per message box.
|
||||
* The convention is to have "cancel" appear first.
|
||||
*/
|
||||
async function init(data)
|
||||
function init(data)
|
||||
{
|
||||
// Set title
|
||||
Engine.GetGUIObjectByName("mbTitleBar").caption = data.title;
|
||||
@ -28,5 +28,5 @@ async function init(data)
|
||||
const closePromise = setButtonCaptionsAndVisibitily(mbButton, captions, mbCancelHotkey, "mbButton");
|
||||
distributeButtonsHorizontally(mbButton, captions);
|
||||
|
||||
Engine.PopGuiPage(await closePromise);
|
||||
return closePromise;
|
||||
}
|
||||
|
@ -137,26 +137,15 @@ JS::Value CGUIManager::PushPage(const CStrW& pageName, Script::StructuredClone i
|
||||
return promise;
|
||||
}
|
||||
|
||||
void CGUIManager::PopPage(Script::StructuredClone args)
|
||||
void CGUIManager::PopPage(JS::HandleValue arg)
|
||||
{
|
||||
if (m_PageStack.size() < 2)
|
||||
{
|
||||
debug_warn(L"Tried to pop GUI page when there's < 2 in the stack");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we unfocus anything on the current page.
|
||||
m_PageStack.back().gui->SendFocusMessage(GUIM_LOST_FOCUS);
|
||||
|
||||
m_PageStack.pop_back();
|
||||
m_PageStack.back().ResolvePromise(args);
|
||||
|
||||
// We return to a page where some object might have been focused.
|
||||
m_PageStack.back().gui->SendFocusMessage(GUIM_GOT_FOCUS);
|
||||
SGUIPage& topmostPage{m_PageStack.back()};
|
||||
const ScriptRequest rq{topmostPage.gui->GetScriptInterface()};
|
||||
JS::ResolvePromise(rq.cx, *topmostPage.sendingPromise, arg);
|
||||
}
|
||||
|
||||
CGUIManager::SGUIPage::SGUIPage(const CStrW& pageName, const Script::StructuredClone initData)
|
||||
: m_Name(pageName), initData(initData), inputs(), gui(), callbackFunction()
|
||||
: m_Name(pageName), initData(initData)
|
||||
{
|
||||
}
|
||||
|
||||
@ -178,6 +167,9 @@ void CGUIManager::SGUIPage::LoadPage(ScriptContext& scriptContext)
|
||||
g_VideoMode.ResetCursor();
|
||||
inputs.clear();
|
||||
gui.reset(new CGUI(scriptContext));
|
||||
const ScriptRequest rq{gui->GetScriptInterface()};
|
||||
sendingPromise = std::make_shared<JS::PersistentRootedObject>(rq.cx,
|
||||
JS::NewPromiseObject(rq.cx, nullptr));
|
||||
|
||||
gui->AddObjectTypes();
|
||||
|
||||
@ -232,9 +224,6 @@ void CGUIManager::SGUIPage::LoadPage(ScriptContext& scriptContext)
|
||||
|
||||
gui->LoadedXmlFiles();
|
||||
|
||||
std::shared_ptr<ScriptInterface> scriptInterface = gui->GetScriptInterface();
|
||||
ScriptRequest rq(scriptInterface);
|
||||
|
||||
JS::RootedValue initDataVal(rq.cx);
|
||||
JS::RootedValue hotloadDataVal(rq.cx);
|
||||
JS::RootedValue global(rq.cx, rq.globalValue());
|
||||
@ -245,40 +234,68 @@ void CGUIManager::SGUIPage::LoadPage(ScriptContext& scriptContext)
|
||||
if (hotloadData)
|
||||
Script::ReadStructuredClone(rq, hotloadData, &hotloadDataVal);
|
||||
|
||||
if (Script::HasProperty(rq, global, "init") &&
|
||||
!ScriptFunction::CallVoid(rq, global, "init", initDataVal, hotloadDataVal))
|
||||
if (!Script::HasProperty(rq, global, "init"))
|
||||
return;
|
||||
|
||||
JS::RootedValue returnValue{rq.cx};
|
||||
if (!ScriptFunction::Call(rq, global, "init", &returnValue, initDataVal, hotloadDataVal))
|
||||
{
|
||||
LOGERROR("GUI page '%s': Failed to call init() function", utf8_from_wstring(m_Name));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!returnValue.isObject())
|
||||
return;
|
||||
|
||||
JS::RootedObject returnObject{rq.cx, &returnValue.toObject()};
|
||||
if (!JS::IsPromiseObject(returnObject))
|
||||
return;
|
||||
|
||||
sendingPromise = std::make_shared<JS::PersistentRootedObject>(rq.cx, returnObject);
|
||||
}
|
||||
|
||||
JS::Value CGUIManager::SGUIPage::ReplacePromise(ScriptInterface& scriptInterface)
|
||||
{
|
||||
JSContext* generalContext{scriptInterface.GetGeneralJSContext()};
|
||||
callbackFunction = std::make_shared<JS::PersistentRootedObject>(generalContext,
|
||||
receivingPromise = std::make_shared<JS::PersistentRootedObject>(generalContext,
|
||||
JS::NewPromiseObject(generalContext, nullptr));
|
||||
return JS::ObjectValue(**callbackFunction);
|
||||
|
||||
return JS::ObjectValue(**receivingPromise);
|
||||
}
|
||||
|
||||
void CGUIManager::SGUIPage::ResolvePromise(Script::StructuredClone args)
|
||||
CGUIManager::SGUIPage::CloseResult CGUIManager::SGUIPage::Close()
|
||||
{
|
||||
if (!callbackFunction)
|
||||
return;
|
||||
// Make sure we unfocus anything on the current page.
|
||||
gui->SendFocusMessage(GUIM_LOST_FOCUS);
|
||||
|
||||
const ScriptRequest rq{gui->GetScriptInterface()};
|
||||
JS::RootedValue arg{rq.cx, JS::GetPromiseResult(*sendingPromise)};
|
||||
return {Script::WriteStructuredClone(rq, arg),
|
||||
JS::GetPromiseState(*sendingPromise) == JS::PromiseState::Rejected};
|
||||
}
|
||||
|
||||
void CGUIManager::SGUIPage::Refocus(const CloseResult& result)
|
||||
{
|
||||
ENSURE(receivingPromise);
|
||||
|
||||
std::shared_ptr<ScriptInterface> scriptInterface = gui->GetScriptInterface();
|
||||
ScriptRequest rq(scriptInterface);
|
||||
|
||||
JS::RootedObject globalObj(rq.cx, rq.glob);
|
||||
|
||||
JS::RootedObject funcVal(rq.cx, *callbackFunction);
|
||||
JS::RootedObject recv(rq.cx, *receivingPromise);
|
||||
|
||||
// Delete the callback function, so that it is not called again
|
||||
callbackFunction.reset();
|
||||
receivingPromise.reset();
|
||||
|
||||
JS::RootedValue argVal(rq.cx);
|
||||
if (args)
|
||||
Script::ReadStructuredClone(rq, args, &argVal);
|
||||
Script::ReadStructuredClone(rq, result.arg, &argVal);
|
||||
|
||||
// This only resolves the promise, it doesn't call the continuation.
|
||||
JS::ResolvePromise(rq.cx, funcVal, argVal);
|
||||
(result.rejected ? JS::RejectPromise : JS::ResolvePromise)(rq.cx, recv, argVal);
|
||||
|
||||
// We return to a page where some object might have been focused.
|
||||
gui->SendFocusMessage(GUIM_GOT_FOCUS);
|
||||
}
|
||||
|
||||
Status CGUIManager::ReloadChangedFile(const VfsPath& path)
|
||||
@ -368,6 +385,19 @@ void CGUIManager::TickObjects()
|
||||
{
|
||||
PROFILE3("gui tick");
|
||||
|
||||
while (!m_PageStack.empty() &&
|
||||
JS::GetPromiseState(*m_PageStack.back().sendingPromise) != JS::PromiseState::Pending)
|
||||
{
|
||||
const size_t stackSize{m_PageStack.size()};
|
||||
const SGUIPage::CloseResult result{m_PageStack.back().Close()};
|
||||
ENSURE(m_PageStack.size() == stackSize);
|
||||
m_PageStack.pop_back();
|
||||
if (!m_PageStack.empty())
|
||||
m_PageStack.back().Refocus(result);
|
||||
|
||||
g_ScriptContext->RunJobs();
|
||||
}
|
||||
|
||||
// We share the script context with everything else that runs in the same thread.
|
||||
// This call makes sure we trigger GC regularly even if the simulation is not running.
|
||||
m_ScriptContext.MaybeIncrementalGC(1.0f);
|
||||
|
@ -76,7 +76,7 @@ public:
|
||||
* Unload the currently active GUI page, and make the previous page active.
|
||||
* (There must be at least two pages when you call this.)
|
||||
*/
|
||||
void PopPage(Script::StructuredClone args);
|
||||
void PopPage(JS::HandleValue arg);
|
||||
|
||||
/**
|
||||
* Called when a file has been modified, to hotload changes.
|
||||
@ -151,10 +151,17 @@ private:
|
||||
*/
|
||||
JS::Value ReplacePromise(ScriptInterface& scriptInterface);
|
||||
|
||||
struct CloseResult
|
||||
{
|
||||
Script::StructuredClone arg;
|
||||
bool rejected;
|
||||
};
|
||||
CloseResult Close();
|
||||
|
||||
/**
|
||||
* Execute the stored callback function with the given arguments.
|
||||
*/
|
||||
void ResolvePromise(Script::StructuredClone args);
|
||||
void Refocus(const CloseResult& result);
|
||||
|
||||
std::wstring m_Name;
|
||||
std::unordered_set<VfsPath> inputs; // for hotloading
|
||||
@ -162,10 +169,16 @@ private:
|
||||
std::shared_ptr<CGUI> gui; // the actual GUI page
|
||||
|
||||
/**
|
||||
* Function executed by this parent GUI page when the child GUI page it pushed is popped.
|
||||
* Notice that storing it in the SGUIPage instead of CGUI means that it will survive the hotloading CGUI reset.
|
||||
* When this promise is setteled this page wants to be closed. It
|
||||
* fullfils with the page completion value.
|
||||
*/
|
||||
std::shared_ptr<JS::PersistentRootedObject> callbackFunction;
|
||||
std::shared_ptr<JS::PersistentRootedObject> sendingPromise;
|
||||
|
||||
/**
|
||||
* The parrent page waits on this promise. It also gets the
|
||||
* completion value through this promise.
|
||||
*/
|
||||
std::shared_ptr<JS::PersistentRootedObject> receivingPromise;
|
||||
};
|
||||
|
||||
std::shared_ptr<CGUI> top() const;
|
||||
|
@ -51,7 +51,7 @@ void PopGuiPage(const ScriptRequest& rq, JS::HandleValue args)
|
||||
return;
|
||||
}
|
||||
|
||||
g_GUI->PopPage(Script::WriteStructuredClone(rq, args));
|
||||
g_GUI->PopPage(args);
|
||||
}
|
||||
|
||||
void SetCursor(const std::wstring& name)
|
||||
|
@ -202,6 +202,12 @@ public:
|
||||
UnloadHotkeys();
|
||||
}
|
||||
|
||||
static void CloseTopmostPage()
|
||||
{
|
||||
g_GUI->PopPage(JS::NullHandleValue);
|
||||
g_GUI->TickObjects();
|
||||
}
|
||||
|
||||
void test_PageRegainedFocusEvent()
|
||||
{
|
||||
// Load up a test page.
|
||||
@ -220,7 +226,7 @@ public:
|
||||
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 1);
|
||||
g_GUI->PushPage(L"regainFocus/page_emptyPage.xml", data);
|
||||
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 2);
|
||||
g_GUI->PopPage(data);
|
||||
CloseTopmostPage();
|
||||
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 1);
|
||||
|
||||
// This page instantly pushes an empty page with a callback that pops another page again.
|
||||
@ -228,9 +234,7 @@ public:
|
||||
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 3);
|
||||
|
||||
// Pop the empty page
|
||||
g_GUI->PopPage(data);
|
||||
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 2);
|
||||
scriptInterface->GetContext().RunJobs();
|
||||
CloseTopmostPage();
|
||||
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 1);
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user