diff --git a/binaries/data/mods/_test.gui/gui/Result/Result.js b/binaries/data/mods/_test.gui/gui/Result/Result.js
new file mode 100644
index 0000000000..6edd414e33
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/Result/Result.js
@@ -0,0 +1,4 @@
+async function init(arg)
+{
+ return arg ? Engine.startAtlas : undefined;
+}
diff --git a/binaries/data/mods/_test.gui/gui/Result/Result.xml b/binaries/data/mods/_test.gui/gui/Result/Result.xml
new file mode 100644
index 0000000000..2a39d60b34
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/Result/Result.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/binaries/data/mods/_test.gui/gui/Result/page_Result.xml b/binaries/data/mods/_test.gui/gui/Result/page_Result.xml
new file mode 100644
index 0000000000..f40f0fa1fd
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/Result/page_Result.xml
@@ -0,0 +1,4 @@
+
+
+ Result/Result.xml
+
diff --git a/binaries/data/mods/public/gui/pregame/MainMenuItems.js b/binaries/data/mods/public/gui/pregame/MainMenuItems.js
index d84217bdc0..943e216dde 100644
--- a/binaries/data/mods/public/gui/pregame/MainMenuItems.js
+++ b/binaries/data/mods/public/gui/pregame/MainMenuItems.js
@@ -275,7 +275,7 @@ var g_MainMenuItems = [
{
"caption": translate("Scenario Editor"),
"tooltip": translate('Open the Atlas Scenario Editor in a new window. You can run this more reliably by starting the game with the command-line argument "-editor".'),
- "onPress": async() => {
+ "onPress": async(closePageCallback) => {
if (!Engine.AtlasIsAvailable())
{
messageBox(
@@ -292,7 +292,7 @@ var g_MainMenuItems = [
[translate("No"), translate("Yes")]);
if (buttonIndex === 1)
- Engine.RestartInAtlas();
+ closePageCallback(Engine.startAtlas);
}
},
{
diff --git a/source/gui/GUIManager.cpp b/source/gui/GUIManager.cpp
index 3eb2a0fe39..d82c135919 100644
--- a/source/gui/GUIManager.cpp
+++ b/source/gui/GUIManager.cpp
@@ -35,11 +35,14 @@
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/StructuredClone.h"
+#include "js/Equality.h"
+
namespace
{
const CStr EVENT_NAME_GAME_LOAD_PROGRESS = "GameLoadProgress";
const CStr EVENT_NAME_WINDOW_RESIZED = "WindowResized";
+constexpr const char* START_ATLAS{"startAtlas"};
} // anonymous namespace
@@ -165,6 +168,12 @@ void CGUIManager::SGUIPage::LoadPage(ScriptContext& scriptContext)
sendingPromise = std::make_shared(rq.cx,
JS::NewPromiseObject(rq.cx, nullptr));
+ {
+ JS::RootedString jsName{rq.cx, JS_NewStringCopyZ(rq.cx, START_ATLAS)};
+ 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);
+ }
gui->AddObjectTypes();
VfsPath path = VfsPath("gui") / m_Name;
@@ -257,7 +266,7 @@ JS::Value CGUIManager::SGUIPage::ReplacePromise(ScriptInterface& scriptInterface
return JS::ObjectValue(**receivingPromise);
}
-std::optional CGUIManager::SGUIPage::MaybeClose()
+std::optional CGUIManager::SGUIPage::MaybeClose(const bool topmostPage)
{
if (JS::GetPromiseState(*sendingPromise) == JS::PromiseState::Pending)
return std::nullopt;
@@ -267,8 +276,20 @@ std::optional CGUIManager::SGUIPage::MaybeCl
const ScriptRequest rq{gui->GetScriptInterface()};
JS::RootedValue arg{rq.cx, JS::GetPromiseResult(*sendingPromise)};
- return CGUIManager::SGUIPage::CloseResult{Script::WriteStructuredClone(rq, arg),
- 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 symbol{rq.cx};
+ Script::GetProperty(rq, nativeScope, START_ATLAS, &symbol);
+ bool equals;
+ if (!JS::StrictlyEqual(rq.cx, arg, symbol, &equals))
+ throw std::runtime_error{"Error while comparing return value to a symbol."};
+
+ if (equals)
+ return CGUIManager::SGUIPage::CloseResult{nullptr, rejected};
+ }
+ return CGUIManager::SGUIPage::CloseResult{Script::WriteStructuredClone(rq, arg), rejected};
}
void CGUIManager::SGUIPage::Refocus(const CloseResult& result)
@@ -375,7 +396,7 @@ void CGUIManager::SendEventToAll(const CStr& eventName, JS::HandleValueArray par
p.gui->SendEventToAll(eventName, paramData);
}
-void CGUIManager::TickObjects()
+std::optional CGUIManager::TickObjects()
{
PROFILE3("gui tick");
@@ -394,16 +415,20 @@ void CGUIManager::TickObjects()
while (!m_PageStack.empty())
{
const size_t stackSize{m_PageStack.size()};
- const std::optional result{m_PageStack.back().MaybeClose()};
+ const std::optional result{
+ m_PageStack.back().MaybeClose(stackSize == 1)};
if (!result.has_value())
break;
ENSURE(m_PageStack.size() == stackSize);
m_PageStack.pop_back();
- if (!m_PageStack.empty())
+ if (m_PageStack.empty())
+ return !result.value().arg;
+ else
m_PageStack.back().Refocus(result.value());
m_ScriptContext.RunJobs();
}
+ return std::nullopt;
}
void CGUIManager::Draw(CCanvas2D& canvas) const
diff --git a/source/gui/GUIManager.h b/source/gui/GUIManager.h
index 140b62a3c4..359a6239cb 100644
--- a/source/gui/GUIManager.h
+++ b/source/gui/GUIManager.h
@@ -96,8 +96,10 @@ public:
/**
* See CGUI::TickObjects; applies to @em all loaded pages.
+ * When the root page is closed it's returned wheter Atlas should be
+ * started.
*/
- void TickObjects();
+ std::optional TickObjects();
/**
* See CGUI::Draw; applies to @em all loaded pages.
@@ -156,7 +158,7 @@ private:
* returns the result of the @c init function.
* If this page wasn't closed an empty optional is returned.
*/
- std::optional MaybeClose();
+ std::optional MaybeClose(const bool topmostPage);
/**
* This function should be called when a child page got closed. The
diff --git a/source/gui/tests/test_GuiManager.h b/source/gui/tests/test_GuiManager.h
index b46da35147..1f3fda722b 100644
--- a/source/gui/tests/test_GuiManager.h
+++ b/source/gui/tests/test_GuiManager.h
@@ -284,4 +284,16 @@ public:
CloseTopmostPage();
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 0);
}
+
+ void test_Result()
+ {
+ const ScriptRequest rq{g_GUI->GetScriptInterface()};
+ g_GUI->OpenChildPage(L"Result/page_Result.xml",
+ Script::WriteStructuredClone(rq, JS::FalseHandleValue));
+ TS_ASSERT(!g_GUI->TickObjects().value());
+
+ g_GUI->OpenChildPage(L"Result/page_Result.xml",
+ Script::WriteStructuredClone(rq, JS::TrueHandleValue));
+ TS_ASSERT(g_GUI->TickObjects().value());
+ }
};
diff --git a/source/main.cpp b/source/main.cpp
index 0e242ebd1f..b10e51b606 100644
--- a/source/main.cpp
+++ b/source/main.cpp
@@ -172,11 +172,6 @@ void RestartEngine()
g_Shutdown = ShutdownType::Restart;
}
-void StartAtlas()
-{
- g_Shutdown = ShutdownType::RestartAsAtlas;
-}
-
// main app message handler
static InReaction MainInputHandler(const SDL_Event_* ev)
{
@@ -430,9 +425,9 @@ static void Frame(RL::Interface* rlInterface)
if (g_NetClient)
g_NetClient->Poll();
- g_GUI->TickObjects();
- if (g_GUI->GetPageCount() == 0)
- QuitEngine();
+ std::optional completionCommand{g_GUI->TickObjects()};
+ if (completionCommand.has_value())
+ g_Shutdown = completionCommand.value() ? ShutdownType::RestartAsAtlas : ShutdownType::Quit;
if (rlInterface)
rlInterface->TryApplyMessage();
diff --git a/source/ps/scripting/JSInterface_Main.cpp b/source/ps/scripting/JSInterface_Main.cpp
index 17e70748ac..13e6f85915 100644
--- a/source/ps/scripting/JSInterface_Main.cpp
+++ b/source/ps/scripting/JSInterface_Main.cpp
@@ -34,15 +34,8 @@
#include "scriptinterface/FunctionWrapper.h"
#include "tools/atlas/GameInterface/GameLoop.h"
-extern void StartAtlas();
-
namespace JSI_Main
{
-void StartAtlas()
-{
- ::StartAtlas();
-}
-
bool AtlasIsAvailable()
{
return ATLAS_IsAvailable();
@@ -120,7 +113,6 @@ std::string CalculateMD5(const std::string& input)
void RegisterScriptFunctions(const ScriptRequest& rq)
{
- ScriptFunction::Register<&StartAtlas>(rq, "RestartInAtlas");
ScriptFunction::Register<&AtlasIsAvailable>(rq, "AtlasIsAvailable");
ScriptFunction::Register<&IsAtlasRunning>(rq, "IsAtlasRunning");
ScriptFunction::Register<&OpenURL>(rq, "OpenURL");