1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <time.h>
27#include <limits.h>
28
29#include "studio/system.h"
30#include "system/sokol/sokol.h"
31
32#if defined(__TIC_WINDOWS__)
33#include <windows.h>
34#endif
35
36static struct
37{
38 Studio* studio;
39 tic80_input input;
40
41 struct
42 {
43 bool state[tic_keys_count];
44 char text;
45 } keyboard;
46
47 struct
48 {
49 saudio_desc desc;
50 float* samples;
51 } audio;
52
53 char* clipboard;
54
55} platform;
56
57void tic_sys_clipboard_set(const char* text)
58{
59 if(platform.clipboard)
60 {
61 free(platform.clipboard);
62 platform.clipboard = NULL;
63 }
64
65 platform.clipboard = strdup(text);
66}
67
68bool tic_sys_clipboard_has()
69{
70 return platform.clipboard != NULL;
71}
72
73char* tic_sys_clipboard_get()
74{
75 return platform.clipboard ? strdup(platform.clipboard) : NULL;
76}
77
78void tic_sys_clipboard_free(const char* text)
79{
80 free((void*)text);
81}
82
83void tic_sys_fullscreen_set(bool value)
84{
85}
86
87bool tic_sys_fullscreen_get()
88{
89}
90
91void tic_sys_message(const char* title, const char* message)
92{
93}
94
95void tic_sys_title(const char* title)
96{
97}
98
99void tic_sys_open_path(const char* path) {}
100void tic_sys_open_url(const char* url) {}
101
102void tic_sys_preseed()
103{
104#if defined(__TIC_MACOSX__)
105 srandom(time(NULL));
106 random();
107#else
108 srand(time(NULL));
109 rand();
110#endif
111}
112
113void tic_sys_update_config()
114{
115
116}
117
118void tic_sys_default_mapping(tic_mapping* mapping)
119{
120 *mapping = (tic_mapping)
121 {
122 tic_key_up,
123 tic_key_down,
124 tic_key_left,
125 tic_key_right,
126
127 tic_key_z, // a
128 tic_key_x, // b
129 tic_key_a, // x
130 tic_key_s, // y
131 };
132}
133
134bool tic_sys_keyboard_text(char* text)
135{
136 *text = platform.keyboard.text;
137 return true;
138}
139
140static void app_init(void)
141{
142 sokol_gfx_init(TIC80_FULLWIDTH, TIC80_FULLHEIGHT, 1, 1, false, true);
143
144 stm_setup();
145
146 platform.audio.samples = calloc(sizeof platform.audio.samples[0], saudio_sample_rate() / TIC80_FRAMERATE * TIC80_SAMPLE_CHANNELS);
147}
148
149static void handleKeyboard()
150{
151 tic80_input* input = &platform.input;
152 input->keyboard.data = 0;
153
154 enum{BufSize = COUNT_OF(input->keyboard.keys)};
155
156 for(s32 i = 0, c = 0; i < COUNT_OF(platform.keyboard.state) && c < BufSize; i++)
157 if(platform.keyboard.state[i])
158 input->keyboard.keys[c++] = i;
159}
160
161static void app_frame(void)
162{
163 if(studio_alive(platform.studio)) exit(0);
164
165 const tic80* product = &studio_mem(platform.studio)->product;
166 tic80_input* input = &platform.input;
167
168 input->gamepads.data = 0;
169 handleKeyboard();
170 studio_tick(platform.studio, platform.input);
171
172 sokol_gfx_draw(product->screen);
173
174 studio_sound(platform.studio);
175 s32 count = product->samples.count;
176 for(s32 i = 0; i < count; i++)
177 platform.audio.samples[i] = (float)product->samples.buffer[i] / SHRT_MAX;
178
179 saudio_push(platform.audio.samples, count / TIC80_SAMPLE_CHANNELS);
180
181 input->mouse.scrollx = input->mouse.scrolly = 0;
182 platform.keyboard.text = '\0';
183}
184
185static void handleKeydown(sapp_keycode keycode, bool down)
186{
187 static const tic_keycode KeyboardCodes[] =
188 {
189 [SAPP_KEYCODE_INVALID] = tic_key_unknown,
190 [SAPP_KEYCODE_SPACE] = tic_key_space,
191 [SAPP_KEYCODE_APOSTROPHE] = tic_key_apostrophe,
192 [SAPP_KEYCODE_COMMA] = tic_key_comma,
193 [SAPP_KEYCODE_MINUS] = tic_key_minus,
194 [SAPP_KEYCODE_PERIOD] = tic_key_period,
195 [SAPP_KEYCODE_SLASH] = tic_key_slash,
196 [SAPP_KEYCODE_0] = tic_key_0,
197 [SAPP_KEYCODE_1] = tic_key_1,
198 [SAPP_KEYCODE_2] = tic_key_2,
199 [SAPP_KEYCODE_3] = tic_key_3,
200 [SAPP_KEYCODE_4] = tic_key_4,
201 [SAPP_KEYCODE_5] = tic_key_5,
202 [SAPP_KEYCODE_6] = tic_key_6,
203 [SAPP_KEYCODE_7] = tic_key_7,
204 [SAPP_KEYCODE_8] = tic_key_8,
205 [SAPP_KEYCODE_9] = tic_key_9,
206 [SAPP_KEYCODE_SEMICOLON] = tic_key_semicolon,
207 [SAPP_KEYCODE_EQUAL] = tic_key_equals,
208 [SAPP_KEYCODE_A] = tic_key_a,
209 [SAPP_KEYCODE_B] = tic_key_b,
210 [SAPP_KEYCODE_C] = tic_key_c,
211 [SAPP_KEYCODE_D] = tic_key_d,
212 [SAPP_KEYCODE_E] = tic_key_e,
213 [SAPP_KEYCODE_F] = tic_key_f,
214 [SAPP_KEYCODE_G] = tic_key_g,
215 [SAPP_KEYCODE_H] = tic_key_h,
216 [SAPP_KEYCODE_I] = tic_key_i,
217 [SAPP_KEYCODE_J] = tic_key_j,
218 [SAPP_KEYCODE_K] = tic_key_k,
219 [SAPP_KEYCODE_L] = tic_key_l,
220 [SAPP_KEYCODE_M] = tic_key_m,
221 [SAPP_KEYCODE_N] = tic_key_n,
222 [SAPP_KEYCODE_O] = tic_key_o,
223 [SAPP_KEYCODE_P] = tic_key_p,
224 [SAPP_KEYCODE_Q] = tic_key_q,
225 [SAPP_KEYCODE_R] = tic_key_r,
226 [SAPP_KEYCODE_S] = tic_key_s,
227 [SAPP_KEYCODE_T] = tic_key_t,
228 [SAPP_KEYCODE_U] = tic_key_u,
229 [SAPP_KEYCODE_V] = tic_key_v,
230 [SAPP_KEYCODE_W] = tic_key_w,
231 [SAPP_KEYCODE_X] = tic_key_x,
232 [SAPP_KEYCODE_Y] = tic_key_y,
233 [SAPP_KEYCODE_Z] = tic_key_z,
234 [SAPP_KEYCODE_LEFT_BRACKET] = tic_key_leftbracket,
235 [SAPP_KEYCODE_BACKSLASH] = tic_key_backslash,
236 [SAPP_KEYCODE_RIGHT_BRACKET] = tic_key_rightbracket,
237 [SAPP_KEYCODE_GRAVE_ACCENT] = tic_key_grave,
238 [SAPP_KEYCODE_WORLD_1] = tic_key_unknown,
239 [SAPP_KEYCODE_WORLD_2] = tic_key_unknown,
240 [SAPP_KEYCODE_ESCAPE] = tic_key_escape,
241 [SAPP_KEYCODE_ENTER] = tic_key_return,
242 [SAPP_KEYCODE_TAB] = tic_key_tab,
243 [SAPP_KEYCODE_BACKSPACE] = tic_key_backspace,
244 [SAPP_KEYCODE_INSERT] = tic_key_insert,
245 [SAPP_KEYCODE_DELETE] = tic_key_delete,
246 [SAPP_KEYCODE_RIGHT] = tic_key_right,
247 [SAPP_KEYCODE_LEFT] = tic_key_left,
248 [SAPP_KEYCODE_DOWN] = tic_key_down,
249 [SAPP_KEYCODE_UP] = tic_key_up,
250 [SAPP_KEYCODE_PAGE_UP] = tic_key_pageup,
251 [SAPP_KEYCODE_PAGE_DOWN] = tic_key_pagedown,
252 [SAPP_KEYCODE_HOME] = tic_key_home,
253 [SAPP_KEYCODE_END] = tic_key_end,
254 [SAPP_KEYCODE_CAPS_LOCK] = tic_key_capslock,
255 [SAPP_KEYCODE_SCROLL_LOCK] = tic_key_unknown,
256 [SAPP_KEYCODE_NUM_LOCK] = tic_key_unknown,
257 [SAPP_KEYCODE_PRINT_SCREEN] = tic_key_unknown,
258 [SAPP_KEYCODE_PAUSE] = tic_key_unknown,
259 [SAPP_KEYCODE_F1] = tic_key_f1,
260 [SAPP_KEYCODE_F2] = tic_key_f2,
261 [SAPP_KEYCODE_F3] = tic_key_f3,
262 [SAPP_KEYCODE_F4] = tic_key_f4,
263 [SAPP_KEYCODE_F5] = tic_key_f5,
264 [SAPP_KEYCODE_F6] = tic_key_f6,
265 [SAPP_KEYCODE_F7] = tic_key_f7,
266 [SAPP_KEYCODE_F8] = tic_key_f8,
267 [SAPP_KEYCODE_F9] = tic_key_f9,
268 [SAPP_KEYCODE_F10] = tic_key_f10,
269 [SAPP_KEYCODE_F11] = tic_key_f11,
270 [SAPP_KEYCODE_F12] = tic_key_f12,
271 [SAPP_KEYCODE_F13] = tic_key_unknown,
272 [SAPP_KEYCODE_F14] = tic_key_unknown,
273 [SAPP_KEYCODE_F15] = tic_key_unknown,
274 [SAPP_KEYCODE_F16] = tic_key_unknown,
275 [SAPP_KEYCODE_F17] = tic_key_unknown,
276 [SAPP_KEYCODE_F18] = tic_key_unknown,
277 [SAPP_KEYCODE_F19] = tic_key_unknown,
278 [SAPP_KEYCODE_F20] = tic_key_unknown,
279 [SAPP_KEYCODE_F21] = tic_key_unknown,
280 [SAPP_KEYCODE_F22] = tic_key_unknown,
281 [SAPP_KEYCODE_F23] = tic_key_unknown,
282 [SAPP_KEYCODE_F24] = tic_key_unknown,
283 [SAPP_KEYCODE_F25] = tic_key_unknown,
284 [SAPP_KEYCODE_KP_0] = tic_key_0,
285 [SAPP_KEYCODE_KP_1] = tic_key_1,
286 [SAPP_KEYCODE_KP_2] = tic_key_2,
287 [SAPP_KEYCODE_KP_3] = tic_key_3,
288 [SAPP_KEYCODE_KP_4] = tic_key_4,
289 [SAPP_KEYCODE_KP_5] = tic_key_5,
290 [SAPP_KEYCODE_KP_6] = tic_key_6,
291 [SAPP_KEYCODE_KP_7] = tic_key_7,
292 [SAPP_KEYCODE_KP_8] = tic_key_8,
293 [SAPP_KEYCODE_KP_9] = tic_key_9,
294 [SAPP_KEYCODE_KP_DECIMAL] = tic_key_unknown,
295 [SAPP_KEYCODE_KP_DIVIDE] = tic_key_unknown,
296 [SAPP_KEYCODE_KP_MULTIPLY] = tic_key_unknown,
297 [SAPP_KEYCODE_KP_SUBTRACT] = tic_key_unknown,
298 [SAPP_KEYCODE_KP_ADD] = tic_key_unknown,
299 [SAPP_KEYCODE_KP_ENTER] = tic_key_return,
300 [SAPP_KEYCODE_KP_EQUAL] = tic_key_equals,
301 [SAPP_KEYCODE_LEFT_SHIFT] = tic_key_shift,
302 [SAPP_KEYCODE_LEFT_CONTROL] = tic_key_ctrl,
303 [SAPP_KEYCODE_LEFT_ALT] = tic_key_alt,
304 [SAPP_KEYCODE_LEFT_SUPER] = tic_key_unknown,
305 [SAPP_KEYCODE_RIGHT_SHIFT] = tic_key_shift,
306 [SAPP_KEYCODE_RIGHT_CONTROL] = tic_key_ctrl,
307 [SAPP_KEYCODE_RIGHT_ALT] = tic_key_alt,
308 [SAPP_KEYCODE_RIGHT_SUPER] = tic_key_unknown,
309 [SAPP_KEYCODE_MENU] = tic_key_unknown,
310
311 };
312
313 tic_key key = KeyboardCodes[keycode];
314
315 if(key > tic_key_unknown)
316 {
317 // ALT+TAB fix
318 if(key == tic_key_tab && platform.keyboard.state[tic_key_alt])
319 platform.keyboard.state[tic_key_alt] = false;
320
321 platform.keyboard.state[key] = down;
322 }
323}
324
325static void processMouse(sapp_mousebutton btn, s32 down)
326{
327 tic80_input* input = &platform.input;
328
329 switch(btn)
330 {
331 case SAPP_MOUSEBUTTON_LEFT: input->mouse.left = down; break;
332 case SAPP_MOUSEBUTTON_MIDDLE: input->mouse.middle = down; break;
333 case SAPP_MOUSEBUTTON_RIGHT: input->mouse.right = down; break;
334 default: break;
335 }
336}
337
338static void app_input(const sapp_event* event)
339{
340 tic80_input* input = &platform.input;
341
342 switch(event->type)
343 {
344 case SAPP_EVENTTYPE_KEY_DOWN:
345 handleKeydown(event->key_code, true);
346 break;
347 case SAPP_EVENTTYPE_KEY_UP:
348 handleKeydown(event->key_code, false);
349 break;
350 case SAPP_EVENTTYPE_CHAR:
351 if(event->char_code < 128)
352 platform.keyboard.text = event->char_code;
353 break;
354 case SAPP_EVENTTYPE_MOUSE_MOVE:
355 {
356 struct {s32 x, y, w, h;}rect;
357 sokol_calc_viewport(&rect.x, &rect.y, &rect.w, &rect.h);
358
359 if (rect.w) {
360 s32 temp_x = ((s32)event->mouse_x - rect.x) * TIC80_FULLWIDTH / rect.w;
361 if (temp_x < 0) temp_x = 0; else if (temp_x >= TIC80_FULLWIDTH) temp_x = TIC80_FULLWIDTH-1; // clip: 0 to TIC80_FULLWIDTH-1
362 input->mouse.x = temp_x;
363 }
364 if (rect.h) {
365 s32 temp_y = ((s32)event->mouse_y - rect.y) * TIC80_FULLHEIGHT / rect.h;
366 if (temp_y < 0) temp_y = 0; else if (temp_y >= TIC80_FULLHEIGHT) temp_y = TIC80_FULLHEIGHT-1; // clip: 0 to TIC80_FULLHEIGHT-1
367 input->mouse.y = temp_y;
368 }
369 }
370 break;
371 case SAPP_EVENTTYPE_MOUSE_DOWN:
372 processMouse(event->mouse_button, 1); break;
373 break;
374 case SAPP_EVENTTYPE_MOUSE_UP:
375 processMouse(event->mouse_button, 0); break;
376 break;
377 case SAPP_EVENTTYPE_MOUSE_SCROLL:
378 input->mouse.scrollx = event->scroll_x;
379 input->mouse.scrolly = event->scroll_y;
380 break;
381 default:
382 break;
383 }
384}
385
386static void app_cleanup(void)
387{
388 studio_delete(platform.studio);
389 free(platform.audio.samples);
390}
391
392sapp_desc sokol_main(s32 argc, char* argv[])
393{
394#if defined(__TIC_WINDOWS__)
395 {
396 CONSOLE_SCREEN_BUFFER_INFO info;
397 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info) && !info.dwCursorPosition.X && !info.dwCursorPosition.Y)
398 FreeConsole();
399 }
400#endif
401
402 memset(&platform, 0, sizeof platform);
403
404 platform.audio.desc.num_channels = TIC80_SAMPLE_CHANNELS;
405 saudio_setup(&platform.audio.desc);
406
407 platform.studio = studio_create(argc, argv, saudio_sample_rate(), TIC80_PIXEL_COLOR_RGBA8888, "./");
408
409 if(studio_config(platform.studio)->cli)
410 {
411 while (!studio_alive(platform.studio))
412 studio_tick(platform.studio, platform.input);
413
414 exit(0);
415 }
416
417 const s32 Width = TIC80_FULLWIDTH * studio_config(platform.studio)->uiScale;
418 const s32 Height = TIC80_FULLHEIGHT * studio_config(platform.studio)->uiScale;
419
420 return(sapp_desc)
421 {
422 .init_cb = app_init,
423 .frame_cb = app_frame,
424 .event_cb = app_input,
425 .cleanup_cb = app_cleanup,
426 .width = Width,
427 .height = Height,
428 .window_title = TIC_TITLE,
429 .ios_keyboard_resizes_canvas = true,
430 .high_dpi = true,
431 };
432}
433