main.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <assert.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <time.h>
  7. #include <SDL2/SDL.h>
  8. #include "./digits.h"
  9. #ifdef PENGER
  10. #include "./penger_walk_sheet.h"
  11. #endif
  12. #define FPS 60
  13. //#define DELTA_TIME (1.0f / FPS)
  14. #define SPRITE_CHAR_WIDTH (300 / 2)
  15. #define SPRITE_CHAR_HEIGHT (380 / 2)
  16. #define CHAR_WIDTH (300 / 2)
  17. #define CHAR_HEIGHT (380 / 2)
  18. #define CHARS_COUNT 8
  19. #define TEXT_WIDTH (CHAR_WIDTH * CHARS_COUNT)
  20. #define TEXT_HEIGHT (CHAR_HEIGHT)
  21. #define WIGGLE_COUNT 3
  22. #define WIGGLE_DURATION (0.40f / WIGGLE_COUNT)
  23. #define COLON_INDEX 10
  24. #define MAIN_COLOR_R 220
  25. #define MAIN_COLOR_G 220
  26. #define MAIN_COLOR_B 220
  27. #define PAUSE_COLOR_R 220
  28. #define PAUSE_COLOR_G 120
  29. #define PAUSE_COLOR_B 120
  30. #define BACKGROUND_COLOR_R 24
  31. #define BACKGROUND_COLOR_G 24
  32. #define BACKGROUND_COLOR_B 24
  33. #define SCALE_FACTOR 0.15f
  34. #define PENGER_SCALE 4
  35. #define PENGER_STEPS_PER_SECOND 3
  36. void secc(int code)
  37. {
  38. if (code < 0) {
  39. fprintf(stderr, "SDL pooped itself: %s\n", SDL_GetError());
  40. abort();
  41. }
  42. }
  43. void *secp(void *ptr)
  44. {
  45. if (ptr == NULL) {
  46. fprintf(stderr, "SDL pooped itself: %s\n", SDL_GetError());
  47. abort();
  48. }
  49. return ptr;
  50. }
  51. SDL_Surface *load_png_file_as_surface(uint32_t *data, size_t width, size_t height)
  52. {
  53. SDL_Surface* image_surface =
  54. secp(SDL_CreateRGBSurfaceFrom(
  55. data,
  56. (int) width,
  57. (int) height,
  58. 32,
  59. (int) width * 4,
  60. 0x000000FF,
  61. 0x0000FF00,
  62. 0x00FF0000,
  63. 0xFF000000));
  64. return image_surface;
  65. }
  66. SDL_Texture *load_digits_png_file_as_texture(SDL_Renderer *renderer)
  67. {
  68. SDL_Surface *image_surface = load_png_file_as_surface(digits_data, digits_width, digits_height);
  69. return secp(SDL_CreateTextureFromSurface(renderer, image_surface));
  70. }
  71. #ifdef PENGER
  72. SDL_Texture *load_penger_png_file_as_texture(SDL_Renderer *renderer)
  73. {
  74. SDL_Surface *image_surface = load_png_file_as_surface(penger_data, penger_width, penger_height);
  75. return secp(SDL_CreateTextureFromSurface(renderer, image_surface));
  76. }
  77. #endif
  78. void render_digit_at(SDL_Renderer *renderer, SDL_Texture *digits, size_t digit_index,
  79. size_t wiggle_index, int *pen_x, int *pen_y, float user_scale, float fit_scale)
  80. {
  81. const int effective_digit_width = (int) floorf((float) CHAR_WIDTH * user_scale * fit_scale);
  82. const int effective_digit_height = (int) floorf((float) CHAR_HEIGHT * user_scale * fit_scale);
  83. const SDL_Rect src_rect = {
  84. (int) (digit_index * SPRITE_CHAR_WIDTH),
  85. (int) (wiggle_index * SPRITE_CHAR_HEIGHT),
  86. SPRITE_CHAR_WIDTH,
  87. SPRITE_CHAR_HEIGHT
  88. };
  89. const SDL_Rect dst_rect = {
  90. *pen_x,
  91. *pen_y,
  92. effective_digit_width,
  93. effective_digit_height
  94. };
  95. SDL_RenderCopy(renderer, digits, &src_rect, &dst_rect);
  96. *pen_x += effective_digit_width;
  97. }
  98. #ifdef PENGER
  99. void render_penger_at(SDL_Renderer *renderer, SDL_Texture *penger, float time, int flipped, SDL_Window *window)
  100. {
  101. int window_width, window_height;
  102. SDL_GetWindowSize(window, &window_width, &window_height);
  103. int sps = PENGER_STEPS_PER_SECOND;
  104. int step = (int)(time*sps)%(60*sps); //step index [0,60*sps-1]
  105. float progress = step/(60.0*sps); // [0,1]
  106. int frame_index = step%2;
  107. float penger_drawn_width = ((float)penger_width / 2) / PENGER_SCALE;
  108. float penger_walk_width = window_width + penger_drawn_width;
  109. const SDL_Rect src_rect = {
  110. (int) (penger_width / 2) * frame_index,
  111. 0,
  112. (int) penger_width / 2,
  113. (int) penger_height
  114. };
  115. SDL_Rect dst_rect = {
  116. floorf((float)penger_walk_width * progress - penger_drawn_width),
  117. window_height - (penger_height / PENGER_SCALE),
  118. (int) (penger_width / 2) / PENGER_SCALE,
  119. (int) penger_height / PENGER_SCALE
  120. };
  121. SDL_RenderCopyEx(renderer, penger, &src_rect, &dst_rect, 0, NULL, flipped);
  122. }
  123. #endif
  124. void initial_pen(SDL_Window *window, int *pen_x, int *pen_y, float user_scale, float *fit_scale)
  125. {
  126. int w, h;
  127. SDL_GetWindowSize(window, &w, &h);
  128. float text_aspect_ratio = (float) TEXT_WIDTH / (float) TEXT_HEIGHT;
  129. float window_aspect_ratio = (float) w / (float) h;
  130. if(text_aspect_ratio > window_aspect_ratio) {
  131. *fit_scale = (float) w / (float) TEXT_WIDTH;
  132. } else {
  133. *fit_scale = (float) h / (float) TEXT_HEIGHT;
  134. }
  135. const int effective_digit_width = (int) floorf((float) CHAR_WIDTH * user_scale * *fit_scale);
  136. const int effective_digit_height = (int) floorf((float) CHAR_HEIGHT * user_scale * *fit_scale);
  137. *pen_x = w / 2 - effective_digit_width * CHARS_COUNT / 2;
  138. *pen_y = h / 2 - effective_digit_height / 2;
  139. }
  140. typedef enum {
  141. MODE_ASCENDING = 0,
  142. MODE_COUNTDOWN,
  143. MODE_CLOCK,
  144. } Mode;
  145. float parse_time(const char *time)
  146. {
  147. float result = 0.0f;
  148. while (*time) {
  149. char *endptr = NULL;
  150. float x = strtof(time, &endptr);
  151. if (time == endptr) {
  152. fprintf(stderr, "`%s` is not a number\n", time);
  153. exit(1);
  154. }
  155. switch (*endptr) {
  156. case '\0':
  157. case 's': result += x; break;
  158. case 'm': result += x * 60.0f; break;
  159. case 'h': result += x * 60.0f * 60.0f; break;
  160. default:
  161. fprintf(stderr, "`%c` is an unknown time unit\n", *endptr);
  162. exit(1);
  163. }
  164. time = endptr;
  165. if (*time) time += 1;
  166. }
  167. return result;
  168. }
  169. typedef struct {
  170. Uint32 frame_delay;
  171. float dt;
  172. Uint64 last_time;
  173. } FpsDeltaTime;
  174. FpsDeltaTime make_fpsdeltatime(const Uint32 fps_cap)
  175. {
  176. return (FpsDeltaTime){
  177. .frame_delay=(1000 / fps_cap),
  178. .dt=0.0f,
  179. .last_time=SDL_GetPerformanceCounter(),
  180. };
  181. }
  182. void frame_start(FpsDeltaTime *fpsdt)
  183. {
  184. const Uint64 now = SDL_GetPerformanceCounter();
  185. const Uint64 elapsed = now - fpsdt->last_time;
  186. fpsdt->dt = ((float)elapsed) / ((float)SDL_GetPerformanceFrequency());
  187. // printf("FPS: %f | dt %f\n", 1.0 / fpsdt->dt, fpsdt->dt);
  188. fpsdt->last_time = now;
  189. }
  190. void frame_end(FpsDeltaTime *fpsdt)
  191. {
  192. const Uint64 now = SDL_GetPerformanceCounter();
  193. const Uint64 elapsed = now - fpsdt->last_time;
  194. const Uint32 cap_frame_end = (Uint32) ((((float)elapsed) * 1000.0f) / ((float)SDL_GetPerformanceFrequency()));
  195. if (cap_frame_end < fpsdt->frame_delay) {
  196. SDL_Delay((fpsdt->frame_delay - cap_frame_end) );
  197. }
  198. }
  199. #define TITLE_CAP 256
  200. int main(int argc, char **argv)
  201. {
  202. Mode mode = MODE_ASCENDING;
  203. float displayed_time = 0.0f;
  204. int paused = 0;
  205. int exit_after_countdown = 0;
  206. for (int i = 1; i < argc; ++i) {
  207. if (strcmp(argv[i], "-p") == 0) {
  208. paused = 1;
  209. } else if (strcmp(argv[i], "-e") == 0) {
  210. exit_after_countdown = 1;
  211. } else if (strcmp(argv[i], "clock") == 0) {
  212. mode = MODE_CLOCK;
  213. } else {
  214. mode = MODE_COUNTDOWN;
  215. displayed_time = parse_time(argv[i]);
  216. }
  217. }
  218. secc(SDL_Init(SDL_INIT_VIDEO));
  219. SDL_Window *window =
  220. secp(SDL_CreateWindow(
  221. "sowon",
  222. 0, 0,
  223. TEXT_WIDTH, TEXT_HEIGHT*2,
  224. SDL_WINDOW_RESIZABLE));
  225. SDL_Renderer *renderer =
  226. secp(SDL_CreateRenderer(
  227. window, -1,
  228. SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED));
  229. secc(SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"));
  230. SDL_Texture *digits = load_digits_png_file_as_texture(renderer);
  231. #ifdef PENGER
  232. SDL_Texture *penger = load_penger_png_file_as_texture(renderer);
  233. #endif
  234. secc(SDL_SetTextureColorMod(digits, MAIN_COLOR_R, MAIN_COLOR_G, MAIN_COLOR_B));
  235. if (paused) {
  236. secc(SDL_SetTextureColorMod(digits, PAUSE_COLOR_R, PAUSE_COLOR_G, PAUSE_COLOR_B));
  237. } else {
  238. secc(SDL_SetTextureColorMod(digits, MAIN_COLOR_R, MAIN_COLOR_G, MAIN_COLOR_B));
  239. }
  240. int quit = 0;
  241. size_t wiggle_index = 0;
  242. float wiggle_cooldown = WIGGLE_DURATION;
  243. float user_scale = 1.0f;
  244. char prev_title[TITLE_CAP];
  245. FpsDeltaTime fps_dt = make_fpsdeltatime(FPS);
  246. while (!quit) {
  247. frame_start(&fps_dt);
  248. // INPUT BEGIN //////////////////////////////
  249. SDL_Event event = {0};
  250. while (SDL_PollEvent(&event)) {
  251. switch (event.type) {
  252. case SDL_QUIT: {
  253. quit = 1;
  254. } break;
  255. case SDL_KEYDOWN: {
  256. switch (event.key.keysym.sym) {
  257. case SDLK_SPACE: {
  258. paused = !paused;
  259. if (paused) {
  260. secc(SDL_SetTextureColorMod(digits, PAUSE_COLOR_R, PAUSE_COLOR_G, PAUSE_COLOR_B));
  261. } else {
  262. secc(SDL_SetTextureColorMod(digits, MAIN_COLOR_R, MAIN_COLOR_G, MAIN_COLOR_B));
  263. }
  264. } break;
  265. case SDLK_KP_PLUS:
  266. case SDLK_EQUALS: {
  267. user_scale += SCALE_FACTOR * user_scale;
  268. } break;
  269. case SDLK_KP_MINUS:
  270. case SDLK_MINUS: {
  271. user_scale -= SCALE_FACTOR * user_scale;
  272. } break;
  273. case SDLK_KP_0:
  274. case SDLK_0: {
  275. user_scale = 1.0f;
  276. } break;
  277. case SDLK_F5: {
  278. displayed_time = 0.0f;
  279. paused = 0;
  280. for (int i = 1; i < argc; ++i) {
  281. if (strcmp(argv[i], "-p") == 0) {
  282. paused = 1;
  283. } else {
  284. displayed_time = parse_time(argv[i]);
  285. }
  286. }
  287. if (paused) {
  288. secc(SDL_SetTextureColorMod(digits, PAUSE_COLOR_R, PAUSE_COLOR_G, PAUSE_COLOR_B));
  289. } else {
  290. secc(SDL_SetTextureColorMod(digits, MAIN_COLOR_R, MAIN_COLOR_G, MAIN_COLOR_B));
  291. }
  292. } break;
  293. case SDLK_F11: {
  294. Uint32 window_flags;
  295. secc(window_flags = SDL_GetWindowFlags(window));
  296. if(window_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
  297. secc(SDL_SetWindowFullscreen(window, 0));
  298. } else {
  299. secc(SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP));
  300. }
  301. } break;
  302. }
  303. } break;
  304. case SDL_MOUSEWHEEL: {
  305. if (SDL_GetModState() & KMOD_CTRL) {
  306. if (event.wheel.y > 0) {
  307. user_scale += SCALE_FACTOR * user_scale;
  308. } else if (event.wheel.y < 0) {
  309. user_scale -= SCALE_FACTOR * user_scale;
  310. }
  311. }
  312. } break;
  313. default: {}
  314. }
  315. }
  316. // INPUT END //////////////////////////////
  317. // RENDER BEGIN //////////////////////////////
  318. SDL_SetRenderDrawColor(renderer, BACKGROUND_COLOR_R, BACKGROUND_COLOR_G, BACKGROUND_COLOR_B, 255);
  319. SDL_RenderClear(renderer);
  320. {
  321. const size_t t = (size_t) floorf(fmaxf(displayed_time, 0.0f));
  322. // PENGER BEGIN //////////////////////////////
  323. #ifdef PENGER
  324. render_penger_at(renderer, penger, displayed_time, mode==MODE_COUNTDOWN, window);
  325. #endif
  326. // PENGER END //////////////////////////////
  327. // DIGITS BEGIN //////////////////////////////
  328. int pen_x, pen_y;
  329. float fit_scale = 1.0;
  330. initial_pen(window, &pen_x, &pen_y, user_scale, &fit_scale);
  331. // TODO: support amount of hours >99
  332. const size_t hours = t / 60 / 60;
  333. render_digit_at(renderer, digits, hours / 10, wiggle_index % WIGGLE_COUNT, &pen_x, &pen_y, user_scale, fit_scale);
  334. render_digit_at(renderer, digits, hours % 10, (wiggle_index + 1) % WIGGLE_COUNT, &pen_x, &pen_y, user_scale, fit_scale);
  335. render_digit_at(renderer, digits, COLON_INDEX, wiggle_index % WIGGLE_COUNT, &pen_x, &pen_y, user_scale, fit_scale);
  336. const size_t minutes = t / 60 % 60;
  337. render_digit_at(renderer, digits, minutes / 10, (wiggle_index + 2) % WIGGLE_COUNT, &pen_x, &pen_y, user_scale, fit_scale);
  338. render_digit_at(renderer, digits, minutes % 10, (wiggle_index + 3) % WIGGLE_COUNT, &pen_x, &pen_y, user_scale, fit_scale);
  339. render_digit_at(renderer, digits, COLON_INDEX, (wiggle_index + 1) % WIGGLE_COUNT, &pen_x, &pen_y, user_scale, fit_scale);
  340. const size_t seconds = t % 60;
  341. render_digit_at(renderer, digits, seconds / 10, (wiggle_index + 4) % WIGGLE_COUNT, &pen_x, &pen_y, user_scale, fit_scale);
  342. render_digit_at(renderer, digits, seconds % 10, (wiggle_index + 5) % WIGGLE_COUNT, &pen_x, &pen_y, user_scale, fit_scale);
  343. char title[TITLE_CAP];
  344. snprintf(title, sizeof(title), "%02zu:%02zu:%02zu - sowon", hours, minutes, seconds);
  345. if (strcmp(prev_title, title) != 0) {
  346. SDL_SetWindowTitle(window, title);
  347. }
  348. memcpy(title, prev_title, TITLE_CAP);
  349. // DIGITS END //////////////////////////////
  350. }
  351. SDL_RenderPresent(renderer);
  352. // RENDER END //////////////////////////////
  353. // UPDATE BEGIN //////////////////////////////
  354. if (wiggle_cooldown <= 0.0f) {
  355. wiggle_index++;
  356. wiggle_cooldown = WIGGLE_DURATION;
  357. }
  358. wiggle_cooldown -= fps_dt.dt;
  359. if (!paused) {
  360. switch (mode) {
  361. case MODE_ASCENDING: {
  362. displayed_time += fps_dt.dt;
  363. } break;
  364. case MODE_COUNTDOWN: {
  365. if (displayed_time > 1e-6) {
  366. displayed_time -= fps_dt.dt;
  367. } else {
  368. displayed_time = 0.0f;
  369. if (exit_after_countdown) {
  370. SDL_Quit();
  371. return 0;
  372. }
  373. }
  374. } break;
  375. case MODE_CLOCK: {
  376. float displayed_time_prev = displayed_time;
  377. time_t t = time(NULL);
  378. struct tm *tm = localtime(&t);
  379. displayed_time = tm->tm_sec
  380. + tm->tm_min * 60.0f
  381. + tm->tm_hour * 60.0f * 60.0f;
  382. if(displayed_time <= displayed_time_prev){
  383. //same second, keep previous count and add subsecond resolution for penger
  384. if(floorf(displayed_time_prev) == floorf(displayed_time_prev+fps_dt.dt)){ //check for no newsecond shenaningans from dt
  385. displayed_time = displayed_time_prev + fps_dt.dt;
  386. }else{
  387. displayed_time = displayed_time_prev;
  388. }
  389. }
  390. } break;
  391. }
  392. }
  393. // UPDATE END //////////////////////////////
  394. frame_end(&fps_dt);
  395. }
  396. SDL_Quit();
  397. return 0;
  398. }