Skip to content

Commit e480e17

Browse files
authored
Add PopupManager for queueing popups & managing other stuff (#2106)
* add the PopupManager class * use PopupManager in some places * change ms to seconds as a float
1 parent 6d99c25 commit e480e17

4 files changed

Lines changed: 447 additions & 21 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#pragma once
2+
3+
#include <Geode/binding/FLAlertLayer.hpp>
4+
#include <Geode/utils/cocos.hpp>
5+
#include <Geode/utils/ZStringView.hpp>
6+
#include <Geode/ui/Notification.hpp>
7+
8+
#include <deque>
9+
10+
namespace geode {
11+
12+
class [[nodiscard("call showQueue or showInstant to show the popup")]] GEODE_DLL ManagedPopup {
13+
friend class PopupManager;
14+
struct Data;
15+
16+
public:
17+
ManagedPopup(FLAlertLayer* layer);
18+
ManagedPopup();
19+
20+
ManagedPopup(const ManagedPopup& other) = default;
21+
ManagedPopup& operator=(const ManagedPopup& other) = default;
22+
ManagedPopup(ManagedPopup&& other) noexcept = default;
23+
ManagedPopup& operator=(ManagedPopup&& other) noexcept = default;
24+
25+
operator FLAlertLayer*() const;
26+
FLAlertLayer* operator->() const;
27+
FLAlertLayer* getInner() const;
28+
29+
// Set whether the popup should follow the user if they transition to another scene,
30+
// instead of disappearing. By default is disabled.
31+
void setPersistent(bool state = true);
32+
33+
// Makes the popup prioritized, ensuring that it will be put in front of the queue,
34+
// and shown even if the user is playing a level (the level will be paused).
35+
// Only effective if `showQueue()` is used, if `showInstant()` is used the popup already shows immediately.
36+
void setPriority(bool state = true);
37+
38+
// Makes it impossible to accidentally close the popup (via esc or back button)
39+
// for a given period of time after it is shown.
40+
void blockClosingFor(const asp::time::Duration& dur);
41+
42+
// Makes it impossible to accidentally close the popup (via esc or back button)
43+
// for a given period of time (in seconds) after it is shown.
44+
void blockClosingFor(float dur);
45+
46+
// Shows the popup to the user immediately, does nothing if already shown.
47+
void showInstant();
48+
49+
// Queues the popup to be shown to the user when it's appropriate,
50+
// e.g. not while they're in a level and unpaused or in the middle of a transition
51+
// This should be the preferred way of showing a popup, and will work as intended when inside e.g. an `init` method of a layer.
52+
void showQueue();
53+
54+
bool isShown();
55+
56+
bool shouldPreventClosing();
57+
58+
private:
59+
geode::Ref<FLAlertLayer> inner;
60+
61+
Data& getFields();
62+
bool hasFields() const;
63+
void doShow(bool reshowing = false);
64+
};
65+
66+
class GEODE_DLL PopupManager final : public cocos2d::CCObject {
67+
friend class SingletonNodeBase;
68+
friend class ManagedPopup;
69+
PopupManager();
70+
71+
public:
72+
constexpr static float DEFAULT_WIDTH = 370.f;
73+
74+
PopupManager(const PopupManager&) = delete;
75+
PopupManager& operator=(const PopupManager&) = delete;
76+
PopupManager(PopupManager&&) = delete;
77+
PopupManager& operator=(PopupManager&&) = delete;
78+
79+
static PopupManager& get();
80+
81+
// Creates a popup with the given title and content (optionally button 1, 2 and width). Does not show the popup to the user.
82+
ManagedPopup alert(
83+
ZStringView title,
84+
const std::string& content,
85+
ZStringView btn1 = "Ok",
86+
ZStringView btn2 = nullptr,
87+
float width = DEFAULT_WIDTH
88+
);
89+
90+
// Creates a popup with the given title and content (optionally button 1, 2 and width). Does not show the popup to the user.
91+
// The callback is involved when the user presses either of the buttons in the popup.
92+
ManagedPopup quickPopup(
93+
ZStringView title,
94+
const std::string& content,
95+
ZStringView btn1 = "Ok",
96+
ZStringView btn2 = nullptr,
97+
geode::Function<void (FLAlertLayer*, bool)> callback = {},
98+
float width = DEFAULT_WIDTH
99+
);
100+
101+
// Creates a popup with the given title and content as a formatted string. Does not show the popup to the user.
102+
template <class... Args>
103+
ManagedPopup alertFormat(
104+
ZStringView title,
105+
fmt::format_string<Args...> fmt,
106+
Args&&... args
107+
) {
108+
return alert(title, fmt::format(fmt, std::forward<Args>(args)...));
109+
}
110+
111+
// Create a ManagedPopup for this custom popup that can be used to manage it.
112+
// Don't call this if the popup has already been shown.
113+
ManagedPopup manage(FLAlertLayer* alert);
114+
115+
bool isManaged(FLAlertLayer* alert);
116+
117+
// Returns whether there are currently any queued popups that can't be shown,
118+
// either because the player is transitioning or in a level and unpaused.
119+
bool hasPendingPopups() const;
120+
121+
private:
122+
cocos2d::CCScene* m_prevScene = nullptr;
123+
bool m_isTransitioning = false;
124+
std::vector<Ref<FLAlertLayer>> m_savedAlerts;
125+
std::deque<ManagedPopup> m_queuedPopups;
126+
127+
void changedScene(cocos2d::CCScene* newScene);
128+
void queuePopup(const ManagedPopup& popup, bool back = true);
129+
void update(float dt);
130+
};
131+
132+
}

loader/src/hooks/MenuLayer.cpp

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <Geode/ui/BasedButtonSprite.hpp>
88
#include <Geode/ui/Notification.hpp>
99
#include <Geode/ui/Popup.hpp>
10+
#include <Geode/ui/PopupManager.hpp>
1011
#include <Geode/ui/MDPopup.hpp>
1112
#include <Geode/utils/cocos.hpp>
1213
#include <Geode/utils/web.hpp>
@@ -143,24 +144,22 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
143144
// show in safe mode
144145
auto isSafeMode = LoaderImpl::get()->isSafeMode();
145146
if (isSafeMode) {
146-
Loader::get()->queueInMainThread([] {
147-
auto popup = createQuickPopup(
148-
"Safe Mode",
149-
"Geode is running in <cy>Safe Mode</c>.\n"
150-
"Mods are <cr>not loaded</c> in this mode.\n"
151-
"\n"
152-
"You can use this to <co>disable</c> or <co>update</c> mods "
153-
"causing issues but all mod features\n"
154-
"<cy>are not available</c>.",
155-
"OK",
156-
nullptr,
157-
[](auto, bool btn2) {},
158-
false
159-
);
147+
auto popup = PopupManager::get().quickPopup(
148+
"Safe Mode",
149+
"Geode is running in <cy>Safe Mode</c>.\n"
150+
"Mods are <cr>not loaded</c> in this mode.\n"
151+
"\n"
152+
"You can use this to <co>disable</c> or <co>update</c> mods "
153+
"causing issues but all mod features\n"
154+
"<cy>are not available</c>.",
155+
"OK",
156+
nullptr,
157+
[](auto, bool btn2) {},
158+
false
159+
);
160160

161-
popup->m_noElasticity = true;
162-
popup->show();
163-
});
161+
popup->m_noElasticity = true;
162+
popup.showQueue();
164163
}
165164

166165
// show if the user tried to be naughty and load arbitrary DLLs

0 commit comments

Comments
 (0)