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 "DownloadQueue.hpp"
24#include "Button.hpp"
25
26CST_Renderer* RootDisplay::renderer = NULL;
27CST_Window* RootDisplay::window = NULL;
28Element* RootDisplay::subscreen = NULL;
29Element* RootDisplay::nextsubscreen = NULL;
30RootDisplay* RootDisplay::mainDisplay = NULL;
31bool RootDisplay::isDebug = false;
32
33int RootDisplay::screenWidth = 1280;
34int RootDisplay::screenHeight = 720;
35float RootDisplay::dpiScale = 1.0f;
36
37bool RootDisplay::idleCursorPulsing = false;
38
39RootDisplay::RootDisplay()
40{
41 // initialize the romfs for switch/wiiu
42#if defined(USE_RAMFS)
43 ramfsInit();
44#endif
45 // initialize internal drawing library
46 CST_DrawInit(this);
47
48 this->x = 0;
49 this->y = 0;
50 this->width = SCREEN_WIDTH;
51 this->height = SCREEN_HEIGHT;
52
53 this->hasBackground = true;
54
55#ifdef __APPLE__
56 chdirForPlatform();
57#endif
58
59 // set the display scale on high resolution displays
60 RootDisplay::dpiScale = CST_GetDpiScale();
61
62 // default background color is dark-gray, can be overridden by the implementing library
63 this->backgroundColor = fromRGB(30, 30, 30);
64
65 // set starting resolution based on SDL version
66#if defined(WII) || defined(WII_MOCK)
67 setScreenResolution(640, 480);
68#elif defined(_3DS) || defined(_3DS_MOCK)
69 setScreenResolution(400, 480); // 3ds has a special resolution!
70#else
71 // setScreenResolution(640, 480);
72 setScreenResolution(1280, 720);
73#endif
74 // the main input handler
75 this->events = new InputEvents();
76
77 // TODO: initialize this in a way that doesn't block the main thread
78 // always load english first, to initialize defaults
79 TextElement::loadI18nCache("en-us");
80
81 // TODO: detect language and system, and store preference
82 // TextElement::loadI18nCache("zh-cn");
83}
84
85void RootDisplay::initMusic()
86{
87#ifdef SWITCH
88 // no music if we're in applet mode
89 // they use up too much memory, and a lot of people only use applet mode
90 AppletType at = appletGetAppletType();
91 if (at != AppletType_Application && at != AppletType_SystemApplication) {
92 return;
93 }
94#endif
95
96 // Initialize CST_mixer
97 CST_MixerInit(this);
98}
99
100void RootDisplay::startMusic()
101{
102 CST_FadeInMusic(this);
103}
104
105void RootDisplay::setScreenResolution(int width, int height)
106{
107 // set the screen resolution
108 SCREEN_WIDTH = width;
109 SCREEN_HEIGHT = height;
110
111 // update the root element
112 this->width = SCREEN_WIDTH;
113 this->height = SCREEN_HEIGHT;
114
115 // update the renderer, but respect the DPI scaling
116 CST_SetWindowSize(window, SCREEN_WIDTH / RootDisplay::dpiScale, SCREEN_HEIGHT / RootDisplay::dpiScale);
117}
118
119RootDisplay::~RootDisplay()
120{
121 CST_DrawExit();
122
123#if defined(USE_RAMFS)
124 ramfsExit();
125#endif
126}
127
129{
130 if (nextsubscreen != subscreen)
131 {
132 delete subscreen;
133 subscreen = nextsubscreen;
134 return true;
135 }
136
137 // process either the subscreen or the children elements, always return true if "dragging"
138 // (may be a mouse cursor or wiimote pointing and moving on the screen)
139
140 if (RootDisplay::subscreen)
141 return RootDisplay::subscreen->process(event) || event->isTouchDrag();
142
143 // keep processing child elements
144 return super::process(event) || event->isTouchDrag();
145}
146
148{
149 if (RootDisplay::subscreen)
150 {
151 RootDisplay::subscreen->render(this);
152 this->update();
153 return;
154 }
155
156 // render the rest of the subelements
158
159 // commit everything to the screen
160 this->update();
161}
162
163void RootDisplay::update()
164{
165 // never exceed 60fps because there's no point
166 // commented out, as if render isn't called manually,
167 // the CST_Delay in the input processing loop should handle this
168
169 // int now = CST_GetTicks();
170 // int diff = now - this->lastFrameTime;
171
172 // if (diff < 16)
173 // return;
174
175 CST_RenderPresent(this->renderer);
176 // this->lastFrameTime = now;
177}
178
179void RootDisplay::switchSubscreen(Element* next)
180{
181 if (nextsubscreen != subscreen)
182 delete nextsubscreen;
183 nextsubscreen = next;
184}
185
186void RootDisplay::requestQuit()
187{
188 // if we've already requested quit, don't proceed
189 if (hasRequestedQuit) {
190 return;
191 }
192 hasRequestedQuit = true;
193
194 // depending on the platform, either break our loop or (wiiu) switch to the home menu
195#ifdef __WIIU__
196 SYSLaunchMenu();
197#else
198 this->isAppRunning = false;
199#endif
200}
201
202int RootDisplay::mainLoop()
203{
204 DownloadQueue::init();
205
206#ifdef __WIIU__
207 // setup procui callback for resuming application to force a chesto render
208 // https://stackoverflow.com/a/56145528 and http://bannalia.blogspot.com/2016/07/passing-capturing-c-lambda-functions-as.html
209 auto updateDisplay = +[](void* display) -> unsigned int {
210 ((RootDisplay*)display)->futureRedrawCounter = 10;
211 return 0;
212 };
213 ProcUIRegisterCallback(PROCUI_CALLBACK_ACQUIRE, updateDisplay, this, 100);
214
215 // also, register a callback for when we need to quit, to break out the main loop
216 // (other platforms will do this directly, but wiiu needs procui to do stuff first)
217 auto actuallyQuit = +[](void* display) -> unsigned int {
218 ((RootDisplay*)display)->isAppRunning = false;
219 return 0;
220 };
221 ProcUIRegisterCallback(PROCUI_CALLBACK_EXIT, actuallyQuit, this, 100);
222#endif
223
224 while (isAppRunning)
225 {
226 bool atLeastOneNewEvent = false;
227 bool viewChanged = false;
228
229 int frameStart = CST_GetTicks();
230
231 // update download queue
232 DownloadQueue::downloadQueue->process();
233
234 // get any new input events
235 while (events->update())
236 {
237 // process the inputs of the supplied event
238 viewChanged |= this->process(events);
239 atLeastOneNewEvent = true;
240
241 // if we see a minus, exit immediately!
242 if (this->canUseSelectToExit && events->pressed(SELECT_BUTTON)) {
243 requestQuit();
244 }
245 }
246
247 // one more event update if nothing changed or there were no previous events seen
248 // needed to non-input related processing that might update the screen to take place
249 if ((!atLeastOneNewEvent && !viewChanged))
250 {
251 events->update();
252 viewChanged |= this->process(events);
253 }
254
255 // draw the display if we processed an event or the view
256 if (viewChanged)
257 this->render(NULL);
258 else
259 {
260 // delay for the remainder of the frame to keep up to 60fps
261 // (we only do this if we didn't draw to not waste energy
262 // if we did draw, then proceed immediately without waiting for smoother progress bars / scrolling)
263 int delayTime = (CST_GetTicks() - frameStart);
264 if (delayTime < 0)
265 delayTime = 0;
266 if (delayTime < 16)
267 CST_Delay(16 - delayTime);
268 }
269
270 // free up any elements that are in the trash
271 this->recycle();
272 }
273
274 delete events;
275
276 if (!isProtected) delete this;
277 DownloadQueue::quit();
278
279 return 0;
280}
281
282void RootDisplay::recycle()
283{
284 for (auto e : trash)
285 e->wipeAll(true);
286 trash.clear();
287}
int process()
process finished and queued downloads
virtual bool process(InputEvents *event)
process any input that is received for this element
Definition: Element.cpp:17
virtual void render(Element *parent)
display the current state of the display
Definition: Element.cpp:60
int width
width and height of this element (must be manually set, isn't usually calculated (but is in some case...
Definition: Element.hpp:123
Element * parent
the parent element (can sometimes be null if it isn't set)
Definition: Element.hpp:107
bool process(InputEvents *event)
process any input that is received for this element
void render(Element *parent)
display the current state of the display