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