2023-07-30 19:18:50 +00:00
//-----------------------------------------------------------------------------
// fps locking
static volatile float framerate = 0 ;
static volatile unsigned fps_active , timer_counter , loop_counter ;
static
int fps__timing_thread ( void * arg ) {
int64_t ns_excess = 0 ;
while ( fps_active ) {
if ( framerate < = 0 ) {
loop_counter = timer_counter = 0 ;
sleep_ms ( 250 ) ;
} else {
timer_counter + + ;
int64_t tt = ( int64_t ) ( 1e9 / ( float ) framerate ) - ns_excess ;
uint64_t took = - time_ns ( ) ;
# if is(win32)
timeBeginPeriod ( 1 ) ; Sleep ( tt > 0 ? tt / 1e6 : 0 ) ;
# else
sleep_ns ( ( float ) tt ) ;
# endif
took + = time_ns ( ) ;
ns_excess = took - tt ;
if ( ns_excess < 0 ) ns_excess = 0 ;
//puts( strf("%lld", ns_excess) );
}
}
fps_active = 1 ;
( void ) arg ;
return thread_exit ( 0 ) , 0 ;
}
static
void fps_locker ( int on ) {
if ( on ) {
// private threaded timer
fps_active = 1 , timer_counter = loop_counter = 0 ;
thread_init ( fps__timing_thread , 0 , " fps__timing_thread() " , 0 ) ;
} else {
fps_active = 0 ;
}
}
// function that locks render to desired `framerate` framerate (in FPS).
// - assumes fps_locker() was enabled beforehand.
// - returns true if must render, else 0.
static
int fps_wait ( ) {
if ( framerate < = 0 ) return 1 ;
if ( ! fps_active ) return 1 ;
// if we throttled too much, cpu idle wait
while ( fps_active & & ( loop_counter > timer_counter ) ) {
//thread_yield();
sleep_ns ( 100 ) ;
}
// max auto frameskip is 10: ie, even if speed is low paint at least one frame every 10
enum { maxframeskip = 10 } ;
if ( timer_counter > loop_counter + maxframeskip ) {
loop_counter = timer_counter ;
}
loop_counter + + ;
// only draw if we are fast enough, otherwise skip the frame
return loop_counter > = timer_counter ;
}
static
void window_vsync ( float hz ) {
if ( hz < = 0 ) return ;
do_once fps_locker ( 1 ) ;
framerate = hz ;
fps_wait ( ) ;
}
//-----------------------------------------------------------------------------
#if 0 // deprecated
static void ( * hooks [ 64 ] ) ( ) = { 0 } ;
static void * userdatas [ 64 ] = { 0 } ;
bool window_hook ( void ( * func ) ( ) , void * user ) {
window_unhook ( func ) ;
for ( int i = 0 ; i < 64 ; + + i ) {
if ( ! hooks [ i ] ) {
hooks [ i ] = func ;
userdatas [ i ] = user ;
return true ;
}
}
return false ;
}
void window_unhook ( void ( * func ) ( ) ) {
for ( int i = 0 ; i < 64 ; + + i ) {
if ( hooks [ i ] = = func ) {
hooks [ i ] = 0 ;
userdatas [ i ] = 0 ;
}
}
}
# endif
static GLFWwindow * window ;
static int w , h , xpos , ypos , paused ;
static int fullscreen , xprev , yprev , wprev , hprev ;
static uint64_t frame_count ;
static double t , dt , fps , hz = 0.00 ;
static char title [ 128 ] = { 0 } ;
static char screenshot_file [ DIR_MAX ] ;
static int locked_aspect_ratio = 0 ;
2023-09-25 10:24:49 +00:00
static vec4 winbgcolor = { 0 , 0 , 0 , 1 } ;
vec4 window_getcolor_ ( ) { return winbgcolor ; } // internal
2023-07-30 19:18:50 +00:00
// -----------------------------------------------------------------------------
// glfw
struct app {
GLFWwindow * window ;
int width , height , keep_running ;
2023-09-11 10:19:29 +00:00
unsigned flags ;
2023-07-30 19:18:50 +00:00
struct nk_context * ctx ;
struct nk_glfw * nk_glfw ;
} appHandle = { 0 } , * g ;
static void glfw_error_callback ( int error , const char * description ) {
if ( is ( osx ) & & error = = 65544 ) return ; // whitelisted
PANIC ( " %s (error %x) " , description , error ) ;
}
void glfw_quit ( void ) {
do_once {
glfwTerminate ( ) ;
}
}
void glfw_init ( ) {
do_once {
g = & appHandle ;
glfwSetErrorCallback ( glfw_error_callback ) ;
int ok = ! ! glfwInit ( ) ;
assert ( ok ) ; // if(!ok) PANIC("cannot initialize glfw");
atexit ( glfw_quit ) ; //glfwTerminate);
}
}
void window_drop_callback ( GLFWwindow * window , int count , const char * * paths ) {
// @fixme: win: convert from utf8 to window16 before processing
char pathdir [ DIR_MAX ] ; snprintf ( pathdir , DIR_MAX , " %s/import/%llu_%s/ " , ART , ( unsigned long long ) date ( ) , ifdef ( linux , getlogin ( ) , getenv ( " USERNAME " ) ) ) ;
mkdir ( pathdir , 0777 ) ;
int errors = 0 ;
for ( int i = 0 ; i < count ; + + i ) {
const char * src = paths [ i ] ;
const char * dst = va ( " %s%s " , pathdir , file_name ( src ) ) ;
errors + = file_copy ( src , dst ) ? 0 : 1 ;
}
if ( errors ) PANIC ( " %d errors found during file dropping " , errors ) ;
else window_reload ( ) ;
( void ) window ;
}
void window_hints ( unsigned flags ) {
# ifdef __APPLE__
//glfwInitHint( GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE );
2023-10-21 13:47:14 +00:00
glfwWindowHint ( GLFW_COCOA_RETINA_FRAMEBUFFER , GLFW_FALSE ) ;
2023-07-30 19:18:50 +00:00
//glfwWindowHint( GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_FALSE );
//glfwWindowHint( GLFW_COCOA_MENUBAR, GLFW_FALSE );
# endif
2023-09-08 06:16:32 +00:00
# ifdef __APPLE__
2023-07-30 19:18:50 +00:00
/* We need to explicitly ask for a 3.2 context on OS X */
glfwWindowHint ( GLFW_CONTEXT_VERSION_MAJOR , 3 ) ; // osx
glfwWindowHint ( GLFW_CONTEXT_VERSION_MINOR , 2 ) ; // osx, 2:#version150,3:330
2023-09-08 06:16:32 +00:00
# else
2023-09-17 09:50:34 +00:00
// Compute shaders need 4.5 otherwise. But...
// According to the GLFW docs, the context version hint acts as a minimum version.
// i.e, the context you actually get may be a higher or highest version (which is usually the case)
2023-09-10 16:16:25 +00:00
glfwWindowHint ( GLFW_CONTEXT_VERSION_MAJOR , 3 ) ;
glfwWindowHint ( GLFW_CONTEXT_VERSION_MINOR , 2 ) ;
2023-09-08 06:16:32 +00:00
# endif
2023-07-30 19:18:50 +00:00
# ifdef __APPLE__
glfwWindowHint ( GLFW_OPENGL_FORWARD_COMPAT , GL_TRUE ) ; //osx
# endif
glfwWindowHint ( GLFW_OPENGL_PROFILE , GLFW_OPENGL_CORE_PROFILE ) ; //osx+ems
glfwWindowHint ( GLFW_STENCIL_BITS , 8 ) ; //osx
glfwWindowHint ( GLFW_OPENGL_DEBUG_CONTEXT , GL_TRUE ) ;
//glfwWindowHint( GLFW_RED_BITS, 8 );
//glfwWindowHint( GLFW_GREEN_BITS, 8 );
//glfwWindowHint( GLFW_BLUE_BITS, 8 );
//glfwWindowHint( GLFW_ALPHA_BITS, 8 );
//glfwWindowHint( GLFW_DEPTH_BITS, 24 );
//glfwWindowHint(GLFW_AUX_BUFFERS, Nth);
//glfwWindowHint(GLFW_STEREO, GL_TRUE);
glfwWindowHint ( GLFW_DOUBLEBUFFER , GL_TRUE ) ;
// Prevent fullscreen window minimize on focus loss
glfwWindowHint ( GLFW_AUTO_ICONIFY , GL_FALSE ) ;
// Fix SRGB on intels
glfwWindowHint ( GLFW_SRGB_CAPABLE , GLFW_TRUE ) ;
glfwWindowHint ( GLFW_FOCUSED , GLFW_TRUE ) ;
glfwWindowHint ( GLFW_VISIBLE , GLFW_FALSE ) ;
// glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
// glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // makes it non-resizable
if ( flags & WINDOW_MSAA2 ) glfwWindowHint ( GLFW_SAMPLES , 2 ) ; // x2 AA
if ( flags & WINDOW_MSAA4 ) glfwWindowHint ( GLFW_SAMPLES , 4 ) ; // x4 AA
if ( flags & WINDOW_MSAA8 ) glfwWindowHint ( GLFW_SAMPLES , 8 ) ; // x8 AA
2023-09-11 10:19:29 +00:00
g - > flags = flags ;
2023-07-30 19:18:50 +00:00
}
struct nk_glfw * window_handle_nkglfw ( ) {
return g - > nk_glfw ;
}
void glNewFrame ( ) {
2023-09-25 10:24:49 +00:00
// @transparent debug
// if( input_down(KEY_F1) ) window_transparent(window_has_transparent()^1);
// if( input_down(KEY_F2) ) window_maximize(window_has_maximize()^1);
// @transparent debug
2023-07-30 19:18:50 +00:00
#if 0 // is(ems)
int canvasWidth , canvasHeight ;
emscripten_get_canvas_element_size ( " #canvas " , & canvasWidth , & canvasHeight ) ;
w = canvasWidth ;
h = canvasHeight ;
//printf("%dx%d\n", w, h);
# else
//glfwGetWindowSize(window, &w, &h);
glfwGetFramebufferSize ( window , & w , & h ) ;
//printf("%dx%d\n", w, h);
# endif
g - > width = w ;
g - > height = h ;
// blending defaults
glEnable ( GL_BLEND ) ;
// culling defaults
// glEnable(GL_CULL_FACE);
// glCullFace(GL_BACK);
// glFrontFace(GL_CCW);
// depth-testing defaults
glEnable ( GL_DEPTH_TEST ) ;
// glDepthFunc(GL_LESS);
// depth-writing defaults
// glDepthMask(GL_TRUE);
// seamless cubemaps
// glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
glViewport ( 0 , 0 , window_width ( ) , window_height ( ) ) ;
2023-09-21 10:45:42 +00:00
// GLfloat bgColor[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE, bgColor);
2023-09-25 10:24:49 +00:00
glClearColor ( winbgcolor . r , winbgcolor . g , winbgcolor . b , window_has_transparent ( ) ? 0 : winbgcolor . a ) ; // @transparent
2023-07-30 19:18:50 +00:00
//glClearColor(0.15,0.15,0.15,1);
//glClearColor( clearColor.r, clearColor.g, clearColor.b, clearColor.a );
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ) ;
}
bool window_create_from_handle ( void * handle , float scale , unsigned flags ) {
2023-10-21 09:18:13 +00:00
// abort run if any test suite failed in unit-test mode
2023-10-23 13:25:03 +00:00
ifdef ( debug , if ( flag ( " --test " ) ) exit ( test_errors ? - test_errors : 0 ) ) ;
2023-10-16 20:07:29 +00:00
2023-07-30 19:18:50 +00:00
glfw_init ( ) ;
2023-08-10 14:30:56 +00:00
v4k_init ( ) ;
2023-07-30 19:18:50 +00:00
if ( ! t ) t = glfwGetTime ( ) ;
# if is(ems)
scale = 100.f ;
# endif
2023-09-21 10:45:42 +00:00
if ( flag ( " --fullscreen " ) ) scale = 100 ;
2023-07-30 19:18:50 +00:00
scale = ( scale < 1 ? scale * 100 : scale ) ;
2023-09-21 10:45:42 +00:00
2023-07-30 19:18:50 +00:00
bool FLAGS_FULLSCREEN = scale > 100 ;
bool FLAGS_FULLSCREEN_DESKTOP = scale = = 100 ;
bool FLAGS_WINDOWED = scale < 100 ;
2023-09-21 10:45:42 +00:00
bool FLAGS_TRANSPARENT = flag ( " --transparent " ) | | ( flags & WINDOW_TRANSPARENT ) ;
if ( FLAGS_TRANSPARENT ) FLAGS_FULLSCREEN = 0 , FLAGS_FULLSCREEN_DESKTOP = 0 , FLAGS_WINDOWED = 1 ;
2023-07-30 19:18:50 +00:00
scale = ( scale > 100 ? 100 : scale ) / 100.f ;
int winWidth = window_canvas ( ) . w * scale ;
int winHeight = window_canvas ( ) . h * scale ;
window_hints ( flags ) ;
GLFWmonitor * monitor = NULL ;
# ifndef __EMSCRIPTEN__
if ( FLAGS_FULLSCREEN | | FLAGS_FULLSCREEN_DESKTOP ) {
monitor = glfwGetPrimaryMonitor ( ) ;
}
if ( FLAGS_FULLSCREEN_DESKTOP ) {
const GLFWvidmode * mode = glfwGetVideoMode ( monitor ) ;
glfwWindowHint ( GLFW_RED_BITS , mode - > redBits ) ;
glfwWindowHint ( GLFW_GREEN_BITS , mode - > greenBits ) ;
glfwWindowHint ( GLFW_BLUE_BITS , mode - > blueBits ) ;
glfwWindowHint ( GLFW_REFRESH_RATE , mode - > refreshRate ) ;
winWidth = mode - > width ;
winHeight = mode - > height ;
}
if ( FLAGS_WINDOWED ) {
2023-09-25 10:24:49 +00:00
# if !is(ems)
if ( FLAGS_TRANSPARENT ) { // @transparent
//glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); // see through. requires undecorated
//glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); // always on top
glfwWindowHint ( GLFW_TRANSPARENT_FRAMEBUFFER , GLFW_TRUE ) ;
}
# endif
2023-07-30 19:18:50 +00:00
// windowed
float ratio = ( float ) winWidth / ( winHeight + ! winHeight ) ;
if ( flags & WINDOW_SQUARE ) winWidth = winHeight = winWidth > winHeight ? winHeight : winWidth ;
//if( flags & WINDOW_LANDSCAPE ) if( winWidth < winHeight ) winHeight = winWidth * ratio;
if ( flags & WINDOW_PORTRAIT ) if ( winWidth > winHeight ) winWidth = winHeight * ( 1.f / ratio ) ;
}
# endif
window = handle ? handle : glfwCreateWindow ( winWidth , winHeight , " " , monitor , NULL ) ;
if ( ! window ) return PANIC ( " GLFW Window creation failed " ) , false ;
glfwGetFramebufferSize ( window , & w , & h ) ; //glfwGetWindowSize(window, &w, &h);
if ( flags & WINDOW_FIXED ) { // disable resizing
glfwSetWindowSizeLimits ( window , w , h , w , h ) ;
}
if ( flags & ( WINDOW_SQUARE | WINDOW_PORTRAIT | WINDOW_LANDSCAPE | WINDOW_ASPECT ) ) { // keep aspect ratio
window_aspect_lock ( w , h ) ;
}
# ifndef __EMSCRIPTEN__
if ( FLAGS_WINDOWED ) {
// center window
monitor = monitor ? monitor : glfwGetPrimaryMonitor ( ) ;
const GLFWvidmode * mode = glfwGetVideoMode ( monitor ) ;
int area_width = mode - > width , area_height = mode - > height ;
glfwGetMonitorWorkarea ( monitor , & xpos , & ypos , & area_width , & area_height ) ;
glfwSetWindowPos ( window , xpos = xpos + ( area_width - winWidth ) / 2 , ypos = ypos + ( area_height - winHeight ) / 2 ) ;
//printf("%dx%d @(%d,%d) [res:%dx%d]\n", winWidth, winHeight, xpos,ypos, area_width, area_height );
wprev = w , hprev = h ;
xprev = xpos , yprev = ypos ;
}
# endif
glfwMakeContextCurrent ( window ) ;
# if is(ems)
if ( FLAGS_FULLSCREEN ) window_fullscreen ( 1 ) ;
# else
2023-09-27 06:49:59 +00:00
int gl_version = gladLoadGL ( glfwGetProcAddress ) ;
2023-07-30 19:18:50 +00:00
# endif
glDebugEnable ( ) ;
// setup nuklear ui
ui_ctx = nk_glfw3_init ( & nk_glfw , window , NK_GLFW3_INSTALL_CALLBACKS ) ;
//glEnable(GL_TEXTURE_2D);
// 0:disable vsync, 1:enable vsync, <0:adaptive (allow vsync when framerate is higher than syncrate and disable vsync when framerate drops below syncrate)
flags | = optioni ( " --vsync " , 1 ) | | flag ( " --vsync " ) ? WINDOW_VSYNC : WINDOW_VSYNC_DISABLED ;
flags | = optioni ( " --vsync-adaptive " , 0 ) | | flag ( " --vsync-adaptive " ) ? WINDOW_VSYNC_ADAPTIVE : 0 ;
int has_adaptive_vsync = glfwExtensionSupported ( " WGL_EXT_swap_control_tear " ) | | glfwExtensionSupported ( " GLX_EXT_swap_control_tear " ) | | glfwExtensionSupported ( " EXT_swap_control_tear " ) ;
int wants_adaptive_vsync = ( flags & WINDOW_VSYNC_ADAPTIVE ) ;
int interval = has_adaptive_vsync & & wants_adaptive_vsync ? - 1 : ( flags & WINDOW_VSYNC_DISABLED ? 0 : 1 ) ;
glfwSwapInterval ( interval ) ;
const GLFWvidmode * mode = glfwGetVideoMode ( monitor ? monitor : glfwGetPrimaryMonitor ( ) ) ;
2023-10-07 17:34:09 +00:00
PRINTF ( " Build version: %s \n " , BUILD_VERSION ) ;
2023-07-30 19:18:50 +00:00
PRINTF ( " Monitor: %s (%dHz, vsync=%d) \n " , glfwGetMonitorName ( monitor ? monitor : glfwGetPrimaryMonitor ( ) ) , mode - > refreshRate , interval ) ;
PRINTF ( " GPU device: %s \n " , glGetString ( GL_RENDERER ) ) ;
PRINTF ( " GPU driver: %s \n " , glGetString ( GL_VERSION ) ) ;
2023-09-21 10:45:42 +00:00
# if !is(ems)
2023-09-27 06:49:59 +00:00
PRINTF ( " GPU OpenGL: %d.%d \n " , GLAD_VERSION_MAJOR ( gl_version ) , GLAD_VERSION_MINOR ( gl_version ) ) ;
2023-09-21 10:45:42 +00:00
if ( FLAGS_TRANSPARENT ) { // @transparent
glfwSetWindowAttrib ( window , GLFW_DECORATED , GLFW_FALSE ) ;
if ( scale > = 1 ) glfwMaximizeWindow ( window ) ;
}
# endif
2023-07-30 19:18:50 +00:00
g - > ctx = ui_ctx ;
g - > nk_glfw = & nk_glfw ;
g - > window = window ;
g - > width = window_width ( ) ;
g - > height = window_height ( ) ;
// window_cursor(flags & WINDOW_NO_MOUSE ? false : true);
glfwSetDropCallback ( window , window_drop_callback ) ;
2023-08-10 14:30:56 +00:00
// camera inits for v4k_pre_init() -> ddraw_flush() -> get_active_camera()
2023-07-30 19:18:50 +00:00
// static camera_t cam = {0}; id44(cam.view); id44(cam.proj); extern camera_t *last_camera; last_camera = &cam;
2023-08-10 14:30:56 +00:00
v4k_pre_init ( ) ;
2023-07-30 19:18:50 +00:00
// display a progress bar meanwhile cook is working in the background
// Sleep(500);
if ( ! COOK_ON_DEMAND )
2023-10-15 11:16:35 +00:00
if ( have_tools ( ) & & cook_jobs ( ) )
2023-07-30 19:18:50 +00:00
while ( cook_progress ( ) < 100 ) {
for ( int frames = 0 ; frames < 2 /*10*/ & & window_swap ( ) ; frames + = cook_progress ( ) > = 100 ) {
window_title ( va ( " %s %.2d%% " , cook_cancelling ? " Aborting " : " Cooking assets " , cook_progress ( ) ) ) ;
if ( input ( KEY_ESC ) ) cook_cancel ( ) ;
glNewFrame ( ) ;
static float previous [ JOBS_MAX ] = { 0 } ;
# define ddraw_progress_bar(JOB_ID, JOB_MAX, PERCENT) do { \
/* NDC coordinates (2d): bottom-left(-1,-1), center(0,0), top-right(+1,+1) */ \
float progress = ( PERCENT + 1 ) / 100.f ; if ( progress > 1 ) progress = 1 ; \
float speed = progress < 1 ? 0.2f : 0.5f ; \
float smooth = previous [ JOB_ID ] = progress * speed + previous [ JOB_ID ] * ( 1 - speed ) ; \
\
float pixel = 2.f / window_height ( ) , dist = smooth * 2 - 1 , y = pixel * 3 * JOB_ID ; \
if ( JOB_ID = = 0 ) ddraw_line ( vec3 ( - 1 , y - pixel * 2 , 0 ) , vec3 ( 1 , y - pixel * 2 , 0 ) ) ; /* full line */ \
ddraw_line ( vec3 ( - 1 , y - pixel , 0 ) , vec3 ( dist , y - pixel , 0 ) ) ; /* progress line */ \
ddraw_line ( vec3 ( - 1 , y + 0 , 0 ) , vec3 ( dist , y + 0 , 0 ) ) ; /* progress line */ \
ddraw_line ( vec3 ( - 1 , y + pixel , 0 ) , vec3 ( dist , y + pixel , 0 ) ) ; /* progress line */ \
if ( JOB_ID = = JOB_MAX - 1 ) ddraw_line ( vec3 ( - 1 , y + pixel * 2 , 0 ) , vec3 ( 1 , y + pixel * 2 , 0 ) ) ; /* full line */ \
} while ( 0 )
2023-09-21 10:45:42 +00:00
if ( FLAGS_TRANSPARENT ) { } else // @transparent
2023-07-30 19:18:50 +00:00
for ( int i = 0 ; i < cook_jobs ( ) ; + + i ) ddraw_progress_bar ( i , cook_jobs ( ) , jobs [ i ] . progress ) ;
// ddraw_progress_bar(0, 1, cook_progress());
ddraw_flush ( ) ;
do_once window_visible ( 1 ) ;
// render progress bar at 30Hz + give the cook threads more time to actually cook the assets.
// no big deal since progress bar is usually quiet when cooking assets most of the time.
// also, make the delay even larger when window is minimized or hidden.
// shaved cook times: 88s -> 57s (tcc), 50s -> 43s (vc)
sleep_ms ( window_has_visible ( ) & & window_has_focus ( ) ? 8 : 16 ) ;
}
// set black screen
glNewFrame ( ) ;
window_swap ( ) ;
2023-10-13 15:13:45 +00:00
# if !ENABLE_RETAIL
2023-07-30 19:18:50 +00:00
window_title ( " " ) ;
2023-10-13 15:13:45 +00:00
# endif
2023-07-30 19:18:50 +00:00
}
if ( cook_cancelling ) cook_stop ( ) , exit ( - 1 ) ;
2023-08-10 14:30:56 +00:00
v4k_post_init ( mode - > refreshRate ) ;
2023-07-30 19:18:50 +00:00
return true ;
}
bool window_create ( float scale , unsigned flags ) {
return window_create_from_handle ( NULL , scale , flags ) ;
}
static double boot_time = 0 ;
char * window_stats ( ) {
static double num_frames = 0 , begin = FLT_MAX , prev_frame = 0 ;
double now = time_ss ( ) ;
if ( boot_time < 0 ) boot_time = now ;
if ( begin > now ) {
begin = now ;
num_frames = 0 ;
}
if ( ( now - begin ) > = 0.25f ) {
fps = num_frames * ( 1.f / ( now - begin ) ) ;
}
if ( ( now - begin ) > 1 ) {
begin = now + ( ( now - begin ) - 1 ) ;
num_frames = 0 ;
}
const char * cmdline = app_cmdline ( ) ;
// @todo: print %used/%avail kib mem, %used/%avail objs as well
static char buf [ 256 ] ;
2023-10-07 17:34:09 +00:00
snprintf ( buf , 256 , " %s%s%s%s | boot %.2fs | %5.2ffps (%.2fms)%s%s " ,
title , BUILD_VERSION [ 0 ] ? " ( " : " " , BUILD_VERSION [ 0 ] ? BUILD_VERSION : " " , BUILD_VERSION [ 0 ] ? " ) " : " " ,
! boot_time ? now : boot_time ,
fps , ( now - prev_frame ) * 1000.f ,
cmdline [ 0 ] ? " | " : " " , cmdline [ 0 ] ? cmdline : " " ) ;
2023-07-30 19:18:50 +00:00
prev_frame = now ;
+ + num_frames ;
2023-10-15 11:16:35 +00:00
return buf + strspn ( buf , " " ) ;
2023-07-30 19:18:50 +00:00
}
int window_frame_begin ( ) {
glfwPollEvents ( ) ;
// we cannot simply terminate threads on some OSes. also, aborted cook jobs could leave temporary files on disc.
// so let's try to be polite: we will be disabling any window closing briefly until all cook is either done or canceled.
2023-10-15 11:16:35 +00:00
static bool has_cook ; do_once has_cook = ! COOK_ON_DEMAND & & have_tools ( ) & & cook_jobs ( ) ;
2023-07-30 19:18:50 +00:00
if ( has_cook ) {
has_cook = cook_progress ( ) < 100 ;
if ( glfwWindowShouldClose ( g - > window ) ) cook_cancel ( ) ;
glfwSetWindowShouldClose ( g - > window , GLFW_FALSE ) ;
}
if ( glfwWindowShouldClose ( g - > window ) ) {
return 0 ;
}
glNewFrame ( ) ;
ui_create ( ) ;
2023-10-15 11:16:35 +00:00
# if !ENABLE_RETAIL
bool has_menu = 0 ; // ui_has_menubar();
bool may_render_debug_panel = 1 ;
2023-09-27 06:49:59 +00:00
2023-10-15 11:16:35 +00:00
if ( have_tools ( ) ) {
static int cook_has_progressbar ; do_once cook_has_progressbar = ! COOK_ON_DEMAND ;
if ( cook_has_progressbar ) {
2023-09-27 06:49:59 +00:00
// render profiler, unless we are in the cook progress screen
static unsigned frames = 0 ; if ( frames < = 0 ) frames + = cook_progress ( ) > = 100 ;
2023-10-15 11:16:35 +00:00
may_render_debug_panel = ( frames > 0 ) ;
2023-09-27 06:49:59 +00:00
}
}
// generate Debug panel contents
2023-10-15 11:16:35 +00:00
if ( may_render_debug_panel ) {
2023-10-01 06:49:08 +00:00
if ( has_menu ? ui_window ( " Debug " ICON_MD_SETTINGS , 0 ) : ui_panel ( " Debug " ICON_MD_SETTINGS , 0 ) ) {
2023-10-13 10:59:44 +00:00
static int time_factor = 0 ;
static int playing = 0 ;
static int paused = 0 ;
int advance_frame = 0 ;
2023-10-01 06:49:08 +00:00
static int do_filter = 0 ;
2023-10-13 10:59:44 +00:00
static int do_profile = 0 ;
static int do_extra = 0 ;
char * EDITOR_TOOLBAR_ICONS = va ( " %s;%s;%s;%s;%s;%s;%s;%s " ,
do_filter ? ICON_MD_CLOSE : ICON_MD_SEARCH ,
ICON_MD_PLAY_ARROW ,
paused ? ICON_MD_SKIP_NEXT : ICON_MD_PAUSE ,
ICON_MD_FAST_FORWARD ,
ICON_MD_STOP ,
ICON_MD_REPLAY ,
ICON_MD_FACE ,
ICON_MD_MENU
) ;
2023-10-01 06:49:08 +00:00
if ( input_down ( KEY_F ) ) if ( input ( KEY_LCTRL ) | | input ( KEY_RCTRL ) ) do_filter ^ = 1 ;
2023-10-13 10:59:44 +00:00
int choice = ui_toolbar ( EDITOR_TOOLBAR_ICONS ) ;
if ( choice = = 1 ) do_filter ^ = 1 , do_profile = 0 , do_extra = 0 ;
if ( choice = = 2 ) playing = 1 , paused = 0 ;
if ( choice = = 3 ) advance_frame = ! ! paused , paused = 1 ;
if ( choice = = 4 ) paused = 0 , time_factor = ( + + time_factor ) % 4 ;
if ( choice = = 5 ) playing = 0 , paused = 0 , advance_frame = 0 , time_factor = 0 ;
if ( choice = = 6 ) window_reload ( ) ;
if ( choice = = 7 ) do_filter = 0 , do_profile ^ = 1 , do_extra = 0 ;
if ( choice = = 8 ) do_filter = 0 , do_profile = 0 , do_extra ^ = 1 ;
static char * filter = 0 ;
2023-10-01 06:49:08 +00:00
if ( do_filter ) {
ui_string ( ICON_MD_CLOSE " Filter " ICON_MD_SEARCH , & filter ) ;
if ( ui_label_icon_clicked_L . x > 0 & & ui_label_icon_clicked_L . x < = 24 ) { // if clicked on CANCEL icon (1st icon)
do_filter = 0 ;
}
} else {
if ( filter ) filter [ 0 ] = ' \0 ' ;
}
char * filter_mask = filter & & filter [ 0 ] ? va ( " *%s* " , filter ) : " * " ;
2023-10-13 10:59:44 +00:00
static char * username = 0 ;
static char * userpass = 0 ;
if ( do_profile ) {
ui_string ( ICON_MD_FACE " Username " , & username ) ;
ui_string ( ICON_MD_FACE " Password " , & userpass ) ;
}
if ( do_extra ) {
int choice2 = ui_label2_toolbar ( NULL ,
ICON_MD_VIEW_IN_AR
ICON_MD_MESSAGE
ICON_MD_TIPS_AND_UPDATES ICON_MD_LIGHTBULB ICON_MD_LIGHTBULB_OUTLINE
ICON_MD_IMAGE_SEARCH ICON_MD_INSERT_PHOTO
ICON_MD_VIDEOGAME_ASSET ICON_MD_VIDEOGAME_ASSET_OFF
ICON_MD_VOLUME_UP ICON_MD_VOLUME_OFF // audio_volume_master(-1) > 0
ICON_MD_TROUBLESHOOT ICON_MD_SCHEMA ICON_MD_MENU
) ;
}
2023-09-27 06:49:59 +00:00
int open = 0 , clicked_or_toggled = 0 ;
2023-10-01 06:49:08 +00:00
# define ui_collapse_filtered(lbl,id) (strmatchi(lbl,filter_mask) && ui_collapse(lbl,id))
2023-10-13 10:59:44 +00:00
# define EDITOR_UI_COLLAPSE(f,...) \
for ( int macro ( p ) = ( open = ui_collapse_filtered ( f , __VA_ARGS__ ) ) , macro ( dummy ) = ( clicked_or_toggled = ui_collapse_clicked ( ) ) ; macro ( p ) ; ui_collapse_end ( ) , macro ( p ) = 0 )
EDITOR_UI_COLLAPSE ( ICON_MD_BUG_REPORT " Bugs 0 " , " Debug.Bugs " ) {
// @todo. parse /bugs.ini, includes saved screenshots & videos.
// @todo. screenshot include parseable level, position screen markers (same info as /bugs.ini)
}
// Art and bookmarks
EDITOR_UI_COLLAPSE ( ICON_MD_FOLDER_SPECIAL " Art " , " Debug.Art " ) {
2023-10-01 06:49:08 +00:00
bool inlined = true ;
const char * file = 0 ;
if ( ui_browse ( & file , & inlined ) ) {
const char * sep = ifdef ( win32 , " \" " , " ' " ) ;
app_exec ( va ( " %s %s%s%s " , ifdef ( win32 , " start \" \" " , ifdef ( osx , " open " , " xdg-open " ) ) , sep , file , sep ) ) ;
}
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_BOOKMARK " Bookmarks " , " Debug.Bookmarks " ) { /* @todo */ }
// E,C,S,W
EDITOR_UI_COLLAPSE ( ICON_MD_ACCOUNT_TREE " Scene " , " Debug.Scene " ) {
EDITOR_UI_COLLAPSE ( ICON_MD_BUBBLE_CHART /*ICON_MD_SCATTER_PLOT*/ " Entities " , " Debug.Entities " ) { /* @todo */ }
EDITOR_UI_COLLAPSE ( ICON_MD_TUNE " Components " , " Debug.Components " ) { /* @todo */ }
EDITOR_UI_COLLAPSE ( ICON_MD_PRECISION_MANUFACTURING " Systems " , " Debug.Systems " ) { /* @todo */ }
EDITOR_UI_COLLAPSE ( ICON_MD_PUBLIC " Levels " , " Debug.Levels " ) {
//node_edit(editor.edit.down,&editor.edit);
2023-10-01 06:49:08 +00:00
}
2023-10-13 10:59:44 +00:00
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Init", "Debug.HierarchyInit") { /* @todo */ }
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Draw", "Debug.HierarchyDraw") { /* @todo */ }
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Tick", "Debug.HierarchyTick") { /* @todo */ }
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Edit", "Debug.HierarchyEdit") { /* @todo */ }
//EDITOR_UI_COLLAPSE(ICON_MD_ACCOUNT_TREE " Quit", "Debug.HierarchyQuit") { /* @todo */ }
// node_edit(&editor.init,&editor.init);
// node_edit(&editor.draw,&editor.draw);
// node_edit(&editor.tick,&editor.tick);
// node_edit(&editor.edit,&editor.edit);
// node_edit(&editor.quit,&editor.quit);
2023-10-01 06:49:08 +00:00
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_ROCKET_LAUNCH " AI " , " Debug.AI " ) {
2023-10-01 06:49:08 +00:00
// @todo
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_VOLUME_UP " Audio " , " Debug.Audio " ) {
ui_audio ( ) ;
2023-09-27 06:49:59 +00:00
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_VIDEOCAM " Camera " , " Debug.Camera " ) {
ui_camera ( camera_get_active ( ) ) ;
2023-09-27 06:49:59 +00:00
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_MONITOR " Display " , " Debug.Display " ) {
// @todo: fps lock, fps target, aspect ratio, fullscreen
char * text = va ( " %s;%s;%s " ,
window_has_fullscreen ( ) ? ICON_MD_FULLSCREEN_EXIT : ICON_MD_FULLSCREEN ,
ICON_MD_PHOTO_CAMERA ,
record_active ( ) ? ICON_MD_VIDEOCAM_OFF : ICON_MD_VIDEOCAM
) ;
int choice = ui_toolbar ( text ) ;
2023-10-26 07:27:46 +00:00
if ( choice = = 1 ) engine_send ( " key_fullscreen " , 0 ) ;
if ( choice = = 2 ) engine_send ( " key_screenshot " , 0 ) ;
if ( choice = = 3 ) engine_send ( " key_record " , 0 ) ;
2023-09-27 06:49:59 +00:00
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_KEYBOARD " Keyboard " , " Debug.Keyboard " ) {
2023-10-01 06:49:08 +00:00
ui_keyboard ( ) ;
2023-09-27 06:49:59 +00:00
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_MOUSE " Mouse " , " Debug.Mouse " ) {
2023-10-01 06:49:08 +00:00
ui_mouse ( ) ;
2023-09-27 06:49:59 +00:00
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_GAMEPAD " Gamepads " , " Debug.Gamepads " ) {
2023-10-01 06:49:08 +00:00
for ( int q = 0 ; q < 4 ; + + q ) {
for ( int r = ( open = ui_collapse ( va ( " Gamepad #%d " , q + 1 ) , va ( " Debug.Gamepads%d " , q ) ) ) , dummy = ( clicked_or_toggled = ui_collapse_clicked ( ) ) ; r ; ui_collapse_end ( ) , r = 0 ) {
ui_gamepad ( q ) ;
}
}
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_CONTENT_PASTE " Scripts " , " Debug.Scripts " ) {
// @todo
}
EDITOR_UI_COLLAPSE ( ICON_MD_STAR_HALF " Shaders " , " Debug.Shaders " ) {
ui_shaders ( ) ;
}
EDITOR_UI_COLLAPSE ( ICON_MD_MOVIE " FXs " , " Debug.FXs " ) {
ui_fxs ( ) ;
}
EDITOR_UI_COLLAPSE ( ICON_MD_VIEW_QUILT " UI " , " Debug.UI " ) {
2023-10-01 06:49:08 +00:00
int choice = ui_toolbar ( ICON_MD_RECYCLING " Reset layout; " ICON_MD_SAVE_AS " Save layout " ) ;
if ( choice = = 1 ) ui_layout_all_reset ( " * " ) ;
if ( choice = = 2 ) file_delete ( WINDOWS_INI ) , ui_layout_all_save_disk ( " * " ) ;
for each_map_ptr_sorted ( ui_windows , char * , k , unsigned , v ) {
bool visible = ui_visible ( * k ) ;
if ( ui_bool ( * k , & visible ) ) {
ui_show ( * k , ui_visible ( * k ) ^ true ) ;
}
}
2023-09-27 06:49:59 +00:00
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_SAVINGS " Budgets " , " Debug.Budgets " ) {
// @todo. // mem,fps,gfx,net,hdd,... also logging
}
EDITOR_UI_COLLAPSE ( ICON_MD_WIFI /*ICON_MD_SIGNAL_CELLULAR_ALT*/ " Network 0/0 KiB " , " Debug.Network " ) {
// @todo
// SIGNAL_CELLULAR_1_BAR SIGNAL_CELLULAR_2_BAR
}
2023-10-27 07:42:40 +00:00
EDITOR_UI_COLLAPSE ( va ( ICON_MD_SPEED " Profiler %5.2f/%dfps " , window_fps ( ) , ( int ) window_fps_target ( ) ) , " Debug.Profiler " ) {
2023-10-13 10:59:44 +00:00
ui_profiler ( ) ;
}
EDITOR_UI_COLLAPSE ( va ( ICON_MD_STORAGE " Storage %s " , xstats ( ) ) , " Debug.Storage " ) {
// @todo
}
// logic: either plug icon (power saving off) or one of the following ones (power saving on):
// if 0% batt (no batt): battery alert
// if discharging: battery levels [alert,0..6,full]
// if charging: battery charging
int battery_read = app_battery ( ) ;
int battery_level = abs ( battery_read ) ;
int battery_discharging = battery_read < 0 & & battery_level < 100 ;
const char * power_icon_label = ICON_MD_POWER " Power " ;
if ( battery_level ) {
const char * battery_levels [ 9 ] = { // @todo: remap [7%..100%] -> [0..1] ?
ICON_MD_BATTERY_ALERT , ICON_MD_BATTERY_0_BAR , ICON_MD_BATTERY_1_BAR ,
ICON_MD_BATTERY_2_BAR , ICON_MD_BATTERY_3_BAR , ICON_MD_BATTERY_4_BAR ,
ICON_MD_BATTERY_5_BAR , ICON_MD_BATTERY_6_BAR , ICON_MD_BATTERY_FULL ,
} ;
power_icon_label = ( const char * ) va ( " %s Power %d%% " ,
battery_discharging ? battery_levels [ ( int ) ( ( 9 - 1 ) * clampf ( battery_level / 100.f , 0 , 1 ) ) ] : ICON_MD_BATTERY_CHARGING_FULL ,
battery_level ) ;
}
EDITOR_UI_COLLAPSE ( power_icon_label , " Debug.Power " ) {
int choice = ui_toolbar ( ICON_MD_POWER " ; " ICON_MD_BOLT ) ;
2023-10-26 07:27:46 +00:00
if ( choice = = 1 ) engine_send ( " key_battery " , " 0 " ) ;
if ( choice = = 2 ) engine_send ( " key_battery " , " 1 " ) ;
2023-10-13 10:59:44 +00:00
}
2023-10-23 13:25:03 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_WATER " Reflection " , " Debug.Reflect " ) {
ui_reflect ( " * " ) ;
}
2023-10-13 10:59:44 +00:00
EDITOR_UI_COLLAPSE ( ICON_MD_EXTENSION " Plugins " , " Debug.Plugins " ) {
// @todo. include VCS
EDITOR_UI_COLLAPSE ( ICON_MD_BUILD " Cook " , " Debug.Cook " ) {
// @todo
}
}
2023-09-27 06:49:59 +00:00
( has_menu ? ui_window_end : ui_panel_end ) ( ) ;
}
2023-10-13 10:59:44 +00:00
2023-10-26 07:27:46 +00:00
API int engine_tick ( ) ;
engine_tick ( ) ;
2023-09-27 06:49:59 +00:00
}
2023-10-13 15:13:45 +00:00
# endif // ENABLE_RETAIL
2023-07-30 19:18:50 +00:00
#if 0 // deprecated
// run user-defined hooks
for ( int i = 0 ; i < 64 ; + + i ) {
if ( hooks [ i ] ) hooks [ i ] ( userdatas [ i ] ) ;
}
# endif
double now = paused ? t : glfwGetTime ( ) ;
dt = now - t ;
t = now ;
2023-10-13 15:13:45 +00:00
# if !ENABLE_RETAIL
2023-07-30 19:18:50 +00:00
char * st = window_stats ( ) ;
static double timer = 0 ;
timer + = window_delta ( ) ;
if ( timer > = 0.25 ) {
glfwSetWindowTitle ( window , st ) ;
timer = 0 ;
}
2023-10-13 15:13:45 +00:00
# else
glfwSetWindowTitle ( window , title ) ;
# endif
2023-07-30 19:18:50 +00:00
void input_update ( ) ;
input_update ( ) ;
return 1 ;
}
void window_frame_end ( ) {
// flush batching systems that need to be rendered before frame swapping. order matters.
{
font_goto ( 0 , 0 ) ;
touch_flush ( ) ;
sprite_flush ( ) ;
// flush all debugdraw calls before swap
dd_ontop = 0 ;
ddraw_flush ( ) ;
glClear ( GL_DEPTH_BUFFER_BIT ) ;
dd_ontop = 1 ;
ddraw_flush ( ) ;
ui_render ( ) ;
}
# if !is(ems)
// save screenshot if queued
if ( screenshot_file [ 0 ] ) {
int n = 3 ;
void * rgb = screenshot ( n ) ;
stbi_flip_vertically_on_write ( true ) ;
if ( ! stbi_write_png ( screenshot_file , w , h , n , rgb , n * w ) ) {
PANIC ( " !could not write screenshot file `%s` \n " , screenshot_file ) ;
}
screenshot_file [ 0 ] = 0 ;
}
if ( record_active ( ) ) {
void record_frame ( ) ;
record_frame ( ) ;
}
# endif
}
void window_frame_swap ( ) {
// glFinish();
# if !is(ems)
window_vsync ( hz ) ;
# endif
glfwSwapBuffers ( window ) ;
// emscripten_webgl_commit_frame();
2023-10-01 06:49:08 +00:00
static int delay = 0 ; do_once delay = optioni ( " --delay " , 0 ) ;
if ( delay & & ! COOK_ON_DEMAND & & cook_progress ( ) > = 100 ) sleep_ms ( delay ) ;
2023-07-30 19:18:50 +00:00
}
static
void window_shutdown ( ) {
do_once {
# if ENABLE_SELFIES
snprintf ( screenshot_file , DIR_MAX , " %s.png " , app_name ( ) ) ;
int n = 3 ;
void * rgb = screenshot ( n ) ;
stbi_flip_vertically_on_write ( true ) ;
if ( ! stbi_write_png ( screenshot_file , w , h , n , rgb , n * w ) ) {
PANIC ( " !could not write screenshot file `%s` \n " , screenshot_file ) ;
}
screenshot_file [ 0 ] = 0 ;
# endif
window_loop_exit ( ) ; // finish emscripten loop automatically
}
}
int window_swap ( ) {
// end frame
if ( frame_count > 0 ) {
window_frame_end ( ) ;
window_frame_swap ( ) ;
}
+ + frame_count ;
// begin frame
int ready = window_frame_begin ( ) ;
if ( ! ready ) {
window_shutdown ( ) ;
return 0 ;
}
return 1 ;
}
static
void ( * window_render_callback ) ( void * loopArg ) ;
2023-10-08 15:53:48 +00:00
static vec2 last_canvas_size = { 0 } ;
2023-09-11 10:19:29 +00:00
static
void window_resize ( ) {
# if is(ems)
EM_ASM ( canvas . canResize = 0 ) ;
if ( g - > flags & WINDOW_FIXED ) return ;
EM_ASM ( canvas . canResize = 1 ) ;
vec2 size = window_canvas ( ) ;
if ( size . x ! = last_canvas_size . x | | size . y ! = last_canvas_size . y ) {
w = size . x ;
h = size . y ;
g - > width = w ;
g - > height = h ;
2023-10-08 15:53:48 +00:00
last_canvas_size = vec2 ( w , h ) ;
emscripten_set_canvas_size ( w , h ) ;
2023-09-11 10:19:29 +00:00
}
# endif /* __EMSCRIPTEN__ */
}
2023-07-30 19:18:50 +00:00
static
void window_loop_wrapper ( void * loopArg ) {
if ( window_frame_begin ( ) ) {
2023-09-11 10:19:29 +00:00
window_resize ( ) ;
2023-07-30 19:18:50 +00:00
window_render_callback ( loopArg ) ;
window_frame_end ( ) ;
window_frame_swap ( ) ;
} else {
do_once window_shutdown ( ) ;
}
}
void window_loop ( void ( * user_function ) ( void * loopArg ) , void * loopArg ) {
# if is(ems)
window_render_callback = user_function ;
emscripten_set_main_loop_arg ( window_loop_wrapper , loopArg , 0 , 1 ) ;
# else
g - > keep_running = true ;
while ( g - > keep_running )
user_function ( loopArg ) ;
# endif /* __EMSCRIPTEN__ */
}
void window_loop_exit ( ) {
# if is(ems)
emscripten_cancel_main_loop ( ) ;
# else
g - > keep_running = false ;
# endif /* __EMSCRIPTEN__ */
}
vec2 window_canvas ( ) {
# if is(ems)
2023-10-08 15:53:48 +00:00
int width = EM_ASM_INT_V ( return canvas . width ) ;
int height = EM_ASM_INT_V ( return canvas . height ) ;
2023-07-30 19:18:50 +00:00
return vec2 ( width , height ) ;
# else
glfw_init ( ) ;
const GLFWvidmode * mode = glfwGetVideoMode ( glfwGetPrimaryMonitor ( ) ) ;
assert ( mode ) ;
return vec2 ( mode - > width , mode - > height ) ;
# endif /* __EMSCRIPTEN__ */
}
int window_width ( ) {
return w ;
}
int window_height ( ) {
return h ;
}
double window_time ( ) {
return t ;
}
double window_delta ( ) {
return dt ;
}
double window_fps ( ) {
return fps ;
}
void window_fps_lock ( float fps ) {
hz = fps ;
}
void window_fps_unlock ( ) {
hz = 0 ;
}
double window_fps_target ( ) {
return hz ;
}
uint64_t window_frame ( ) {
return frame_count ;
}
void window_title ( const char * title_ ) {
snprintf ( title , 128 , " %s " , title_ ) ;
if ( ! title [ 0 ] ) glfwSetWindowTitle ( window , title ) ;
}
void window_color ( unsigned color ) {
unsigned b = ( color > > 0 ) & 255 ;
unsigned g = ( color > > 8 ) & 255 ;
unsigned r = ( color > > 16 ) & 255 ;
unsigned a = ( color > > 24 ) & 255 ;
2023-09-25 10:24:49 +00:00
winbgcolor = vec4 ( r / 255.0 , g / 255.0 , b / 255.0 , a / 255.0 ) ;
2023-07-30 19:18:50 +00:00
}
2023-10-14 19:47:24 +00:00
static int has_icon ;
int window_has_icon ( ) {
return has_icon ;
}
2023-07-30 19:18:50 +00:00
void window_icon ( const char * file_icon ) {
2023-10-14 19:47:24 +00:00
int len = 0 ;
void * data = vfs_load ( file_icon , & len ) ;
if ( ! data ) data = file_read ( file_icon ) , len = file_size ( file_icon ) ;
if ( data & & len ) {
2023-07-30 19:18:50 +00:00
image_t img = image_from_mem ( data , len , IMAGE_RGBA ) ;
if ( img . w & & img . h & & img . pixels ) {
GLFWimage images [ 1 ] ;
images [ 0 ] . width = img . w ;
images [ 0 ] . height = img . h ;
images [ 0 ] . pixels = img . pixels ;
glfwSetWindowIcon ( window , 1 , images ) ;
2023-10-14 19:47:24 +00:00
has_icon = 1 ;
2023-07-30 19:18:50 +00:00
return ;
}
}
2023-10-14 19:47:24 +00:00
#if 0 // is(win32)
2023-07-30 19:18:50 +00:00
HANDLE hIcon = LoadImageA ( 0 , file_icon , IMAGE_ICON , 0 , 0 , LR_DEFAULTSIZE | LR_LOADFROMFILE ) ;
if ( hIcon ) {
HWND hWnd = glfwGetWin32Window ( window ) ;
SendMessage ( hWnd , WM_SETICON , ICON_SMALL , ( LPARAM ) hIcon ) ;
SendMessage ( hWnd , WM_SETICON , ICON_BIG , ( LPARAM ) hIcon ) ;
SendMessage ( GetWindow ( hWnd , GW_OWNER ) , WM_SETICON , ICON_SMALL , ( LPARAM ) hIcon ) ;
SendMessage ( GetWindow ( hWnd , GW_OWNER ) , WM_SETICON , ICON_BIG , ( LPARAM ) hIcon ) ;
2023-10-14 19:47:24 +00:00
has_icon = 1 ;
2023-07-30 19:18:50 +00:00
return ;
}
# endif
}
void * window_handle ( ) {
return window ;
}
void window_reload ( ) {
// @todo: save_on_exit();
fflush ( 0 ) ;
2023-10-09 15:05:56 +00:00
// chdir(app_path());
2023-07-30 19:18:50 +00:00
execv ( __argv [ 0 ] , __argv ) ;
exit ( 0 ) ;
}
int window_record ( const char * outfile_mp4 ) {
record_start ( outfile_mp4 ) ;
// @todo: if( flags & RECORD_MOUSE )
if ( record_active ( ) ) window_cursor_shape ( CURSOR_SW_AUTO ) ; else window_cursor_shape ( CURSOR_HW_ARROW ) ;
return record_active ( ) ;
}
2023-10-11 19:01:06 +00:00
vec2 window_dpi ( ) {
2023-10-14 20:20:34 +00:00
vec2 dpi = vec2 ( 1 , 1 ) ;
2023-10-21 13:47:14 +00:00
# if !defined(__EMSCRIPTEN__) && !defined(__APPLE__)
2023-10-14 20:20:34 +00:00
glfwGetMonitorContentScale ( glfwGetPrimaryMonitor ( ) , & dpi . x , & dpi . y ) ;
# endif
return dpi ;
2023-10-11 19:01:06 +00:00
}
2023-07-30 19:18:50 +00:00
// -----------------------------------------------------------------------------
// fullscreen
static
GLFWmonitor * window_find_monitor ( int wx , int wy ) {
GLFWmonitor * monitor = glfwGetPrimaryMonitor ( ) ;
// find best monitor given current window coordinates. @todo: select by ocuppied window area inside each monitor instead.
int num_monitors = 0 ;
GLFWmonitor * * monitors = glfwGetMonitors ( & num_monitors ) ;
# if is(ems)
return * monitors ;
# else
for ( int i = 0 ; i < num_monitors ; + + i ) {
int mx = 0 , my = 0 , mw = 0 , mh = 0 ;
glfwGetMonitorWorkarea ( monitors [ i ] , & mx , & my , & mw , & mh ) ;
monitor = wx > = mx & & wx < = ( mx + mw ) & & wy > = my & & wy < = ( my + mh ) ? monitors [ i ] : monitor ;
}
return monitor ;
# endif
}
#if 0 // to deprecate
void window_fullscreen ( int enabled ) {
fullscreen = ! ! enabled ;
# ifndef __EMSCRIPTEN__
if ( fullscreen ) {
int wx = 0 , wy = 0 ; glfwGetWindowPos ( window , & wx , & wy ) ;
GLFWmonitor * monitor = window_find_monitor ( wx , wy ) ;
wprev = w , hprev = h , xprev = wx , yprev = wy ; // save window context for further restoring
int width , height ;
glfwGetMonitorWorkarea ( monitor , NULL , NULL , & width , & height ) ;
glfwSetWindowMonitor ( window , monitor , 0 , 0 , width , height , GLFW_DONT_CARE ) ;
} else {
glfwSetWindowMonitor ( window , NULL , xpos , ypos , wprev , hprev , GLFW_DONT_CARE ) ;
glfwSetWindowPos ( window , xprev , yprev ) ;
}
# endif
}
int window_has_fullscreen ( ) {
return fullscreen ;
}
# else
int window_has_fullscreen ( ) {
# if is(ems)
EmscriptenFullscreenChangeEvent fsce ;
emscripten_get_fullscreen_status ( & fsce ) ;
return ! ! fsce . isFullscreen ;
# else
return ! ! glfwGetWindowMonitor ( g - > window ) ;
# endif /* __EMSCRIPTEN__ */
}
void window_fullscreen ( int enabled ) {
if ( window_has_fullscreen ( ) = = ! ! enabled ) return ;
# if is(ems)
#if 0 // deprecated: crash
if ( enabled ) {
emscripten_exit_soft_fullscreen ( ) ;
/* Workaround https://github.com/kripken/emscripten/issues/5124#issuecomment-292849872 */
EM_ASM ( JSEvents . inEventHandler = true ) ;
EM_ASM ( JSEvents . currentEventHandler = { allowsDeferredCalls : true } ) ;
EmscriptenFullscreenStrategy strategy = { 0 } ;
strategy . scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH ; // _ASPECT
strategy . canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF ; // _NONE _HIDEF
strategy . filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT ; // _NEAREST
emscripten_request_fullscreen_strategy ( NULL , EM_FALSE /*EM_TRUE*/ , & strategy ) ;
//emscripten_enter_soft_fullscreen(NULL, &strategy);
} else {
emscripten_exit_fullscreen ( ) ;
}
# else
if ( enabled )
EM_ASM ( Module . requestFullscreen ( 1 , 1 ) ) ;
else
EM_ASM ( Module . exitFullscreen ( ) ) ;
# endif
# else
#if 0
if ( enabled ) {
/*glfwGetWindowPos(g->window, &g->window_xpos, &g->window_ypos);*/
glfwGetWindowSize ( g - > window , & g - > width , & g - > height ) ;
glfwSetWindowMonitor ( g - > window , glfwGetPrimaryMonitor ( ) , 0 , 0 , g - > width , g - > height , GLFW_DONT_CARE ) ;
} else {
glfwSetWindowMonitor ( g - > window , NULL , 0 , 0 , g - > width , g - > height , GLFW_DONT_CARE ) ;
}
# else
if ( enabled ) {
int wx = 0 , wy = 0 ; glfwGetWindowPos ( window , & wx , & wy ) ;
GLFWmonitor * monitor = window_find_monitor ( wx , wy ) ;
wprev = w , hprev = h , xprev = wx , yprev = wy ; // save window context for further restoring
int width , height ;
glfwGetMonitorWorkarea ( monitor , NULL , NULL , & width , & height ) ;
glfwSetWindowMonitor ( window , monitor , 0 , 0 , width , height , GLFW_DONT_CARE ) ;
} else {
glfwSetWindowMonitor ( window , NULL , xpos , ypos , wprev , hprev , GLFW_DONT_CARE ) ;
glfwSetWindowPos ( window , xprev , yprev ) ;
}
# endif
# endif
}
# endif
void window_pause ( int enabled ) {
paused = enabled ;
}
int window_has_pause ( ) {
return paused ;
}
void window_focus ( ) {
glfwFocusWindow ( window ) ;
}
int window_has_focus ( ) {
return ! ! glfwGetWindowAttrib ( window , GLFW_FOCUSED ) ;
}
void window_cursor ( int visible ) {
glfwSetInputMode ( window , GLFW_CURSOR , visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED ) ;
}
int window_has_cursor ( ) {
return glfwGetInputMode ( window , GLFW_CURSOR ) = = GLFW_CURSOR_NORMAL ;
}
void window_cursor_shape ( unsigned mode ) {
static GLFWcursor * cursors [ 7 ] = { 0 } ;
static unsigned enums [ 7 ] = {
0 ,
GLFW_ARROW_CURSOR ,
GLFW_IBEAM_CURSOR ,
GLFW_CROSSHAIR_CURSOR ,
GLFW_HAND_CURSOR ,
GLFW_HRESIZE_CURSOR ,
GLFW_VRESIZE_CURSOR ,
} ;
do_once {
static unsigned pixels [ 16 * 16 ] = { 0x01000000 } ; // ABGR(le) glfw3 note: A(0x00) means 0xFF for some reason
static GLFWimage image = { 16 , 16 , ( void * ) pixels } ;
static GLFWcursor * empty ;
for ( int x = 0 ; x < 16 * 16 ; + + x ) pixels [ x ] = pixels [ 0 ] ;
empty = glfwCreateCursor ( & image , 0 , 0 ) ;
for ( int i = 0 ; i < countof ( enums ) ; + + i ) cursors [ i ] = i ? glfwCreateStandardCursor ( enums [ i ] ) : empty ;
}
if ( mode = = CURSOR_SW_AUTO ) { // UI (nuklear) driven cursor
nk_style_show_cursor ( ui_handle ( ) ) ;
glfwSetCursor ( window , cursors [ 0 ] ) ;
} else {
nk_style_hide_cursor ( ui_handle ( ) ) ;
glfwSetCursor ( window , mode < countof ( enums ) ? cursors [ mode ] : NULL ) ;
}
}
void window_visible ( int visible ) {
if ( ! window ) return ;
//if(window) (visible ? glfwRestoreWindow : glfwIconifyWindow)(window);
( visible ? glfwShowWindow : glfwHideWindow ) ( window ) ;
// call glfwpollevents in linux to flush visiblity changes that would happen in next frame otherwise
# if is(linux) || is(osx)
glfwPollEvents ( ) ;
# endif
}
int window_has_visible ( ) {
return glfwGetWindowAttrib ( window , GLFW_VISIBLE ) ;
}
void window_screenshot ( const char * outfile_png ) {
snprintf ( screenshot_file , DIR_MAX , " %s " , outfile_png ? outfile_png : " " ) ;
}
double window_aspect ( ) {
return ( double ) w / ( h + ! h ) ;
}
void window_aspect_lock ( unsigned numer , unsigned denom ) {
if ( ! window ) return ;
if ( numer * denom ) {
glfwSetWindowAspectRatio ( window , numer , denom ) ;
} else {
glfwSetWindowAspectRatio ( window , GLFW_DONT_CARE , GLFW_DONT_CARE ) ;
}
}
void window_aspect_unlock ( ) {
if ( ! window ) return ;
window_aspect_lock ( 0 , 0 ) ;
}
2023-09-21 10:45:42 +00:00
void window_transparent ( int enabled ) {
# if !is(ems)
if ( ! window_has_fullscreen ( ) ) {
if ( enabled ) {
glfwSetWindowAttrib ( window , GLFW_DECORATED , GLFW_FALSE ) ;
2023-09-25 10:24:49 +00:00
//glfwSetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH , GLFW_TRUE);
2023-09-21 10:45:42 +00:00
//glfwMaximizeWindow(window);
} else {
//glfwRestoreWindow(window);
2023-09-25 10:24:49 +00:00
//glfwSetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH , GLFW_FALSE);
2023-09-21 10:45:42 +00:00
glfwSetWindowAttrib ( window , GLFW_DECORATED , GLFW_TRUE ) ;
}
}
# endif
}
int window_has_transparent ( ) {
return ifdef ( ems , 0 , glfwGetWindowAttrib ( window , GLFW_DECORATED ) ! = GLFW_TRUE ) ;
}
void window_maximize ( int enabled ) {
ifdef ( ems , return ) ;
if ( ! window_has_fullscreen ( ) ) {
if ( enabled ) {
glfwMaximizeWindow ( window ) ;
} else {
glfwRestoreWindow ( window ) ;
}
}
}
int window_has_maximize ( ) {
return ifdef ( ems , 0 , glfwGetWindowAttrib ( window , GLFW_MAXIMIZED ) = = GLFW_TRUE ) ;
}