1 // Copyright 2021 Jean Pierre Cimalando
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 // SPDX-License-Identifier: Apache-2.0
19 #include "ysfx_config.hpp"
20 #include "ysfx_api_gfx.hpp"
21 #include "ysfx_eel_utils.hpp"
22 #if !defined(YSFX_NO_GFX)
23 # include "lice_stb/lice_stb_loaders.hpp"
24 # define WDL_NO_DEFINE_MINMAX
25 # include "WDL/swell/swell.h"
26 # include "WDL/lice/lice.h"
27 # include "WDL/lice/lice_text.h"
28 # include "WDL/wdlstring.h"
32 #include <unordered_set>
37 #if !defined(YSFX_NO_GFX)
38 #define GFX_GET_CONTEXT(opaque) (((opaque)) ? ysfx_gfx_get_context((ysfx_t *)(opaque)) : nullptr)
41 ysfx_gfx_max_images
= 1024,
42 ysfx_gfx_max_fonts
= 128,
43 ysfx_gfx_max_input
= 1024,
48 struct ysfx_gfx_state_t
{
49 ysfx_gfx_state_t(ysfx_t
*fx
);
51 std::unique_ptr
<eel_lice_state
> lice
;
52 std::queue
<uint32_t> input_queue
;
53 std::unordered_set
<uint32_t> keys_pressed
;
54 ysfx_real scale
= 0.0;
55 void *callback_data
= nullptr;
56 int (*show_menu
)(void *, const char *, int32_t, int32_t) = nullptr;
57 void (*set_cursor
)(void *, int32_t) = nullptr;
58 const char *(*get_drop_file
)(void *user_data
, int32_t index
) = nullptr;
63 //------------------------------------------------------------------------------
64 #if !defined(YSFX_NO_GFX)
65 static bool eel_lice_get_filename_for_string(void *opaque
, EEL_F idx
, WDL_FastString
*fs
, int iswrite
)
68 return false; // this is neither supported nor used
70 ysfx_t
*fx
= (ysfx_t
*)opaque
;
73 if (!ysfx_find_data_file(fx
, &idx
, filepath
))
76 if (fs
) fs
->Set(filepath
.data(), (uint32_t)filepath
.size());
80 #define EEL_LICE_GET_FILENAME_FOR_STRING(idx, fs, p) \
81 eel_lice_get_filename_for_string(opaque, (idx), (fs), (p))
85 //------------------------------------------------------------------------------
86 #if !defined(YSFX_NO_GFX)
87 # include "ysfx_api_gfx_lice.hpp"
89 # include "ysfx_api_gfx_dummy.hpp"
92 //------------------------------------------------------------------------------
93 #if !defined(YSFX_NO_GFX)
95 static bool translate_special_key(uint32_t uni_key
, uint32_t &jsfx_key
)
97 auto key_c
= [](uint8_t a
, uint8_t b
, uint8_t c
, uint8_t d
) -> uint32_t {
98 return a
| (b
<< 8) | (c
<< 16) | (d
<< 24);
102 default: return false;
103 case ysfx_key_delete
: jsfx_key
= key_c('d', 'e', 'l', 0); break;
104 case ysfx_key_f1
: jsfx_key
= key_c('f', '1', 0, 0); break;
105 case ysfx_key_f2
: jsfx_key
= key_c('f', '2', 0, 0); break;
106 case ysfx_key_f3
: jsfx_key
= key_c('f', '3', 0, 0); break;
107 case ysfx_key_f4
: jsfx_key
= key_c('f', '4', 0, 0); break;
108 case ysfx_key_f5
: jsfx_key
= key_c('f', '5', 0, 0); break;
109 case ysfx_key_f6
: jsfx_key
= key_c('f', '6', 0, 0); break;
110 case ysfx_key_f7
: jsfx_key
= key_c('f', '7', 0, 0); break;
111 case ysfx_key_f8
: jsfx_key
= key_c('f', '8', 0, 0); break;
112 case ysfx_key_f9
: jsfx_key
= key_c('f', '9', 0, 0); break;
113 case ysfx_key_f10
: jsfx_key
= key_c('f', '1', '0', 0); break;
114 case ysfx_key_f11
: jsfx_key
= key_c('f', '1', '1', 0); break;
115 case ysfx_key_f12
: jsfx_key
= key_c('f', '1', '2', 0); break;
116 case ysfx_key_left
: jsfx_key
= key_c('l', 'e', 'f', 't'); break;
117 case ysfx_key_up
: jsfx_key
= key_c('u', 'p', 0, 0); break;
118 case ysfx_key_right
: jsfx_key
= key_c('r', 'g', 'h', 't'); break;
119 case ysfx_key_down
: jsfx_key
= key_c('d', 'o', 'w', 'n'); break;
120 case ysfx_key_page_up
: jsfx_key
= key_c('p', 'g', 'u', 'p'); break;
121 case ysfx_key_page_down
: jsfx_key
= key_c('p', 'g', 'd', 'n'); break;
122 case ysfx_key_home
: jsfx_key
= key_c('h', 'o', 'm', 'e'); break;
123 case ysfx_key_end
: jsfx_key
= key_c('e', 'n', 'd', 0); break;
124 case ysfx_key_insert
: jsfx_key
= key_c('i', 'n', 's', 0); break;
130 static EEL_F NSEEL_CGEN_CALL
ysfx_api_gfx_getchar(void *opaque
, EEL_F
*p
)
132 ysfx_gfx_state_t
*state
= GFX_GET_CONTEXT(opaque
);
136 if (*p
>= 1/*2*/) { // NOTE(jpc) this is 2.0 originally, which seems wrong
138 // TODO implement window flags
142 // current key down status
143 uint32_t key
= (uint32_t)*p
;
145 if (translate_special_key(key
, key
))
148 key_id
= ysfx::latin1_tolower(key
);
149 else // support the Latin-1 character set only
151 return (EEL_F
)(state
->keys_pressed
.find(key_id
) != state
->keys_pressed
.end());
154 if (!state
->input_queue
.empty()) {
155 uint32_t key
= state
->input_queue
.front();
156 state
->input_queue
.pop();
163 static EEL_F NSEEL_CGEN_CALL
ysfx_api_gfx_showmenu(void *opaque
, INT_PTR nparms
, EEL_F
**parms
)
165 ysfx_gfx_state_t
*state
= GFX_GET_CONTEXT(opaque
);
166 if (!state
|| !state
->show_menu
)
169 ysfx_t
*fx
= (ysfx_t
*)state
->lice
->m_user_ctx
;
172 if (!ysfx_string_get(fx
, *parms
[0], desc
) || desc
.empty())
175 int32_t x
= (int32_t)*fx
->var
.gfx_x
;
176 int32_t y
= (int32_t)*fx
->var
.gfx_y
;
177 return state
->show_menu(state
->callback_data
, desc
.c_str(), x
, y
);
180 static EEL_F NSEEL_CGEN_CALL
ysfx_api_gfx_setcursor(void *opaque
, INT_PTR nparms
, EEL_F
**parms
)
182 ysfx_gfx_state_t
*state
= GFX_GET_CONTEXT(opaque
);
183 if (!state
|| !state
->set_cursor
)
186 int32_t id
= (int32_t)*parms
[0];
187 state
->set_cursor(state
->callback_data
, id
);
191 static EEL_F NSEEL_CGEN_CALL
ysfx_api_gfx_getdropfile(void *opaque
, INT_PTR np
, EEL_F
**parms
)
193 ysfx_gfx_state_t
*state
= GFX_GET_CONTEXT(opaque
);
194 if (!state
|| !state
->get_drop_file
)
197 const int32_t idx
= (int)*parms
[0];
199 state
->get_drop_file(state
->callback_data
, -1);
203 const char *file
= state
->get_drop_file(state
->callback_data
, idx
);
208 ysfx_t
*fx
= (ysfx_t
*)state
->lice
->m_user_ctx
;
209 ysfx_string_set(fx
, *parms
[1], file
);
215 //------------------------------------------------------------------------------
216 #if !defined(YSFX_NO_GFX)
217 ysfx_gfx_state_t::ysfx_gfx_state_t(ysfx_t
*fx
)
218 : lice
{new eel_lice_state
{fx
->vm
.get(), fx
, ysfx_gfx_max_images
, ysfx_gfx_max_fonts
}}
220 lice
->m_framebuffer
= new LICE_WrapperBitmap
{nullptr, 0, 0, 0, false};
223 ysfx_gfx_state_t::~ysfx_gfx_state_t()
228 ysfx_gfx_state_t
*ysfx_gfx_state_new(ysfx_t
*fx
)
230 return new ysfx_gfx_state_t
{fx
};
233 void ysfx_gfx_state_free(ysfx_gfx_state_t
*state
)
238 void ysfx_gfx_state_set_bitmap(ysfx_gfx_state_t
*state
, uint8_t *data
, uint32_t w
, uint32_t h
, uint32_t stride
)
243 eel_lice_state
*lice
= state
->lice
.get();
245 assert(stride
% 4 == 0);
246 *static_cast<LICE_WrapperBitmap
*>(lice
->m_framebuffer
) = LICE_WrapperBitmap
{(LICE_pixel
*)data
, (int)w
, (int)h
, (int)(stride
/ 4), false};
249 void ysfx_gfx_state_set_scale_factor(ysfx_gfx_state_t
*state
, ysfx_real scale
)
251 state
->scale
= scale
;
254 void ysfx_gfx_state_set_callback_data(ysfx_gfx_state_t
*state
, void *callback_data
)
256 state
->callback_data
= callback_data
;
259 void ysfx_gfx_state_set_show_menu_callback(ysfx_gfx_state_t
*state
, int (*callback
)(void *, const char *, int32_t, int32_t))
261 state
->show_menu
= callback
;
264 void ysfx_gfx_state_set_set_cursor_callback(ysfx_gfx_state_t
*state
, void (*callback
)(void *, int32_t))
266 state
->set_cursor
= callback
;
269 void ysfx_gfx_state_set_get_drop_file_callback(ysfx_gfx_state_t
*state
, const char *(*callback
)(void *, int32_t))
271 state
->get_drop_file
= callback
;
274 bool ysfx_gfx_state_is_dirty(ysfx_gfx_state_t
*state
)
276 return state
->lice
->m_framebuffer_dirty
;
279 void ysfx_gfx_state_add_key(ysfx_gfx_state_t
*state
, uint32_t mods
, uint32_t key
, bool press
)
285 if (translate_special_key(key
, key
))
288 key_id
= ysfx::latin1_tolower(key
);
289 else // support the Latin-1 character set only
292 uint32_t key_with_mod
= key
;
293 if (key_id
>= 'a' && key_id
<= 'z') {
294 uint32_t off
= (uint32_t)(key_id
- 'a');
295 if (mods
& (ysfx_mod_ctrl
|ysfx_mod_alt
))
296 key_with_mod
= off
+ 257;
297 else if (mods
& ysfx_mod_ctrl
)
298 key_with_mod
= off
+ 1;
299 else if (mods
& ysfx_mod_alt
)
300 key_with_mod
= off
+ 321;
303 if (press
&& key_with_mod
> 0) {
304 while (state
->input_queue
.size() >= ysfx_gfx_max_input
)
305 state
->input_queue
.pop();
306 state
->input_queue
.push(key_with_mod
);
310 state
->keys_pressed
.insert(key_id
);
312 state
->keys_pressed
.erase(key_id
);
315 //------------------------------------------------------------------------------
316 void ysfx_gfx_enter(ysfx_t
*fx
, bool doinit
)
318 fx
->gfx
.mutex
.lock();
319 ysfx_gfx_state_t
*state
= fx
->gfx
.state
.get();
322 if (fx
->gfx
.must_init
.exchange(false, std::memory_order_acquire
)) {
323 *fx
->var
.gfx_r
= 1.0;
324 *fx
->var
.gfx_g
= 1.0;
325 *fx
->var
.gfx_b
= 1.0;
326 *fx
->var
.gfx_a
= 1.0;
327 *fx
->var
.gfx_a2
= 1.0;
328 *fx
->var
.gfx_dest
= -1.0;
329 *fx
->var
.mouse_wheel
= 0.0;
330 *fx
->var
.mouse_hwheel
= 0.0;
331 // NOTE the above are according to eel_lice.h `resetVarsToStock`
332 // it helps to reset a few more, especially for clearing
333 *fx
->var
.gfx_mode
= 0;
334 *fx
->var
.gfx_clear
= 0;
335 *fx
->var
.gfx_texth
= 0;
336 *fx
->var
.mouse_cap
= 0;
339 state
->input_queue
= {};
340 state
->keys_pressed
= {};
343 eel_lice_state
*lice
= state
->lice
.get();
344 LICE_WrapperBitmap framebuffer
= *static_cast<LICE_WrapperBitmap
*>(lice
->m_framebuffer
);
346 lice
= new eel_lice_state
{fx
->vm
.get(), fx
, ysfx_gfx_max_images
, ysfx_gfx_max_fonts
};
347 state
->lice
.reset(lice
);
348 lice
->m_framebuffer
= new LICE_WrapperBitmap(framebuffer
);
350 // load images from filenames
351 uint32_t numfiles
= (uint32_t)fx
->source
.main
->header
.filenames
.size();
352 for (uint32_t i
= 0; i
< numfiles
; ++i
)
353 lice
->gfx_loadimg(fx
, (int32_t)i
, (EEL_F
)i
);
355 fx
->gfx
.ready
= true;
359 ysfx_set_thread_id(ysfx_thread_id_gfx
);
362 void ysfx_gfx_leave(ysfx_t
*fx
)
364 ysfx_set_thread_id(ysfx_thread_id_none
);
366 fx
->gfx
.mutex
.unlock();
369 ysfx_gfx_state_t
*ysfx_gfx_get_context(ysfx_t
*fx
)
374 // NOTE: make sure that this will be used from the @gfx thread only
375 if (ysfx_get_thread_id() != ysfx_thread_id_gfx
)
378 return fx
->gfx
.state
.get();
381 void ysfx_gfx_prepare(ysfx_t
*fx
)
383 ysfx_gfx_state_t
*state
= ysfx_gfx_get_context(fx
);
384 eel_lice_state
*lice
= state
->lice
.get();
386 lice
->m_framebuffer_dirty
= false;
388 // set variables `gfx_w` and `gfx_h`
389 ysfx_real gfx_w
= (ysfx_real
)lice
->m_framebuffer
->getWidth();
390 ysfx_real gfx_h
= (ysfx_real
)lice
->m_framebuffer
->getHeight();
391 if (state
->scale
> 1.0) {
392 gfx_w
*= state
->scale
;
393 gfx_h
*= state
->scale
;
394 *fx
->var
.gfx_ext_retina
= state
->scale
;
396 *fx
->var
.gfx_w
= gfx_w
;
397 *fx
->var
.gfx_h
= gfx_h
;
401 //------------------------------------------------------------------------------
402 void ysfx_api_init_gfx()
404 #if !defined(YSFX_NO_GFX)
405 lice_stb_install_loaders();
408 NSEEL_addfunc_retptr("gfx_lineto", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_lineto
);
409 NSEEL_addfunc_retptr("gfx_lineto", 2, NSEEL_PProc_THIS
, &ysfx_api_gfx_lineto2
);
410 NSEEL_addfunc_retptr("gfx_rectto", 2, NSEEL_PProc_THIS
, &ysfx_api_gfx_rectto
);
411 NSEEL_addfunc_varparm("gfx_rect", 4, NSEEL_PProc_THIS
, &ysfx_api_gfx_rect
);
412 NSEEL_addfunc_varparm("gfx_line", 4, NSEEL_PProc_THIS
, &ysfx_api_gfx_line
);
413 NSEEL_addfunc_varparm("gfx_gradrect", 8, NSEEL_PProc_THIS
, &ysfx_api_gfx_gradrect
);
414 NSEEL_addfunc_varparm("gfx_muladdrect", 7, NSEEL_PProc_THIS
, &ysfx_api_gfx_muladdrect
);
415 NSEEL_addfunc_varparm("gfx_deltablit", 9, NSEEL_PProc_THIS
, &ysfx_api_gfx_deltablit
);
416 NSEEL_addfunc_exparms("gfx_transformblit", 8, NSEEL_PProc_THIS
, &ysfx_api_gfx_transformblit
);
417 NSEEL_addfunc_varparm("gfx_circle", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_circle
);
418 NSEEL_addfunc_varparm("gfx_triangle", 6, NSEEL_PProc_THIS
, &ysfx_api_gfx_triangle
);
419 NSEEL_addfunc_varparm("gfx_roundrect", 5, NSEEL_PProc_THIS
, &ysfx_api_gfx_roundrect
);
420 NSEEL_addfunc_varparm("gfx_arc", 5, NSEEL_PProc_THIS
, &ysfx_api_gfx_arc
);
421 NSEEL_addfunc_retptr("gfx_blurto", 2, NSEEL_PProc_THIS
, &ysfx_api_gfx_blurto
);
422 NSEEL_addfunc_exparms("gfx_showmenu", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_showmenu
);
423 NSEEL_addfunc_varparm("gfx_setcursor", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_setcursor
);
424 NSEEL_addfunc_retptr("gfx_drawnumber", 2, NSEEL_PProc_THIS
, &ysfx_api_gfx_drawnumber
);
425 NSEEL_addfunc_retptr("gfx_drawchar", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_drawchar
);
426 NSEEL_addfunc_varparm("gfx_drawstr", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_drawstr
);
427 NSEEL_addfunc_retptr("gfx_measurestr", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_measurestr
);
428 NSEEL_addfunc_retptr("gfx_measurechar", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_measurechar
);
429 NSEEL_addfunc_varparm("gfx_printf", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_printf
);
430 NSEEL_addfunc_retptr("gfx_setpixel", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_setpixel
);
431 NSEEL_addfunc_retptr("gfx_getpixel", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_getpixel
);
432 NSEEL_addfunc_retptr("gfx_getimgdim", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_getimgdim
);
433 NSEEL_addfunc_retval("gfx_setimgdim", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_setimgdim
);
434 NSEEL_addfunc_retval("gfx_loadimg", 2, NSEEL_PProc_THIS
, &ysfx_api_gfx_loadimg
);
435 NSEEL_addfunc_retptr("gfx_blitext", 3, NSEEL_PProc_THIS
, &ysfx_api_gfx_blitext
);
436 NSEEL_addfunc_varparm("gfx_blit", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_blit2
);
437 NSEEL_addfunc_varparm("gfx_setfont", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_setfont
);
438 NSEEL_addfunc_varparm("gfx_getfont", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_getfont
);
439 NSEEL_addfunc_varparm("gfx_set", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_set
);
440 NSEEL_addfunc_varparm("gfx_getdropfile", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_getdropfile
);
441 NSEEL_addfunc_varparm("gfx_getsyscol", 0, NSEEL_PProc_THIS
, &ysfx_api_gfx_getsyscol
);
442 NSEEL_addfunc_retval("gfx_getchar", 1, NSEEL_PProc_THIS
, &ysfx_api_gfx_getchar
);
445 //------------------------------------------------------------------------------
448 #if !defined(YSFX_NO_GFX)
450 // implement these GL functions on SWELL targets where they are missing
452 #if !defined(SWELL_TARGET_GDK) && !defined(SWELL_TARGET_OSX)
453 void SWELL_SetViewGL(HWND h
, char wantGL
)
457 bool SWELL_GetViewGL(HWND h
)
462 bool SWELL_SetGLContextToView(HWND h
)
467 #endif // !defined(SWELL_TARGET_GDK) && !defined(SWELL_TARGET_OSX)