Chesto 0.9
A declarative and element-based library for creating GUIs on homebrew'd consoles
RootDisplay.cpp
1#if defined(__EMSCRIPTEN__)
2#include <emscripten.h>
3#elif defined(SWITCH)
4#include <switch.h>
5#define PLATFORM "Switch"
6#elif defined(__WIIU__)
7#define PLATFORM "Wii U"
8#include <coreinit/core.h>
9#include <coreinit/foreground.h>
10#include <proc_ui/procui.h>
11#include <sysapp/launch.h>
12#elif defined(_3DS)
13#define PLATFORM "3DS"
14#else
15#define PLATFORM "Console"
16#endif
17
18#if defined(USE_RAMFS)
19#include "../libs/resinfs/include/romfs-wiiu.h"
20#endif
21
22#include "RootDisplay.hpp"
23#include "Screen.hpp"
24#include "DownloadQueue.hpp"
25#include "Button.hpp"
26#include "TextElement.hpp"
27#include <vector>
28
29namespace Chesto {
30
31CST_Renderer* RootDisplay::renderer = NULL;
32CST_Window* RootDisplay::window = NULL;
33RootDisplay* RootDisplay::mainDisplay = NULL;
34bool RootDisplay::isDebug = false;
35
36std::vector<std::unique_ptr<Screen>> RootDisplay::screenStack;
37std::vector<std::function<void()>> RootDisplay::deferredActions;
38bool RootDisplay::isProcessingEvents = false;
39
40int RootDisplay::screenWidth = 1280;
41int RootDisplay::screenHeight = 720;
42float RootDisplay::dpiScale = 1.0f;
43float RootDisplay::globalScale = 1.0f;
44
45bool RootDisplay::idleCursorPulsing = false;
46
47RootDisplay::RootDisplay()
48{
49 // initialize the romfs for switch/wiiu
50#if defined(USE_RAMFS)
51 ramfsInit();
52#endif
53 // initialize internal drawing library
54 CST_DrawInit(this);
55
56 this->x = 0;
57 this->y = 0;
58 this->width = SCREEN_WIDTH;
59 this->height = SCREEN_HEIGHT;
60
61 this->hasBackground = true;
62
63#ifdef __APPLE__
64 chdirForPlatform();
65#endif
66
67 // set the display scale on high resolution displays
68 RootDisplay::dpiScale = CST_GetDpiScale();
69
70 // default background color is dark-gray, can be overridden by the implementing library
71 this->backgroundColor = fromRGB(30, 30, 30);
72
73 // set starting resolution based on SDL version
74#if defined(WII) || defined(WII_MOCK)
75 // setScreenResolution(640, 480);
76 setScreenResolution(854, 480);
77#elif defined(_3DS) || defined(_3DS_MOCK)
78 setScreenResolution(400, 480); // 3ds has a special resolution!
79#else
80 // setScreenResolution(640, 480);
81 setScreenResolution(1280, 720);
82#endif
83 // the main input handler
84 this->events = std::make_unique<InputEvents>();
85
86 // TODO: initialize this in a way that doesn't block the main thread
87 // always load english first, to initialize defaults
88 TextElement::loadI18nCache("en-us");
89
90 // TODO: detect language and system, and store preference
91 // TextElement::loadI18nCache("zh-cn");
92
93 // Initialize download queue early so it's available during screen construction
94 DownloadQueue::init();
95}
96
97void RootDisplay::initMusic()
98{
99#ifdef SWITCH
100 // no music if we're in applet mode
101 // they use up too much memory, and a lot of people only use applet mode
102 AppletType at = appletGetAppletType();
103 if (at != AppletType_Application && at != AppletType_SystemApplication) {
104 return;
105 }
106#endif
107
108 // Initialize CST_mixer
109 CST_MixerInit(this);
110}
111
112void RootDisplay::startMusic()
113{
114 CST_FadeInMusic(this);
115}
116
117void RootDisplay::setScreenResolution(int width, int height)
118{
119 // set the screen resolution
120 SCREEN_WIDTH = width;
121 SCREEN_HEIGHT = height;
122
123 // update the root element
124 this->width = SCREEN_WIDTH;
125 this->height = SCREEN_HEIGHT;
126
127 // update the renderer, but respect the DPI scaling
128 CST_SetWindowSize(window, SCREEN_WIDTH / RootDisplay::dpiScale, SCREEN_HEIGHT / RootDisplay::dpiScale);
129
130 RootDisplay::deferAction([]() {
131 // inform all screens of the resolution change
132 for (auto& screen : screenStack) {
133 screen->rebuildUI();
134 }
135 });
136}
137
138// Screen stack management
139void RootDisplay::pushScreen(std::unique_ptr<Screen> screen)
140{
141 if (!screen) return;
142
143 // PUSHes are not deferred! They go right onto the stack and render immediately
144 screenStack.push_back(std::move(screen));
145 if (mainDisplay) mainDisplay->needsRedraw = true;
146}
147
148void RootDisplay::popScreen()
149{
150 if (isProcessingEvents) {
151 // Defer POP until after event processing
152 deferAction([]() {
153 if (!screenStack.empty()) {
154 screenStack.pop_back();
155 if (mainDisplay) mainDisplay->needsRedraw = true;
156 }
157 });
158 } else {
159 // Safe to execute immediately
160 if (!screenStack.empty()) {
161 screenStack.pop_back();
162 if (mainDisplay) mainDisplay->needsRedraw = true;
163 }
164 }
165}
166
167void RootDisplay::clearScreens()
168{
169 if (isProcessingEvents) {
170 // Defer operation until after event processing
171 deferAction([]() {
172 screenStack.clear();
173 if (mainDisplay) mainDisplay->needsRedraw = true;
174 });
175 } else {
176 screenStack.clear();
177 if (mainDisplay) mainDisplay->needsRedraw = true;
178 }
179}
180
181void RootDisplay::deferAction(std::function<void()> action)
182{
183 // Queue the action to be executed after event processing
184 deferredActions.push_back(std::move(action));
185}
186
187void RootDisplay::processDeferredActions()
188{
189 // Execute all deferred actions, on a copy
190 auto actionsToRun = std::move(deferredActions);
191 deferredActions.clear();
192
193 for (auto& action : actionsToRun) {
194 if (action) {
195 action();
196 }
197 }
198}
199
200Screen* RootDisplay::topScreen()
201{
202 return screenStack.empty() ? nullptr : screenStack.back().get();
203}
204
205bool RootDisplay::hasScreens()
206{
207 return !screenStack.empty();
208}
209
210RootDisplay::~RootDisplay()
211{
212 // Clean up screens and root element children before destroying download queue
213 // This ensures NetImageElements can cancel downloads properly
214 screenStack.clear();
215 elements.clear();
216
217 // Now safe to destroy download queue
218 DownloadQueue::quit();
219
220 CST_DrawExit();
221
222#if defined(USE_RAMFS)
223 ramfsExit();
224#endif
225}
226
228{
229 // process either the subscreen or the children elements, always return true if "dragging"
230 // (may be a mouse cursor or wiimote pointing and moving on the screen)
231
232 bool result = false;
233
234 if (!screenStack.empty()) {
235 result = screenStack.back()->process(event) || event->isTouchDrag();
236 } else {
237 // keep processing child elements
238 result = super::process(event) || event->isTouchDrag();
239 }
240
241 return result;
242}
243
245{
246 // if we have a screen stack, render each screen as layers
247 if (!screenStack.empty())
248 {
250
251 for (const auto& screen : screenStack) {
252 screen->render(this);
253 }
254
255 this->update();
256 return;
257 }
258
259 // render the rest of the subelements
261
262 // commit everything to the screen
263 this->update();
264}
265
266void RootDisplay::update()
267{
268 // never exceed 60fps because there's no point
269 // commented out, as if render isn't called manually,
270 // the CST_Delay in the input processing loop should handle this
271
272 // int now = CST_GetTicks();
273 // int diff = now - this->lastFrameTime;
274
275 // if (diff < 16)
276 // return;
277
278 CST_RenderPresent(this->renderer);
279 // this->lastFrameTime = now;
280}
281
282void RootDisplay::requestQuit()
283{
284 // if we've already requested quit, don't proceed
285 if (hasRequestedQuit) {
286 return;
287 }
288 hasRequestedQuit = true;
289
290 // depending on the platform, either break our loop or (wiiu) switch to the home menu
291#ifdef __WIIU__
292 SYSLaunchMenu();
293#else
294 this->isAppRunning = false;
295#endif
296}
297
298int RootDisplay::mainLoop()
299{
300#ifdef __WIIU__
301 // setup procui callback for resuming application to force a chesto render
302 // https://stackoverflow.com/a/56145528 and http://bannalia.blogspot.com/2016/07/passing-capturing-c-lambda-functions-as.html
303 auto updateDisplay = +[](void* display) -> unsigned int {
304 ((RootDisplay*)display)->futureRedrawCounter = 10;
305 return 0;
306 };
307 ProcUIRegisterCallback(PROCUI_CALLBACK_ACQUIRE, updateDisplay, this, 100);
308
309 // also, register a callback for when we need to quit, to break out the main loop
310 // (other platforms will do this directly, but wiiu needs procui to do stuff first)
311 auto actuallyQuit = +[](void* display) -> unsigned int {
312 ((RootDisplay*)display)->isAppRunning = false;
313 return 0;
314 };
315 ProcUIRegisterCallback(PROCUI_CALLBACK_EXIT, actuallyQuit, this, 100);
316#endif
317
318 while (isAppRunning)
319 {
320 bool atLeastOneNewEvent = false;
321 bool viewChanged = false;
322
323 int frameStart = CST_GetTicks();
324
325 // update download queue
326 DownloadQueue::downloadQueue->process();
327
328 // get any new input events
329 while (events->update())
330 {
331 isProcessingEvents = true;
332
333 // process the inputs of the supplied event
334 viewChanged |= this->process(events.get());
335 atLeastOneNewEvent = true;
336
337 isProcessingEvents = false;
338
339 // if we see a minus, exit immediately!
340 if (this->canUseSelectToExit && events->pressed(SELECT_BUTTON)) {
341 requestQuit();
342 }
343 } // one more event update if nothing changed or there were no previous events seen
344 // needed to non-input related processing that might update the screen to take place
345 if ((!atLeastOneNewEvent && !viewChanged))
346 {
347 isProcessingEvents = true;
348 events->update();
349 viewChanged |= this->process(events.get());
350 isProcessingEvents = false;
351 }
352
353 // Event processing done, so run any deferred actions
354 processDeferredActions();
355
356 // draw the display if we processed an event or the view
357 if (viewChanged)
358 this->render(NULL);
359 else
360 {
361 // delay for the remainder of the frame to keep up to 60fps
362 // (we only do this if we didn't draw to not waste energy
363 // if we did draw, then proceed immediately without waiting for smoother progress bars / scrolling)
364 int delayTime = (CST_GetTicks() - frameStart);
365 if (delayTime < 0)
366 delayTime = 0;
367 if (delayTime < 16)
368 CST_Delay(16 - delayTime);
369 }
370 }
371
372 // unique_ptr will automatically clean up events
373
374 return 0;
375}
376
377} // namespace Chesto
std::vector< std::unique_ptr< Element, std::function< void(Element *)> > > elements
visible GUI child elements of this element
Definition: Element.hpp:59
int width
width and height of this element (must be manually set, isn't usually calculated (but is in some case...
Definition: Element.hpp:132
virtual void render(Element *parent)
display the current state of the display
Definition: Element.cpp:89
virtual bool process(InputEvents *event)
process any input that is received for this element
Definition: Element.cpp:29
bool needsRedraw
whether or not this element needs the screen redrawn next time it's processed
Definition: Element.hpp:95
Element * parent
the parent element (reference only, not owned)
Definition: Element.hpp:116
std::function< void()> action
the action to call (from binded callback) on touch or button selection https://stackoverflow....
Definition: Element.hpp:55
bool process(InputEvents *event)
process any input that is received for this element
void render(Element *parent)
display the current state of the display