From: hgn Date: Fri, 17 May 2024 12:18:41 +0000 (+0100) Subject: wonky start to vg3 X-Git-Url: https://harrygodden.com/git/?a=commitdiff_plain;h=39f7d2905f0801a92083ac36df88cbaf5f6b80f2;p=vg.git wonky start to vg3 --- diff --git a/vg_audio_dsp.h b/vg_audio_dsp.h index d74bc02..e3239eb 100644 --- a/vg_audio_dsp.h +++ b/vg_audio_dsp.h @@ -3,7 +3,7 @@ //#define VG_ECHO_LPF_BUTTERWORTH #include "vg_platform.h" -#include "dep/glad/glad.h" +#include "vg_opengl.h" #include "vg_m.h" struct vg_dsp diff --git a/vg_build.h b/vg_build.h index b40ecd3..f284821 100644 --- a/vg_build.h +++ b/vg_build.h @@ -25,7 +25,8 @@ struct vg_env { k_platform_anyplatform, k_platform_windows, - k_platform_linux + k_platform_linux, + k_platform_android } platform; @@ -34,6 +35,8 @@ struct vg_env k_architecture_anyarch, k_architecture_i386, k_architecture_x86_64, + k_architecture_aarch64, + k_architecture_armv7a } arch; @@ -41,7 +44,8 @@ struct vg_env { k_compiler_blob, k_compiler_clang, - k_compiler_zigcc + k_compiler_zigcc, + k_compiler_clang_android } compiler; @@ -73,6 +77,16 @@ struct vg_env vg_release_env = { .debug_asan = 0 }; +struct vg_env vg_test_android_env = { + .arch = k_architecture_armv7a, + .compiler = k_compiler_clang_android, + .libc = k_libc_version_native, + .debug_asan = 0, + .fresh = 0, + .platform = k_platform_android, + .optimization = 0 +}; + struct vg_project { struct vg_env *env; @@ -104,28 +118,33 @@ static const char *platform_names[] = { [k_platform_anyplatform] = "anyplatform", [k_platform_windows] = "windows", - [k_platform_linux] = "linux" + [k_platform_linux] = "linux", + [k_platform_android] = "android" }; static const char *architecture_names[] = { [k_architecture_anyarch] = "anyarch", [k_architecture_i386] = "i386", - [k_architecture_x86_64] = "x86_64" + [k_architecture_x86_64] = "x86_64", + [k_architecture_aarch64] = "aarch64", + [k_architecture_armv7a] = "armv7a" }; static const char *compiler_names[] = { [k_compiler_blob] = "blob", [k_compiler_clang] = "clang", - [k_compiler_zigcc] = "zig-cc" + [k_compiler_zigcc] = "zig-cc", + [k_compiler_clang_android] = "clang" }; static const char *compiler_paths[] = { [k_compiler_blob] = NULL, [k_compiler_clang] = "clang", - [k_compiler_zigcc] = "zig cc" + [k_compiler_zigcc] = "zig cc", + [k_compiler_clang_android] = "/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \\\n-target armv7a-linux-androideabi22 \\\n" }; static const char *libc_names[] = @@ -212,6 +231,12 @@ void vg_tarball_project( struct vg_project *proj ) proj->uid.buffer, time(NULL), proj->uid.buffer ); } +bool vg_platform_posix( enum platform p ) +{ + if( (p == k_platform_linux) || (p == k_platform_android) ) return 1; + else return 0; +} + /* * The project configurator and compiler. * -------------------------------------------------------------------------- */ @@ -243,7 +268,7 @@ void vg_project_new_target( struct vg_project *proj, const char *name, } - if( proj->env->platform == k_platform_linux ) + if( vg_platform_posix( proj->env->platform ) ) { if( type == k_obj_type_shared ) vg_strcat( &proj->target, ".so" ); @@ -258,8 +283,10 @@ void vg_project_new_target( struct vg_project *proj, const char *name, { vg_include_dir( proj, "-I." ); vg_include_dir( proj, "-I./vg" ); +#if 0 vg_library_dir( proj, "-L." ); vg_library_dir( proj, "-L/usr/lib" ); +#endif } vg_info( " New target: %s\n", name ); @@ -276,16 +303,28 @@ void vg_project_init( struct vg_project *proj, vg_strnull( &proj->compiled_objects, NULL, -1 ); /* check for problems in configuration */ - if( env->libc != k_libc_version_native ){ - if( env->compiler != k_compiler_zigcc ){ + if( env->libc != k_libc_version_native ) + { + if( env->compiler != k_compiler_zigcc ) + { vg_fatal_error( "Cannot specify libc version using the '%s' compiler.\n", compiler_names[ env->compiler ] ); } } - if( env->compiler == k_compiler_clang ){ - if( env->platform != k_platform_linux ){ + if( env->compiler == k_compiler_clang ) + { + if( env->platform != k_platform_linux ) + { + vg_fatal_error( "Cannot compile for '%s' using the '%s' compiler;" ); + } + } + + if( env->compiler == k_compiler_clang_android ) + { + if( env->platform != k_platform_android ) + { vg_fatal_error( "Cannot compile for '%s' using the '%s' compiler;" ); } } @@ -367,11 +406,20 @@ void vg_compile_project( struct vg_project *proj ) vg_strcat( &cmd, " " ); if( proj->type == k_obj_type_obj ) - vg_strcat( &cmd, "-c " ); + vg_strcat( &cmd, "-c -fPIC " ); if( proj->type == k_obj_type_shared ) + { vg_strcat( &cmd, "-shared -fPIC " ); + if( proj->env->platform == k_platform_android ) + { + vg_strcat( &cmd, "-Wl,-soname," ); + vg_strcat( &cmd, proj->target.buffer ); + vg_strcat( &cmd, " " ); + } + } + vg_strcat( &cmd, proj->sources.buffer ); vg_strcat( &cmd, "\\\n" ); @@ -393,13 +441,15 @@ void vg_compile_project( struct vg_project *proj ) } /* target platform specification (zig-cc only) */ - if( proj->env->compiler == k_compiler_zigcc ){ + if( proj->env->compiler == k_compiler_zigcc ) + { vg_strcat( &cmd, " -target " ); vg_strcat( &cmd, architecture_names[proj->env->arch] ); vg_strcat( &cmd, "-" ); vg_strcat( &cmd, platform_names[proj->env->platform] ); - if( proj->env->platform == k_platform_linux ){ + if( proj->env->platform == k_platform_linux ) + { vg_strcat( &cmd, "-gnu" ); vg_strcat( &cmd, libc_names[proj->env->libc] ); } @@ -459,6 +509,7 @@ void vg_add_engine( struct vg_project *proj, struct vg_engine_config *config ) vg_strcat( &config_string, "-DVG_TIMESTEP_FIXED=\"(1.0/" ); vg_strcati32( &config_string, config->fixed_update_hz ); vg_strcat( &config_string, ".0)\" \\\n" ); + if( config->legacy_support_vg_msg1 ) vg_strcat( &config_string, "-DVG_MSG_V1_SUPPORT \\\n" ); if( config->log_source_info ) @@ -468,6 +519,31 @@ void vg_add_engine( struct vg_project *proj, struct vg_engine_config *config ) if( config->custom_shaders ) vg_strcat( &config_string, "-DVG_CUSTOM_SHADERS \\\n" ); + if( proj->env->arch == k_architecture_i386 || + proj->env->arch == k_architecture_armv7a ) + vg_strcat( &config_string, "-DVG_32 \\\n" ); + else + vg_strcat( &config_string, "-DVG_64 \\\n" ); + + if( proj->env->platform == k_platform_android ) + { + vg_strcat( &config_string, "-DVG_ANDROID \\\n" ); + vg_strcat( &config_string, + "-DANDROID -DAPPNAME=\\\"carrot\\\" -DANDROID_FULLSCREEN \\\n" + "-DANDROIDVERSION=22 -mfloat-abi=softfp -m32 \\\n" ); + +#if 0 + vg_strcat( &config_string, + "-L/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/" + "sysroot/usr/lib/arm-linux-androideabi/22 \\\n" + "-I/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/" + "sysroot/usr/include \\\n" + "-I/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/" + "sysroot/usr/include/android \\\n" + ); +#endif + } + vg_strcat( &config_string, "\\\n" ); /* compile heavy dependencies seperately */ @@ -480,14 +556,18 @@ void vg_add_engine( struct vg_project *proj, struct vg_engine_config *config ) /* external dependencies */ vg_project_new_target( &dep_proj, "vg_deps", k_obj_type_obj ); + vg_add_source( &dep_proj, config_string.buffer ); vg_add_source( &dep_proj, "vg/vg_depencies.c" ); vg_compile_project( &dep_proj ); /* glad */ - vg_project_new_target( &dep_proj, "vg_glad", k_obj_type_obj ); - vg_add_source( &dep_proj, "vg/dep/glad/glad.c" ); - vg_include_dir( &dep_proj, "-I./vg/dep " ); - vg_compile_project( &dep_proj ); + if( proj->env->platform != k_platform_android ) + { + vg_project_new_target( &dep_proj, "vg_glad", k_obj_type_obj ); + vg_add_source( &dep_proj, "vg/dep/glad/glad.c" ); + vg_include_dir( &dep_proj, "-I./vg/dep " ); + vg_compile_project( &dep_proj ); + } /* core engine */ vg_project_new_target( &dep_proj, "vg_engine_core", k_obj_type_obj ); @@ -499,6 +579,11 @@ void vg_add_engine( struct vg_project *proj, struct vg_engine_config *config ) /* steamworks */ if( config->steam_api ) { + if( env.platform == k_platform_android ) + { + vg_fatal_error( "Cannot use steam_api on android" ); + } + vg_project_new_target( &dep_proj, "vg_steam", k_obj_type_obj ); vg_add_source( &dep_proj, "vg/vg_steam.c" ); vg_compile_project( &dep_proj ); @@ -533,12 +618,15 @@ void vg_add_engine( struct vg_project *proj, struct vg_engine_config *config ) vg_add_blob( proj, "vg/dep/sdl/SDL2.dll ", "" ); vg_library_dir( proj, "-L./vg/dep/sdl " ); } + else if( proj->env->platform == k_platform_android ) + { + vg_link( proj, "-lGLESv3 -lEGL -lOpenSLES -pthread -landroid -llog " ); + } vg_add_source( proj, config_string.buffer ); vg_add_source( proj, dep_proj.compiled_objects.buffer ); vg_add_source( proj, "\\\n" ); vg_include_dir( proj, "-I./vg/dep " ); - vg_link( proj, "-lm " ); } void vg_add_controller_database( struct vg_project *proj ) diff --git a/vg_console.c b/vg_console.c index 561bb63..57a8134 100644 --- a/vg_console.c +++ b/vg_console.c @@ -1,6 +1,6 @@ #include "vg_engine.h" #include "vg_console.h" -#include "vg_imgui.h" +#include "vg_ui/imgui.h" #include "vg_log.h" #include "vg_string.h" #include @@ -326,8 +326,10 @@ void console_suggest_score_text( const char *str, const char *input, static void console_update_suggestions(void) { - if( vg_ui.focused_control_type != k_ui_control_textbox || - vg_ui.textbuf != vg_console.input ) + ui_context *ctx = ui_current_context(); + + if( ctx->focused_control_type != k_ui_control_textbox || + ctx->textbuf != vg_console.input ) return; vg_console.suggestion_count = 0; @@ -341,12 +343,12 @@ static void console_update_suggestions(void) * - cursors should match */ - if( vg_ui.textbox.cursor_pos == 0 ) return; - if( vg_ui.textbox.cursor_pos != vg_ui.textbox.cursor_user ) return; - if( vg_console.input[ vg_ui.textbox.cursor_pos ] != '\0' ) return; + if( ctx->textbox.cursor_pos == 0 ) return; + if( ctx->textbox.cursor_pos != ctx->textbox.cursor_user ) return; + if( vg_console.input[ ctx->textbox.cursor_pos ] != '\0' ) return; - if( (vg_console.input[ vg_ui.textbox.cursor_pos -1 ] == ' ') || - (vg_console.input[ vg_ui.textbox.cursor_pos -1 ] == '\t') ) + if( (vg_console.input[ ctx->textbox.cursor_pos -1 ] == ' ') || + (vg_console.input[ ctx->textbox.cursor_pos -1 ] == '\t') ) return; char temp[128]; @@ -396,20 +398,22 @@ static void console_update_suggestions(void) */ static void _console_fetch_suggestion(void) { + ui_context *ctx = ui_current_context(); + char *target = &vg_console.input[ vg_console.suggestion_pastepos ]; if( vg_console.suggestion_select == -1 ){ strcpy( target, vg_console.input_copy ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, 10000, 1 ); + _ui_textbox_move_cursor( &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, 10000, 1 ); } else{ strncpy( target, vg_console.suggestions[ vg_console.suggestion_select ].str, vg_list_size( vg_console.input )-1 ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, 10000, 1 ); + _ui_textbox_move_cursor( &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, 10000, 1 ); _ui_textbox_put_char( ' ' ); } } @@ -470,6 +474,7 @@ static void console_history_get( char* buf, int entry_num ) static void _vg_console_on_up( char *buf, u32 len ) { + ui_context *ctx = ui_current_context(); if( buf == vg_console.input ){ vg_console.history_pos = VG_MAX @@ -487,26 +492,28 @@ static void _vg_console_on_up( char *buf, u32 len ) ); console_history_get( vg_console.input, vg_console.history_pos ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, + _ui_textbox_move_cursor( &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, vg_list_size(vg_console.input)-1, 1); } } static void _vg_console_on_down( char *buf, u32 len ) { + ui_context *ctx = ui_current_context(); if( buf == vg_console.input ){ vg_console.history_pos = VG_MAX( 0, vg_console.history_pos-1 ); console_history_get( vg_console.input, vg_console.history_pos ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, + _ui_textbox_move_cursor( &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, vg_list_size(vg_console.input)-1, 1 ); } } static void _vg_console_on_enter( char *buf, u32 len ) { + ui_context *ctx = ui_current_context(); if( buf == vg_console.input ){ if( !strlen( vg_console.input ) ) return; @@ -527,8 +534,8 @@ static void _vg_console_on_enter( char *buf, u32 len ) vg_console.history_pos = -1; vg_execute_console_input( vg_console.input, 0 ); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, -10000, 1 ); + _ui_textbox_move_cursor( &ctx->textbox.cursor_user, + &ctx->textbox.cursor_pos, -10000, 1 ); vg_console.input[0] = '\0'; console_update_suggestions(); @@ -593,7 +600,7 @@ void vg_console_draw(void) SDL_AtomicLock( &vg_log.print_sl ); int ptr = vg_log.log_line_current; - int const fh = vg_ui.font->sy, log_lines = 32; + int const fh = ui_current_context()->font->sy, log_lines = 32; int console_lines = VG_MIN( log_lines, vg_log.log_line_count ); ui_rect rect_log = { 0, 0, vg.window_x, log_lines*fh }, @@ -638,9 +645,9 @@ void vg_console_draw(void) ui_rect rect_suggest; rect_copy( rect_input, rect_suggest ); - rect_suggest[0] += 6 + vg_ui.font->sx*vg_console.suggestion_pastepos; + rect_suggest[0] += 6 + ui_current_context()->font->sx*vg_console.suggestion_pastepos; rect_suggest[1] += rect_input[3]; - rect_suggest[2] = vg_ui.font->sx * vg_console.suggestion_maxlen; + rect_suggest[2] = ui_current_context()->font->sx * vg_console.suggestion_maxlen; rect_suggest[3] = vg_console.suggestion_count * fh; ui_fill( rect_suggest, bg_colour ); diff --git a/vg_engine.c b/vg_engine.c index fe3a7e9..48fe045 100644 --- a/vg_engine.c +++ b/vg_engine.c @@ -3,30 +3,59 @@ struct vg_engine vg = { .time_rate = 1.0, - .time_fixed_delta = VG_TIMESTEP_FIXED + .time_fixed_delta = VG_TIMESTEP_FIXED, +#ifdef VG_ANDROID + .sl_status = PTHREAD_MUTEX_INITIALIZER +#endif }; #include +#include "vg/vg_ui/imgui.c" +#include "vg/vg_ui/imgui_impl_opengl.c" +#include "vg/vg_default_font.gc" + +#ifdef VG_ANDROID + +int _wrap_sem_getvalue( sem_t *sem ) +{ + int v; + sem_getvalue( sem, &v ); + return v; +} + +#define TEMP_STATUS_LOCK pthread_mutex_lock +#define TEMP_STATUS_UNLOCK pthread_mutex_unlock +#define TEMP_SEM_POST(X) sem_post( &X ) +#define TEMP_SEM_WAIT(X) sem_wait( &X ) +#define TEMP_SEM_GET(X) _wrap_sem_getvalue( &X ) +#else +#define TEMP_STATUS_LOCK SDL_AtomicLock +#define TEMP_STATUS_UNLOCK SDL_AtomicUnlock +#define TEMP_SEM_POST(X) SDL_SemPost( X ) +#define TEMP_SEM_WAIT(X) SDL_SemWait( X ) +#define TEMP_SEM_GET(X) SDL_SemValue( X ) +#endif enum engine_status _vg_engine_status(void) { - SDL_AtomicLock( &vg.sl_status ); + TEMP_STATUS_LOCK( &vg.sl_status ); enum engine_status status = vg.engine_status; - SDL_AtomicUnlock( &vg.sl_status ); + TEMP_STATUS_UNLOCK( &vg.sl_status ); return status; } enum vg_thread_purpose vg_thread_purpose(void) { - SDL_AtomicLock( &vg.sl_status ); - - if( vg.thread_id_main == SDL_GetThreadID(NULL) ){ - SDL_AtomicUnlock( &vg.sl_status ); + TEMP_STATUS_LOCK( &vg.sl_status ); + if( vg.thread_id_main == SDL_GetThreadID(NULL) ) + { + TEMP_STATUS_UNLOCK( &vg.sl_status ); return k_thread_purpose_main; } - else{ - SDL_AtomicUnlock( &vg.sl_status ); + else + { + TEMP_STATUS_UNLOCK( &vg.sl_status ); return k_thread_purpose_loader; } } @@ -42,7 +71,11 @@ static void vg_assert_thread( enum vg_thread_purpose required ) static void _vg_opengl_sync_init(void) { +#ifdef VG_ANDROID + sem_init( &vg.sem_loader, 0, 1 ); +#else vg.sem_loader = SDL_CreateSemaphore(1); +#endif } #include "vg_console.h" @@ -53,11 +86,11 @@ static void _vg_opengl_sync_init(void) #include "vg_shader.h" #include "vg_tex.h" #include "vg_input.h" -#include "vg_imgui.h" #include "vg_lines.h" #include "vg_rigidbody_view.h" #include "vg_loader.h" #include "vg_opt.h" +#include "vg/vg_ui/imgui.h" /* Diagnostic */ static struct vg_profile vg_prof_update = {.name="update()"}, @@ -92,17 +125,32 @@ void vg_bake_shaders(void) void async_internal_complete( void *payload, u32 size ) { vg_success( "Internal async setup complete\n" ); - SDL_AtomicLock( &vg.sl_status ); - if( vg.engine_status == k_engine_status_crashed ){ - SDL_AtomicUnlock( &vg.sl_status ); +#ifdef VG_ANDROID + pthread_mutex_lock( &vg.sl_status ); +#else + TEMP_STATUS_LOCK( &vg.sl_status ); +#endif + + if( vg.engine_status == k_engine_status_crashed ) + { +#ifdef VG_ANDROID + pthread_mutex_unlock( &vg.sl_status ); +#else + TEMP_STATUS_UNLOCK( &vg.sl_status ); +#endif return; } - else{ + else + { vg.engine_status = k_engine_status_running; } - SDL_AtomicUnlock( &vg.sl_status ); +#ifdef VG_ANDROID + pthread_mutex_unlock( &vg.sl_status ); +#else + TEMP_STATUS_UNLOCK( &vg.sl_status ); +#endif } #ifdef VG_CUSTOM_SHADERS @@ -114,7 +162,9 @@ static void _vg_load_full( void *data ) vg_preload(); vg_tex2d_replace_with_error_async( &vg.tex_missing ); vg_async_stall(); - vg_ui.tex_bg = vg.tex_missing; +#ifndef VG_ANDROID + g_ui_ctx->tex_bg = vg.tex_missing; +#endif /* internal */ vg_loader_step( vg_input_init, vg_input_free ); @@ -141,14 +191,20 @@ static void _vg_process_events(void) v2_zero( vg.mouse_wheel ); v2_zero( vg.mouse_delta ); +#ifdef VG_ANDROID +#else /* SDL event loop */ SDL_Event event; - while( SDL_PollEvent( &event ) ){ - if( event.type == SDL_KEYDOWN ){ + while( SDL_PollEvent( &event ) ) + { + if( event.type == SDL_KEYDOWN ) + { if( vg_console.enabled && - (vg_ui.focused_control_type != k_ui_control_modal) ){ + (g_ui_ctx->focused_control_type != k_ui_control_modal) ) + { if( event.key.keysym.sym == SDLK_ESCAPE || - event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){ + event.key.keysym.scancode == SDL_SCANCODE_GRAVE ) + { vg_console.enabled = 0; ui_defocus_all(); } @@ -160,20 +216,24 @@ static void _vg_process_events(void) event.key.keysym.sym == SDLK_p ){ console_suggest_prev(); } - else{ + else + { ui_proc_key( event.key.keysym ); } } else{ - if( event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){ + if( event.key.keysym.scancode == SDL_SCANCODE_GRAVE ) + { vg_console.enabled = 1; } - else { + else + { ui_proc_key( event.key.keysym ); } } } - else if( event.type == SDL_MOUSEWHEEL ){ + else if( event.type == SDL_MOUSEWHEEL ) + { vg.mouse_wheel[0] += event.wheel.preciseX; vg.mouse_wheel[1] += event.wheel.preciseY; } @@ -217,8 +277,9 @@ static void _vg_process_events(void) } } - SDL_GetMouseState( &vg.mouse_pos[0], &vg.mouse_pos[1] ); + vg.mouse_state = SDL_GetMouseState( &vg.mouse_pos[0], &vg.mouse_pos[1] ); vg_process_inputs(); +#endif } static void _vg_gameloop_update(void) @@ -267,12 +328,17 @@ static void _vg_gameloop_render(void) /* ui */ vg.engine_stage = k_engine_stage_ui; { - ui_prerender(); - if( vg_console.enabled ){ - vg_ui.ignore_input_frames = 10; + ui_prerender( (ui_px[2]){ vg.window_x, vg.window_y }, + (ui_px[2]){ vg.mouse_pos[0], vg.mouse_pos[1] }, + vg.mouse_state ); + ui_set_screen( vg.window_x, vg.window_y ); + + if( vg_console.enabled ) + { + ui_ignore_input_frames( 10 ); vg_gui(); - vg_ui.ignore_input_frames = 0; - vg_ui.wants_mouse = 1; + ui_ignore_input_frames( 0 ); + ui_capture_mouse( 1 ); vg_console_draw(); } else vg_gui(); @@ -315,7 +381,8 @@ static void _vg_gameloop_render(void) ui_text( (ui_rect){258,4,900,900},perf,1,0,k_ui_align_left); } - ui_postrender(); + ui_postrender( vg.time_frame_delta ); + ui_post_update(); } } @@ -418,7 +485,8 @@ static void _vg_gameloop(void){ vg.time_hp_last = vg.time_hp; int post_start = 0; - while(1){ + while(1) + { vg.time_hp = SDL_GetPerformanceCounter(); u64 udt = vg.time_hp - vg.time_hp_last; vg.time_hp_last = vg.time_hp; @@ -437,7 +505,11 @@ static void _vg_gameloop(void){ if( status == k_engine_status_running ) vg_profile_begin( &vg_prof_swap ); +#ifdef VG_ANDROID + // TODO: eglSwapBuffers( +#else SDL_GL_SwapWindow( vg.window ); +#endif if( status == k_engine_status_running ) vg_profile_end( &vg_prof_swap ); @@ -505,6 +577,11 @@ static void _vg_process_launch_opts_internal( int argc, char *argv[] ) static void _vg_init_window( const char *window_name ) { +#ifdef VG_ANDROID + + + +#else vg_info( "SDL_INIT\n" ); if( SDL_Init( SDL_INIT_VIDEO ) != 0 ){ @@ -638,6 +715,7 @@ static void _vg_init_window( const char *window_name ) vg_info( "Display refresh rate: %d\n", dispmode.refresh_rate ); if( !vg.fps_limit) vg.fps_limit = vg.display_refresh_rate; +#endif } static void _vg_terminate(void) @@ -645,16 +723,22 @@ static void _vg_terminate(void) /* Shutdown */ vg_console_write_persistent(); - SDL_AtomicLock( &vg.sl_status ); + TEMP_STATUS_LOCK( &vg.sl_status ); vg.engine_status = k_engine_status_none; - SDL_AtomicUnlock( &vg.sl_status ); + TEMP_STATUS_UNLOCK( &vg.sl_status ); vg_loader_free(); vg_success( "If you see this it means everything went.. \"well\".....\n" ); +#ifdef VG_ANDROID + + + +#else SDL_GL_DeleteContext( vg.gl_context ); SDL_Quit(); +#endif exit(0); } @@ -685,7 +769,11 @@ void vg_enter( int argc, char *argv[], const char *window_name ) vg_async_init(); SDL_SetRelativeMouseMode(1); +#ifdef VG_ANDROID + vg.thread_id_main = pthread_self(); +#else vg.thread_id_main = SDL_GetThreadID(NULL); +#endif /* Opengl-required systems */ vg_ui_init(); @@ -708,9 +796,9 @@ void vg_fatal_error( const char *fmt, ... ) vg_print_backtrace(); - SDL_AtomicLock( &vg.sl_status ); + TEMP_STATUS_LOCK( &vg.sl_status ); vg.engine_status = k_engine_status_crashed; - SDL_AtomicUnlock( &vg.sl_status ); + TEMP_STATUS_UNLOCK( &vg.sl_status ); if( vg_thread_purpose() == k_thread_purpose_loader ) { @@ -885,8 +973,8 @@ void vg_settings_ui_header( ui_rect inout_panel, const char *name ) bool vg_settings_apply_button( ui_rect inout_panel, bool validated ) { ui_rect last_row; - ui_px height = (vg_ui.font->sy + 18) * k_ui_scale; - ui_split( inout_panel, k_ui_axis_h, -height, k_ui_padding, + ui_px height = ui_standard_widget_height( 1 ); + ui_split( inout_panel, k_ui_axis_h, -height, 8, inout_panel, last_row ); const char *string = "Apply"; @@ -908,16 +996,23 @@ bool vg_settings_apply_button( ui_rect inout_panel, bool validated ) return 0; } -static void vg_settings_video_apply(void){ - if( vg_settings_enum_diff( &vg_settings.screenmode ) ){ +static void vg_settings_video_apply(void) +{ +#ifdef VG_ANDROID +#else + if( vg_settings_enum_diff( &vg_settings.screenmode ) ) + { vg.screen_mode = vg_settings.screenmode.new_value; - if( (vg.screen_mode == 0) || (vg.screen_mode == 1) ){ + if( (vg.screen_mode == 0) || (vg.screen_mode == 1) ) + { SDL_DisplayMode video_mode; - if( SDL_GetDesktopDisplayMode( 0, &video_mode ) ){ + if( SDL_GetDesktopDisplayMode( 0, &video_mode ) ) + { vg_error("SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError()); } - else { + else + { //vg.display_refresh_rate = video_mode.refresh_rate; vg.window_x = video_mode.w; vg.window_y = video_mode.h; @@ -937,6 +1032,7 @@ static void vg_settings_video_apply(void){ SDL_SetWindowMaximumSize( vg.window, 4096, 4096 ); } } +#endif vg.fps_limit = vg_settings.fps_limit.new_value; vg.quality_profile = vg_settings.quality.new_value; @@ -1114,7 +1210,7 @@ static void vg_settings_gui(void) ui_rect screen = { 0, 0, vg.window_x, vg.window_y }; ui_rect window = { 0, 0, 1000, 700 }; ui_rect_center( screen, window ); - vg_ui.wants_mouse = 1; + ui_capture_mouse( 1 ); ui_fill( window, ui_colour( k_ui_bg+1 ) ); ui_outline( window, 1, ui_colour( k_ui_bg+7 ), 0 ); @@ -1178,7 +1274,6 @@ int AmdPowerXpressRequestHighPerformance = 1; #include "vg_camera.c" #include "vg_lines.c" #include "vg_console.c" -#include "vg_imgui.c" #include "vg_input.c" #include "vg_io.c" #include "vg_loader.c" diff --git a/vg_engine.h b/vg_engine.h index 81bbda5..4f56768 100644 --- a/vg_engine.h +++ b/vg_engine.h @@ -69,15 +69,24 @@ #endif #define VG_ENGINE -#define SDL_MAIN_HANDLED +#include "vg_opengl.h" -#include "dep/glad/glad.h" -#include "dep/sdl/include/SDL.h" +#ifdef VG_ANDROID + +/* TODO: android gl includes */ +#include +#include + +#else + #define SDL_MAIN_HANDLED + #include "dep/sdl/include/SDL.h" +#endif #include "vg_platform.h" #include "vg_mem.h" #include "vg_m.h" -#include "vg_imgui.h" +#include "vg_font.h" +#include "vg_ui/imgui.h" #include @@ -116,19 +125,28 @@ enum vg_thread_purpose k_thread_purpose_loader }; -struct vg_engine { + +struct vg_engine +{ /* Engine sync */ +#ifdef VG_ANDROID + pthread_mutex_t sl_status; + pthread_t thread_id_main, thread_id_loader; + sem_t sem_loader; +#else SDL_Window *window; SDL_GLContext gl_context; - SDL_sem *sem_loader; /* allows only one loader at a time */ - jmp_buf env_loader_exit; SDL_threadID thread_id_main, thread_id_loader; - void *thread_data; SDL_SpinLock sl_status; +#endif + + jmp_buf env_loader_exit; + void *thread_data; + enum engine_status{ k_engine_status_none, k_engine_status_load_internal, @@ -160,7 +178,7 @@ struct vg_engine { } vsync_feature; - i32 mouse_pos[2]; + i32 mouse_pos[2], mouse_state; v2f mouse_delta, mouse_wheel; diff --git a/vg_font.h b/vg_font.h index 5fcb29b..9a17561 100644 --- a/vg_font.h +++ b/vg_font.h @@ -1,3 +1,5 @@ +#pragma once + typedef struct vg_font_char vg_font_char; typedef struct vg_font_face vg_font_face; typedef struct vg_font_sheet vg_font_sheet; diff --git a/vg_imgui.c b/vg_imgui.c deleted file mode 100644 index bb4f46f..0000000 --- a/vg_imgui.c +++ /dev/null @@ -1,2333 +0,0 @@ -/* Copyright (C) 2021-2024 Harry Godden (hgn) - All Rights Reserved */ - -/* - * Principles: - * - * 1. layout is defined by subdividing - * 2. a parent node should never be resized by the content after creation - * 3. when the ui is in an interactive state, no controls should ever move - * 4. controls directly reference a memory location and use that as their - * unique id - * 5. a maximum of ONE control per memory location can be drawn at any given - * point. - */ - -#pragma once - -#include "vg_imgui.h" -#include "vg_engine.h" -#include "vg_tex.h" -#include "vg_shader.h" -#include - -ui_px k_ui_widget_height = 28, - k_ui_scale = 1, - k_ui_padding = 8; - -#include "vg/vg_default_font.gc" - -struct vg_imgui vg_ui = { - .scheme = { - [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ), - [ k_ui_bg+1 ] = UI_RGB( 0x282828 ), - [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ), - [ k_ui_bg+3 ] = UI_RGB( 0x504945 ), - [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ), - [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ), - [ k_ui_bg+6 ] = UI_RGB( 0x928374 ), - [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ), - - [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ), - [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ), - [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ), - [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ), - [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ), - [ k_ui_fg+5 ] = UI_RGB( 0x000000 ), - [ k_ui_fg+6 ] = UI_RGB( 0x000000 ), - [ k_ui_fg+7 ] = UI_RGB( 0x000000 ), - - [ k_ui_red ] = UI_RGB( 0xcc241d ), - [ k_ui_orange ] = UI_RGB( 0xd65d0e ), - [ k_ui_yellow ] = UI_RGB( 0xd79921 ), - [ k_ui_green ] = UI_RGB( 0x98971a ), - [ k_ui_aqua ] = UI_RGB( 0x689d6a ), - [ k_ui_blue ] = UI_RGB( 0x458588 ), - [ k_ui_purple ] = UI_RGB( 0xb16286 ), - [ k_ui_gray ] = UI_RGB( 0x928374 ), - [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ), - [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ), - [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ), - [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ), - [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ), - [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ), - [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ), - [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ), - }, - .font = &vgf_default_small, - .colour = {1.0f,1.0f,1.0f,1.0f}, - .bg_inverse_ratio = {1,1} -}; - -static struct vg_shader _shader_ui ={ - .name = "[vg] ui - transparent", - .vs = { - .orig_file = NULL, - .static_src = - "layout (location=0) in vec2 a_co;" - "layout (location=1) in vec2 a_uv;" - "layout (location=2) in vec4 a_colour;" - "uniform mat3 uPv;" - "uniform vec2 uBGInverseRatio;" - "uniform vec2 uInverseFontSheet;" - "" - "out vec4 aTexCoords;" - "out vec4 aColour;" - "" - "void main(){" - "vec4 proj_pos = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "gl_Position = proj_pos;" - "aTexCoords = vec4( a_uv * uInverseFontSheet, " - " (proj_pos.xy*0.5+0.5) * uBGInverseRatio );" - "aColour = a_colour;" - "}", - }, - .fs = { - .orig_file = NULL, - .static_src = - "uniform sampler2D uTexGlyphs;" - "uniform sampler2D uTexBG;" - "uniform vec4 uColour;" - "uniform float uSpread;" - "out vec4 FragColor;" - "" - "in vec4 aTexCoords;" - "in vec4 aColour;" - "" - "vec2 rand_hash22( vec2 p ){" - "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);" - "p3 += dot(p3, p3.yzx+19.19);" - "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));" - "}" - "" - "void main(){" - "vec4 diffuse = aColour;" - - "vec4 avg = vec4(0.0);" - - "if( aColour.a == 0.0 ){" - "avg = aColour;" - "avg.a = texture( uTexGlyphs, aTexCoords.xy ).r;" - "}" - "else{" - "if( uSpread > 0.0001 ){" - "for( int i=0; i<4; i ++ ){" - "vec2 spread = rand_hash22(aTexCoords.zw+vec2(float(i)));" - "avg += texture( uTexBG, aTexCoords.zw + (spread-0.5)*uSpread );" - "}" - "avg *= 0.25;" - "avg.a = 1.0;" - "avg.rgb = mix( avg.rgb, aColour.rgb, aColour.a );" - "}" - "else{" - "avg = aColour;" - "}" - "}" - - "FragColor = avg * uColour;" - "}" - } -}; - -static struct vg_shader _shader_ui_image = { - .name = "[vg] ui_image", - .vs = - { - .orig_file = NULL, - .static_src = - "layout (location=0) in vec2 a_co;" - "layout (location=1) in vec2 a_uv;" - "layout (location=2) in vec4 a_colour;" - "uniform mat3 uPv;" - - "out vec2 aTexCoords;" - "out vec4 aColour;" - "out vec2 aWsp;" - - "void main()" - "{" - "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "aTexCoords = a_uv * 0.00390625;" - "aColour = a_colour;" - - "aWsp = a_co;" - "}", - }, - .fs = - { - .orig_file = NULL, - .static_src = - "uniform sampler2D uTexImage;" - "uniform vec4 uColour;" - "out vec4 FragColor;" - - "in vec2 aTexCoords;" - "in vec4 aColour;" - "in vec2 aWsp;" - - "void main()" - "{" - "vec4 colour = texture( uTexImage, aTexCoords );" - "FragColor = colour * uColour;" - "}" - } -}; - -static struct vg_shader _shader_ui_hsv = { - .name = "[vg] ui_hsv", - .vs = { - .orig_file = NULL, - .static_src = - "layout (location=0) in vec2 a_co;" - "layout (location=1) in vec2 a_uv;" - "layout (location=2) in vec4 a_colour;" - "uniform mat3 uPv;" - - "out vec2 aTexCoords;" - "out vec4 aColour;" - "out vec2 aWsp;" - - "void main()" - "{" - "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" - "aTexCoords = a_uv * 0.00390625;" - "aColour = a_colour;" - - "aWsp = a_co;" - "}", - }, - .fs = - { - .orig_file = NULL, - .static_src = - "uniform float uHue;" - "out vec4 FragColor;" - - "in vec2 aTexCoords;" - "in vec4 aColour;" - "in vec2 aWsp;" - - "void main()" - "{" - "vec3 c = vec3( uHue, aTexCoords );" - "vec4 K = vec4(1.0,2.0/3.0,1.0/3.0,3.0);" - "vec3 p = abs(fract(c.xxx+K.xyz)*6.0 - K.www);" - "vec3 colour = c.z*mix(K.xxx,clamp(p-K.xxx,0.0,1.0),c.y);" - "FragColor = vec4( colour, 1.0 );" - "}" - } -}; - -void vg_ui_init(void) -{ - if( !vg_shader_compile( &_shader_ui ) || - !vg_shader_compile( &_shader_ui_image ) || - !vg_shader_compile( &_shader_ui_hsv ) ){ - vg_fatal_error( "Failed to compile ui shader" ); - } - - /* - * Vertex buffer - * ---------------------------------------- - */ - - vg_ui.max_indices = 20000; - vg_ui.max_verts = 30000; - - /* Generate the buffer we are gonna be drawing to */ - glGenVertexArrays( 1, &vg_ui.vao ); - glGenBuffers( 1, &vg_ui.vbo ); - glGenBuffers( 1, &vg_ui.ebo ); - - glBindVertexArray( vg_ui.vao ); - glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); - - glBufferData( GL_ARRAY_BUFFER, - vg_ui.max_verts * sizeof( struct ui_vert ), - NULL, GL_DYNAMIC_DRAW ); - glBindVertexArray( vg_ui.vao ); - - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); - glBufferData( GL_ELEMENT_ARRAY_BUFFER, - vg_ui.max_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW ); - - VG_CHECK_GL_ERR(); - - /* Set pointers */ - u32 const stride = sizeof( struct ui_vert ); - - /* XY */ - glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride, - (void *)offsetof( struct ui_vert, co ) ); - glEnableVertexAttribArray( 0 ); - - /* UV */ - glVertexAttribPointer( 1, 2, GL_UNSIGNED_SHORT, GL_FALSE, stride, - (void *)offsetof( struct ui_vert, uv ) ); - glEnableVertexAttribArray( 1 ); - - /* COLOUR */ - glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, - (void *)offsetof( struct ui_vert, colour ) ); - glEnableVertexAttribArray( 2 ); - - VG_CHECK_GL_ERR(); - - /* Alloc RAM default context */ - u32 vert_size = vg_ui.max_verts*sizeof(struct ui_vert), - inds_size = vg_align8( vg_ui.max_indices*sizeof(u16) ); - - vg_ui.vertex_buffer = vg_linear_alloc( vg_mem.rtmemory, vert_size ); - vg_ui.indice_buffer = vg_linear_alloc( vg_mem.rtmemory, inds_size ); - - /* font - * ----------------------------------------------------- - */ - - /* Load default font */ - - vg_font_sheet *sheet = &vg_default_font_sheet; - u32 pixels = 0, - total = sheet->w*sheet->h, - data = 0; - - vg_linear_clear( vg_mem.scratch ); - u8 *image = vg_linear_alloc( vg_mem.scratch, total ); - - while( pixels < total ) - { - for( int b = 31; b >= 0; b-- ) - { - image[ pixels ++ ] = (sheet->bitmap[data] & (0x1u << b))? 0xffu: 0x00u; - - if( pixels >= total ) - { - total = 0; - break; - } - } - data++; - } - - vg_ui.inverse_font_sheet[0] = 1.0/(f64)sheet->w; - vg_ui.inverse_font_sheet[1] = 1.0/(f64)sheet->h; - - glGenTextures( 1, &vg_ui.tex_glyphs ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); - glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, sheet->w, sheet->h, 0, - GL_RED, GL_UNSIGNED_BYTE, image ); - - VG_CHECK_GL_ERR(); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); - - /* - * Cursors - * --------------------------------------------------------------- - */ - - vg_ui.cursor_map[ k_ui_cursor_default ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW ); - vg_ui.cursor_map[ k_ui_cursor_hand ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND ); - vg_ui.cursor_map[ k_ui_cursor_ibeam ] = - SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM ); -} - -void rect_copy( ui_rect a, ui_rect b ){ - for( int i=0; i<4; i++ ) - b[i] = a[i]; -} - -void ui_flush( enum ui_shader shader, f32 w, f32 h ){ - u32 vertex_offset = vg_ui.vert_start*sizeof(ui_vert), - vertex_count = vg_ui.cur_vert-vg_ui.vert_start, - vertex_size = vertex_count*sizeof(ui_vert), - - indice_offset = vg_ui.indice_start*sizeof(u16), - indice_count = vg_ui.cur_indice-vg_ui.indice_start, - indice_size = indice_count * sizeof(u16); - - if( !vertex_size || !indice_size ) - return; - - glBindVertexArray( vg_ui.vao ); - glBindBuffer( GL_ARRAY_BUFFER, vg_ui.vbo ); - glBufferSubData( GL_ARRAY_BUFFER, vertex_offset, vertex_size, - vg_ui.vertex_buffer+vg_ui.vert_start ); - - glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vg_ui.ebo ); - glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, indice_offset, indice_size, - vg_ui.indice_buffer+vg_ui.indice_start ); - - glDisable( GL_DEPTH_TEST ); - glEnable( GL_BLEND ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - glBlendEquation( GL_FUNC_ADD ); - glDisable( GL_CULL_FACE ); - - m3x3f view = M3X3_IDENTITY; - m3x3_translate( view, (v3f){ -1.0f, 1.0f, 0.0f } ); - m3x3_scale( view, (v3f){ 1.0f/(w*0.5f), - -1.0f/(h*0.5f), 1.0f } ); - - if( shader == k_ui_shader_colour ){ - glUseProgram( _shader_ui.id ); - - glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform4fv( glGetUniformLocation( _shader_ui.id, "uColour" ), 1, - vg_ui.colour ); - - glActiveTexture( GL_TEXTURE0 ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_glyphs ); - glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 ); - - glActiveTexture( GL_TEXTURE1 ); - glBindTexture( GL_TEXTURE_2D, vg_ui.tex_bg ); - glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexBG" ), 1 ); - glUniform1f( glGetUniformLocation( _shader_ui.id, "uSpread" ), - vg_ui.frosting ); - glUniform2fv( glGetUniformLocation( _shader_ui.id, "uBGInverseRatio" ), - 1, vg_ui.bg_inverse_ratio ); - glUniform2fv( glGetUniformLocation( _shader_ui.id, "uInverseFontSheet" ), - 1, vg_ui.inverse_font_sheet ); - } - else if( shader == k_ui_shader_image ){ - glUseProgram( _shader_ui_image.id ); - glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 ); - glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1, - vg_ui.colour ); - } - else if( shader == k_ui_shader_hsv ){ - glUseProgram( _shader_ui_hsv.id ); - glUniformMatrix3fv( glGetUniformLocation( _shader_ui_hsv.id, "uPv" ), 1, - GL_FALSE, (float *)view ); - glUniform1f( glGetUniformLocation(_shader_ui_hsv.id,"uHue"), vg_ui.hue ); - } - else - vg_fatal_error( "Invalid UI shader (%d)\n", shader ); - - glDrawElements( GL_TRIANGLES, indice_count, GL_UNSIGNED_SHORT, - (void *)(vg_ui.indice_start*sizeof(u16)) ); - - glDisable( GL_BLEND ); - - vg_ui.indice_start = vg_ui.cur_indice; - vg_ui.vert_start = vg_ui.cur_vert; -} - -struct ui_vert *ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ) -{ - /* this if far from ideal but stops us from crashing */ - if( (vg_ui.cur_vert + 4 > vg_ui.max_verts) || - (vg_ui.cur_indice + 6 > vg_ui.max_indices)) - { - return &vg_ui.vertex_buffer[0]; - } - - struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ]; - u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ]; - - for( int i=0; i<4; i++ ) - { - vertices[i].colour = colour; - } - - vertices[0].co[0] = rect[0]; - vertices[0].co[1] = rect[1]; - vertices[0].uv[0] = uv[0]; - vertices[0].uv[1] = uv[1]; - vertices[1].co[0] = rect[0]+rect[2]; - vertices[1].co[1] = rect[1]; - vertices[1].uv[0] = uv[2]; - vertices[1].uv[1] = uv[1]; - vertices[2].co[0] = rect[0]+rect[2]; - vertices[2].co[1] = rect[1]+rect[3]; - vertices[2].uv[0] = uv[2]; - vertices[2].uv[1] = uv[3]; - vertices[3].co[0] = rect[0]; - vertices[3].co[1] = rect[1]+rect[3]; - vertices[3].uv[0] = uv[0]; - vertices[3].uv[1] = uv[3]; - u16 ind_start = vg_ui.cur_vert; - - u16 start = vg_ui.cur_vert; - u32 mesh[] = { 0,2,1, 0,3,2 }; - - for( u32 i=0; i vg_ui.max_verts) || - (vg_ui.cur_indice + 24 > vg_ui.max_indices)) - return; - - struct ui_vert *vertices = &vg_ui.vertex_buffer[ vg_ui.cur_vert ]; - u16 *indices = &vg_ui.indice_buffer[ vg_ui.cur_indice ]; - - for( int i=0; i<8; i++ ){ - vertices[i].uv[0] = 4; - vertices[i].uv[1] = 4; - vertices[i].colour = colour; - } - - vertices[0].co[0] = rect[0]; - vertices[0].co[1] = rect[1]; - vertices[1].co[0] = rect[0]+rect[2]; - vertices[1].co[1] = rect[1]; - vertices[2].co[0] = rect[0]+rect[2]; - vertices[2].co[1] = rect[1]+rect[3]; - vertices[3].co[0] = rect[0]; - vertices[3].co[1] = rect[1]+rect[3]; - vertices[4].co[0] = vertices[0].co[0]-thickness; - vertices[4].co[1] = vertices[0].co[1]-thickness; - vertices[5].co[0] = vertices[1].co[0]+thickness; - vertices[5].co[1] = vertices[1].co[1]-thickness; - vertices[6].co[0] = vertices[2].co[0]+thickness; - vertices[6].co[1] = vertices[2].co[1]+thickness; - vertices[7].co[0] = vertices[3].co[0]-thickness; - vertices[7].co[1] = vertices[3].co[1]+thickness; - - u16 start = vg_ui.cur_vert; - u32 mesh[] = { 0,5,4, 0,1,5, 1,6,5, 1,2,6, 2,7,6, 2,3,7, 3,4,7, 3,0,4 }; - - if( !mask ) - mask = UI_TOP|UI_LEFT|UI_BOTTOM|UI_RIGHT; - - u32 c = 0; - for( u32 i=0; i rp ) dir = k_ui_axis_h; - else dir = k_ui_axis_v; - other = dir ^ 0x1; - - d[2+dir] = rect[2+dir]; - d[2+other] = (rect[2+dir] * size[other]) / size[dir]; - - ui_rect_center( rect, d ); -} - -void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, - ui_px gap, ui_rect l, ui_rect r ) -{ - ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio; - ui_split( rect, dir, width, gap, l, r ); -} - -void ui_rect_pad( ui_rect rect, ui_px pad[2] ) -{ - rect[0] += pad[0]; - rect[1] += pad[1]; - rect[2] -= pad[0]*2; - rect[3] -= pad[1]*2; -} - -ui_px ui_text_line_width( const char *str ) -{ - int length = 0; - const char *_c = str; - u8 c; - - while( (c = *(_c ++)) ){ - if( c >= 32 ) length ++; - else if( c == '\n' ) break; - } - - return length * vg_ui.font->sx; -} - -ui_px ui_text_string_height( const char *str ) -{ - int height = 1; - const char *_c = str; - u8 c; - - while( (c = *(_c ++)) ) - { - if( c == '\n' ) height ++; - } - - return height * vg_ui.font->sy; -} - -ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, - enum ui_align align ) -{ - enum ui_align lwr = k_ui_align_lwr & align; - if( lwr == k_ui_align_left ){ - return rect[0]; - } - else{ - ui_px width = ui_text_line_width( str ) * scale; - - if( lwr == k_ui_align_right ) - return rect[0] + rect[2]-width; - else - return rect[0] + (rect[2]-width)/2; - } -} - -static ui_px ui_min( ui_px a, ui_px b ){ return ab?a:b; } -static ui_px ui_clamp( ui_px a, ui_px min, ui_px max ){ - return ui_min( max, ui_max( a, min ) ); -} - -int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ) -{ - ui_px parent_max[2], child_max[2]; - parent_max[0] = parent[0]+parent[2]; - parent_max[1] = parent[1]+parent[3]; - child_max[0] = child[0]+child[2]; - child_max[1] = child[1]+child[3]; - - clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] ); - clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] ); - clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] ); - clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] ); - - if( clipped[0] == clipped[2] || - clipped[1] == clipped[3] ) - return 0; - - clipped[2] -= clipped[0]; - clipped[3] -= clipped[1]; - - return 1; -} - -int ui_inside_rect( ui_rect rect, ui_px co[2] ) -{ - if( co[0] >= rect[0] && - co[1] >= rect[1] && - co[0] < rect[0]+rect[2] && - co[1] < rect[1]+rect[3] ){ - return 1; - } - else - return 0; -} - -int ui_click_down( u32 mask ) -{ - if( vg_ui.ignore_input_frames ) return 0; - if( (vg_ui.mouse_state[0] & mask) && - !(vg_ui.mouse_state[1] & mask) ) - return 1; - else - return 0; -} - -int ui_clicking( u32 mask ) -{ - if( vg_ui.ignore_input_frames ) return 0; - return vg_ui.mouse_state[0] & mask; -} - -int ui_click_up( u32 mask ) -{ - if( vg_ui.ignore_input_frames ) return 0; - if( (vg_ui.mouse_state[1] & mask) && - !(vg_ui.mouse_state[0] & mask) ) - return 1; - else - return 0; -} - -void ui_set_mouse_pos( ui_px x, ui_px y ) -{ - SDL_WarpMouseInWindow( vg.window, x, y ); - vg_ui.mouse[0] = x; - vg_ui.mouse[1] = y; - vg_ui.mouse_pos_overriden = 1; -} - -void ui_prerender(void) -{ - int x, y; - vg_ui.mouse_state[1] = vg_ui.mouse_state[0]; - vg_ui.mouse_state[0] = SDL_GetMouseState( &x, &y ); - vg_ui.mouse_delta[0] = x-vg_ui.mouse[0]; - vg_ui.mouse_delta[1] = y-vg_ui.mouse[1]; - - if( !vg_ui.mouse_pos_overriden ) - { - vg_ui.mouse[0] = x; - vg_ui.mouse[1] = y; - } - - vg_ui.cur_vert = 0; - vg_ui.cur_indice = 0; - vg_ui.vert_start = 0; - vg_ui.indice_start = 0; - vg_ui.focused_control_hit = 0; - vg_ui.cursor = k_ui_cursor_default; - vg_ui.wants_mouse = 0; - vg_ui.mouse_pos_overriden = 0; - - if( vg_ui.ignore_input_frames ) - { - vg_ui.ignore_input_frames --; - return; - } - - if( ui_click_down(UI_MOUSE_LEFT)||ui_click_down(UI_MOUSE_MIDDLE)|| - ui_click_down(UI_MOUSE_RIGHT) ) - { - vg_ui.mouse_click[0] = vg_ui.mouse[0]; - vg_ui.mouse_click[1] = vg_ui.mouse[1]; - } -} - -u32 ui_colour( enum ui_scheme_colour id ) -{ - return vg_ui.scheme[ id ]; -} - -/* get an appropriately contrasting colour given the base */ -u32 ui_colourcont( enum ui_scheme_colour id ) -{ - if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg ); - else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 ); - else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 ); - else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg ); - else return ui_colour( k_ui_fg+1 ); -} - -void ui_hex_to_norm( u32 hex, v4f norm ) -{ - norm[0] = ((hex ) & 0xff); - norm[1] = ((hex>>8 ) & 0xff); - norm[2] = ((hex>>16) & 0xff); - norm[3] = ((hex>>24) & 0xff); - v4_muls( norm, 1.0f/255.0f, norm ); -} - -u32 v4f_u32_colour( v4f colour ) -{ - u32 r = colour[0] * 255.0f, - g = colour[1] * 255.0f, - b = colour[2] * 255.0f, - a = colour[3] * 255.0f; - - return r | (g<<8) | (b<<16) | (a<<24); -} - -static void ui_text_glyph( const struct vg_font_face *ff, - u8 glyph, ui_rect out_texcoords ) -{ - const vg_font_char *ch = &ff->map[ glyph ]; - - out_texcoords[0] = ch->x; - out_texcoords[1] = ch->y; - out_texcoords[2] = ch->x + ff->cw; - out_texcoords[3] = ch->y + ff->ch; -} - -u32 ui_opacity( u32 colour, f32 opacity ) -{ - u32 alpha = opacity * 255.0f; - return (colour & 0x00ffffff) | (alpha << 24); -} - -u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, - enum ui_align align, u32 colour ) -{ - ui_px glow_text = 0; - - ui_rect text_cursor; - if( colour == 0 ) colour = ui_colour( k_ui_fg ); - - colour &= 0x00ffffff; - - const char *_c = str; - u8 c; - - text_cursor[0] = ui_text_aligned_x( str, rect, scale, align ); - text_cursor[1] = rect[1]; - text_cursor[2] = vg_ui.font->cw*scale; - text_cursor[3] = vg_ui.font->ch*scale; - - u32 printed_chars = 0; - - if( align & (k_ui_align_middle|k_ui_align_bottom) ) - { - ui_px height = ui_text_string_height( str ) * scale; - - if( align & k_ui_align_bottom ) - text_cursor[1] += rect[3]-height; - else - text_cursor[1] += (rect[3]-height)/2; - } - - while( (c = *(_c ++)) ) - { - if( printed_chars >= len ) - { - printed_chars = 0; - text_cursor[1] += vg_ui.font->sy*scale; - text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); - text_cursor[0] -= vg_ui.font->sx*scale; - - ui_rect glyph; - ui_text_glyph( vg_ui.font, '\xb1' /*FIXME*/, glyph ); - ui_fill_rect( text_cursor, 0x00ffffff, glyph ); - text_cursor[0] += vg_ui.font->sx*scale; - } - - if( c == '\n' ) - { - text_cursor[1] += vg_ui.font->sy*scale; - text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); - printed_chars = 0; - continue; - } - else if( c >= 33 ) - { - ui_rect glyph; - ui_text_glyph( vg_ui.font, c, glyph ); - - ui_rect cursor_clipped; - if( ui_clip( rect, text_cursor, cursor_clipped ) ) - { - if( glow_text ) - { - cursor_clipped[1] += glow_text; - ui_fill_rect( cursor_clipped, 0x00ffffff, glyph ); - cursor_clipped[1] -= glow_text; - } - - ui_fill_rect( cursor_clipped, colour, glyph ); - } - } - else if( c == '\x1B' ) - { - /* vt codes */ - _c ++; - u16 colour_id = 0; - for( int i=0; i<3; i ++ ) - { - if( _c[i] ) - { - if( _c[i] == 'm' ) - { - _c = _c + i + 1; - - switch( colour_id ){ - case '0': colour = ui_colour( k_ui_fg ); break; - case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break; - case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break; - case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break; - case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break; - case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break; - case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break; - case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break; - case '3'|'7'<<8: colour = 0xffffffff; break; - } - - colour &= 0x00ffffff; - break; - } - - colour_id |= _c[i] << (i*8); - } - else - { - _c = _c +i; - break; - } - } - - continue; - } - else if( c == '\x06' ) - { - glow_text = *_c; - _c ++; - continue; - } - else if( c == '\t' ) - { - text_cursor[0] += vg_ui.font->sx*scale*4; - printed_chars += 4; - continue; - } - - text_cursor[0] += vg_ui.font->sx*scale; - printed_chars ++; - } - - return printed_chars; -} - -u32 ui_text( ui_rect rect, const char *str, ui_px scale, - enum ui_align align, u32 colour ) -{ - return ui_ntext( rect, str, 1024, scale, align, colour ); -} - -void ui_font_face( vg_font_face *ff ) -{ - vg_ui.font = ff; -} - -/* - * Standard layout stuff - * ----------------------------------------------------------------------------- - */ - -void ui_panel( ui_rect in_rect, ui_rect out_panel ) -{ - ui_fill( in_rect, ui_colour( k_ui_bg+1 ) ); - ui_outline( in_rect, 1, ui_colour( k_ui_bg+7 ), 0 ); - rect_copy( in_rect, out_panel ); - ui_rect_pad( out_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); -} - -void ui_label( ui_rect rect, const char *text, ui_px size, - ui_px gap, ui_rect r ) -{ - ui_rect l; - ui_px width = (ui_text_line_width(text)+vg_ui.font->sx) * size; - ui_split( rect, k_ui_axis_v, width, gap, l, r ); - ui_text( l, text, 1, k_ui_align_middle_left, 0 ); -} - -void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count ) -{ - ui_px height = (count * vg_ui.font->sy + 18) * k_ui_scale; - ui_split( inout_panel, k_ui_axis_h, height, k_ui_padding, - out_rect, inout_panel ); -} - -void ui_info( ui_rect inout_panel, const char *text ) -{ - ui_rect box; - ui_standard_widget( inout_panel, box, 1 ); - ui_text( box, text, 1, k_ui_align_middle_left, 0 ); -} - -void ui_image( ui_rect rect, GLuint image ) -{ - ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); - glActiveTexture( GL_TEXTURE0 ); - glBindTexture( GL_TEXTURE_2D, image ); - ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); - ui_flush( k_ui_shader_image, vg.window_x, vg.window_y ); -} - -void ui_defocus_all(void) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - SDL_StopTextInput(); - if( vg_ui.textbox.callbacks.escape ) - vg_ui.textbox.callbacks.escape(); - } - - vg_ui.focused_control_id = NULL; - vg_ui.focused_control_hit = 0; - vg_ui.focused_control_type = k_ui_control_none; -} - -enum ui_button_state ui_button_base( ui_rect rect ) -{ - int clickup= ui_click_up(UI_MOUSE_LEFT), - click = ui_clicking(UI_MOUSE_LEFT) | clickup, - target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, - hover = ui_inside_rect( rect, vg_ui.mouse ); - - if( vg_ui.focused_control_type != k_ui_control_none ) - { - clickup = 0; - click = 0; - target = 0; - hover = 0; - } - - if( hover ) - vg_ui.cursor = k_ui_cursor_hand; - - if( click ) - { - if( target ) - { - if( hover ) - { - if( clickup ) - { - vg_ui.ignore_input_frames = 2; - ui_defocus_all(); - return k_ui_button_click; - } - else return k_ui_button_holding_inside; - } - else return k_ui_button_holding_outside; - } - else return k_ui_button_none; - } - else - { - if( hover ) return k_ui_button_hover; - else return k_ui_button_none; - } -} - -/* TODO: split this out into a formatless button and one that auto fills */ -enum ui_button_state ui_colourbutton( ui_rect rect, - enum ui_scheme_colour colour, - enum ui_scheme_colour hover_colour, - enum ui_scheme_colour hi_colour ) -{ - enum ui_button_state state = ui_button_base( rect ); - - u32 col_base = vg_ui.scheme[ colour ], - col_highlight = vg_ui.scheme[ hi_colour? hi_colour: k_ui_fg ], - col_hover = vg_ui.scheme[ hover_colour? hover_colour: - colour + k_ui_brighter ]; - - if( state == k_ui_button_click ) - { - ui_fill( rect, col_highlight ); - rect_copy( rect, vg_ui.click_fader ); - rect_copy( rect, vg_ui.click_fader_end ); - vg_ui.click_fader_end[3] = 0; - ui_rect_center( rect, vg_ui.click_fader_end ); - vg_ui.click_fade_opacity = 1.0f; - } - else if( state == k_ui_button_holding_inside ) - { - ui_fill( rect, col_highlight ); - } - else if( state == k_ui_button_holding_outside ) - { - ui_fill( rect, col_base ); - ui_outline( rect, 1, col_highlight, 0 ); - } - else if( state == k_ui_button_hover ) - { - ui_fill( rect, col_hover ); - } - else ui_fill( rect, col_base ); - - return state; -} - -enum ui_button_state ui_colourbutton_text( - ui_rect rect, const char *string, ui_px scale, - enum ui_scheme_colour colour ) -{ - enum ui_button_state state = ui_colourbutton( rect, colour, 0, 0 ); - - u32 text_colour = ui_colourcont(colour); - if( state == k_ui_button_holding_inside ) - text_colour = colour; - - ui_text( rect, string, scale, k_ui_align_middle_center, text_colour ); - return state; -} - -enum ui_button_state ui_button_text( ui_rect rect, - const char *string, ui_px scale ) -{ - return ui_colourbutton_text( rect, string, scale, k_ui_bg+4 ); -} - -enum ui_button_state ui_button( ui_rect inout_panel, const char *string ) -{ - ui_rect rect; - ui_standard_widget( inout_panel, rect, 1 ); - return ui_colourbutton_text( rect, string, 1, k_ui_bg+4 ); -} - -static void ui_enum_post(void); -void ui_postrender(void) -{ - if( vg_ui.click_fade_opacity > 0.0f ){ - float scale = vg_ui.click_fade_opacity; - scale = vg_maxf( 1.0f/255.0f, scale*scale ); - - vg_ui.click_fade_opacity -= vg.time_frame_delta * 3.8f; - u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; - - v4f begin, end, dest; - for( int i=0; i<4; i++ ){ - begin[i] = vg_ui.click_fader[i]; - end[i] = vg_ui.click_fader_end[i]+1; - } - - v4_lerp( end, begin, scale, dest ); - - ui_rect rect; - for( int i=0; i<4; i++ ){ - rect[i] = dest[i]; - } - - ui_fill( rect, colour ); - } - - if( vg_ui.focused_control_type == k_ui_control_enum ){ - ui_enum_post(); - } - else if( vg_ui.focused_control_type == k_ui_control_modal ){ - ui_rect screen = {0,0,vg.window_x,vg.window_y}; - ui_fill( screen, 0xa0000000 ); - ui_rect box = {0,0,400,200}; - - u32 colour = ui_colour(k_ui_fg), - type = vg_ui.modal.options & UI_MODAL_TYPE_BITS; - if ( type == 1 ) colour = ui_colour(k_ui_green); - else if( type == 2 ) colour = ui_colour(k_ui_red); - else if( type == 3 ) colour = ui_colour(k_ui_yellow); - - ui_rect_center( screen, box ); - ui_fill( box, ui_colour(k_ui_bg) ); - ui_outline( box, -1, colour, 0 ); - - ui_rect message; - rect_copy( box, message ); - message[3] = 100; - ui_rect_center( box, message ); - - ui_rect row0, row1, btn; - ui_split_ratio( message, k_ui_axis_h, 0.5f, 0, row0, row1 ); - row0[0] += vg_ui.font->sx; - ui_ntext( row0, vg_ui.modal.message, (box[2]/vg_ui.font->sx)-2, 1, - k_ui_align_left, colour ); - - rect_copy( row1, btn ); - btn[2] = 86; - btn[3] = 28; - ui_rect_center( row1, btn ); - - vg_ui.focused_control_type = k_ui_control_none; /* HACK */ - if( ui_button_text( btn, "OK", 1 ) != 1 ) - vg_ui.focused_control_hit = 1; - vg_ui.focused_control_type = k_ui_control_modal; /* HACK */ - vg_ui.wants_mouse = 1; - } - - ui_flush( k_ui_shader_colour, vg.window_x, vg.window_y ); - - if( !vg_ui.focused_control_hit ){ - ui_defocus_all(); - } - - if( vg_ui.wants_mouse ){ - SDL_SetWindowGrab( vg.window, SDL_FALSE ); - SDL_SetRelativeMouseMode( SDL_FALSE ); - } - else{ - SDL_SetWindowGrab( vg.window, SDL_TRUE ); - SDL_SetRelativeMouseMode( SDL_TRUE ); - } - - SDL_SetCursor( vg_ui.cursor_map[ vg_ui.cursor ] ); - SDL_ShowCursor(1); -} - -/* - * checkbox - * ----------------------------------------------------------------------------- - */ - -enum ui_button_state ui_checkbox_base( ui_rect box, i32 *data ) -{ - enum ui_button_state state = ui_button_base( box ); - if( state == k_ui_button_click ) - *data = (*data) ^ 0x1; - return state; -} - -int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ) -{ - ui_rect rect, label, box; - ui_standard_widget( inout_panel, rect, 1 ); - - ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box ); - ui_text( label, str_label, k_ui_scale, k_ui_align_middle_left, 0 ); - - enum ui_button_state state = ui_checkbox_base( box, data ); - - if( state == k_ui_button_holding_inside ) - { - ui_fill( box, ui_colour(k_ui_bg+2) ); - ui_outline( box, 1, ui_colour(k_ui_fg), 0 ); - } - else if( state == k_ui_button_holding_outside ) - { - ui_fill( box, ui_colour(k_ui_bg) ); - ui_outline( box, 1, ui_colour(k_ui_fg), 0 ); - } - else if( state == k_ui_button_hover ) - { - ui_fill( box, ui_colour(k_ui_bg) ); - ui_outline( box, 1, ui_colour(k_ui_fg), 0 ); - } - else - { - ui_fill( box, ui_colour(k_ui_bg) ); - ui_outline( box, 1, ui_colour(k_ui_bg+4), 0 ); - } - - bool changed = (state == k_ui_button_click); - - if( *data ) - { - ui_rect_pad( box, (ui_px[2]){4,4} ); - ui_fill( box, ui_colour( k_ui_orange ) ); - } - - return changed; -} - -/* - * Dropdown / Enum - * ----------------------------------------------------------------------------- - */ - -/* - * unfortunately no return value since we only find out that event in the - * postrender step. - */ -void ui_enum( ui_rect inout_panel, const char *str_label, - struct ui_enum_opt *options, u32 len, i32 *value ) -{ - ui_rect rect, label, box; - ui_standard_widget( inout_panel, rect, 1 ); - ui_label( rect, str_label, k_ui_scale, 0, box ); - - const char *display = "OUT OF RANGE"; - int valid = 0; - for( u32 i=0; iterminator to start */ - int remaining_length = strlen( vg_ui.textbuf )+1-end; - memmove( &vg_ui.textbuf[ start ], - &vg_ui.textbuf[ end ], - remaining_length ); - return start; -} - -static void _ui_textbox_to_clipboard(void) -{ - int start, end; - _ui_textbox_make_selection( &start, &end ); - char buffer[512]; - - if( end-start ){ - memcpy( buffer, &vg_ui.textbuf[ start ], end-start ); - buffer[ end-start ] = 0x00; - SDL_SetClipboardText( buffer ); - } -} - -static void _ui_textbox_change_callback(void) -{ - if( vg_ui.textbox.callbacks.change ){ - vg_ui.textbox.callbacks.change( vg_ui.textbuf, vg_ui.textbox.len ); - - /* we gave permission to modify the buffer in this callback so.. */ - int len = strlen( vg_ui.textbuf ); - vg_ui.textbox.cursor_user = VG_MIN( vg_ui.textbox.cursor_user, len ); - vg_ui.textbox.cursor_pos = VG_MIN( vg_ui.textbox.cursor_pos, len ); - } -} - -void ui_start_modal( const char *message, u32 options ); -static void _ui_textbox_clipboard_paste(void) -{ - if( !SDL_HasClipboardText() ) - return; - - char *text = SDL_GetClipboardText(); - - if( !text ) - return; - - int datastart = _ui_textbox_delete_char( 0 ); - int length = strlen( text ); - - if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) < length ){ - ui_start_modal( "Clipboard content exceeds buffer size.", UI_MODAL_BAD ); - return; - } - - int cpylength = _ui_textbox_makeroom( datastart, length ); - - memcpy( vg_ui.textbuf + datastart, text, cpylength); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, cpylength, 1 ); - SDL_free( text ); - _ui_textbox_change_callback(); -} - -void _ui_textbox_put_char( char c ) -{ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); - if( (vg_ui.textbox.len - strlen(vg_ui.textbuf)) <= 1 ) return; - - if( _ui_textbox_makeroom( vg_ui.textbox.cursor_user, 1 ) ) - vg_ui.textbuf[ vg_ui.textbox.cursor_user ] = c; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, 1, 1 ); -} - -/* Receed secondary cursor */ -void _ui_textbox_left_select(void) -{ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, -1, 0 ); -} - -/* Match and receed both cursors */ -void _ui_textbox_left(void) -{ - int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, -cursor_diff, 1 ); -} - -void _ui_textbox_up(void) -{ - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - int line_begin = vg_ui.textbox.cursor_user; - - while( line_begin ){ - if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ - break; - } - - line_begin --; - } - - if( line_begin ){ - int line_above_begin = line_begin-1; - - while( line_above_begin ){ - if( vg_ui.textbuf[ line_above_begin-1 ] == '\n' ){ - break; - } - - line_above_begin --; - } - - int offset = vg_ui.textbox.cursor_user - line_begin, - line_length_above = line_begin - line_above_begin -1; - - offset = VG_MIN( line_length_above, offset ); - - vg_ui.textbox.cursor_user = line_above_begin+offset; - vg_ui.textbox.cursor_pos = line_above_begin+offset; - } - else{ - vg_ui.textbox.cursor_user = line_begin; - vg_ui.textbox.cursor_pos = line_begin; - } - } - else{ - if( vg_ui.textbox.callbacks.up ){ - vg_ui.textbox.callbacks.up( vg_ui.textbuf, vg_ui.textbox.len ); - } - } -} - -void _ui_textbox_down(void) -{ - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - int line_begin = vg_ui.textbox.cursor_user; - - while( line_begin ){ - if( vg_ui.textbuf[ line_begin-1 ] == '\n' ){ - break; - } - - line_begin --; - } - - int line_below_begin = vg_ui.textbox.cursor_user; - - while(1){ - if( vg_ui.textbuf[ line_below_begin ] == '\0' ){ - vg_ui.textbox.cursor_user = line_below_begin; - vg_ui.textbox.cursor_pos = line_below_begin; - return; - } - - if( vg_ui.textbuf[ line_below_begin ] == '\n' ){ - line_below_begin ++; - break; - } - - line_below_begin ++; - } - - int line_below_end = line_below_begin; - while(1){ - if( vg_ui.textbuf[ line_below_end ] == '\0' || - vg_ui.textbuf[ line_below_end ] == '\n' ){ - line_below_end ++; - break; - } - line_below_end ++; - } - - int offset = vg_ui.textbox.cursor_user - line_begin, - line_length_below = line_below_end - line_below_begin -1; - - offset = VG_MIN( line_length_below, offset ); - - vg_ui.textbox.cursor_user = line_below_begin+offset; - vg_ui.textbox.cursor_pos = line_below_begin+offset; - } - else{ - if( vg_ui.textbox.callbacks.down ){ - vg_ui.textbox.callbacks.down( vg_ui.textbuf, vg_ui.textbox.len ); - } - } -} - -void _ui_textbox_right_select(void) -{ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 1, 0 ); -} - -void _ui_textbox_right(void) -{ - int cursor_diff = vg_ui.textbox.cursor_pos - vg_ui.textbox.cursor_user? 0: 1; - - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, - &vg_ui.textbox.cursor_pos, +cursor_diff, 1 ); -} - -void _ui_textbox_backspace(void) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char( -1 ); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); - } -} - -void _ui_textbox_delete(void) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.textbox.cursor_user = _ui_textbox_delete_char( 1 ); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); - } -} - -void _ui_textbox_home_select(void) -{ - i32 start = vg_ui.textbox.cursor_user; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - while( start ){ - if( vg_ui.textbuf[start-1] == '\n' ) - break; - else - start --; - } - } - else - start = 0; - - vg_ui.textbox.cursor_user = start; -} - -void _ui_textbox_home(void) -{ - _ui_textbox_home_select(); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; -} - -void _ui_textbox_end_select(void) -{ - i32 end = vg_ui.textbox.cursor_user; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - while( vg_ui.textbuf[end] ){ - if( vg_ui.textbuf[end] == '\n' ) - break; - else - end ++; - } - } - else - end = VG_MIN( vg_ui.textbox.len-1, strlen(vg_ui.textbuf) ); - - vg_ui.textbox.cursor_user = end; -} - -void _ui_textbox_end(void) -{ - _ui_textbox_end_select(); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; -} - -void _ui_textbox_select_all(void) -{ - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_user, NULL, 10000, 0); - _ui_textbox_move_cursor( &vg_ui.textbox.cursor_pos, NULL, -10000, 0); -} - -void _ui_textbox_cut(void) -{ - _ui_textbox_to_clipboard(); - vg_ui.textbox.cursor_user = _ui_textbox_delete_char(0); - vg_ui.textbox.cursor_pos = vg_ui.textbox.cursor_user; - _ui_textbox_change_callback(); -} - -void _ui_textbox_enter(void) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - vg_ui.ignore_input_frames = 2; - - if( vg_ui.textbox.callbacks.enter ) - vg_ui.textbox.callbacks.enter( vg_ui.textbuf, vg_ui.textbox.len ); - - if( vg_ui.focused_control_type != k_ui_control_textbox ) return; - - if( vg_ui.textbox.flags & UI_TEXTBOX_MULTILINE ){ - _ui_textbox_put_char( '\n' ); - _ui_textbox_change_callback(); - } - else{ - if( !(vg_ui.textbox.flags & UI_TEXTBOX_AUTOFOCUS ) ) - ui_defocus_all(); - } - } -} - -/* - * based on a visual character coordinate relative to the anchor of the textbox, - * this works out the linear place in the buffer that coordinate maps to - * - * input coordinates go in co[0], co[1], and the result index is in co[2] - */ -static void _ui_textbox_calc_index_from_grid( int co[3], int wrap_length ){ - int i[3] = {0,0,0}; - - char c; - while( (c = vg_ui.textbuf[i[2]]) ){ - if( i[1]==co[1] && i[0]>=co[0] ) break; - - if( i[0] >= wrap_length ){ - i[1] ++; - i[0] = 0; - } - - if( c >= 32 && c <= 126 ){ - i[0] ++; - i[2] ++; - } - else if( c == '\n' ){ - i[1] ++; - - if( i[1] > co[1] ) break; - - i[2] ++; - i[0] = 0; - } - else i[2] ++; - } - - co[0] = i[0]; - co[1] = i[1]; - co[2] = i[2]; -} - -/* - * based on the index specied in co[2], work out the visual character - * coordinates and store them in co[0], co[1] - */ -static void _ui_textbox_index_calc_coords( int co[3], int wrap_length ){ - co[0] = 0; - co[1] = 0; - - char c; - int i=0; - - while( (c = vg_ui.textbuf[i ++]) ){ - if( i > co[2] ) break; - if( co[0] >= wrap_length ){ - co[1] ++; - co[0] = 0; - } - if( c >= 32 && c <= 126 ) co[0] ++; - else if( c == '\n' ){ - co[1] ++; - co[0] = 0; - } - } -} - -/* - * calculate the number of characters remaining until either: - * - the wrap_length limit is hit - * - end of the line/string - * - * index must be fully populated with visual X/Y, and linear index - */ -static int _ui_textbox_run_remaining( int index[3], int wrap_length ){ - int i=0, printed_chars=0; - char c; - while( (c = vg_ui.textbuf[index[2] + (i ++)]) ){ - if( index[0]+i >= wrap_length ) break; - if( c >= 32 && c <= 126 ) printed_chars ++; - else if( c == '\n' ) break; - } - - return printed_chars+1; -} - -int ui_textbox( ui_rect inout_panel, const char *label, - char *buf, u32 len, u32 lines, u32 flags, - struct ui_textbox_callbacks *callbacks ) -{ - if( lines > 1 ) flags |= UI_TEXTBOX_MULTILINE; - - ui_rect rect; - ui_standard_widget( inout_panel, rect, lines ); - - if( label ) - ui_label( rect, label, 1, 0, rect ); - - int clickup= ui_click_up(UI_MOUSE_LEFT), - clickdown = ui_click_down(UI_MOUSE_LEFT), - click = ui_clicking(UI_MOUSE_LEFT) | clickup, - target = ui_inside_rect( rect, vg_ui.mouse_click ) && click, - hover = ui_inside_rect( rect, vg_ui.mouse ); - - /* allow instant transitions from textbox->textbox */ - if( (vg_ui.focused_control_type != k_ui_control_none) && - (vg_ui.focused_control_type != k_ui_control_textbox) ){ - clickup = 0; - clickdown = 0; - click = 0; - target = 0; - hover = 0; - flags &= ~UI_TEXTBOX_AUTOFOCUS; - } - - u32 col_base = ui_colour( k_ui_bg ), - col_highlight = ui_colour( k_ui_fg ), - col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; - - ui_px border = -1; - - ui_rect text_rect; - rect_copy( rect, text_rect ); - - if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16; - else text_rect[3] = vg_ui.font->sy; - - text_rect[2] -= 16; - ui_rect_center( rect, text_rect ); - - ui_px wrap_length = 1024; - - if( flags & UI_TEXTBOX_WRAP ) - wrap_length = text_rect[2] / vg_ui.font->sx; - - if( hover ) - { - vg_ui.cursor = k_ui_cursor_ibeam; - } - - if( vg_ui.focused_control_id == buf ) - { - ui_fill( rect, col_base ); - ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); - - if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)) - { - ui_defocus_all(); - } - else - { - vg_ui.focused_control_hit = 1; - if( click && target ){ - int p0[3] ={ - (vg_ui.mouse_click[0] - text_rect[0]) / vg_ui.font->sx, - (vg_ui.mouse_click[1] - text_rect[1]) / vg_ui.font->sy, - -1 - }, - p1[3] = { - (vg_ui.mouse[0] - text_rect[0]) / vg_ui.font->sx, - (vg_ui.mouse[1] - text_rect[1]) / vg_ui.font->sy, - -1 - }; - - if( flags & UI_TEXTBOX_MULTILINE ) - { - _ui_textbox_calc_index_from_grid( p0, wrap_length ); - _ui_textbox_calc_index_from_grid( p1, wrap_length ); - - vg_ui.textbox.cursor_pos = p0[2]; - vg_ui.textbox.cursor_user = p1[2]; - } - else - { - int max = strlen( buf ); - vg_ui.textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )), - vg_ui.textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] )); - } - } - - ui_outline( rect, -2, vg_ui.scheme[ k_ui_orange ], 0 ); - - ui_rect cursor; - - int c0 = vg_ui.textbox.cursor_pos, - c1 = vg_ui.textbox.cursor_user, - start = VG_MIN( c0, c1 ), - end = VG_MAX( c0, c1 ), - chars = end-start; - - if( flags & (UI_TEXTBOX_WRAP|UI_TEXTBOX_MULTILINE) ) - { - int pos[3], remaining = chars; - - pos[2] = start; - _ui_textbox_index_calc_coords( pos, wrap_length ); - - if( start==end ) - { - cursor[0] = text_rect[0] + pos[0]*vg_ui.font->sx-1; - cursor[1] = text_rect[1] + pos[1]*14; - cursor[2] = 2; - cursor[3] = 13; - ui_fill( cursor, col_cursor ); - rect_copy( cursor, vg_ui.click_fader_end ); - } - else - { - while( remaining ) - { - int run = _ui_textbox_run_remaining( pos, wrap_length ); - run = VG_MIN( run, remaining ); - - cursor[0] = text_rect[0] + pos[0]*vg_ui.font->sx-1; - cursor[1] = text_rect[1] + pos[1]*14; - cursor[2] = (float)(run)*(float)vg_ui.font->sx; - cursor[3] = 13; - - ui_fill( cursor, col_cursor ); - - remaining -= run; - pos[0] = 0; - pos[1] ++; - pos[2] += run; - } - rect_copy( cursor, vg_ui.click_fader_end ); - } - } - else - { - cursor[0] = text_rect[0] + start*vg_ui.font->sx-1; - cursor[1] = text_rect[1]; - cursor[3] = 13; - - if( start==end ) - { - cursor[2] = 2; - } - else - { - cursor[2] = (float)(chars)*(float)vg_ui.font->sx; - } - - if( (vg_ui.click_fade_opacity<=0.0f) && - ui_clip( rect, cursor, cursor ) ) - { - ui_fill( cursor, col_cursor ); - } - - rect_copy( cursor, vg_ui.click_fader_end ); - } - } - - return 0; - } - - if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ) - { - if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ) - { - ui_defocus_all(); - - ui_fill( rect, col_highlight ); - vg_ui.ignore_input_frames = 2; - rect_copy( rect, vg_ui.click_fader ); - rect_copy( rect, vg_ui.click_fader_end ); - - vg_ui.click_fade_opacity = 1.0f; - vg_ui.textbuf = buf; - vg_ui.focused_control_hit = 1; - vg_ui.focused_control_type = k_ui_control_textbox; - vg_ui.textbox.len = len; - vg_ui.textbox.flags = flags; - vg_ui.textbox.cursor_pos = 0; - vg_ui.textbox.cursor_user = 0; - - if( callbacks ) - { - vg_ui.textbox.callbacks = *callbacks; - } - else - { - vg_ui.textbox.callbacks.change = NULL; - vg_ui.textbox.callbacks.down = NULL; - vg_ui.textbox.callbacks.up = NULL; - vg_ui.textbox.callbacks.enter = NULL; - } - - SDL_StartTextInput(); - } - } - - ui_fill( rect, col_base ); - - if( hover ) - { - ui_outline( rect, -1, col_highlight, 0 ); - } - - ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); - return 0; -} - -/* - * Tabs - * ----------------------------------------------------------------------------- - */ - -void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, - const char **titles, u32 count, i32 *page ) -{ - ui_rect bar; - ui_standard_widget( inout_panel, bar, 1 ); - - i32 cur_page = *page; - - f32 width = (f32)inout_panel[2] / (f32)count; - - ui_px h = (inout_panel[1] + inout_panel[3]) - (bar[1]+bar[3]); - inout_panel[1] = bar[1]+bar[3]; - inout_panel[3] = h; - - ui_fill( inout_panel, ui_colour( k_ui_bg+2 ) ); - ui_outline( inout_panel, 1, ui_colour( k_ui_bg+5 ), 0 ); - - rect_copy( inout_panel, out_content_panel ); - ui_rect_pad( out_content_panel, (ui_px[2]){ k_ui_padding, k_ui_padding } ); - - /* place buttons */ - for( i32 i=0; ikey == ev.sym ){ - if( mapping->mod == 0 ){ - if( mod == 0 ){ - mapping->handler(); - return; - } - } - else if( (mod & mapping->mod) == mapping->mod ){ - mapping->handler(); - return; - } - } - } -} - -/* - * Callback for text entry mode - */ -void ui_proc_utf8( const char *text ) -{ - if( vg_ui.focused_control_type == k_ui_control_textbox ){ - const char *ptr = text; - - while( *ptr ){ - if( *ptr != '`' ) _ui_textbox_put_char( *ptr ); - ptr ++; - } - - _ui_textbox_change_callback(); - } -} - -/* - * Development utils - * ----------------------------------------------------------------------------- - */ - -void ui_dev_colourview(void) -{ - ui_rect window = {vg.window_x-256,0,256,vg.window_y}, swatch; - - const char *names[vg_list_size(vg_ui.scheme)] = { - [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3", - "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7", - - [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3", - "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7", - - [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green", - "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray", - "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8", - "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" }; - - ui_rect col[2]; - ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] ); - - for( int i=0; i>16) - -#define UI_TEXTBOX_MULTILINE 0x1 -#define UI_TEXTBOX_WRAP 0x2 -#define UI_TEXTBOX_AUTOFOCUS 0x4 - -#define UI_MODAL_OK 0x0 -#define UI_MODAL_GOOD 0x1 -#define UI_MODAL_BAD 0x2 -#define UI_MODAL_WARN 0x3 -#define UI_MODAL_TYPE_BITS 0x3 - -#define UI_MOUSE_LEFT (SDL_BUTTON(SDL_BUTTON_LEFT)) -#define UI_MOUSE_RIGHT (SDL_BUTTON(SDL_BUTTON_RIGHT)) -#define UI_MOUSE_MIDDLE (SDL_BUTTON(SDL_BUTTON_MIDDLE)) - -#define UI_TOP 0x1 -#define UI_LEFT 0x2 -#define UI_BOTTOM 0x4 -#define UI_RIGHT 0x8 - -struct vg_imgui -{ - struct ui_vert *vertex_buffer; - u16 *indice_buffer; - u32 max_verts, max_indices, - cur_vert, cur_indice, - vert_start, indice_start; - - union { - void *focused_control_id; /* uses the memory location of various locking - controls as an id */ - char *textbuf; - i32 *ptr_enum; - }; - - u32 focused_control_hit; - enum ui_control_type{ - k_ui_control_none, - k_ui_control_textbox, - k_ui_control_enum, - k_ui_control_modal - } - focused_control_type; - - union{ /* controls that can be focused */ - struct ui_textbuf{ - int cursor_user, cursor_pos; - u32 len; - u32 flags; - - struct ui_textbox_callbacks{ - void (*enter)( char *, u32 ), - (*up)( char *, u32 ), - (*down)( char *, u32 ), - (*change)( char *, u32 ), - (*escape)( void ); - } - callbacks; - } - textbox; - - struct ui_enum{ - struct ui_enum_opt{ - i32 value; - const char *alias; - } - *options; - u32 option_count; - ui_rect rect; - } - _enum; - }; - - struct ui_modal{ - const char *message; - u32 options; - - struct ui_modal_callbacks{ - void (*close)(u32); - } - callbacks; - } - modal; - - GLuint tex_glyphs, vao, vbo, ebo, tex_bg; - v2f bg_inverse_ratio; - - ui_px mouse[2], mouse_delta[2], mouse_click[2]; - u32 mouse_state[2]; - u32 ignore_input_frames; - bool mouse_pos_overriden; - int wants_mouse; - - ui_rect click_fader, click_fader_end; - float click_fade_opacity; - f32 frosting; - - ui_scheme scheme; - const vg_font_face *font; - v2f inverse_font_sheet; - - enum ui_cursor{ - k_ui_cursor_default, - k_ui_cursor_ibeam, - k_ui_cursor_hand, - k_ui_cursor_max - } - cursor; - - SDL_Cursor *cursor_map[ k_ui_cursor_max ]; - v4f colour; - - f32 hue; /* lol this sucks */ -} -extern vg_ui; - -enum ui_button_state { - k_ui_button_none = 0x0, - k_ui_button_click = 0x1, - k_ui_button_holding_inside = 0x2, - k_ui_button_holding_outside = 0x4, - k_ui_button_hover = 0x8 -}; - -/* TODO: docu.. */ - -void vg_ui_init(void); -void rect_copy( ui_rect a, ui_rect b ); -void ui_flush( enum ui_shader shader, f32 w, f32 h ); -struct ui_vert *ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ); -struct ui_vert *ui_fill( ui_rect rect, u32 colour ); -void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask ); -void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap, - ui_rect l, ui_rect r ); -void ui_rect_center( ui_rect parent, ui_rect rect ); -void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d ); -void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, - ui_px gap, ui_rect l, ui_rect r ); -void ui_rect_pad( ui_rect rect, ui_px pad[2] ); -ui_px ui_text_line_width( const char *str ); -ui_px ui_text_string_height( const char *str ); -ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, - enum ui_align align ); -int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ); -int ui_inside_rect( ui_rect rect, ui_px co[2] ); -int ui_click_down( u32 mask ); -int ui_clicking( u32 mask ); -int ui_click_up( u32 mask ); -void ui_set_mouse_pos( ui_px x, ui_px y ); -void ui_prerender(void); -u32 ui_colour( enum ui_scheme_colour id ); - -/* get an appropriately contrasting colour given the base */ -u32 ui_colourcont( enum ui_scheme_colour id ); - -void ui_hex_to_norm( u32 hex, v4f norm ); -u32 v4f_u32_colour( v4f colour ); - -u32 ui_opacity( u32 colour, f32 opacity ); -void ui_font_face( vg_font_face *ff ); -u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, - enum ui_align align, u32 colour ); -u32 ui_text( ui_rect rect, const char *str, ui_px scale, - enum ui_align align, u32 colour ); -void ui_panel( ui_rect in_rect, ui_rect out_panel ); -void ui_label( ui_rect rect, const char *text, ui_px size, - ui_px gap, ui_rect r ); -void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count ); -void ui_info( ui_rect inout_panel, const char *text ); -void ui_image( ui_rect rect, GLuint image ); -void ui_defocus_all(void); - -enum ui_button_state ui_button_base( ui_rect rect ); -enum ui_button_state ui_colourbutton( ui_rect rect, - enum ui_scheme_colour colour, - enum ui_scheme_colour hover_colour, - enum ui_scheme_colour hi_colour ); -enum ui_button_state ui_colourbutton_text( - ui_rect rect, const char *string, ui_px scale, - enum ui_scheme_colour colour ); -enum ui_button_state ui_button_text( ui_rect rect, - const char *string, ui_px scale ); -enum ui_button_state ui_button( ui_rect inout_panel, const char *string ); - -void ui_postrender(void); - -enum ui_button_state ui_checkbox_base( ui_rect box, i32 *data ); -int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ); -void ui_enum( ui_rect inout_panel, const char *str_label, - struct ui_enum_opt *options, u32 len, i32 *value ); -enum ui_button_state ui_slider_base( ui_rect box, f32 min, f32 max, f32 *value, - f32 *out_t ); -bool ui_slider( ui_rect inout_panel, const char *str_label, - f32 min, f32 max, f32 *value ); -void ui_slider_text( ui_rect box, const char *format, f32 value ); -void ui_colourpicker( ui_rect inout_panel, const char *str_label, v4f value ); -int ui_textbox( ui_rect inout_panel, const char *label, - char *buf, u32 len, u32 lines, u32 flags, - struct ui_textbox_callbacks *callbacks ); -void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, - const char **titles, u32 count, i32 *page ); -void ui_start_modal( const char *message, u32 options ); -void ui_proc_key( SDL_Keysym ev ); -void ui_proc_utf8( const char *text ); -void ui_dev_colourview(void); - -void _ui_textbox_move_cursor( int *cursor0, int *cursor1, - int dir, int snap_together ); -int _ui_textbox_delete_char( int direction ); -void _ui_textbox_put_char( char c ); -void _ui_textbox_up(void); -void _ui_textbox_left(void); -void _ui_textbox_left_select(void); -void _ui_textbox_down(void); -void _ui_textbox_right_select(void); -void _ui_textbox_right(void); -void _ui_textbox_backspace(void); -void _ui_textbox_delete(void); -void _ui_textbox_home_select(void); -void _ui_textbox_home(void); -void _ui_textbox_end_select(void); -void _ui_textbox_end(void); -void _ui_textbox_select_all(void); -void _ui_textbox_cut(void); -void _ui_textbox_enter(void); diff --git a/vg_loader.c b/vg_loader.c index 55ab081..b497f45 100644 --- a/vg_loader.c +++ b/vg_loader.c @@ -126,16 +126,26 @@ void vg_loader_render(void) vg.loader_ring = 1.0f; } -static int _vg_loader_thread( void *pfn ){ +static int _vg_loader_thread( void *pfn ) +{ +#ifdef VG_ANDROID + vg.thread_id_loader = pthread_self(); +#else + vg.thread_id_loader = SDL_GetThreadID(NULL); +#endif + if( setjmp( vg.env_loader_exit ) ) + { + vg.thread_id_loader = 0; return 0; + } /* Run client loader */ //vg_info( "Starting client loader thread @%p\n", pfn ); void (*call_func)(void *data) = pfn; call_func( vg.thread_data ); - SDL_SemPost( vg.sem_loader ); + TEMP_SEM_POST( vg.sem_loader ); vg.thread_id_loader = 0; return 0; @@ -143,7 +153,8 @@ static int _vg_loader_thread( void *pfn ){ int vg_loader_availible(void) { - if( SDL_SemValue( vg.sem_loader ) ){ + if( TEMP_SEM_GET( vg.sem_loader ) ) + { if( !(vg_async.start) ) return 1; } @@ -153,7 +164,7 @@ int vg_loader_availible(void) void vg_loader_start( void(*pfn)(void *data), void *data ) { - SDL_SemWait( vg.sem_loader ); + TEMP_SEM_WAIT( vg.sem_loader ); vg.thread_data = data; SDL_CreateThread( _vg_loader_thread, "vg: loader", pfn ); diff --git a/vg_log.c b/vg_log.c index 84807da..8c2edc2 100644 --- a/vg_log.c +++ b/vg_log.c @@ -114,7 +114,7 @@ void vg_logx( FILE *file, void vg_print_backtrace(void) { -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(VG_ANDROID) void *array[20]; char **strings; int size, i; diff --git a/vg_log.h b/vg_log.h index cf2b737..bb7411b 100644 --- a/vg_log.h +++ b/vg_log.h @@ -33,7 +33,7 @@ #define PRINTF_v3f( V3 ) "%.4f %.4f %.4f\n", V3[0], V3[1], V3[2] #define PRINTF_v4f( V4 ) "%.4f %.4f %.4f %.4f\n", V4[0], V4[1], V4[2], V4[3] -#ifdef _WIN32 +#if VG_32 #define PRINTF_U64 "%llu" #else #define PRINTF_U64 "%lu" diff --git a/vg_msg.c b/vg_msg.c index 65d8ebd..16ce1d8 100644 --- a/vg_msg.c +++ b/vg_msg.c @@ -472,7 +472,7 @@ void vg_msg_print( vg_msg *msg, u32 len ) if( base == k_vg_msg_unsigned ){ printf( -#ifdef _WIN32 +#ifdef VG_32 "%llu" #else "%lu" @@ -481,7 +481,7 @@ void vg_msg_print( vg_msg *msg, u32 len ) } else if( base == k_vg_msg_signed ){ printf( -#ifdef _WIN32 +#ifdef VG_32 "%lld" #else "%ld" diff --git a/vg_opengl.h b/vg_opengl.h new file mode 100644 index 0000000..f112ce9 --- /dev/null +++ b/vg_opengl.h @@ -0,0 +1,6 @@ +#ifdef VG_ANDROID +#include +#include +#else +#include "dep/glad/glad.h" +#endif diff --git a/vg_platform/android/AndroidManifest.xml b/vg_platform/android/AndroidManifest.xml new file mode 100644 index 0000000..8fdb601 --- /dev/null +++ b/vg_platform/android/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/vg_platform/android/android_native_app_glue.c b/vg_platform/android/android_native_app_glue.c new file mode 100644 index 0000000..1e63c5e --- /dev/null +++ b/vg_platform/android/android_native_app_glue.c @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android_native_app_glue.h" + +#include + +#include +#include +#include +#include + +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__)) + +/* For debug builds, always enable the debug traces in this library */ +#ifndef NDEBUG +# define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __VA_ARGS__)) +#else +# define LOGV(...) ((void)0) +#endif + +static void free_saved_state(struct android_app* android_app) { + pthread_mutex_lock(&android_app->mutex); + if (android_app->savedState != NULL) { + free(android_app->savedState); + android_app->savedState = NULL; + android_app->savedStateSize = 0; + } + pthread_mutex_unlock(&android_app->mutex); +} + +int8_t android_app_read_cmd(struct android_app* android_app) { + int8_t cmd; + if (read(android_app->msgread, &cmd, sizeof(cmd)) != sizeof(cmd)) { + LOGE("No data on command pipe!"); + return -1; + } + if (cmd == APP_CMD_SAVE_STATE) free_saved_state(android_app); + return cmd; +} + +static void print_cur_config(struct android_app* android_app) { + char lang[2], country[2]; + AConfiguration_getLanguage(android_app->config, lang); + AConfiguration_getCountry(android_app->config, country); + + LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d " + "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d " + "modetype=%d modenight=%d", + AConfiguration_getMcc(android_app->config), + AConfiguration_getMnc(android_app->config), + lang[0], lang[1], country[0], country[1], + AConfiguration_getOrientation(android_app->config), + AConfiguration_getTouchscreen(android_app->config), + AConfiguration_getDensity(android_app->config), + AConfiguration_getKeyboard(android_app->config), + AConfiguration_getNavigation(android_app->config), + AConfiguration_getKeysHidden(android_app->config), + AConfiguration_getNavHidden(android_app->config), + AConfiguration_getSdkVersion(android_app->config), + AConfiguration_getScreenSize(android_app->config), + AConfiguration_getScreenLong(android_app->config), + AConfiguration_getUiModeType(android_app->config), + AConfiguration_getUiModeNight(android_app->config)); +} + +void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { + switch (cmd) { + case APP_CMD_INPUT_CHANGED: + LOGV("APP_CMD_INPUT_CHANGED"); + pthread_mutex_lock(&android_app->mutex); + if (android_app->inputQueue != NULL) { + AInputQueue_detachLooper(android_app->inputQueue); + } + android_app->inputQueue = android_app->pendingInputQueue; + if (android_app->inputQueue != NULL) { + LOGV("Attaching input queue to looper"); + AInputQueue_attachLooper(android_app->inputQueue, + android_app->looper, LOOPER_ID_INPUT, NULL, + &android_app->inputPollSource); + } + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_INIT_WINDOW: + LOGV("APP_CMD_INIT_WINDOW"); + pthread_mutex_lock(&android_app->mutex); + android_app->window = android_app->pendingWindow; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_TERM_WINDOW: + LOGV("APP_CMD_TERM_WINDOW"); + pthread_cond_broadcast(&android_app->cond); + break; + + case APP_CMD_RESUME: + case APP_CMD_START: + case APP_CMD_PAUSE: + case APP_CMD_STOP: + LOGV("activityState=%d", cmd); + pthread_mutex_lock(&android_app->mutex); + android_app->activityState = cmd; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_CONFIG_CHANGED: + LOGV("APP_CMD_CONFIG_CHANGED"); + AConfiguration_fromAssetManager(android_app->config, + android_app->activity->assetManager); + print_cur_config(android_app); + break; + + case APP_CMD_DESTROY: + LOGV("APP_CMD_DESTROY"); + android_app->destroyRequested = 1; + break; + } +} + +void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) { + switch (cmd) { + case APP_CMD_TERM_WINDOW: + LOGV("APP_CMD_TERM_WINDOW"); + pthread_mutex_lock(&android_app->mutex); + android_app->window = NULL; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_SAVE_STATE: + LOGV("APP_CMD_SAVE_STATE"); + pthread_mutex_lock(&android_app->mutex); + android_app->stateSaved = 1; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_RESUME: + free_saved_state(android_app); + break; + } +} + +void app_dummy() { +} + +static void android_app_destroy(struct android_app* android_app) { + LOGV("android_app_destroy!"); + free_saved_state(android_app); + pthread_mutex_lock(&android_app->mutex); + if (android_app->inputQueue != NULL) { + AInputQueue_detachLooper(android_app->inputQueue); + } + AConfiguration_delete(android_app->config); + android_app->destroyed = 1; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + // Can't touch android_app object after this. +} + +static void process_input(struct android_app* app, struct android_poll_source* source) { + AInputEvent* event = NULL; + while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) { + LOGV("New input event: type=%d", AInputEvent_getType(event)); + if (AInputQueue_preDispatchEvent(app->inputQueue, event)) { + continue; + } + int32_t handled = 0; + if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event); + AInputQueue_finishEvent(app->inputQueue, event, handled); + } +} + +static void process_cmd(struct android_app* app, struct android_poll_source* source) { + int8_t cmd = android_app_read_cmd(app); + android_app_pre_exec_cmd(app, cmd); + if (app->onAppCmd != NULL) app->onAppCmd(app, cmd); + android_app_post_exec_cmd(app, cmd); +} + +static void* android_app_entry(void* param) { + struct android_app* android_app = (struct android_app*)param; + + android_app->config = AConfiguration_new(); + AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager); + + print_cur_config(android_app); + + android_app->cmdPollSource.id = LOOPER_ID_MAIN; + android_app->cmdPollSource.app = android_app; + android_app->cmdPollSource.process = process_cmd; + android_app->inputPollSource.id = LOOPER_ID_INPUT; + android_app->inputPollSource.app = android_app; + android_app->inputPollSource.process = process_input; + + ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); + ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, + &android_app->cmdPollSource); + android_app->looper = looper; + + pthread_mutex_lock(&android_app->mutex); + android_app->running = 1; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + + android_main(android_app); + + android_app_destroy(android_app); + return NULL; +} + +// -------------------------------------------------------------------- +// Native activity interaction (called from main thread) +// -------------------------------------------------------------------- + +static struct android_app* android_app_create(ANativeActivity* activity, + void* savedState, size_t savedStateSize) { + struct android_app* android_app = calloc(1, sizeof(struct android_app)); + android_app->activity = activity; + + pthread_mutex_init(&android_app->mutex, NULL); + pthread_cond_init(&android_app->cond, NULL); + + if (savedState != NULL) { + android_app->savedState = malloc(savedStateSize); + android_app->savedStateSize = savedStateSize; + memcpy(android_app->savedState, savedState, savedStateSize); + } + + int msgpipe[2]; + if (pipe(msgpipe)) { + LOGE("could not create pipe: %s", strerror(errno)); + return NULL; + } + android_app->msgread = msgpipe[0]; + android_app->msgwrite = msgpipe[1]; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&android_app->thread, &attr, android_app_entry, android_app); + + // Wait for thread to start. + pthread_mutex_lock(&android_app->mutex); + while (!android_app->running) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); + + return android_app; +} + +static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) { + if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { + LOGE("Failure writing android_app cmd: %s", strerror(errno)); + } +} + +static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) { + pthread_mutex_lock(&android_app->mutex); + android_app->pendingInputQueue = inputQueue; + android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED); + while (android_app->inputQueue != android_app->pendingInputQueue) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { + pthread_mutex_lock(&android_app->mutex); + if (android_app->pendingWindow != NULL) { + android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW); + } + android_app->pendingWindow = window; + if (window != NULL) { + android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW); + } + while (android_app->window != android_app->pendingWindow) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) { + pthread_mutex_lock(&android_app->mutex); + android_app_write_cmd(android_app, cmd); + while (android_app->activityState != cmd) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_free(struct android_app* android_app) { + pthread_mutex_lock(&android_app->mutex); + android_app_write_cmd(android_app, APP_CMD_DESTROY); + while (!android_app->destroyed) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); + + close(android_app->msgread); + close(android_app->msgwrite); + pthread_cond_destroy(&android_app->cond); + pthread_mutex_destroy(&android_app->mutex); + free(android_app); +} + +static struct android_app* ToApp(ANativeActivity* activity) { + return (struct android_app*) activity->instance; +} + +static void onDestroy(ANativeActivity* activity) { + LOGV("Destroy: %p", activity); + android_app_free(ToApp(activity)); +} + +static void onStart(ANativeActivity* activity) { + LOGV("Start: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_START); +} + +static void onResume(ANativeActivity* activity) { + LOGV("Resume: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_RESUME); +} + +static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { + LOGV("SaveInstanceState: %p", activity); + + struct android_app* android_app = ToApp(activity); + void* savedState = NULL; + pthread_mutex_lock(&android_app->mutex); + android_app->stateSaved = 0; + android_app_write_cmd(android_app, APP_CMD_SAVE_STATE); + while (!android_app->stateSaved) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + + if (android_app->savedState != NULL) { + savedState = android_app->savedState; + *outLen = android_app->savedStateSize; + android_app->savedState = NULL; + android_app->savedStateSize = 0; + } + + pthread_mutex_unlock(&android_app->mutex); + + return savedState; +} + +static void onPause(ANativeActivity* activity) { + LOGV("Pause: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE); +} + +static void onStop(ANativeActivity* activity) { + LOGV("Stop: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_STOP); +} + +static void onConfigurationChanged(ANativeActivity* activity) { + LOGV("ConfigurationChanged: %p", activity); + android_app_write_cmd(ToApp(activity), APP_CMD_CONFIG_CHANGED); +} + +static void onContentRectChanged(ANativeActivity* activity, const ARect* r) { + LOGV("ContentRectChanged: l=%d,t=%d,r=%d,b=%d", r->left, r->top, r->right, r->bottom); + struct android_app* android_app = ToApp(activity); + pthread_mutex_lock(&android_app->mutex); + android_app->contentRect = *r; + pthread_mutex_unlock(&android_app->mutex); + android_app_write_cmd(ToApp(activity), APP_CMD_CONTENT_RECT_CHANGED); +} + +static void onLowMemory(ANativeActivity* activity) { + LOGV("LowMemory: %p", activity); + android_app_write_cmd(ToApp(activity), APP_CMD_LOW_MEMORY); +} + +static void onWindowFocusChanged(ANativeActivity* activity, int focused) { + LOGV("WindowFocusChanged: %p -- %d", activity, focused); + android_app_write_cmd(ToApp(activity), focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); +} + +static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowCreated: %p -- %p", activity, window); + android_app_set_window(ToApp(activity), window); +} + +static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowDestroyed: %p -- %p", activity, window); + android_app_set_window(ToApp(activity), NULL); +} + +static void onNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowRedrawNeeded: %p -- %p", activity, window); + android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_REDRAW_NEEDED); +} + +static void onNativeWindowResized(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowResized: %p -- %p", activity, window); + android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_RESIZED); +} + +static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { + LOGV("InputQueueCreated: %p -- %p", activity, queue); + android_app_set_input(ToApp(activity), queue); +} + +static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { + LOGV("InputQueueDestroyed: %p -- %p", activity, queue); + android_app_set_input(ToApp(activity), NULL); +} + +JNIEXPORT +void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize) { + LOGV("Creating: %p", activity); + + activity->callbacks->onConfigurationChanged = onConfigurationChanged; + activity->callbacks->onContentRectChanged = onContentRectChanged; + activity->callbacks->onDestroy = onDestroy; + activity->callbacks->onInputQueueCreated = onInputQueueCreated; + activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; + activity->callbacks->onLowMemory = onLowMemory; + activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; + activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; + activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; + activity->callbacks->onNativeWindowResized = onNativeWindowResized; + activity->callbacks->onPause = onPause; + activity->callbacks->onResume = onResume; + activity->callbacks->onSaveInstanceState = onSaveInstanceState; + activity->callbacks->onStart = onStart; + activity->callbacks->onStop = onStop; + activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; + + activity->instance = android_app_create(activity, savedState, savedStateSize); +} diff --git a/vg_platform/android/android_native_app_glue.h b/vg_platform/android/android_native_app_glue.h new file mode 100644 index 0000000..35a786e --- /dev/null +++ b/vg_platform/android/android_native_app_glue.h @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The native activity interface provided by + * is based on a set of application-provided callbacks that will be called + * by the Activity's main thread when certain events occur. + * + * This means that each one of this callbacks _should_ _not_ block, or they + * risk having the system force-close the application. This programming + * model is direct, lightweight, but constraining. + * + * The 'android_native_app_glue' static library is used to provide a different + * execution model where the application can implement its own main event + * loop in a different thread instead. Here's how it works: + * + * 1/ The application must provide a function named "android_main()" that + * will be called when the activity is created, in a new thread that is + * distinct from the activity's main thread. + * + * 2/ android_main() receives a pointer to a valid "android_app" structure + * that contains references to other important objects, e.g. the + * ANativeActivity object instance the application is running in. + * + * 3/ the "android_app" object holds an ALooper instance that already + * listens to two important things: + * + * - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX + * declarations below. + * + * - input events coming from the AInputQueue attached to the activity. + * + * Each of these correspond to an ALooper identifier returned by + * ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT, + * respectively. + * + * Your application can use the same ALooper to listen to additional + * file-descriptors. They can either be callback based, or with return + * identifiers starting with LOOPER_ID_USER. + * + * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event, + * the returned data will point to an android_poll_source structure. You + * can call the process() function on it, and fill in android_app->onAppCmd + * and android_app->onInputEvent to be called for your own processing + * of the event. + * + * Alternatively, you can call the low-level functions to read and process + * the data directly... look at the process_cmd() and process_input() + * implementations in the glue to see how to do this. + * + * See the sample named "native-activity" that comes with the NDK with a + * full usage example. Also look at the JavaDoc of NativeActivity. + */ + +struct android_app; + +/** + * Data associated with an ALooper fd that will be returned as the "outData" + * when that source has data ready. + */ +struct android_poll_source { + // The identifier of this source. May be LOOPER_ID_MAIN or + // LOOPER_ID_INPUT. + int32_t id; + + // The android_app this ident is associated with. + struct android_app* app; + + // Function to call to perform the standard processing of data from + // this source. + void (*process)(struct android_app* app, struct android_poll_source* source); +}; + +/** + * This is the interface for the standard glue code of a threaded + * application. In this model, the application's code is running + * in its own thread separate from the main thread of the process. + * It is not required that this thread be associated with the Java + * VM, although it will need to be in order to make JNI calls any + * Java objects. + */ +struct android_app { + // The application can place a pointer to its own state object + // here if it likes. + void* userData; + + // Fill this in with the function to process main app commands (APP_CMD_*) + void (*onAppCmd)(struct android_app* app, int32_t cmd); + + // Fill this in with the function to process input events. At this point + // the event has already been pre-dispatched, and it will be finished upon + // return. Return 1 if you have handled the event, 0 for any default + // dispatching. + int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); + + // The ANativeActivity object instance that this app is running in. + ANativeActivity* activity; + + // The current configuration the app is running in. + AConfiguration* config; + + // This is the last instance's saved state, as provided at creation time. + // It is NULL if there was no state. You can use this as you need; the + // memory will remain around until you call android_app_exec_cmd() for + // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL. + // These variables should only be changed when processing a APP_CMD_SAVE_STATE, + // at which point they will be initialized to NULL and you can malloc your + // state and place the information here. In that case the memory will be + // freed for you later. + void* savedState; + size_t savedStateSize; + + // The ALooper associated with the app's thread. + ALooper* looper; + + // When non-NULL, this is the input queue from which the app will + // receive user input events. + AInputQueue* inputQueue; + + // When non-NULL, this is the window surface that the app can draw in. + ANativeWindow* window; + + // Current content rectangle of the window; this is the area where the + // window's content should be placed to be seen by the user. + ARect contentRect; + + // Current state of the app's activity. May be either APP_CMD_START, + // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below. + int activityState; + + // This is non-zero when the application's NativeActivity is being + // destroyed and waiting for the app thread to complete. + int destroyRequested; + + // ------------------------------------------------- + // Below are "private" implementation of the glue code. + + pthread_mutex_t mutex; + pthread_cond_t cond; + + int msgread; + int msgwrite; + + pthread_t thread; + + struct android_poll_source cmdPollSource; + struct android_poll_source inputPollSource; + + int running; + int stateSaved; + int destroyed; + int redrawNeeded; + AInputQueue* pendingInputQueue; + ANativeWindow* pendingWindow; + ARect pendingContentRect; +}; + +enum { + /** + * Looper data ID of commands coming from the app's main thread, which + * is returned as an identifier from ALooper_pollOnce(). The data for this + * identifier is a pointer to an android_poll_source structure. + * These can be retrieved and processed with android_app_read_cmd() + * and android_app_exec_cmd(). + */ + LOOPER_ID_MAIN = 1, + + /** + * Looper data ID of events coming from the AInputQueue of the + * application's window, which is returned as an identifier from + * ALooper_pollOnce(). The data for this identifier is a pointer to an + * android_poll_source structure. These can be read via the inputQueue + * object of android_app. + */ + LOOPER_ID_INPUT = 2, + + /** + * Start of user-defined ALooper identifiers. + */ + LOOPER_ID_USER = 3, +}; + +enum { + /** + * Command from main thread: the AInputQueue has changed. Upon processing + * this command, android_app->inputQueue will be updated to the new queue + * (or NULL). + */ + APP_CMD_INPUT_CHANGED, + + /** + * Command from main thread: a new ANativeWindow is ready for use. Upon + * receiving this command, android_app->window will contain the new window + * surface. + */ + APP_CMD_INIT_WINDOW, + + /** + * Command from main thread: the existing ANativeWindow needs to be + * terminated. Upon receiving this command, android_app->window still + * contains the existing window; after calling android_app_exec_cmd + * it will be set to NULL. + */ + APP_CMD_TERM_WINDOW, + + /** + * Command from main thread: the current ANativeWindow has been resized. + * Please redraw with its new size. + */ + APP_CMD_WINDOW_RESIZED, + + /** + * Command from main thread: the system needs that the current ANativeWindow + * be redrawn. You should redraw the window before handing this to + * android_app_exec_cmd() in order to avoid transient drawing glitches. + */ + APP_CMD_WINDOW_REDRAW_NEEDED, + + /** + * Command from main thread: the content area of the window has changed, + * such as from the soft input window being shown or hidden. You can + * find the new content rect in android_app::contentRect. + */ + APP_CMD_CONTENT_RECT_CHANGED, + + /** + * Command from main thread: the app's activity window has gained + * input focus. + */ + APP_CMD_GAINED_FOCUS, + + /** + * Command from main thread: the app's activity window has lost + * input focus. + */ + APP_CMD_LOST_FOCUS, + + /** + * Command from main thread: the current device configuration has changed. + */ + APP_CMD_CONFIG_CHANGED, + + /** + * Command from main thread: the system is running low on memory. + * Try to reduce your memory use. + */ + APP_CMD_LOW_MEMORY, + + /** + * Command from main thread: the app's activity has been started. + */ + APP_CMD_START, + + /** + * Command from main thread: the app's activity has been resumed. + */ + APP_CMD_RESUME, + + /** + * Command from main thread: the app should generate a new saved state + * for itself, to restore from later if needed. If you have saved state, + * allocate it with malloc and place it in android_app.savedState with + * the size in android_app.savedStateSize. The will be freed for you + * later. + */ + APP_CMD_SAVE_STATE, + + /** + * Command from main thread: the app's activity has been paused. + */ + APP_CMD_PAUSE, + + /** + * Command from main thread: the app's activity has been stopped. + */ + APP_CMD_STOP, + + /** + * Command from main thread: the app's activity is being destroyed, + * and waiting for the app thread to clean up and exit before proceeding. + */ + APP_CMD_DESTROY, +}; + +/** + * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next + * app command message. + */ +int8_t android_app_read_cmd(struct android_app* android_app); + +/** + * Call with the command returned by android_app_read_cmd() to do the + * initial pre-processing of the given command. You can perform your own + * actions for the command after calling this function. + */ +void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd); + +/** + * Call with the command returned by android_app_read_cmd() to do the + * final post-processing of the given command. You must have done your own + * actions for the command before calling this function. + */ +void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); + +/** + * No-op function that used to be used to prevent the linker from stripping app + * glue code. No longer necessary, since __attribute__((visibility("default"))) + * does this for us. + */ +__attribute__(( + deprecated("Calls to app_dummy are no longer necessary. See " + "https://github.com/android-ndk/ndk/issues/381."))) void +app_dummy(); + +/** + * This is the function that application code must implement, representing + * the main entry to the app. + */ +extern void android_main(struct android_app* app); + +#ifdef __cplusplus +} +#endif diff --git a/vg_platform/android/debug.keystore b/vg_platform/android/debug.keystore new file mode 100644 index 0000000..6affde7 Binary files /dev/null and b/vg_platform/android/debug.keystore differ diff --git a/vg_platform/android/resources/mipmap/icon.png b/vg_platform/android/resources/mipmap/icon.png new file mode 100644 index 0000000..7441eaa Binary files /dev/null and b/vg_platform/android/resources/mipmap/icon.png differ diff --git a/vg_platform/android/resources/values/strings.xml b/vg_platform/android/resources/values/strings.xml new file mode 100644 index 0000000..a702659 --- /dev/null +++ b/vg_platform/android/resources/values/strings.xml @@ -0,0 +1,7 @@ + + + carrot + carrot + org.mtzero.carrot + + diff --git a/vg_profiler.c b/vg_profiler.c index d203c92..3684ae8 100644 --- a/vg_profiler.c +++ b/vg_profiler.c @@ -1,7 +1,7 @@ #include "vg_platform.h" #include "vg_profiler.h" #include "vg_engine.h" -#include "vg_imgui.h" +#include "vg_ui/imgui.h" int vg_profiler = 0; diff --git a/vg_profiler.h b/vg_profiler.h index 74190cb..5de304e 100644 --- a/vg_profiler.h +++ b/vg_profiler.h @@ -1,6 +1,6 @@ #pragma once #include "vg_platform.h" -#include "vg_imgui.h" +#include "vg_ui/imgui.h" #define VG_PROFILE_SAMPLE_COUNT 128 extern int vg_profiler; diff --git a/vg_shader.c b/vg_shader.c index c12495c..b17c64a 100644 --- a/vg_shader.c +++ b/vg_shader.c @@ -14,7 +14,12 @@ const char *vg_shader_gl_ver = "#version 330 core\n"; -struct vg_shaders vg_shaders; +struct vg_shaders +{ + vg_shader *shaders[48]; + u32 count; +} +static vg_shaders; static GLuint vg_shader_subshader( const char *src, GLint gliShaderType ) { @@ -141,7 +146,7 @@ int vg_shader_compile( struct vg_shader *shader ) return 1; } -static void vg_free_shader( struct vg_shader *shader ) +void vg_free_shader( struct vg_shader *shader ) { if( shader->compiled ) { diff --git a/vg_shader.h b/vg_shader.h index 5e2ad35..1ea106c 100644 --- a/vg_shader.h +++ b/vg_shader.h @@ -1,28 +1,22 @@ #pragma once typedef struct vg_shader vg_shader; - -struct vg_shaders +struct vg_shader { - struct vg_shader - { - GLuint id; - const char *name; + GLuint id; + const char *name; - struct vg_subshader - { - const char *orig_file, - *static_src; - } - vs, fs; - int compiled; + struct vg_subshader + { + const char *orig_file, + *static_src; } - * shaders[48]; - u32 count; -} -extern vg_shaders; + vs, fs; + int compiled; +}; void vg_shaders_compile(void); int vg_shaders_live_recompile(int argc, const char *argv[]); void vg_shader_register( struct vg_shader *shader ); int vg_shader_compile( struct vg_shader *shader ); +void vg_free_shader( struct vg_shader *shader ); diff --git a/vg_ui/imgui.c b/vg_ui/imgui.c new file mode 100644 index 0000000..7cf9df8 --- /dev/null +++ b/vg_ui/imgui.c @@ -0,0 +1,2001 @@ +#pragma once + +#include "vg_ui/imgui.h" +#include "vg_platform.h" +#include "vg_m.h" +#include "vg_font.h" +#include "vg_log.h" +#include + +ui_context *g_ui_ctx; + +//__attribute__((deprecated("message", "replacement"))) +void ui_bind_context( ui_context *context ) +{ + g_ui_ctx = context; +} + +//__attribute__((deprecated("message", "replacement"))) +ui_context *ui_current_context(void) +{ + return g_ui_ctx; +} + +void ui_init( ui_vert *verts_buf, u32 verts_max, + u16 *indices_buf, u32 indices_max ) +{ + g_ui_ctx->vertex_buffer = verts_buf; + g_ui_ctx->max_verts = verts_max; + g_ui_ctx->cur_vert = 0; + g_ui_ctx->vert_start = 0; + + g_ui_ctx->indice_buffer = indices_buf; + g_ui_ctx->max_indices = indices_max; + g_ui_ctx->cur_indice = 0; + g_ui_ctx->indice_start = 0; + + if( !verts_buf || !indices_buf ) + exit(0); +} + +ui_vert *ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ) +{ + /* this if far from ideal but stops us from crashing */ + if( (g_ui_ctx->cur_vert + 4 > g_ui_ctx->max_verts) || + (g_ui_ctx->cur_indice + 6 > g_ui_ctx->max_indices)) + { + return &g_ui_ctx->vertex_buffer[0]; + } + + ui_vert *vertices = &g_ui_ctx->vertex_buffer[ g_ui_ctx->cur_vert ]; + u16 *indices = &g_ui_ctx->indice_buffer[ g_ui_ctx->cur_indice ]; + + for( int i=0; i<4; i++ ) + vertices[i].colour = colour; + + vertices[0].co[0] = rect[0]; + vertices[0].co[1] = rect[1]; + vertices[0].uv[0] = uv[0]; + vertices[0].uv[1] = uv[1]; + vertices[1].co[0] = rect[0]+rect[2]; + vertices[1].co[1] = rect[1]; + vertices[1].uv[0] = uv[2]; + vertices[1].uv[1] = uv[1]; + vertices[2].co[0] = rect[0]+rect[2]; + vertices[2].co[1] = rect[1]+rect[3]; + vertices[2].uv[0] = uv[2]; + vertices[2].uv[1] = uv[3]; + vertices[3].co[0] = rect[0]; + vertices[3].co[1] = rect[1]+rect[3]; + vertices[3].uv[0] = uv[0]; + vertices[3].uv[1] = uv[3]; + + u16 start = g_ui_ctx->cur_vert; + u32 mesh[] = { 0,2,1, 0,3,2 }; + + for( u32 i=0; icur_indice += 6; + g_ui_ctx->cur_vert += 4; + + return vertices; +} + +ui_vert *ui_fill( ui_rect rect, u32 colour ) +{ + return ui_fill_rect( rect, colour, (ui_px[4]){ 4,4,4,4 } ); +} + +void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask ) +{ + /* this if far from ideal but stops us from crashing */ + if( (g_ui_ctx->cur_vert + 8 > g_ui_ctx->max_verts) || + (g_ui_ctx->cur_indice + 24 > g_ui_ctx->max_indices)) + return; + + ui_vert *vertices = &g_ui_ctx->vertex_buffer[ g_ui_ctx->cur_vert ]; + u16 *indices = &g_ui_ctx->indice_buffer[ g_ui_ctx->cur_indice ]; + + for( int i=0; i<8; i++ ) + { + vertices[i].uv[0] = 4; + vertices[i].uv[1] = 4; + vertices[i].colour = colour; + } + + vertices[0].co[0] = rect[0]; + vertices[0].co[1] = rect[1]; + vertices[1].co[0] = rect[0]+rect[2]; + vertices[1].co[1] = rect[1]; + vertices[2].co[0] = rect[0]+rect[2]; + vertices[2].co[1] = rect[1]+rect[3]; + vertices[3].co[0] = rect[0]; + vertices[3].co[1] = rect[1]+rect[3]; + vertices[4].co[0] = vertices[0].co[0]-thickness; + vertices[4].co[1] = vertices[0].co[1]-thickness; + vertices[5].co[0] = vertices[1].co[0]+thickness; + vertices[5].co[1] = vertices[1].co[1]-thickness; + vertices[6].co[0] = vertices[2].co[0]+thickness; + vertices[6].co[1] = vertices[2].co[1]+thickness; + vertices[7].co[0] = vertices[3].co[0]-thickness; + vertices[7].co[1] = vertices[3].co[1]+thickness; + + u16 start = g_ui_ctx->cur_vert; + u32 mesh[] = { 0,5,4, 0,1,5, 1,6,5, 1,2,6, 2,7,6, 2,3,7, 3,4,7, 3,0,4 }; + + if( !mask ) + mask = UI_TOP|UI_LEFT|UI_BOTTOM|UI_RIGHT; + + u32 c = 0; + for( u32 i=0; icur_indice += c; + g_ui_ctx->cur_vert += 8; +} + +void rect_copy( ui_rect a, ui_rect b ) +{ + for( int i=0; i<4; i++ ) + b[i] = a[i]; +} + +void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap, + ui_rect l, ui_rect r ) +{ + enum ui_axis dir = other ^ 0x1; + + if( width < 0 ) width = rect[ 2+dir ] + width; + + ui_rect temp; + rect_copy( rect, temp ); + + l[ dir ] = temp[ dir ]; + r[ dir ] = temp[ dir ] + width + (gap/2); + l[ other ] = temp[ other ]; + r[ other ] = temp[ other ]; + l[ 2+dir ] = width - (gap/2); + r[ 2+dir ] = temp[ 2+dir ] - width - (gap/2); + l[ 2+other ] = temp[ 2+other ]; + r[ 2+other ] = temp[ 2+other ]; +} + +void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, + ui_px gap, ui_rect l, ui_rect r ) +{ + ui_px width = (float)rect[ 2+(dir^0x1) ] * ratio; + ui_split( rect, dir, width, gap, l, r ); +} + +void ui_rect_pad( ui_rect rect, ui_px pad[2] ) +{ + ui_px tmp[2]; + if( !pad ) + { + tmp[0] = g_ui_ctx->padding; + tmp[1] = g_ui_ctx->padding; + pad = tmp; + } + + rect[0] += pad[0]; + rect[1] += pad[1]; + rect[2] -= pad[0]*2; + rect[3] -= pad[1]*2; +} + +static ui_px ui_min( ui_px a, ui_px b ){ return ab?a:b; } +static ui_px ui_clamp( ui_px a, ui_px min, ui_px max ) +{ + return ui_min( max, ui_max( a, min ) ); +} + +int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ) +{ + ui_px parent_max[2], child_max[2]; + parent_max[0] = parent[0]+parent[2]; + parent_max[1] = parent[1]+parent[3]; + child_max[0] = child[0]+child[2]; + child_max[1] = child[1]+child[3]; + + clipped[0] = ui_clamp( child[0], parent[0], parent_max[0] ); + clipped[1] = ui_clamp( child[1], parent[1], parent_max[1] ); + clipped[2] = ui_clamp( child_max[0], parent[0], parent_max[0] ); + clipped[3] = ui_clamp( child_max[1], parent[1], parent_max[1] ); + + if( clipped[0] == clipped[2] || + clipped[1] == clipped[3] ) + return 0; + + clipped[2] -= clipped[0]; + clipped[3] -= clipped[1]; + + return 1; +} + +int ui_inside_rect( ui_rect rect, ui_px co[2] ) +{ + if( co[0] >= rect[0] && + co[1] >= rect[1] && + co[0] < rect[0]+rect[2] && + co[1] < rect[1]+rect[3] ){ + return 1; + } + else + return 0; +} + +int ui_click_down( u32 mask ) +{ + if( g_ui_ctx->ignore_input_frames ) return 0; + if( (g_ui_ctx->mouse_state[0] & mask) && + !(g_ui_ctx->mouse_state[1] & mask) ) + return 1; + else + return 0; +} + +int ui_clicking( u32 mask ) +{ + if( g_ui_ctx->ignore_input_frames ) return 0; + return g_ui_ctx->mouse_state[0] & mask; +} + +int ui_click_up( u32 mask ) +{ + if( g_ui_ctx->ignore_input_frames ) return 0; + if( (g_ui_ctx->mouse_state[1] & mask) && + !(g_ui_ctx->mouse_state[0] & mask) ) + return 1; + else + return 0; +} + +void ui_prerender( ui_px dims[2], ui_px mouse[2], i32 mouse_state ) +{ + g_ui_ctx->mouse_state[1] = g_ui_ctx->mouse_state[0]; + g_ui_ctx->mouse_state[0] = mouse_state; + g_ui_ctx->mouse_delta[0] = mouse[0]-g_ui_ctx->mouse[0]; + g_ui_ctx->mouse_delta[1] = mouse[1]-g_ui_ctx->mouse[1]; + g_ui_ctx->area[0] = dims[0]; + g_ui_ctx->area[1] = dims[1]; + + if( !g_ui_ctx->mouse_pos_overriden ) + { + g_ui_ctx->mouse[0] = mouse[0]; + g_ui_ctx->mouse[1] = mouse[1]; + } + + g_ui_ctx->cur_vert = 0; + g_ui_ctx->cur_indice = 0; + g_ui_ctx->vert_start = 0; + g_ui_ctx->indice_start = 0; + g_ui_ctx->focused_control_hit = 0; + g_ui_ctx->cursor = k_ui_cursor_default; + g_ui_ctx->wants_mouse = 0; + g_ui_ctx->mouse_pos_overriden = 0; + + if( g_ui_ctx->ignore_input_frames ) + { + g_ui_ctx->ignore_input_frames --; + return; + } + + if( ui_click_down(UI_MOUSE_LEFT)||ui_click_down(UI_MOUSE_MIDDLE)|| + ui_click_down(UI_MOUSE_RIGHT) ) + { + g_ui_ctx->mouse_click[0] = g_ui_ctx->mouse[0]; + g_ui_ctx->mouse_click[1] = g_ui_ctx->mouse[1]; + } +} + +void ui_ignore_input_frames( u32 frames ) +{ + g_ui_ctx->ignore_input_frames = frames; +} + +void ui_capture_mouse( bool on ) +{ + g_ui_ctx->wants_mouse = on; +} + +void ui_impl_render_batch( ui_batch *batch ); + +void ui_flush( enum ui_shader shader ) +{ + ui_batch batch; + batch.shader = shader; + batch.vert_offset = g_ui_ctx->vert_start * sizeof(ui_vert); + batch.indice_offset = g_ui_ctx->indice_start * sizeof(u16); + batch.vert_buf = g_ui_ctx->vertex_buffer + g_ui_ctx->vert_start; + batch.vert_count = g_ui_ctx->cur_vert - g_ui_ctx->vert_start; + batch.indice_buf = g_ui_ctx->indice_buffer + g_ui_ctx->indice_start; + batch.indice_count = g_ui_ctx->cur_indice - g_ui_ctx->indice_start; + + ui_impl_render_batch( &batch ); + + g_ui_ctx->indice_start = g_ui_ctx->cur_indice; + g_ui_ctx->vert_start = g_ui_ctx->cur_vert; +} + +void ui_rect_center( ui_rect parent, ui_rect rect ) +{ + rect[0] = parent[0] + (parent[2]-rect[2])/2; + rect[1] = parent[1] + (parent[3]-rect[3])/2; +} + +void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d ) +{ + i32 rp = (i32)rect[2] * (i32)size[1], + rc = (i32)size[0] * (i32)rect[3]; + + enum ui_axis dir, other; + if( rc > rp ) dir = k_ui_axis_h; + else dir = k_ui_axis_v; + other = dir ^ 0x1; + + d[2+dir] = rect[2+dir]; + d[2+other] = (rect[2+dir] * size[other]) / size[dir]; + + ui_rect_center( rect, d ); +} + +ui_px ui_text_line_width( const char *str ) +{ + int length = 0; + const char *_c = str; + u8 c; + + while( (c = *(_c ++)) ){ + if( c >= 32 ) length ++; + else if( c == '\n' ) break; + } + + return length * g_ui_ctx->font->sx; +} + +ui_px ui_text_string_height( const char *str ) +{ + int height = 1; + const char *_c = str; + u8 c; + + while( (c = *(_c ++)) ) + { + if( c == '\n' ) height ++; + } + + return height * g_ui_ctx->font->sy; +} + +ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, + enum ui_align align ) +{ + enum ui_align lwr = k_ui_align_lwr & align; + if( lwr == k_ui_align_left ){ + return rect[0]; + } + else{ + ui_px width = ui_text_line_width( str ) * scale; + + if( lwr == k_ui_align_right ) + return rect[0] + rect[2]-width; + else + return rect[0] + (rect[2]-width)/2; + } +} + +u32 ui_colour( enum ui_scheme_colour id ) +{ + return g_ui_ctx->scheme[ id ]; +} + +/* get an appropriately contrasting colour given the base */ +u32 ui_colourcont( enum ui_scheme_colour id ) +{ + if ( id < k_ui_bg+6 ) return ui_colour( k_ui_fg ); + else if( id < k_ui_fg ) return ui_colour( k_ui_bg+1 ); + else if( id < k_ui_hue ) return ui_colour( k_ui_bg+3 ); + else if( id < k_ui_red+k_ui_brighter ) return ui_colour( k_ui_fg ); + else return ui_colour( k_ui_fg+1 ); +} + +void ui_hex_to_norm( u32 hex, v4f norm ) +{ + norm[0] = ((hex ) & 0xff); + norm[1] = ((hex>>8 ) & 0xff); + norm[2] = ((hex>>16) & 0xff); + norm[3] = ((hex>>24) & 0xff); + v4_muls( norm, 1.0f/255.0f, norm ); +} + +u32 v4f_u32_colour( v4f colour ) +{ + u32 r = colour[0] * 255.0f, + g = colour[1] * 255.0f, + b = colour[2] * 255.0f, + a = colour[3] * 255.0f; + + return r | (g<<8) | (b<<16) | (a<<24); +} + +static void ui_text_glyph( const struct vg_font_face *ff, + u8 glyph, ui_rect out_texcoords ) +{ + const vg_font_char *ch = &ff->map[ glyph ]; + + out_texcoords[0] = ch->x; + out_texcoords[1] = ch->y; + out_texcoords[2] = ch->x + ff->cw; + out_texcoords[3] = ch->y + ff->ch; +} + +u32 ui_opacity( u32 colour, f32 opacity ) +{ + u32 alpha = opacity * 255.0f; + return (colour & 0x00ffffff) | (alpha << 24); +} + +u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, + enum ui_align align, u32 colour ) +{ + ui_px glow_text = 0; + + ui_rect text_cursor; + if( colour == 0 ) colour = ui_colour( k_ui_fg ); + + colour &= 0x00ffffff; + + const char *_c = str; + u8 c; + + text_cursor[0] = ui_text_aligned_x( str, rect, scale, align ); + text_cursor[1] = rect[1]; + text_cursor[2] = g_ui_ctx->font->cw*scale; + text_cursor[3] = g_ui_ctx->font->ch*scale; + + u32 printed_chars = 0; + + if( align & (k_ui_align_middle|k_ui_align_bottom) ) + { + ui_px height = ui_text_string_height( str ) * scale; + + if( align & k_ui_align_bottom ) + text_cursor[1] += rect[3]-height; + else + text_cursor[1] += (rect[3]-height)/2; + } + + while( (c = *(_c ++)) ) + { + if( printed_chars >= len ) + { + printed_chars = 0; + text_cursor[1] += g_ui_ctx->font->sy*scale; + text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); + text_cursor[0] -= g_ui_ctx->font->sx*scale; + + ui_rect glyph; + ui_text_glyph( g_ui_ctx->font, '\xb1' /*FIXME*/, glyph ); + ui_fill_rect( text_cursor, 0x00ffffff, glyph ); + text_cursor[0] += g_ui_ctx->font->sx*scale; + } + + if( c == '\n' ) + { + text_cursor[1] += g_ui_ctx->font->sy*scale; + text_cursor[0] = ui_text_aligned_x( _c, rect, scale, align ); + printed_chars = 0; + continue; + } + else if( c >= 33 ) + { + ui_rect glyph; + ui_text_glyph( g_ui_ctx->font, c, glyph ); + + ui_rect cursor_clipped; + if( ui_clip( rect, text_cursor, cursor_clipped ) ) + { + if( glow_text ) + { + cursor_clipped[1] += glow_text; + ui_fill_rect( cursor_clipped, 0x00ffffff, glyph ); + cursor_clipped[1] -= glow_text; + } + + ui_fill_rect( cursor_clipped, colour, glyph ); + } + } + else if( c == '\x1B' ) + { + /* vt codes */ + _c ++; + u16 colour_id = 0; + for( int i=0; i<3; i ++ ) + { + if( _c[i] ) + { + if( _c[i] == 'm' ) + { + _c = _c + i + 1; + + switch( colour_id ){ + case '0': colour = ui_colour( k_ui_fg ); break; + case '3'|'0'<<8: colour = ui_colour( k_ui_bg ); break; + case '3'|'1'<<8: colour = ui_colour( k_ui_red ); break; + case '3'|'2'<<8: colour = ui_colour( k_ui_green ); break; + case '3'|'3'<<8: colour = ui_colour( k_ui_yellow ); break; + case '3'|'4'<<8: colour = ui_colour( k_ui_blue ); break; + case '3'|'5'<<8: colour = ui_colour( k_ui_purple ); break; + case '3'|'6'<<8: colour = ui_colour( k_ui_aqua ); break; + case '3'|'7'<<8: colour = 0xffffffff; break; + } + + colour &= 0x00ffffff; + break; + } + + colour_id |= _c[i] << (i*8); + } + else + { + _c = _c +i; + break; + } + } + + continue; + } + else if( c == '\x06' ) + { + glow_text = *_c; + _c ++; + continue; + } + else if( c == '\t' ) + { + text_cursor[0] += g_ui_ctx->font->sx*scale*4; + printed_chars += 4; + continue; + } + + text_cursor[0] += g_ui_ctx->font->sx*scale; + printed_chars ++; + } + + return printed_chars; +} + +u32 ui_text( ui_rect rect, const char *str, ui_px scale, + enum ui_align align, u32 colour ) +{ + return ui_ntext( rect, str, 1024, scale, align, colour ); +} + +void ui_font_face( vg_font_face *ff ) +{ + g_ui_ctx->font = ff; +} + +const vg_font_face *ui_current_font(void) +{ + return g_ui_ctx->font; +} + +/* + * Standard layout stuff + * ----------------------------------------------------------------------------- + */ + +void ui_panel( ui_rect in_rect, ui_rect out_panel ) +{ + ui_fill( in_rect, ui_colour( k_ui_bg+1 ) ); + ui_outline( in_rect, 1, ui_colour( k_ui_bg+7 ), 0 ); + rect_copy( in_rect, out_panel ); + ui_rect_pad( out_panel, NULL ); +} + +void ui_label( ui_rect rect, const char *text, ui_px size, + ui_px gap, ui_rect r ) +{ + ui_rect l; + ui_px width = (ui_text_line_width(text)+g_ui_ctx->font->sx) * size; + ui_split( rect, k_ui_axis_v, width, gap, l, r ); + ui_text( l, text, 1, k_ui_align_middle_left, 0 ); +} + +ui_px ui_standard_widget_height( ui_px count ) +{ + return (count * g_ui_ctx->font->sy + 18) * g_ui_ctx->scale; +} + +void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count ) +{ + ui_px height = ui_standard_widget_height( count ); + ui_split( inout_panel, k_ui_axis_h, height, g_ui_ctx->padding, + out_rect, inout_panel ); +} + +void ui_info( ui_rect inout_panel, const char *text ) +{ + ui_rect box; + ui_standard_widget( inout_panel, box, 1 ); + ui_text( box, text, 1, k_ui_align_middle_left, 0 ); +} + +void ui_image( ui_rect rect, GLuint image ) +{ + ui_flush( k_ui_shader_colour ); + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, image ); + ui_fill_rect( rect, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); + ui_flush( k_ui_shader_image ); +} + +void ui_defocus_all(void) +{ + if( g_ui_ctx->focused_control_type == k_ui_control_textbox ) + { +#if VG_ANDROID +#else + SDL_StopTextInput(); +#endif + if( g_ui_ctx->textbox.callbacks.escape ) + g_ui_ctx->textbox.callbacks.escape(); + } + + g_ui_ctx->focused_control_id = NULL; + g_ui_ctx->focused_control_hit = 0; + g_ui_ctx->focused_control_type = k_ui_control_none; +} + +enum ui_button_state ui_button_base( ui_rect rect ) +{ + int clickup= ui_click_up(UI_MOUSE_LEFT), + click = ui_clicking(UI_MOUSE_LEFT) | clickup, + target = ui_inside_rect( rect, g_ui_ctx->mouse_click ) && click, + hover = ui_inside_rect( rect, g_ui_ctx->mouse ); + + if( g_ui_ctx->focused_control_type != k_ui_control_none ) + { + clickup = 0; + click = 0; + target = 0; + hover = 0; + } + + if( hover ) + g_ui_ctx->cursor = k_ui_cursor_hand; + + if( click ) + { + if( target ) + { + if( hover ) + { + if( clickup ) + { + g_ui_ctx->ignore_input_frames = 2; + ui_defocus_all(); + return k_ui_button_click; + } + else return k_ui_button_holding_inside; + } + else return k_ui_button_holding_outside; + } + else return k_ui_button_none; + } + else + { + if( hover ) return k_ui_button_hover; + else return k_ui_button_none; + } +} + +/* TODO: split this out into a formatless button and one that auto fills */ +enum ui_button_state ui_colourbutton( ui_rect rect, + enum ui_scheme_colour colour, + enum ui_scheme_colour hover_colour, + enum ui_scheme_colour hi_colour ) +{ + enum ui_button_state state = ui_button_base( rect ); + + u32 col_base = g_ui_ctx->scheme[ colour ], + col_highlight = g_ui_ctx->scheme[ hi_colour? hi_colour: k_ui_fg ], + col_hover = g_ui_ctx->scheme[ hover_colour? hover_colour: + colour + k_ui_brighter ]; + + if( state == k_ui_button_click ) + { + ui_fill( rect, col_highlight ); + rect_copy( rect, g_ui_ctx->click_fader ); + rect_copy( rect, g_ui_ctx->click_fader_end ); + g_ui_ctx->click_fader_end[3] = 0; + ui_rect_center( rect, g_ui_ctx->click_fader_end ); + g_ui_ctx->click_fade_opacity = 1.0f; + } + else if( state == k_ui_button_holding_inside ) + { + ui_fill( rect, col_highlight ); + } + else if( state == k_ui_button_holding_outside ) + { + ui_fill( rect, col_base ); + ui_outline( rect, 1, col_highlight, 0 ); + } + else if( state == k_ui_button_hover ) + { + ui_fill( rect, col_hover ); + } + else ui_fill( rect, col_base ); + + return state; +} + +enum ui_button_state ui_colourbutton_text( + ui_rect rect, const char *string, ui_px scale, + enum ui_scheme_colour colour ) +{ + enum ui_button_state state = ui_colourbutton( rect, colour, 0, 0 ); + + u32 text_colour = ui_colourcont(colour); + if( state == k_ui_button_holding_inside ) + text_colour = colour; + + ui_text( rect, string, scale, k_ui_align_middle_center, text_colour ); + return state; +} + +enum ui_button_state ui_button_text( ui_rect rect, + const char *string, ui_px scale ) +{ + return ui_colourbutton_text( rect, string, scale, k_ui_bg+4 ); +} + +enum ui_button_state ui_button( ui_rect inout_panel, const char *string ) +{ + ui_rect rect; + ui_standard_widget( inout_panel, rect, 1 ); + return ui_colourbutton_text( rect, string, 1, k_ui_bg+4 ); +} + +static void ui_enum_post(void); +void ui_postrender( f32 delta_time ) +{ + if( g_ui_ctx->click_fade_opacity > 0.0f ) + { + float scale = g_ui_ctx->click_fade_opacity; + scale = vg_maxf( 1.0f/255.0f, scale*scale ); + + g_ui_ctx->click_fade_opacity -= delta_time * 3.8f; + u32 colour = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; + + v4f begin, end, dest; + for( int i=0; i<4; i++ ){ + begin[i] = g_ui_ctx->click_fader[i]; + end[i] = g_ui_ctx->click_fader_end[i]+1; + } + + v4_lerp( end, begin, scale, dest ); + + ui_rect rect; + for( int i=0; i<4; i++ ){ + rect[i] = dest[i]; + } + + ui_fill( rect, colour ); + } + + if( g_ui_ctx->focused_control_type == k_ui_control_enum ){ + ui_enum_post(); + } + else if( g_ui_ctx->focused_control_type == k_ui_control_modal ){ + ui_rect screen = { 0,0, g_ui_ctx->area[0], g_ui_ctx->area[1] }; + ui_fill( screen, 0xa0000000 ); + ui_rect box = {0,0,400,200}; + + u32 colour = ui_colour(k_ui_fg), + type = g_ui_ctx->modal.options & UI_MODAL_TYPE_BITS; + if ( type == 1 ) colour = ui_colour(k_ui_green); + else if( type == 2 ) colour = ui_colour(k_ui_red); + else if( type == 3 ) colour = ui_colour(k_ui_yellow); + + ui_rect_center( screen, box ); + ui_fill( box, ui_colour(k_ui_bg) ); + ui_outline( box, -1, colour, 0 ); + + ui_rect message; + rect_copy( box, message ); + message[3] = 100; + ui_rect_center( box, message ); + + ui_rect row0, row1, btn; + ui_split_ratio( message, k_ui_axis_h, 0.5f, 0, row0, row1 ); + row0[0] += g_ui_ctx->font->sx; + ui_ntext( row0, g_ui_ctx->modal.message, (box[2]/g_ui_ctx->font->sx)-2, 1, + k_ui_align_left, colour ); + + rect_copy( row1, btn ); + btn[2] = 86; + btn[3] = 28; + ui_rect_center( row1, btn ); + + g_ui_ctx->focused_control_type = k_ui_control_none; /* HACK */ + if( ui_button_text( btn, "OK", 1 ) != 1 ) + g_ui_ctx->focused_control_hit = 1; + g_ui_ctx->focused_control_type = k_ui_control_modal; /* HACK */ + g_ui_ctx->wants_mouse = 1; + } + + ui_flush( k_ui_shader_colour ); + + if( !g_ui_ctx->focused_control_hit ) + { + ui_defocus_all(); + } +} + +/* + * checkbox + * ----------------------------------------------------------------------------- + */ + +enum ui_button_state ui_checkbox_base( ui_rect box, i32 *data ) +{ + enum ui_button_state state = ui_button_base( box ); + if( state == k_ui_button_click ) + *data = (*data) ^ 0x1; + return state; +} + +int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ) +{ + ui_rect rect, label, box; + ui_standard_widget( inout_panel, rect, 1 ); + + ui_split( rect, k_ui_axis_v, -rect[3], 0, label, box ); + ui_text( label, str_label, g_ui_ctx->scale, k_ui_align_middle_left, 0 ); + + enum ui_button_state state = ui_checkbox_base( box, data ); + + if( state == k_ui_button_holding_inside ) + { + ui_fill( box, ui_colour(k_ui_bg+2) ); + ui_outline( box, 1, ui_colour(k_ui_fg), 0 ); + } + else if( state == k_ui_button_holding_outside ) + { + ui_fill( box, ui_colour(k_ui_bg) ); + ui_outline( box, 1, ui_colour(k_ui_fg), 0 ); + } + else if( state == k_ui_button_hover ) + { + ui_fill( box, ui_colour(k_ui_bg) ); + ui_outline( box, 1, ui_colour(k_ui_fg), 0 ); + } + else + { + ui_fill( box, ui_colour(k_ui_bg) ); + ui_outline( box, 1, ui_colour(k_ui_bg+4), 0 ); + } + + bool changed = (state == k_ui_button_click); + + if( *data ) + { + ui_rect_pad( box, (ui_px[2]){4,4} ); + ui_fill( box, ui_colour( k_ui_orange ) ); + } + + return changed; +} + +/* + * Dropdown / Enum + * ----------------------------------------------------------------------------- + */ + +/* + * unfortunately no return value since we only find out that event in the + * postrender step. + */ +void ui_enum( ui_rect inout_panel, const char *str_label, + struct ui_enum_opt *options, u32 len, i32 *value ) +{ + ui_rect rect, label, box; + ui_standard_widget( inout_panel, rect, 1 ); + ui_label( rect, str_label, g_ui_ctx->scale, 0, box ); + + const char *display = "OUT OF RANGE"; + int valid = 0; + for( u32 i=0; iscale ) == 1 ){ + g_ui_ctx->focused_control_type = k_ui_control_enum; + g_ui_ctx->ptr_enum = value; + g_ui_ctx->_enum.option_count = len; + g_ui_ctx->_enum.options = options; + rect_copy( box, g_ui_ctx->_enum.rect ); + } + + if( !valid ) + ui_outline( box, 1, ui_colour(k_ui_red), 0 ); +} + +static void ui_enum_post(void){ + ui_rect drawer; + rect_copy( g_ui_ctx->_enum.rect, drawer ); + drawer[3] *= g_ui_ctx->_enum.option_count; + + int close = 0; + int clickany= ui_click_up(UI_MOUSE_LEFT|UI_MOUSE_RIGHT|UI_MOUSE_MIDDLE), + hover = ui_inside_rect( drawer, g_ui_ctx->mouse ); + + if( clickany && !hover ){ + return; + } + + /* HACK */ + g_ui_ctx->focused_control_type = k_ui_control_none; + i32 *value = g_ui_ctx->ptr_enum; + + for( u32 i=0; i_enum.option_count; i++ ){ + ui_rect button; + ui_split( drawer, k_ui_axis_h, g_ui_ctx->_enum.rect[3], 0, button,drawer ); + + enum ui_scheme_colour colour = k_ui_bg+3; + if( g_ui_ctx->_enum.options[i].value == *value ) + colour = k_ui_orange; + + if( ui_colourbutton_text( button, g_ui_ctx->_enum.options[i].alias, + g_ui_ctx->scale, colour ) == 1 ){ + *value = g_ui_ctx->_enum.options[i].value; + close = 1; + } + } + + /* HACK */ + g_ui_ctx->focused_control_type = k_ui_control_enum; + + if( !close ) + g_ui_ctx->focused_control_hit = 1; +} + +/* + * Slider + * ----------------------------------------------------------------------------- + */ + +enum ui_button_state ui_slider_base( + ui_rect box, f32 min, f32 max, f32 *value, f32 *out_t ) +{ + enum ui_button_state mask_using = + k_ui_button_holding_inside | + k_ui_button_holding_outside | + k_ui_button_click, + state = ui_button_base( box ); + + f32 t; + if( state & mask_using ) + { + t = vg_clampf( (f32)(g_ui_ctx->mouse[0] - box[0]) / (f32)( box[2] ), 0,1 ); + *value = vg_lerpf( min, max, t ); + } + else + t = vg_clampf( (*value - min) / (max-min), 0.0f, 1.0f ); + + *out_t = t; + + return state; +} + +void ui_slider_text( ui_rect box, const char *format, f32 value ) +{ + /* TODO: replace this one day with our own function */ + char buf[32]; + snprintf( buf, sizeof(buf), format? format: "%.2f", value ); + ui_text( box, buf, 1, k_ui_align_middle_center, 0 ); +} + +bool ui_slider_standard( ui_rect box, f32 min, f32 max, f32 *value, + const char *format ) +{ + f32 t; + + enum ui_button_state mask_using = + k_ui_button_holding_inside | + k_ui_button_holding_outside | + k_ui_button_click, + mask_brighter = mask_using | k_ui_button_hover, + state = ui_slider_base( box, min, max, value, &t ); + + ui_rect line = { box[0], box[1], t * (f32)box[2], box[3] }; + ui_fill( line, ui_colour(state&mask_brighter? k_ui_bg+4: k_ui_bg+2) ); + + ui_fill( (ui_rect){ box[0]+line[2], box[1], box[2]-line[2], box[3] }, + ui_colour( k_ui_bg ) ); + ui_outline( box, 1, ui_colour(state? k_ui_fg+3: k_ui_bg+3), 0 ); + ui_slider_text( box, NULL, *value ); + + return (state & mask_using) && 1; +} + +bool ui_slider( ui_rect inout_panel, const char *str_label, + f32 min, f32 max, f32 *value ) +{ + ui_rect rect, label, box; + ui_standard_widget( inout_panel, rect, 1 ); + ui_label( rect, str_label, g_ui_ctx->scale, 0, box ); + return ui_slider_standard( box, min, max, value, NULL ); +} + +/* + * Colour picker + * ----------------------------------------------------------------------------- + */ + +void ui_colourpicker( ui_rect inout_panel, const char *str_label, v4f value ) +{ + ui_rect widget, left, right; + ui_standard_widget( inout_panel, widget, 8 ); + ui_split_ratio( widget, k_ui_axis_v, 0.5f, 8, left, right ); + + ui_rect sliders[4]; + ui_split_ratio( right, k_ui_axis_h, 0.5f, 2, sliders[0], sliders[2] ); + ui_split_ratio( sliders[0], k_ui_axis_h, 0.5f, 2, sliders[0], sliders[1] ); + ui_split_ratio( sliders[2], k_ui_axis_h, 0.5f, 2, sliders[2], sliders[3] ); + + v4f hsv; + vg_rgb_hsv( value, hsv ); + hsv[3] = value[3]; + + enum ui_button_state modified = 0x00; + + for( u32 i=0; i<4; i ++ ) + { + modified |= ui_slider_standard( sliders[i], 0.0f, 1.0f, hsv+i, + (const char *[]){ "hue %.2f", + "sat %.2f", + "lum %.2f", + "alpha %.2f" }[i] ); + } + + ui_rect preview, square; + ui_split_ratio( left, k_ui_axis_v, 0.8f, 8, square, preview ); + + u32 state = ui_button_base( square ); + modified |= state; + + enum ui_button_state + mask_using = + k_ui_button_holding_inside | + k_ui_button_holding_outside | + k_ui_button_click; + + if( state & mask_using ) + { + for( u32 i=0; i<2; i ++ ){ + hsv[1+i] = vg_clampf( + (f32)(g_ui_ctx->mouse[i] - square[i]) / (f32)(square[2+i]), + 0.0f, 1.0f ); + } + + hsv[2] = 1.0f-hsv[2]; + } + + if( modified & (k_ui_button_click|k_ui_button_holding_inside| + k_ui_button_holding_outside) ) + { + vg_hsv_rgb( hsv, value ); + value[3] = hsv[3]; + } + + ui_outline( square, 1, ui_colour( state? k_ui_fg+3: k_ui_bg+3 ), 0 ); + + /* preview colour */ + v4f colour; + vg_hsv_rgb( hsv, colour ); + colour[3] = 1.0f; + ui_fill( preview, v4f_u32_colour( colour ) ); + + /* Draw hsv shader thingy */ + ui_flush( k_ui_shader_colour ); + ui_fill_rect( square, 0xffffffff, (ui_px[4]){ 0,256,256,0 } ); + g_ui_ctx->hue = hsv[0]; + ui_flush( k_ui_shader_hsv ); + + ui_rect marker = { square[0] + hsv[1] * (f32)square[2] - 4, + square[1] + (1.0f-hsv[2]) * (f32)square[3] - 4, + 8, 8 }, + lx = { square[0], + square[1] + (1.0f-hsv[2]) * (f32)square[3], + square[2], 1 }, + ly = { square[0] + hsv[1] * (f32)square[2], + square[1], + 1, square[3] }; + + ui_fill( marker, ui_colour( k_ui_fg ) ); + ui_fill( lx, ui_colour( k_ui_fg ) ); + ui_fill( ly, ui_colour( k_ui_fg ) ); +} + +/* + * Textbox chaos + * ----------------------------------------------------------------------------- + */ + +static void _ui_textbox_make_selection( int *start, int *end ) +{ + *start = VG_MIN( g_ui_ctx->textbox.cursor_pos, g_ui_ctx->textbox.cursor_user ); + *end = VG_MAX( g_ui_ctx->textbox.cursor_pos, g_ui_ctx->textbox.cursor_user ); +} + +void _ui_textbox_move_cursor( int *cursor0, int *cursor1, + int dir, int snap_together ) +{ + *cursor0 = VG_MAX( 0, g_ui_ctx->textbox.cursor_user + dir ); + *cursor0 = + VG_MIN( + VG_MIN( g_ui_ctx->textbox.len-1, strlen( g_ui_ctx->textbuf )), + *cursor0 ); + + if( snap_together ) + *cursor1 = *cursor0; +} + +static int _ui_textbox_makeroom( int datastart, int length ) +{ + int move_to = VG_MIN( datastart+length, g_ui_ctx->textbox.len-1 ); + int move_amount = strlen( g_ui_ctx->textbuf )-datastart; + int move_end = VG_MIN( move_to+move_amount, g_ui_ctx->textbox.len-1 ); + move_amount = move_end-move_to; + + if( move_amount ) + memmove( &g_ui_ctx->textbuf[ move_to ], + &g_ui_ctx->textbuf[ datastart ], + move_end-move_to ); + + g_ui_ctx->textbuf[ move_end ] = '\0'; + + return VG_MIN( length, g_ui_ctx->textbox.len-datastart-1 ); +} + +int _ui_textbox_delete_char( int direction ) +{ + int start, end; + _ui_textbox_make_selection( &start, &end ); + + /* There is no selection */ + if( !(end-start) ){ + if( direction == 1 ) end = VG_MIN( end+1, strlen( g_ui_ctx->textbuf ) ); + else if( direction == -1 ) start = VG_MAX( start-1, 0 ); + } + + /* Still no selction, no need to do anything */ + if( !(end-start) ) + return start; + + /* Copy the end->terminator to start */ + int remaining_length = strlen( g_ui_ctx->textbuf )+1-end; + memmove( &g_ui_ctx->textbuf[ start ], + &g_ui_ctx->textbuf[ end ], + remaining_length ); + return start; +} + +static void _ui_textbox_to_clipboard(void) +{ + int start, end; + _ui_textbox_make_selection( &start, &end ); + char buffer[512]; + + if( end-start ) + { + memcpy( buffer, &g_ui_ctx->textbuf[ start ], end-start ); + buffer[ end-start ] = 0x00; + +#ifdef VG_ANDROID +#else + SDL_SetClipboardText( buffer ); +#endif + } +} + +static void _ui_textbox_change_callback(void) +{ + if( g_ui_ctx->textbox.callbacks.change ) + { + g_ui_ctx->textbox.callbacks.change( g_ui_ctx->textbuf, + g_ui_ctx->textbox.len ); + + /* we gave permission to modify the buffer in this callback so.. */ + int len = strlen( g_ui_ctx->textbuf ); + g_ui_ctx->textbox.cursor_user = VG_MIN( g_ui_ctx->textbox.cursor_user, len ); + g_ui_ctx->textbox.cursor_pos = VG_MIN( g_ui_ctx->textbox.cursor_pos, len ); + } +} + +void ui_start_modal( const char *message, u32 options ); +static void _ui_textbox_clipboard_paste(void) +{ +#if VG_ANDROID + return; +#else + if( !SDL_HasClipboardText() ) + return; + + char *text = SDL_GetClipboardText(); + + if( !text ) + return; + + int datastart = _ui_textbox_delete_char( 0 ); + int length = strlen( text ); + + if( (g_ui_ctx->textbox.len - strlen(g_ui_ctx->textbuf)) < length ){ + ui_start_modal( "Clipboard content exceeds buffer size.", UI_MODAL_BAD ); + return; + } + + int cpylength = _ui_textbox_makeroom( datastart, length ); + + memcpy( g_ui_ctx->textbuf + datastart, text, cpylength); + _ui_textbox_move_cursor( &g_ui_ctx->textbox.cursor_user, + &g_ui_ctx->textbox.cursor_pos, cpylength, 1 ); + SDL_free( text ); + _ui_textbox_change_callback(); +#endif +} + +void _ui_textbox_put_char( char c ) +{ + g_ui_ctx->textbox.cursor_user = _ui_textbox_delete_char(0); + if( (g_ui_ctx->textbox.len - strlen(g_ui_ctx->textbuf)) <= 1 ) return; + + if( _ui_textbox_makeroom( g_ui_ctx->textbox.cursor_user, 1 ) ) + g_ui_ctx->textbuf[ g_ui_ctx->textbox.cursor_user ] = c; + + _ui_textbox_move_cursor( &g_ui_ctx->textbox.cursor_user, + &g_ui_ctx->textbox.cursor_pos, 1, 1 ); +} + +/* Receed secondary cursor */ +void _ui_textbox_left_select(void) +{ + _ui_textbox_move_cursor( &g_ui_ctx->textbox.cursor_user, NULL, -1, 0 ); +} + +/* Match and receed both cursors */ +void _ui_textbox_left(void) +{ + int cursor_diff = g_ui_ctx->textbox.cursor_pos - g_ui_ctx->textbox.cursor_user? 0: 1; + + _ui_textbox_move_cursor( &g_ui_ctx->textbox.cursor_user, + &g_ui_ctx->textbox.cursor_pos, -cursor_diff, 1 ); +} + +void _ui_textbox_up(void) +{ + if( g_ui_ctx->textbox.flags & UI_TEXTBOX_MULTILINE ){ + int line_begin = g_ui_ctx->textbox.cursor_user; + + while( line_begin ){ + if( g_ui_ctx->textbuf[ line_begin-1 ] == '\n' ){ + break; + } + + line_begin --; + } + + if( line_begin ){ + int line_above_begin = line_begin-1; + + while( line_above_begin ){ + if( g_ui_ctx->textbuf[ line_above_begin-1 ] == '\n' ){ + break; + } + + line_above_begin --; + } + + int offset = g_ui_ctx->textbox.cursor_user - line_begin, + line_length_above = line_begin - line_above_begin -1; + + offset = VG_MIN( line_length_above, offset ); + + g_ui_ctx->textbox.cursor_user = line_above_begin+offset; + g_ui_ctx->textbox.cursor_pos = line_above_begin+offset; + } + else{ + g_ui_ctx->textbox.cursor_user = line_begin; + g_ui_ctx->textbox.cursor_pos = line_begin; + } + } + else{ + if( g_ui_ctx->textbox.callbacks.up ){ + g_ui_ctx->textbox.callbacks.up( g_ui_ctx->textbuf, g_ui_ctx->textbox.len ); + } + } +} + +void _ui_textbox_down(void) +{ + if( g_ui_ctx->textbox.flags & UI_TEXTBOX_MULTILINE ){ + int line_begin = g_ui_ctx->textbox.cursor_user; + + while( line_begin ){ + if( g_ui_ctx->textbuf[ line_begin-1 ] == '\n' ){ + break; + } + + line_begin --; + } + + int line_below_begin = g_ui_ctx->textbox.cursor_user; + + while(1){ + if( g_ui_ctx->textbuf[ line_below_begin ] == '\0' ){ + g_ui_ctx->textbox.cursor_user = line_below_begin; + g_ui_ctx->textbox.cursor_pos = line_below_begin; + return; + } + + if( g_ui_ctx->textbuf[ line_below_begin ] == '\n' ){ + line_below_begin ++; + break; + } + + line_below_begin ++; + } + + int line_below_end = line_below_begin; + while(1){ + if( g_ui_ctx->textbuf[ line_below_end ] == '\0' || + g_ui_ctx->textbuf[ line_below_end ] == '\n' ){ + line_below_end ++; + break; + } + line_below_end ++; + } + + int offset = g_ui_ctx->textbox.cursor_user - line_begin, + line_length_below = line_below_end - line_below_begin -1; + + offset = VG_MIN( line_length_below, offset ); + + g_ui_ctx->textbox.cursor_user = line_below_begin+offset; + g_ui_ctx->textbox.cursor_pos = line_below_begin+offset; + } + else{ + if( g_ui_ctx->textbox.callbacks.down ){ + g_ui_ctx->textbox.callbacks.down( g_ui_ctx->textbuf, g_ui_ctx->textbox.len ); + } + } +} + +void _ui_textbox_right_select(void) +{ + _ui_textbox_move_cursor( &g_ui_ctx->textbox.cursor_user, NULL, 1, 0 ); +} + +void _ui_textbox_right(void) +{ + int cursor_diff = g_ui_ctx->textbox.cursor_pos - g_ui_ctx->textbox.cursor_user? 0: 1; + + _ui_textbox_move_cursor( &g_ui_ctx->textbox.cursor_user, + &g_ui_ctx->textbox.cursor_pos, +cursor_diff, 1 ); +} + +void _ui_textbox_backspace(void) +{ + if( g_ui_ctx->focused_control_type == k_ui_control_textbox ){ + g_ui_ctx->textbox.cursor_user = _ui_textbox_delete_char( -1 ); + g_ui_ctx->textbox.cursor_pos = g_ui_ctx->textbox.cursor_user; + _ui_textbox_change_callback(); + } +} + +void _ui_textbox_delete(void) +{ + if( g_ui_ctx->focused_control_type == k_ui_control_textbox ){ + g_ui_ctx->textbox.cursor_user = _ui_textbox_delete_char( 1 ); + g_ui_ctx->textbox.cursor_pos = g_ui_ctx->textbox.cursor_user; + _ui_textbox_change_callback(); + } +} + +void _ui_textbox_home_select(void) +{ + i32 start = g_ui_ctx->textbox.cursor_user; + + if( g_ui_ctx->textbox.flags & UI_TEXTBOX_MULTILINE ){ + while( start ){ + if( g_ui_ctx->textbuf[start-1] == '\n' ) + break; + else + start --; + } + } + else + start = 0; + + g_ui_ctx->textbox.cursor_user = start; +} + +void _ui_textbox_home(void) +{ + _ui_textbox_home_select(); + g_ui_ctx->textbox.cursor_pos = g_ui_ctx->textbox.cursor_user; +} + +void _ui_textbox_end_select(void) +{ + i32 end = g_ui_ctx->textbox.cursor_user; + + if( g_ui_ctx->textbox.flags & UI_TEXTBOX_MULTILINE ){ + while( g_ui_ctx->textbuf[end] ){ + if( g_ui_ctx->textbuf[end] == '\n' ) + break; + else + end ++; + } + } + else + end = VG_MIN( g_ui_ctx->textbox.len-1, strlen(g_ui_ctx->textbuf) ); + + g_ui_ctx->textbox.cursor_user = end; +} + +void _ui_textbox_end(void) +{ + _ui_textbox_end_select(); + g_ui_ctx->textbox.cursor_pos = g_ui_ctx->textbox.cursor_user; +} + +void _ui_textbox_select_all(void) +{ + _ui_textbox_move_cursor( &g_ui_ctx->textbox.cursor_user, NULL, 10000, 0); + _ui_textbox_move_cursor( &g_ui_ctx->textbox.cursor_pos, NULL, -10000, 0); +} + +void _ui_textbox_cut(void) +{ + _ui_textbox_to_clipboard(); + g_ui_ctx->textbox.cursor_user = _ui_textbox_delete_char(0); + g_ui_ctx->textbox.cursor_pos = g_ui_ctx->textbox.cursor_user; + _ui_textbox_change_callback(); +} + +void _ui_textbox_enter(void) +{ + if( g_ui_ctx->focused_control_type == k_ui_control_textbox ){ + g_ui_ctx->ignore_input_frames = 2; + + if( g_ui_ctx->textbox.callbacks.enter ) + g_ui_ctx->textbox.callbacks.enter( g_ui_ctx->textbuf, g_ui_ctx->textbox.len ); + + if( g_ui_ctx->focused_control_type != k_ui_control_textbox ) return; + + if( g_ui_ctx->textbox.flags & UI_TEXTBOX_MULTILINE ){ + _ui_textbox_put_char( '\n' ); + _ui_textbox_change_callback(); + } + else{ + if( !(g_ui_ctx->textbox.flags & UI_TEXTBOX_AUTOFOCUS ) ) + ui_defocus_all(); + } + } +} + +/* + * based on a visual character coordinate relative to the anchor of the textbox, + * this works out the linear place in the buffer that coordinate maps to + * + * input coordinates go in co[0], co[1], and the result index is in co[2] + */ +static void _ui_textbox_calc_index_from_grid( int co[3], int wrap_length ){ + int i[3] = {0,0,0}; + + char c; + while( (c = g_ui_ctx->textbuf[i[2]]) ){ + if( i[1]==co[1] && i[0]>=co[0] ) break; + + if( i[0] >= wrap_length ){ + i[1] ++; + i[0] = 0; + } + + if( c >= 32 && c <= 126 ){ + i[0] ++; + i[2] ++; + } + else if( c == '\n' ){ + i[1] ++; + + if( i[1] > co[1] ) break; + + i[2] ++; + i[0] = 0; + } + else i[2] ++; + } + + co[0] = i[0]; + co[1] = i[1]; + co[2] = i[2]; +} + +/* + * based on the index specied in co[2], work out the visual character + * coordinates and store them in co[0], co[1] + */ +static void _ui_textbox_index_calc_coords( int co[3], int wrap_length ){ + co[0] = 0; + co[1] = 0; + + char c; + int i=0; + + while( (c = g_ui_ctx->textbuf[i ++]) ){ + if( i > co[2] ) break; + if( co[0] >= wrap_length ){ + co[1] ++; + co[0] = 0; + } + if( c >= 32 && c <= 126 ) co[0] ++; + else if( c == '\n' ){ + co[1] ++; + co[0] = 0; + } + } +} + +/* + * calculate the number of characters remaining until either: + * - the wrap_length limit is hit + * - end of the line/string + * + * index must be fully populated with visual X/Y, and linear index + */ +static int _ui_textbox_run_remaining( int index[3], int wrap_length ){ + int i=0, printed_chars=0; + char c; + while( (c = g_ui_ctx->textbuf[index[2] + (i ++)]) ){ + if( index[0]+i >= wrap_length ) break; + if( c >= 32 && c <= 126 ) printed_chars ++; + else if( c == '\n' ) break; + } + + return printed_chars+1; +} + +int ui_textbox( ui_rect inout_panel, const char *label, + char *buf, u32 len, u32 lines, u32 flags, + struct ui_textbox_callbacks *callbacks ) +{ + if( lines > 1 ) flags |= UI_TEXTBOX_MULTILINE; + + ui_rect rect; + ui_standard_widget( inout_panel, rect, lines ); + + if( label ) + ui_label( rect, label, 1, 0, rect ); + + int clickup= ui_click_up(UI_MOUSE_LEFT), + clickdown = ui_click_down(UI_MOUSE_LEFT), + click = ui_clicking(UI_MOUSE_LEFT) | clickup, + target = ui_inside_rect( rect, g_ui_ctx->mouse_click ) && click, + hover = ui_inside_rect( rect, g_ui_ctx->mouse ); + + /* allow instant transitions from textbox->textbox */ + if( (g_ui_ctx->focused_control_type != k_ui_control_none) && + (g_ui_ctx->focused_control_type != k_ui_control_textbox) ){ + clickup = 0; + clickdown = 0; + click = 0; + target = 0; + hover = 0; + flags &= ~UI_TEXTBOX_AUTOFOCUS; + } + + u32 col_base = ui_colour( k_ui_bg ), + col_highlight = ui_colour( k_ui_fg ), + col_cursor = (0x00ffffff & ui_colour(k_ui_fg))|0x7f000000; + + ui_px border = -1; + + ui_rect text_rect; + rect_copy( rect, text_rect ); + + if( flags & UI_TEXTBOX_MULTILINE ) text_rect[3] = rect[3]-16; + else text_rect[3] = g_ui_ctx->font->sy; + + text_rect[2] -= 16; + ui_rect_center( rect, text_rect ); + + ui_px wrap_length = 1024; + + if( flags & UI_TEXTBOX_WRAP ) + wrap_length = text_rect[2] / g_ui_ctx->font->sx; + + if( hover ) + { + g_ui_ctx->cursor = k_ui_cursor_ibeam; + } + + if( g_ui_ctx->focused_control_id == buf ) + { + ui_fill( rect, col_base ); + ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); + + if( !(flags & UI_TEXTBOX_AUTOFOCUS) && ((clickup||clickdown) && !target)) + { + ui_defocus_all(); + } + else + { + g_ui_ctx->focused_control_hit = 1; + if( click && target ){ + int p0[3] ={ + (g_ui_ctx->mouse_click[0] - text_rect[0]) / g_ui_ctx->font->sx, + (g_ui_ctx->mouse_click[1] - text_rect[1]) / g_ui_ctx->font->sy, + -1 + }, + p1[3] = { + (g_ui_ctx->mouse[0] - text_rect[0]) / g_ui_ctx->font->sx, + (g_ui_ctx->mouse[1] - text_rect[1]) / g_ui_ctx->font->sy, + -1 + }; + + if( flags & UI_TEXTBOX_MULTILINE ) + { + _ui_textbox_calc_index_from_grid( p0, wrap_length ); + _ui_textbox_calc_index_from_grid( p1, wrap_length ); + + g_ui_ctx->textbox.cursor_pos = p0[2]; + g_ui_ctx->textbox.cursor_user = p1[2]; + } + else + { + int max = strlen( buf ); + g_ui_ctx->textbox.cursor_pos = VG_MAX( 0, VG_MIN( max, p0[0] )), + g_ui_ctx->textbox.cursor_user = VG_MAX( 0, VG_MIN( max, p1[0] )); + } + } + + ui_outline( rect, -2, g_ui_ctx->scheme[ k_ui_orange ], 0 ); + + ui_rect cursor; + + int c0 = g_ui_ctx->textbox.cursor_pos, + c1 = g_ui_ctx->textbox.cursor_user, + start = VG_MIN( c0, c1 ), + end = VG_MAX( c0, c1 ), + chars = end-start; + + if( flags & (UI_TEXTBOX_WRAP|UI_TEXTBOX_MULTILINE) ) + { + int pos[3], remaining = chars; + + pos[2] = start; + _ui_textbox_index_calc_coords( pos, wrap_length ); + + if( start==end ) + { + cursor[0] = text_rect[0] + pos[0]*g_ui_ctx->font->sx-1; + cursor[1] = text_rect[1] + pos[1]*14; + cursor[2] = 2; + cursor[3] = 13; + ui_fill( cursor, col_cursor ); + rect_copy( cursor, g_ui_ctx->click_fader_end ); + } + else + { + while( remaining ) + { + int run = _ui_textbox_run_remaining( pos, wrap_length ); + run = VG_MIN( run, remaining ); + + cursor[0] = text_rect[0] + pos[0]*g_ui_ctx->font->sx-1; + cursor[1] = text_rect[1] + pos[1]*14; + cursor[2] = (float)(run)*(float)g_ui_ctx->font->sx; + cursor[3] = 13; + + ui_fill( cursor, col_cursor ); + + remaining -= run; + pos[0] = 0; + pos[1] ++; + pos[2] += run; + } + rect_copy( cursor, g_ui_ctx->click_fader_end ); + } + } + else + { + cursor[0] = text_rect[0] + start*g_ui_ctx->font->sx-1; + cursor[1] = text_rect[1]; + cursor[3] = 13; + + if( start==end ) + { + cursor[2] = 2; + } + else + { + cursor[2] = (float)(chars)*(float)g_ui_ctx->font->sx; + } + + if( (g_ui_ctx->click_fade_opacity<=0.0f) && + ui_clip( rect, cursor, cursor ) ) + { + ui_fill( cursor, col_cursor ); + } + + rect_copy( cursor, g_ui_ctx->click_fader_end ); + } + } + + return 0; + } + + if( click || (flags & UI_TEXTBOX_AUTOFOCUS) ) + { + if( (target && hover) || (flags & UI_TEXTBOX_AUTOFOCUS) ) + { + ui_defocus_all(); + + ui_fill( rect, col_highlight ); + g_ui_ctx->ignore_input_frames = 2; + rect_copy( rect, g_ui_ctx->click_fader ); + rect_copy( rect, g_ui_ctx->click_fader_end ); + + g_ui_ctx->click_fade_opacity = 1.0f; + g_ui_ctx->textbuf = buf; + g_ui_ctx->focused_control_hit = 1; + g_ui_ctx->focused_control_type = k_ui_control_textbox; + g_ui_ctx->textbox.len = len; + g_ui_ctx->textbox.flags = flags; + g_ui_ctx->textbox.cursor_pos = 0; + g_ui_ctx->textbox.cursor_user = 0; + + if( callbacks ) + { + g_ui_ctx->textbox.callbacks = *callbacks; + } + else + { + g_ui_ctx->textbox.callbacks.change = NULL; + g_ui_ctx->textbox.callbacks.down = NULL; + g_ui_ctx->textbox.callbacks.up = NULL; + g_ui_ctx->textbox.callbacks.enter = NULL; + } + +#ifdef VG_ANDROID +#else + SDL_StartTextInput(); +#endif + } + } + + ui_fill( rect, col_base ); + + if( hover ) + { + ui_outline( rect, -1, col_highlight, 0 ); + } + + ui_ntext( text_rect, buf, wrap_length, 1, k_ui_align_left, 0 ); + return 0; +} + +/* + * Tabs + * ----------------------------------------------------------------------------- + */ + +void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, + const char **titles, u32 count, i32 *page ) +{ + ui_rect bar; + ui_standard_widget( inout_panel, bar, 1 ); + + i32 cur_page = *page; + + f32 width = (f32)inout_panel[2] / (f32)count; + + ui_px h = (inout_panel[1] + inout_panel[3]) - (bar[1]+bar[3]); + inout_panel[1] = bar[1]+bar[3]; + inout_panel[3] = h; + + ui_fill( inout_panel, ui_colour( k_ui_bg+2 ) ); + ui_outline( inout_panel, 1, ui_colour( k_ui_bg+5 ), 0 ); + + rect_copy( inout_panel, out_content_panel ); + ui_rect_pad( out_content_panel, + (ui_px[2]){ g_ui_ctx->padding, g_ui_ctx->padding } ); + + /* place buttons */ + for( i32 i=0; ifocused_control_type = k_ui_control_modal; + g_ui_ctx->modal.message = message; + g_ui_ctx->modal.callbacks.close = NULL; + g_ui_ctx->modal.options = options; + + u32 type = options & UI_MODAL_TYPE_BITS; + if( type == UI_MODAL_OK ) vg_info( message ); + else if( type == UI_MODAL_WARN ) vg_warn( message ); + else if( type == UI_MODAL_GOOD ) vg_success( message ); + else if( type == UI_MODAL_BAD ) vg_error( message ); +} + +/* + * Input handling + * ----------------------------------------------------------------------------- + */ + +/* + * Handles binds + */ +#ifdef VG_ANDROID +#else +void ui_proc_key( SDL_Keysym ev ) +{ + if( g_ui_ctx->focused_control_type != k_ui_control_textbox ){ + return; + } + + struct textbox_mapping{ + u16 mod; + SDL_Keycode key; + + void (*handler)(void); + } + mappings[] = + { + { 0, SDLK_LEFT, _ui_textbox_left }, + { KMOD_SHIFT, SDLK_LEFT, _ui_textbox_left_select }, + { 0, SDLK_RIGHT, _ui_textbox_right }, + { KMOD_SHIFT, SDLK_RIGHT, _ui_textbox_right_select }, + { 0, SDLK_DOWN, _ui_textbox_down }, + { 0, SDLK_UP, _ui_textbox_up }, + { 0, SDLK_BACKSPACE, _ui_textbox_backspace }, + { KMOD_SHIFT, SDLK_BACKSPACE, _ui_textbox_backspace }, + { KMOD_CTRL, SDLK_BACKSPACE, _ui_textbox_backspace }, + { 0, SDLK_DELETE, _ui_textbox_delete }, + { 0, SDLK_HOME, _ui_textbox_home }, + { KMOD_SHIFT, SDLK_HOME, _ui_textbox_home_select }, + { 0, SDLK_END, _ui_textbox_end }, + { KMOD_SHIFT, SDLK_END, _ui_textbox_end_select }, + { KMOD_CTRL, SDLK_a, _ui_textbox_select_all }, + { KMOD_CTRL, SDLK_c, _ui_textbox_to_clipboard }, + { KMOD_CTRL, SDLK_x, _ui_textbox_cut }, + { KMOD_CTRL, SDLK_v, _ui_textbox_clipboard_paste }, + { 0, SDLK_RETURN, _ui_textbox_enter }, + { 0, SDLK_ESCAPE, ui_defocus_all }, + }; + + SDL_Keymod mod = 0; + + if( ev.mod & KMOD_SHIFT ) + mod |= KMOD_SHIFT; + + if( ev.mod & KMOD_CTRL ) + mod |= KMOD_CTRL; + + if( ev.mod & KMOD_ALT ) + mod |= KMOD_ALT; + + for( int i=0; ikey == ev.sym ){ + if( mapping->mod == 0 ){ + if( mod == 0 ){ + mapping->handler(); + return; + } + } + else if( (mod & mapping->mod) == mapping->mod ){ + mapping->handler(); + return; + } + } + } +} +#endif + +/* + * Callback for text entry mode + */ +void ui_proc_utf8( const char *text ) +{ + if( g_ui_ctx->focused_control_type == k_ui_control_textbox ) + { + const char *ptr = text; + + while( *ptr ) + { + if( *ptr != '`' ) _ui_textbox_put_char( *ptr ); + ptr ++; + } + + _ui_textbox_change_callback(); + } +} + +/* + * Development utils + * ----------------------------------------------------------------------------- + */ + +void ui_dev_colourview(void) +{ + ui_rect window = {g_ui_ctx->area[0]-256,0,256,g_ui_ctx->area[1]}, swatch; + + const char *names[vg_list_size(g_ui_ctx->scheme)] = { + [k_ui_bg] = "k_ui_bg", "k_ui_bg+1", "k_ui_bg+2", "k_ui_bg+3", + "k_ui_bg+4", "k_ui_bg+5", "k_ui_bg+6", "k_ui_bg+7", + + [k_ui_fg] = "k_ui_fg", "k_ui_fg+1", "k_ui_fg+2", "k_ui_fg+3", + "k_ui_fg+4", "k_ui_fg+5", "k_ui_fg+6", "k_ui_fg+7", + + [k_ui_red] = "k_ui_red", "k_ui_orange", "k_ui_yellow", "k_ui_green", + "k_ui_aqua", "k_ui_blue", "k_ui_purple", "k_ui_gray", + "k_ui_red+8","k_ui_orange+8","k_ui_yellow+8","k_ui_green+8", + "k_ui_aqua+8","k_ui_blue+8","k_ui_purple+8","k_ui_gray+8" }; + + ui_rect col[2]; + ui_split_ratio( window, k_ui_axis_v, 0.5f, 0, col[0], col[1] ); + + for( int i=0; ischeme); i++ ){ + int which = (i/8)%2; + + ui_split( col[which], k_ui_axis_h, 24, 0, swatch, col[which] ); + ui_fill( swatch, ui_colour(i) ); + + if( names[i] ) + ui_text(swatch, names[i], 1, k_ui_align_middle_left, ui_colourcont(i)); + } +} diff --git a/vg_ui/imgui.h b/vg_ui/imgui.h new file mode 100644 index 0000000..4da6162 --- /dev/null +++ b/vg_ui/imgui.h @@ -0,0 +1,344 @@ +#pragma once + +#include "vg_platform.h" +#include "vg_m.h" +#include "vg_font.h" +#include "vg_log.h" +#include + +typedef i16 ui_px; +typedef ui_px ui_rect[4]; +typedef ui_px ui_point[2]; +typedef struct ui_vert ui_vert; +typedef struct ui_context ui_context; +typedef struct ui_batch ui_batch; + +enum ui_axis { + k_ui_axis_h = 0x0u, + k_ui_axis_v = 0x1u, +}; + +/* Relative to cursor p0 */ +enum ui_align +{ /* DC BA */ + k_ui_align_lwr = 0xff, + k_ui_align_left = 0x0000| 0x00, + k_ui_align_right = 0x0000| 0x01, + k_ui_align_center = 0x0000| 0x02, + + k_ui_align_middle = 0x0100, + k_ui_align_middle_left = 0x0100| 0x00, + k_ui_align_middle_right = 0x0100| 0x01, + k_ui_align_middle_center = 0x0100| 0x02, + + k_ui_align_bottom = 0x0200, + k_ui_align_bottom_left = 0x0200| 0x00, + k_ui_align_bottom_right = 0x0200| 0x01, + k_ui_align_bottom_center = 0x0200| 0x02, +}; + +enum ui_shader +{ + k_ui_shader_colour, + k_ui_shader_image, + k_ui_shader_hsv, +}; + +typedef u32 ui_scheme[8*4]; +enum ui_scheme_colour +{ + k_ui_bg = 0, + k_ui_fg = 8, + k_ui_hue = 16, + k_ui_red = 16, + k_ui_orange, + k_ui_yellow, + k_ui_green, + k_ui_aqua, + k_ui_blue, + k_ui_purple, + k_ui_gray, + k_ui_brighter = 8 +}; + +#define UI_RGB( STDHEX ) 0xff000000 |\ + ((STDHEX&0x000000ff)<<16) |\ + ((STDHEX&0x0000ff00) ) |\ + ((STDHEX&0x00ff0000)>>16) + +#define UI_TEXTBOX_MULTILINE 0x1 +#define UI_TEXTBOX_WRAP 0x2 +#define UI_TEXTBOX_AUTOFOCUS 0x4 + +#define UI_MODAL_OK 0x0 +#define UI_MODAL_GOOD 0x1 +#define UI_MODAL_BAD 0x2 +#define UI_MODAL_WARN 0x3 +#define UI_MODAL_TYPE_BITS 0x3 + +#if 0 +#define UI_MOUSE_LEFT (SDL_BUTTON(SDL_BUTTON_LEFT)) +#define UI_MOUSE_RIGHT (SDL_BUTTON(SDL_BUTTON_RIGHT)) +#define UI_MOUSE_MIDDLE (SDL_BUTTON(SDL_BUTTON_MIDDLE)) +#else +#define UI_MOUSE_LEFT 1 +#define UI_MOUSE_RIGHT 2 +#define UI_MOUSE_MIDDLE 4 +#endif + +#define UI_TOP 0x1 +#define UI_LEFT 0x2 +#define UI_BOTTOM 0x4 +#define UI_RIGHT 0x8 + +#pragma pack(push,1) +struct ui_vert +{ + ui_px co[2]; + u16 uv[2]; + u32 colour; +}; +#pragma pack(pop) + +enum ui_button_state +{ + k_ui_button_none = 0x0, + k_ui_button_click = 0x1, + k_ui_button_holding_inside = 0x2, + k_ui_button_holding_outside = 0x4, + k_ui_button_hover = 0x8 +}; + +struct ui_context +{ + struct ui_vert *vertex_buffer; + u16 *indice_buffer; + u32 max_verts, max_indices, + cur_vert, cur_indice, + vert_start, indice_start; + + ui_px area[2]; + + union + { + void *focused_control_id; /* uses the memory location of various locking + controls as an id */ + char *textbuf; + i32 *ptr_enum; + }; + + u32 focused_control_hit; + enum ui_control_type{ + k_ui_control_none, + k_ui_control_textbox, + k_ui_control_enum, + k_ui_control_modal + } + focused_control_type; + + union /* controls data that can be focused */ + { + struct ui_textbuf + { + int cursor_user, cursor_pos; + u32 len; + u32 flags; + + struct ui_textbox_callbacks + { + void (*enter)( char *, u32 ), + (*up)( char *, u32 ), + (*down)( char *, u32 ), + (*change)( char *, u32 ), + (*escape)( void ); + } + callbacks; + } + textbox; + + struct ui_enum + { + struct ui_enum_opt + { + i32 value; + const char *alias; + } + *options; + u32 option_count; + ui_rect rect; + } + _enum; + }; + + struct ui_modal + { + const char *message; + u32 options; + + struct ui_modal_callbacks + { + void (*close)(u32); + } + callbacks; + } + modal; + + ui_px mouse[2], mouse_delta[2], mouse_click[2]; + u32 mouse_state[2]; + u32 ignore_input_frames; + + bool mouse_pos_overriden; + int wants_mouse; + + ui_rect click_fader, click_fader_end; + float click_fade_opacity; + + ui_scheme scheme; + const vg_font_face *font; + v2f inverse_font_sheet; + + enum ui_cursor + { + k_ui_cursor_default, + k_ui_cursor_ibeam, + k_ui_cursor_hand, + k_ui_cursor_max + } + cursor; + + ui_px widget_height, scale, padding; + f32 hue; + + /* at some point this should be implementation specific? */ + v4f colour; + f32 frosting; + v2f bg_inverse_ratio; + GLuint tex_bg; +}; + +struct ui_batch +{ + enum ui_shader shader; + + ui_vert *vert_buf; + u32 vert_count, vert_offset; + + u16 *indice_buf; + u32 indice_count, indice_offset; +}; + +void ui_bind_context( ui_context *context ); +void ui_init( ui_vert *verts_buf, u32 verts_max, + u16 *indices_buf, u32 indices_max ); +ui_vert *ui_fill_rect( ui_rect rect, u32 colour, ui_px uv[4] ); +ui_vert *ui_fill( ui_rect rect, u32 colour ); +void ui_outline( ui_rect rect, ui_px thickness, u32 colour, u32 mask ); +void rect_copy( ui_rect a, ui_rect b ); +void ui_split( ui_rect rect, enum ui_axis other, ui_px width, ui_px gap, + ui_rect l, ui_rect r ); +void ui_split_ratio( ui_rect rect, enum ui_axis dir, float ratio, + ui_px gap, ui_rect l, ui_rect r ); +void ui_rect_pad( ui_rect rect, ui_px pad[2] ); +int ui_clip( ui_rect parent, ui_rect child, ui_rect clipped ); +int ui_inside_rect( ui_rect rect, ui_px co[2] ); +int ui_click_down( u32 mask ); +int ui_clicking( u32 mask ); +int ui_click_up( u32 mask ); +void ui_prerender( ui_px dims[2], ui_px mouse[2], i32 mouse_state ); +void ui_ignore_input_frames( u32 frames ); +void ui_capture_mouse( bool on ); +void ui_flush( enum ui_shader shader ); +void ui_rect_center( ui_rect parent, ui_rect rect ); +void ui_fit_item( ui_rect rect, ui_px size[2], ui_rect d ); +ui_px ui_text_line_width( const char *str ); +ui_px ui_text_string_height( const char *str ); +ui_px ui_text_aligned_x( const char *str, ui_rect rect, ui_px scale, + enum ui_align align ); +u32 ui_colour( enum ui_scheme_colour id ); +u32 ui_colourcont( enum ui_scheme_colour id ); +void ui_hex_to_norm( u32 hex, v4f norm ); +u32 v4f_u32_colour( v4f colour ); +u32 ui_opacity( u32 colour, f32 opacity ); +u32 ui_ntext( ui_rect rect, const char *str, u32 len, ui_px scale, + enum ui_align align, u32 colour ); +u32 ui_text( ui_rect rect, const char *str, ui_px scale, + enum ui_align align, u32 colour ); +void ui_font_face( vg_font_face *ff ); +void ui_panel( ui_rect in_rect, ui_rect out_panel ); +void ui_label( ui_rect rect, const char *text, ui_px size, + ui_px gap, ui_rect r ); +ui_px ui_standard_widget_height( ui_px count ); +void ui_standard_widget( ui_rect inout_panel, ui_rect out_rect, ui_px count ); +void ui_info( ui_rect inout_panel, const char *text ); +void ui_image( ui_rect rect, GLuint image ); +void ui_defocus_all(void); +enum ui_button_state ui_button_base( ui_rect rect ); + +/* TODO: split this out into a formatless button and one that auto fills */ +enum ui_button_state ui_colourbutton( ui_rect rect, + enum ui_scheme_colour colour, + enum ui_scheme_colour hover_colour, + enum ui_scheme_colour hi_colour ); +enum ui_button_state ui_colourbutton_text( + ui_rect rect, const char *string, ui_px scale, + enum ui_scheme_colour colour ); +enum ui_button_state ui_button_text( ui_rect rect, + const char *string, ui_px scale ); +enum ui_button_state ui_button( ui_rect inout_panel, const char *string ); +void ui_postrender( f32 delta_time ); +enum ui_button_state ui_checkbox_base( ui_rect box, i32 *data ); +int ui_checkbox( ui_rect inout_panel, const char *str_label, i32 *data ); +void ui_enum( ui_rect inout_panel, const char *str_label, + struct ui_enum_opt *options, u32 len, i32 *value ); + +enum ui_button_state ui_slider_base( + ui_rect box, f32 min, f32 max, f32 *value, f32 *out_t ); +void ui_slider_text( ui_rect box, const char *format, f32 value ); +bool ui_slider_standard( ui_rect box, f32 min, f32 max, f32 *value, + const char *format ); +bool ui_slider( ui_rect inout_panel, const char *str_label, + f32 min, f32 max, f32 *value ); +void ui_colourpicker( ui_rect inout_panel, const char *str_label, v4f value ); +void _ui_textbox_move_cursor( int *cursor0, int *cursor1, + int dir, int snap_together ); +int _ui_textbox_delete_char( int direction ); +void ui_start_modal( const char *message, u32 options ); +void _ui_textbox_put_char( char c ); +void _ui_textbox_left_select(void); +void _ui_textbox_left(void); +void _ui_textbox_up(void); +void _ui_textbox_down(void); +void _ui_textbox_right_select(void); +void _ui_textbox_right(void); +void _ui_textbox_backspace(void); +void _ui_textbox_delete(void); +void _ui_textbox_home_select(void); +void _ui_textbox_home(void); +void _ui_textbox_end_select(void); +void _ui_textbox_end(void); +void _ui_textbox_select_all(void); +void _ui_textbox_cut(void); +void _ui_textbox_enter(void); +int ui_textbox( ui_rect inout_panel, const char *label, + char *buf, u32 len, u32 lines, u32 flags, + struct ui_textbox_callbacks *callbacks ); +void ui_tabs( ui_rect inout_panel, ui_rect out_content_panel, + const char **titles, u32 count, i32 *page ); +void ui_start_modal( const char *message, u32 options ); +#ifdef VG_ANDROID +#else +void ui_proc_key( SDL_Keysym ev ); +#endif +void ui_proc_utf8( const char *text ); +void ui_dev_colourview(void); +void ui_post_update(void); +void ui_set_mouse_pos( ui_px x, ui_px y ); +void vg_ui_init(void); + +const vg_font_face *ui_current_font(void); +ui_context *ui_current_context(void); + +extern vg_font_sheet vg_default_font_sheet; +extern vg_font_face vgf_default_small, vgf_default_large, vgf_default_title; +void ui_set_screen( i32 width, i32 height ); +extern ui_context *g_ui_ctx; diff --git a/vg_ui/imgui_impl_opengl.c b/vg_ui/imgui_impl_opengl.c new file mode 100644 index 0000000..5e43e14 --- /dev/null +++ b/vg_ui/imgui_impl_opengl.c @@ -0,0 +1,479 @@ +#include "vg_opengl.h" +#include "vg_shader.h" + +struct +{ + GLuint vao, vbo, ebo; + m3x3f pv; + ui_context ctx; + GLuint tex_glyphs; + +#ifdef VG_ANDROID +#else + SDL_Cursor *cursor_map[ k_ui_cursor_max ]; +#endif +} +static opengl_ui = +{ + .ctx = + { + .scheme = + { + [ k_ui_bg+0 ] = UI_RGB( 0x1d2021 ), + [ k_ui_bg+1 ] = UI_RGB( 0x282828 ), + [ k_ui_bg+2 ] = UI_RGB( 0x3c3836 ), + [ k_ui_bg+3 ] = UI_RGB( 0x504945 ), + [ k_ui_bg+4 ] = UI_RGB( 0x665c54 ), + [ k_ui_bg+5 ] = UI_RGB( 0x7c6f64 ), + [ k_ui_bg+6 ] = UI_RGB( 0x928374 ), + [ k_ui_bg+7 ] = UI_RGB( 0xa89984 ), + + [ k_ui_fg+0 ] = UI_RGB( 0xebdbb2 ), + [ k_ui_fg+1 ] = UI_RGB( 0xfbf1c7 ), + [ k_ui_fg+2 ] = UI_RGB( 0xd5c4a1 ), + [ k_ui_fg+3 ] = UI_RGB( 0xbdae93 ), + [ k_ui_fg+4 ] = UI_RGB( 0xa89984 ), + [ k_ui_fg+5 ] = UI_RGB( 0x000000 ), + [ k_ui_fg+6 ] = UI_RGB( 0x000000 ), + [ k_ui_fg+7 ] = UI_RGB( 0x000000 ), + + [ k_ui_red ] = UI_RGB( 0xcc241d ), + [ k_ui_orange ] = UI_RGB( 0xd65d0e ), + [ k_ui_yellow ] = UI_RGB( 0xd79921 ), + [ k_ui_green ] = UI_RGB( 0x98971a ), + [ k_ui_aqua ] = UI_RGB( 0x689d6a ), + [ k_ui_blue ] = UI_RGB( 0x458588 ), + [ k_ui_purple ] = UI_RGB( 0xb16286 ), + [ k_ui_gray ] = UI_RGB( 0x928374 ), + [ k_ui_red + k_ui_brighter ] = UI_RGB( 0xfb4934 ), + [ k_ui_orange + k_ui_brighter ] = UI_RGB( 0xfe8019 ), + [ k_ui_yellow + k_ui_brighter ] = UI_RGB( 0xfabd2f ), + [ k_ui_green + k_ui_brighter ] = UI_RGB( 0xb8bb26 ), + [ k_ui_aqua + k_ui_brighter ] = UI_RGB( 0x8ec07c ), + [ k_ui_blue + k_ui_brighter ] = UI_RGB( 0x83a598 ), + [ k_ui_purple + k_ui_brighter ] = UI_RGB( 0xd3869b ), + [ k_ui_gray + k_ui_brighter ] = UI_RGB( 0xa89984 ), + }, + .font = &vgf_default_small, + .colour = {1.0f,1.0f,1.0f,1.0f}, + .bg_inverse_ratio = {1,1}, + .scale = 1, + .padding = 8, + .widget_height = 28 + } +}; + +#if 0 +static vg_shader _shader_ui = +{ + .vs.static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + "uniform vec2 uInverseFontSheet;" + "" + "out vec2 aTexCoords;" + "out vec4 aColour;" + "" + "void main(){" + "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "aTexCoords = a_uv * uInverseFontSheet;" + "aColour = a_colour;" + "}", + .fs.static_src = + "uniform sampler2D uTexGlyphs;" + "uniform lowp vec4 uColour;" + "out lowp vec4 FragColor;" + "" + "in highp vec2 aTexCoords;" + "in lowp vec4 aColour;" + "" + "void main(){" + "lowp vec4 avg = vec4(0.0);" + + "if( aColour.a == 0.0 ){" + "avg = aColour;" + "avg.a = texture( uTexGlyphs, aTexCoords ).r;" + "}" + "else{" + "avg = aColour;" + "}" + + "FragColor = avg * uColour;" + "}" +}; +#else + +static struct vg_shader _shader_ui ={ + .name = "[vg] ui - transparent", + .vs = { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + "uniform vec2 uBGInverseRatio;" + "uniform vec2 uInverseFontSheet;" + "" + "out vec4 aTexCoords;" + "out vec4 aColour;" + "" + "void main(){" + "vec4 proj_pos = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "gl_Position = proj_pos;" + "aTexCoords = vec4( a_uv * uInverseFontSheet, " + " (proj_pos.xy*0.5+0.5) * uBGInverseRatio );" + "aColour = a_colour;" + "}", + }, + .fs = { + .orig_file = NULL, + .static_src = + "uniform sampler2D uTexGlyphs;" + "uniform sampler2D uTexBG;" + "uniform vec4 uColour;" + "uniform float uSpread;" + "out vec4 FragColor;" + "" + "in vec4 aTexCoords;" + "in vec4 aColour;" + "" + "vec2 rand_hash22( vec2 p ){" + "vec3 p3 = fract(vec3(p.xyx) * 213.8976123);" + "p3 += dot(p3, p3.yzx+19.19);" + "return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));" + "}" + "" + "void main(){" + "vec4 diffuse = aColour;" + + "vec4 avg = vec4(0.0);" + + "if( aColour.a == 0.0 ){" + "avg = aColour;" + "avg.a = texture( uTexGlyphs, aTexCoords.xy ).r;" + "}" + "else{" + "if( uSpread > 0.0001 ){" + "for( int i=0; i<4; i ++ ){" + "vec2 spread = rand_hash22(aTexCoords.zw+vec2(float(i)));" + "avg += texture( uTexBG, aTexCoords.zw + (spread-0.5)*uSpread );" + "}" + "avg *= 0.25;" + "avg.a = 1.0;" + "avg.rgb = mix( avg.rgb, aColour.rgb, aColour.a );" + "}" + "else{" + "avg = aColour;" + "}" + "}" + + "FragColor = avg * uColour;" + "}" + } +}; + +static struct vg_shader _shader_ui_image = { + .name = "[vg] ui_image", + .vs = + { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + + "out vec2 aTexCoords;" + "out vec4 aColour;" + "out vec2 aWsp;" + + "void main()" + "{" + "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "aTexCoords = a_uv * 0.00390625;" + "aColour = a_colour;" + + "aWsp = a_co;" + "}", + }, + .fs = + { + .orig_file = NULL, + .static_src = + "uniform sampler2D uTexImage;" + "uniform vec4 uColour;" + "out vec4 FragColor;" + + "in vec2 aTexCoords;" + "in vec4 aColour;" + "in vec2 aWsp;" + + "void main()" + "{" + "vec4 colour = texture( uTexImage, aTexCoords );" + "FragColor = colour * uColour;" + "}" + } +}; + +static struct vg_shader _shader_ui_hsv = { + .name = "[vg] ui_hsv", + .vs = { + .orig_file = NULL, + .static_src = + "layout (location=0) in vec2 a_co;" + "layout (location=1) in vec2 a_uv;" + "layout (location=2) in vec4 a_colour;" + "uniform mat3 uPv;" + + "out vec2 aTexCoords;" + "out vec4 aColour;" + "out vec2 aWsp;" + + "void main()" + "{" + "gl_Position = vec4( uPv * vec3( a_co, 1.0 ), 1.0 );" + "aTexCoords = a_uv * 0.00390625;" + "aColour = a_colour;" + + "aWsp = a_co;" + "}", + }, + .fs = + { + .orig_file = NULL, + .static_src = + "uniform float uHue;" + "out vec4 FragColor;" + + "in vec2 aTexCoords;" + "in vec4 aColour;" + "in vec2 aWsp;" + + "void main()" + "{" + "vec3 c = vec3( uHue, aTexCoords );" + "vec4 K = vec4(1.0,2.0/3.0,1.0/3.0,3.0);" + "vec3 p = abs(fract(c.xxx+K.xyz)*6.0 - K.www);" + "vec3 colour = c.z*mix(K.xxx,clamp(p-K.xxx,0.0,1.0),c.y);" + "FragColor = vec4( colour, 1.0 );" + "}" + } +}; +#endif + +void ui_set_screen( i32 width, i32 height ) +{ + m3x3_identity( opengl_ui.pv ); + m3x3_translate( opengl_ui.pv, (v3f){ -1.0f, 1.0f, 0.0f } ); + m3x3_scale( opengl_ui.pv, (v3f){ 1.0f/((f32)width*0.5f), + -1.0f/((f32)height*0.5f), 1.0f } ); +} + +void vg_ui_init(void) +{ + if( !vg_shader_compile( &_shader_ui ) || + !vg_shader_compile( &_shader_ui_hsv ) || + !vg_shader_compile( &_shader_ui_image ) ) + { + vg_fatal_error( "" ); + } + + u32 verts = 30000, indices = 20000; + ui_bind_context( &opengl_ui.ctx ); + ui_init( malloc( sizeof(ui_vert)*verts ), verts, + malloc( sizeof(u16)*indices ), indices ); + + /* Generate the buffer we are gonna be drawing to */ + glGenVertexArrays( 1, &opengl_ui.vao ); + glGenBuffers( 1, &opengl_ui.vbo ); + glGenBuffers( 1, &opengl_ui.ebo ); + + glBindVertexArray( opengl_ui.vao ); + glBindBuffer( GL_ARRAY_BUFFER, opengl_ui.vbo ); + glBufferData( GL_ARRAY_BUFFER, verts*sizeof(ui_vert), NULL, GL_DYNAMIC_DRAW ); + + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, opengl_ui.ebo ); + glBufferData( GL_ELEMENT_ARRAY_BUFFER, + indices*sizeof(u16), NULL, GL_DYNAMIC_DRAW ); + + /* Set pointers */ + u32 const stride = sizeof(ui_vert); + + /* XY */ + glVertexAttribPointer( 0, 2, GL_SHORT, GL_FALSE, stride, + (void *)offsetof( ui_vert, co ) ); + glEnableVertexAttribArray( 0 ); + + /* UV */ + glVertexAttribPointer( 1, 2, GL_UNSIGNED_SHORT, GL_FALSE, stride, + (void *)offsetof( ui_vert, uv ) ); + glEnableVertexAttribArray( 1 ); + + /* COLOUR */ + glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, + (void *)offsetof( ui_vert, colour ) ); + glEnableVertexAttribArray( 2 ); + +#ifdef VG_ANDROID +#else + opengl_ui.cursor_map[ k_ui_cursor_default ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_ARROW ); + opengl_ui.cursor_map[ k_ui_cursor_hand ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_HAND ); + opengl_ui.cursor_map[ k_ui_cursor_ibeam ] = + SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_IBEAM ); +#endif + + /* font + * ----------------------------------------------------- + */ + + /* Load default font */ + + vg_font_sheet *sheet = &vg_default_font_sheet; + u32 pixels = 0, + total = sheet->w*sheet->h, + data = 0; + + vg_linear_clear( vg_mem.scratch ); + u8 *image = vg_linear_alloc( vg_mem.scratch, total ); + + while( pixels < total ) + { + for( int b = 31; b >= 0; b-- ) + { + image[ pixels ++ ] = (sheet->bitmap[data] & (0x1u << b))? 0xffu: 0x00u; + + if( pixels >= total ) + { + total = 0; + break; + } + } + data++; + } + + opengl_ui.ctx.inverse_font_sheet[0] = 1.0/(f64)sheet->w; + opengl_ui.ctx.inverse_font_sheet[1] = 1.0/(f64)sheet->h; + + glGenTextures( 1, &opengl_ui.tex_glyphs ); + glBindTexture( GL_TEXTURE_2D, opengl_ui.tex_glyphs ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, sheet->w, sheet->h, 0, + GL_RED, GL_UNSIGNED_BYTE, image ); + + VG_CHECK_GL_ERR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); +} + +void ui_free(void) +{ + vg_free_shader( &_shader_ui ); + ui_bind_context( NULL ); + free( opengl_ui.ctx.indice_buffer ); + free( opengl_ui.ctx.vertex_buffer ); +} + +void ui_impl_render_batch( ui_batch *batch ) +{ + glBindVertexArray( opengl_ui.vao ); + glBindBuffer( GL_ARRAY_BUFFER, opengl_ui.vbo ); + glBufferSubData( GL_ARRAY_BUFFER, + batch->vert_offset, + batch->vert_count*sizeof(ui_vert), + batch->vert_buf ); + + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, opengl_ui.ebo ); + glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, + batch->indice_offset, + batch->indice_count*sizeof(u16), + batch->indice_buf ); + + glDisable( GL_DEPTH_TEST ); + glDisable( GL_CULL_FACE ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glBlendEquation( GL_FUNC_ADD ); + + if( batch->shader == k_ui_shader_colour ) + { + glUseProgram( _shader_ui.id ); + glUniformMatrix3fv( glGetUniformLocation( _shader_ui.id, "uPv" ), 1, + GL_FALSE, (float *)opengl_ui.pv ); + glUniform4fv( glGetUniformLocation( _shader_ui.id, "uColour" ), 1, + g_ui_ctx->colour ); + + glActiveTexture( GL_TEXTURE0 ); + glBindTexture( GL_TEXTURE_2D, opengl_ui.tex_glyphs ); + glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexGlyphs" ), 0 ); + + glActiveTexture( GL_TEXTURE1 ); + glBindTexture( GL_TEXTURE_2D, g_ui_ctx->tex_bg ); + glUniform1i( glGetUniformLocation( _shader_ui.id, "uTexBG" ), 1 ); + glUniform1f( glGetUniformLocation( _shader_ui.id, "uSpread" ), + g_ui_ctx->frosting ); + glUniform2fv( glGetUniformLocation( _shader_ui.id, "uBGInverseRatio" ), + 1, g_ui_ctx->bg_inverse_ratio ); + glUniform2fv( glGetUniformLocation( _shader_ui.id, "uInverseFontSheet" ), + 1, g_ui_ctx->inverse_font_sheet ); + } + else if( batch->shader == k_ui_shader_image ) + { + glUseProgram( _shader_ui_image.id ); + glUniformMatrix3fv( glGetUniformLocation( _shader_ui_image.id, "uPv" ), 1, + GL_FALSE, (float *)opengl_ui.pv ); + glUniform1i( glGetUniformLocation(_shader_ui_image.id,"uTexImage"), 0 ); + glUniform4fv( glGetUniformLocation( _shader_ui_image.id, "uColour" ), 1, + g_ui_ctx->colour ); + } + else if( batch->shader == k_ui_shader_hsv ) + { + glUseProgram( _shader_ui_hsv.id ); + glUniformMatrix3fv( glGetUniformLocation( _shader_ui_hsv.id, "uPv" ), 1, + GL_FALSE, (float *)opengl_ui.pv ); + glUniform1f( glGetUniformLocation(_shader_ui_hsv.id,"uHue"), + g_ui_ctx->hue ); + } + else + vg_fatal_error( "Invalid UI shader (%d)\n", batch->shader ); + + glDrawElements( GL_TRIANGLES, batch->indice_count, GL_UNSIGNED_SHORT, + (void *)((size_t)batch->indice_offset) ); +} + +void ui_post_update(void) +{ +#ifdef VG_ANDROID +#else + if( g_ui_ctx->wants_mouse ) + { + SDL_SetWindowGrab( vg.window, SDL_FALSE ); + SDL_SetRelativeMouseMode( SDL_FALSE ); + } + else + { + SDL_SetWindowGrab( vg.window, SDL_TRUE ); + SDL_SetRelativeMouseMode( SDL_TRUE ); + } + + SDL_SetCursor( opengl_ui.cursor_map[ g_ui_ctx->cursor ] ); + SDL_ShowCursor(1); +#endif +} + +void ui_set_mouse_pos( ui_px x, ui_px y ) +{ +#ifdef VG_ANDROID +#else + SDL_WarpMouseInWindow( vg.window, x, y ); + g_ui_ctx->mouse[0] = x; + g_ui_ctx->mouse[1] = y; + g_ui_ctx->mouse_pos_overriden = 1; +#endif +}