Chesto 0.9
A declarative and element-based library for creating GUIs on homebrew'd consoles
DrawUtils.cpp
1#include <stdio.h>
2#include <stdlib.h>
3#include <unistd.h>
4
5
6// This file should contain all external drawing SDL2 calls
7// programs outside of chesto should not be
8// responsible for directly interacting with SDL!
9#include "DrawUtils.hpp"
10#include "RootDisplay.hpp"
11
12char* musicData = NULL;
13
14bool CST_DrawInit(RootDisplay* root)
15{
16 int sdl2Flags = SDL_INIT_GAMECONTROLLER;
17
18 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | sdl2Flags) < 0)
19 {
20 printf("Failed to initialize SDL2 drawing library: %s\n", SDL_GetError());
21 return false;
22 }
23
24 CST_SetQualityHint("linear");
25 if (TTF_Init() < 0)
26 {
27 printf("Failed to initialize TTF font library: %s\n", SDL_GetError());
28 return false;
29 }
30
31 int SDLFlags = 0;
32 int windowFlags = 0;
33
34 SDLFlags |= SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC;
35 windowFlags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
36
37 root->window = SDL_CreateWindow(
38 NULL, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
39 SCREEN_WIDTH, SCREEN_HEIGHT, windowFlags);
40 root->renderer = SDL_CreateRenderer(root->window, -1, SDLFlags);
41 //Detach the texture
42 SDL_SetRenderTarget(root->renderer, NULL);
43
44 if (root->renderer == NULL || root->window == NULL)
45 {
46 SDL_Quit();
47 return false;
48 }
49
50 RootDisplay::mainDisplay = root;
51
52 for (int i = 0; i < SDL_NumJoysticks(); i++)
53 {
54 if (SDL_JoystickOpen(i) == NULL)
55 {
56 printf("SDL_JoystickOpen: %s\n", SDL_GetError());
57 SDL_Quit();
58 return false;
59 }
60 }
61
62 // set up the SDL needsRender event
63 root->needsRender.type = SDL_USEREVENT;
64 return true;
65}
66
67void CST_DrawExit()
68{
69 //IMG_Quit();
70 TTF_Quit();
71
72 SDL_Delay(10);
73 SDL_DestroyWindow(RootDisplay::mainDisplay->window);
74 SDL_QuitSubSystem(SDL_INIT_VIDEO);
75
76#ifdef MUSIC
77 auto root = RootDisplay::mainDisplay;
78 if (root->music != NULL)
79 {
80 Mix_FreeMusic(root->music);
81 root->music = NULL;
82 }
83 if (musicData != NULL)
84 {
85 free(musicData);
86 musicData = NULL;
87 }
88#endif
89
90 SDL_Quit();
91}
92
93void CST_MixerInit(RootDisplay* root)
94{
95#if defined(MUSIC)
96 Mix_Init(MIX_INIT_MP3);
97 if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 4096) != 0) {
98 printf("Failed to initialize SDL2 mixer: %s\n", Mix_GetError());
99 return;
100 }
101 // root->music will be null if file does not exist or some issue
102 // if it does exist, we'll read it all into memory to avoid streaming from disk
103 const char* filename = "background.mp3";
104 FILE* f = fopen(filename, "rb");
105 if (f == NULL) {
106 return;
107 }
108 fseek(f, 0, SEEK_END);
109 long fsize = ftell(f);
110 if (fsize <= 0) {
111 fclose(f);
112 return;
113 }
114
115 fseek(f, 0, SEEK_SET);
116
117 musicData = (char*)(malloc(fsize + 1));
118 fread(musicData, fsize, 1, f);
119 fclose(f);
120
121 musicData[fsize] = 0;
122
123 root->music = Mix_LoadMUS_RW(SDL_RWFromMem(musicData, fsize), 1);
124#endif
125}
126
127
128void CST_GetRGBA(Uint32 pixel, SDL_PixelFormat* format, CST_Color* cstColor)
129{
130 SDL_GetRGBA(pixel, format, &cstColor->r, &cstColor->g, &cstColor->b, &cstColor->a);
131}
132
133// https://stackoverflow.com/a/51238719/4953343
134bool CST_SavePNG(CST_Texture* texture, const char* file_name)
135{
136 auto renderer = RootDisplay::mainDisplay->renderer;
137 SDL_Texture* target = SDL_GetRenderTarget(renderer);
138 SDL_SetRenderTarget(renderer, texture);
139 printf("Error: %s\n", SDL_GetError());
140 int width, height;
141 SDL_QueryTexture(texture, NULL, NULL, &width, &height);
142 SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32, 0, 0, 0, 0);
143 SDL_RenderReadPixels(renderer, NULL, surface->format->format, surface->pixels, surface->pitch);
144 IMG_SavePNG(surface, file_name);
145 SDL_FreeSurface(surface);
146 SDL_SetRenderTarget(renderer, target);
147 return true;
148}
149
150void CST_FadeInMusic(RootDisplay* root)
151{
152#if defined(MUSIC)
153 if (root->music)
154 {
155 Mix_VolumeMusic(0.85 * MIX_MAX_VOLUME);
156 Mix_PlayMusic(root->music, -1);
157 Mix_VolumeMusic(0.85 * MIX_MAX_VOLUME);
158 }
159#endif
160}
161
162void CST_RenderPresent(CST_Renderer* renderer)
163{
164#ifdef _3DS_MOCK
165 // draw some borders around parts of the 3ds screen
166 CST_SetDrawColorRGBA(renderer, 0, 0, 0, 255);
167 CST_Rect rect = { 0, 240, 40, 240 };
168 CST_FillRect(renderer, &rect);
169 CST_Rect rect2 = { 360, 240, 40, 240 };
170 CST_FillRect(renderer, &rect2);
171 CST_Rect rect3 = { 0, 240, 400, 240 };
172 CST_DrawRect(renderer, &rect3);
173#endif
174
175 SDL_RenderPresent(renderer);
176}
177
178void CST_FreeSurface(CST_Surface* surface)
179{
180 SDL_FreeSurface(surface);
181}
182
183void CST_RenderCopy(CST_Renderer* dest, CST_Texture* src, CST_Rect* src_rect, CST_Rect* dest_rect)
184{
185 SDL_RenderCopy(dest, src, src_rect, dest_rect);
186}
187
188void CST_RenderCopyRotate(CST_Renderer* dest, CST_Texture* src, CST_Rect* src_rect, CST_Rect* dest_rect, int angle)
189{
190 SDL_RenderCopyEx(dest, src, src_rect, dest_rect, angle, NULL, SDL_FLIP_NONE);
191}
192
193void CST_SetDrawColor(CST_Renderer* renderer, CST_Color c)
194{
195 CST_SetDrawColorRGBA(renderer, c.r, c.g, c.b, c.a);
196}
197
198void CST_SetDrawColorRGBA(CST_Renderer* renderer, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
199{
200 SDL_SetRenderDrawColor(renderer, r, g, b, a);
201}
202
203void CST_FillRect(CST_Renderer* renderer, CST_Rect* dimens)
204{
205 SDL_RenderFillRect(renderer, dimens);
206}
207
208void CST_DrawRect(CST_Renderer* renderer, CST_Rect* dimens)
209{
210 SDL_RenderDrawRect(renderer, dimens);
211}
212
213void CST_DrawLine(CST_Renderer* renderer, int x, int y, int w, int h)
214{
215 SDL_RenderDrawLine(renderer, x, y, w, h);
216}
217
218void CST_SetDrawBlend(CST_Renderer* renderer, bool enabled)
219{
220 SDL_BlendMode mode = enabled ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE;
221 SDL_SetRenderDrawBlendMode(renderer, mode);
222}
223
224void CST_QueryTexture(CST_Texture* texture, int* w, int* h)
225{
226 SDL_QueryTexture(texture, nullptr, nullptr, w, h);
227}
228
229CST_Texture* CST_CreateTextureFromSurface(CST_Renderer* renderer, CST_Surface* surface, bool isAccessible )
230{
231 return SDL_CreateTextureFromSurface(renderer, surface);
232}
233
234void CST_SetQualityHint(const char* quality)
235{
236 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, quality);
237}
238
239void CST_filledCircleRGBA(CST_Renderer* renderer, uint32_t x, uint32_t y, uint32_t radius, uint32_t r, uint32_t g, uint32_t b, uint32_t a)
240{
241 #if !defined(_3DS) && !defined(_3DS_MOCK)
242 // TODO: filledCircleRGBA needs to take a surface on 3DS
243 filledCircleRGBA(renderer, x, y, radius, r, g, b, a);
244 #endif
245}
246
247void CST_SetWindowSize(CST_Window* window, int w, int h)
248{
249 // actually resize the window, and adjust it for high DPI
250 SDL_SetWindowSize(window, w, h);
251}
252
253
254void CST_Delay(int time)
255{
256 SDL_Delay(time);
257}
258
259int CST_GetTicks()
260{
261 return SDL_GetTicks();
262}
263
264void CST_LowRumble(InputEvents* event, int ms)
265{
266 auto joystick = SDL_JoystickFromInstanceID(event->event.jdevice.which);
267 if (joystick && SDL_JoystickGetAttached(joystick)) {
268 SDL_JoystickRumble(joystick, 0x400, 0x400, 200);
269 }
270}
271
272void CST_SetCursor(int cursor)
273{
274 if (cursor == CST_CURSOR_NONE) {
275 SDL_ShowCursor(SDL_DISABLE);
276 return;
277 }
278
279 SDL_ShowCursor(SDL_ENABLE);
280 auto sdlCursor = SDL_SYSTEM_CURSOR_ARROW;
281 if (cursor == CST_CURSOR_HAND) {
282 sdlCursor = SDL_SYSTEM_CURSOR_HAND;
283 } else if (cursor == CST_CURSOR_TEXT) {
284 sdlCursor = SDL_SYSTEM_CURSOR_IBEAM;
285 } else if (cursor == CST_CURSOR_SPINNER) {
286 sdlCursor = SDL_SYSTEM_CURSOR_WAIT;
287 }
288
289 SDL_SetCursor(SDL_CreateSystemCursor(sdlCursor));
290}
291
292bool CST_isRectOffscreen(CST_Rect* rect)
293{
294 // if this element will be offscreen, don't try to render it
295 if (rect->x > SCREEN_WIDTH + 10 || rect->y > SCREEN_HEIGHT + 10)
296 return true;
297
298 // same but for up direction (weirder, cause width and height need to be correct)
299 // (which may not be true for container elements (sounds like a css float problem...))
300 if (rect->x + rect->w < -10 || rect->y + rect->h < -10)
301 return true;
302
303 return false;
304}
305
306// returns the high-dpi scaling factor, by measuring the window size and the drawable size (ratio)
307float CST_GetDpiScale()
308{
309 int w, h;
310 SDL_GetWindowSize(RootDisplay::window, &w, &h);
311 int dw, dh;
312 SDL_GL_GetDrawableSize(RootDisplay::window, &dw, &dh);
313 return (dw / (float) w);
314}
315
316void CST_SetWindowTitle(const char* title)
317{
318 SDL_SetWindowTitle(RootDisplay::mainDisplay->window, title);
319}
320
321void CST_roundedBoxRGBA (
322 CST_Renderer *renderer,
323 Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
324 Sint16 rad, Uint8 r, Uint8 g, Uint8 b, Uint8 a
325) {
326 #if !defined(_3DS) && !defined(_3DS_MOCK)
327 // TODO: roundedBoxRGBA needs to take a surface on 3DS
328 roundedBoxRGBA(renderer, x1, y1, x2, y2, rad, r, g, b, a);
329 #endif
330}
331
332void CST_roundedRectangleRGBA (
333 CST_Renderer *renderer,
334 Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
335 Sint16 rad, Uint8 r, Uint8 g, Uint8 b, Uint8 a
336) {
337 #if !defined(_3DS) && !defined(_3DS_MOCK)
338 // TODO: roundedRectangleRGBA needs to take a surface on 3DS
339 roundedRectangleRGBA(renderer, x1, y1, x2, y2, rad, r, g, b, a);
340 #endif
341}
342
343void CST_rectangleRGBA (
344 CST_Renderer *renderer,
345 Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2,
346 Uint8 r, Uint8 g, Uint8 b, Uint8 a
347) {
348 #if !defined(_3DS) && !defined(_3DS_MOCK)
349 // TODO: rectangleRGBA needs to take a surface on 3DS
350 rectangleRGBA(renderer, x1, y1, x2, y2, r, g, b, a);
351 #endif
352}
353
354#ifdef MUSIC
355
356// returns a size-3 vector of: (title, artist, album)
357std::vector<std::string> CST_GetMusicInfo(CST_Music* music) {
358 std::vector<std::string> info;
359 // these methods can use recent (late 2019) SDL_Mixer versions to read info about the song
360 // (Currently commented out)
361 // info.push_back(Mix_GetMusicTitle(music));
362 // info.push_back(Mix_GetMusicArtistTag(music));
363 // info.push_back(Mix_GetMusicAlbumTag(music));
364
365 // have to use the mpg123 library manually to get this info
366 // adapted from https://gist.github.com/deepakjois/988032/640b7a41b0e62a394515697c142777ad3a1b8905
367 auto m = mpg123_new(NULL, NULL);
368 mpg123_open(m, "./background.mp3");
369 mpg123_seek(m, 0, SEEK_SET);
370 auto meta = mpg123_meta_check(m);
371
372 mpg123_id3v1* v1;
373 mpg123_id3v2* v2;
374
375 if (meta == MPG123_ID3 && mpg123_id3(m, &v1, &v2) == MPG123_OK) {
376 if (v2 != NULL) {
377 // fmt.Println("ID3V2 tag found")
378 info.push_back(v2->title && v2->title->p ? v2->title->p : "");
379 info.push_back(v2->artist && v2->artist->p ? v2->artist->p : "");
380 info.push_back(v2->album && v2->album->p ? v2->album->p : "");
381 mpg123_close(m);
382 return info;
383 }
384 if (v1 != NULL) {
385 // fmt.Println("ID3V1 tag found")
386 info.push_back(v1->title[0] ? v1->title : "");
387 info.push_back(v1->artist[0] ? v1->artist : "");
388 info.push_back(v1->album[0] ? v1->album : "");
389 mpg123_close(m);
390 return info;
391 }
392 }
393
394 // we couldn't find the tags, so just return empty strings
395 info.push_back("");
396 info.push_back("");
397 info.push_back("");
398 mpg123_close(m);
399 return info;
400
401}
402#endif
403
404// change into the directory of the executable for the current platform
405void chdirForPlatform()
406{
407 auto basePath = SDL_GetBasePath();
408 if (basePath != NULL) {
409 chdir(basePath);
410 SDL_free(basePath);
411 }
412}
413
414std::string replaceAll(std::string str, const std::string& from, const std::string& to) {
415 size_t start_pos = 0;
416 while((start_pos = str.find(from, start_pos)) != std::string::npos) {
417 str.replace(start_pos, from.length(), to);
418 start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
419 }
420 return str;
421}