#define EDITOR_VERSION "2022.7" #if 1 #define EDITOR_PRINTF PRINTF #else #define EDITOR_PRINTF(...) do {} while(0) #endif #define ICON_PLAY ICON_MD_PLAY_ARROW #define ICON_PAUSE ICON_MD_PAUSE #define ICON_STOP ICON_MD_STOP #define ICON_CANCEL ICON_MD_CLOSE #define ICON_WARNING ICON_MD_WARNING #define ICON_BROWSER ICON_MD_FOLDER_SPECIAL #define ICON_OUTLINER ICON_MD_VIEW_IN_AR #define ICON_BUILD ICON_MD_BUILD #define ICON_SCREENSHOT ICON_MD_PHOTO_CAMERA #define ICON_CAMERA_ON ICON_MD_VIDEOCAM #define ICON_CAMERA_OFF ICON_MD_VIDEOCAM_OFF #define ICON_GAMEPAD_ON ICON_MD_VIDEOGAME_ASSET #define ICON_GAMEPAD_OFF ICON_MD_VIDEOGAME_ASSET_OFF #define ICON_AUDIO_ON ICON_MD_VOLUME_UP #define ICON_AUDIO_OFF ICON_MD_VOLUME_OFF #define ICON_WINDOWED ICON_MD_FULLSCREEN_EXIT #define ICON_FULLSCREEN ICON_MD_FULLSCREEN #define ICON_LIGHTS_ON ICON_MD_LIGHTBULB #define ICON_LIGHTS_OFF ICON_MD_LIGHTBULB_OUTLINE #define ICON_RENDER_BASIC ICON_MD_IMAGE_SEARCH #define ICON_RENDER_FULL ICON_MD_INSERT_PHOTO #define ICON_SIGNAL ICON_MD_SIGNAL_CELLULAR_ALT #define ICON_DISK ICON_MD_STORAGE #define ICON_RATE ICON_MD_SPEED #define ICON_CLOCK ICON_MD_TODAY #define ICON_CHRONO ICON_MD_TIMELAPSE #define ICON_SETTINGS ICON_MD_SETTINGS #define ICON_LANGUAGE ICON_MD_G_TRANSLATE #define ICON_PERSONA ICON_MD_FACE #define ICON_SOCIAL ICON_MD_MESSAGE #define ICON_GAME ICON_MD_ROCKET_LAUNCH #define ICON_KEYBOARD ICON_MD_KEYBOARD #define ICON_MOUSE ICON_MD_MOUSE #define ICON_GAMEPAD ICON_MD_GAMEPAD #define ICON_MONITOR ICON_MD_MONITOR #define ICON_WIFI ICON_MD_WIFI #define ICON_BUDGET ICON_MD_SAVINGS #define ICON_NEW_FOLDER ICON_MD_CREATE_NEW_FOLDER #define ICON_PLUGIN ICON_MD_EXTENSION #define ICON_RESTART ICON_MD_REPLAY #define ICON_QUIT ICON_MD_CLOSE #define ICON_POWER ICON_MD_BOLT // ICON_MD_POWER #define ICON_BATTERY_CHARGING ICON_MD_BATTERY_CHARGING_FULL #define ICON_BATTERY_LEVELS \ ICON_MD_BATTERY_ALERT, \ ICON_MD_BATTERY_0_BAR,ICON_MD_BATTERY_1_BAR, \ ICON_MD_BATTERY_2_BAR,ICON_MD_BATTERY_3_BAR, \ ICON_MD_BATTERY_4_BAR,ICON_MD_BATTERY_5_BAR, \ ICON_MD_BATTERY_6_BAR,ICON_MD_BATTERY_FULL // state - retained mode typedef int property; // @fixme typedef struct editor_state_t { array(property) properties; array(char) buffer; array(vec2i) history; unsigned cursor; } editor_state_t; typedef map(char*, char*) editor_dict_t; static map(void*, editor_state_t) editor_state; // world static map(void*, editor_dict_t) editor_dicts; static set(void*) editor_world; static set(void*) editor_selection; // objects selected in scene // editor controls //static int editor_attached = 1; static int editor_enabled = 1; static void* editor_selected_obj = 0; static int editor_key = 0; static vec2 editor_mouse = {0}; // 2d coord for ray/picking static bool editor_gamepad = 1; static int editor_hz = 60; static int editor_hz_mid = 18; static int editor_hz_low = 5; static bool editor_power_saving = 0; static double editor_t = 0, editor_dt = 0; static bool editor_lit = 1; static bool editor_ddraw = 1; static void editor_init() { do_once { map_init_ptr(editor_state); map_init_ptr(editor_dicts); set_init_ptr(editor_world); set_init_ptr(editor_selection); profiler_enable( false ); window_pause( true ); } } void editor_tick_() { // timing editor_dt = window_delta() * !window_has_pause(); if(editor_dt > 1/60.f) editor_dt = 1/60.f; } bool editor_active() { return ui_hover() || ui_active() || gizmo_active() ? editor_enabled : 0; } double editor_ss() { return 1000 + editor_t; } double editor_delta() { return editor_dt; } void editor_select_all() {} void editor_select_none() {} enum editor_keys { key_none, key_pause, key_reload, key_browser, key_recording, key_fullscreen, key_screenshot, // @todo: add meta-info in exif or invisibile pixels (cam details, player details, map level, map location, level state, etc) key_quit, key_mute, key_battery, key_profiler, key_stop, key_outliner, key_undo, key_redo, key_save_mem, key_save_disk, key_load_disk, key_reset, key_debugger, key_gamepad, key_lit, key_ddraw, }; void editor_menubar() { do_once editor_init(); int alts = input(KEY_LALT) || input(KEY_RALT); // @todo: move to v4k.c int ctrls = input(KEY_LCTRL) || input(KEY_RCTRL); // @todo: move to v4k.c int shifts = input(KEY_LSHIFT) || input(KEY_RSHIFT); // @todo: move to v4k.c int mods = alts || ctrls || shifts; // @todo: move to v4k.c if( input_down(KEY_F5) ) editor_key = key_reload; if( input_down(KEY_F11) ) editor_key = key_fullscreen; if( input_down(KEY_PAUSE) ) editor_key = key_pause; if( input_down(KEY_PRINT) ) editor_key = (mods ? key_recording : key_screenshot); // if( input_down(KEY_W) && input_held(KEY_LCTRL) ) editor_key = key_quit; if( ctrls ) { /**/ if( input_down(KEY_Z) ) editor_key = key_undo; else if( input_down(KEY_Y) ) editor_key = key_redo; else if( input_down(KEY_S) ) editor_key = key_save_disk; else if( input_down(KEY_A) ) editor_select_all(); else if( input_down(KEY_D) ) editor_select_none(); } if( !editor_key && editor_selected_obj ) { if( input_up(MOUSE_L) ) editor_key = key_save_mem; if( input_down(MOUSE_R) ) ui_show("Properties", true); } // @fixme: send all editor keys to game? // if( input_repeat(KEY_ESC, 300)) {} // if( input_repeat(KEY_F1, 300)) {} // etc... // menubar if( ui_menu( ICON_SETTINGS "@Preferences;" ICON_LANGUAGE " Language;" ICON_PERSONA " Profile;" // editor account, but also fake profile and 1st party credentials ICON_SOCIAL " Social;" ICON_GAME " Game;" // ICON_KEYBOARD " Keyboard;" ICON_MOUSE " Mouse;" ICON_GAMEPAD " Gamepad;" ICON_MONITOR " Display;" // @todo: RENDER settings, AUDIO settings ICON_WIFI " Network;" ICON_BUDGET " Budget;" // mem,gfx,net,hdd,... also logging ICON_NEW_FOLDER " Folders;" // including project folders ICON_PLUGIN " Plugins;" // including VCS ICON_RESTART " Restart;" ICON_QUIT " Quit;" "-" ICON_MD_RECYCLING " Reset all preferences;" ICON_MD_SAVE_AS " Save all preferences" ) ) { if( ui_item() == 3 ) {} // key mappings if( ui_item() == 4 ) {} // sensitivity, invert xylrw if( ui_item() == 5 ) {} // sensitivity, invert xy,ab, deadzones if( ui_item() == 7 ) {} // name,email,icon,link,github if( ui_item() == 13) editor_key = key_reload; if( ui_item() == 14) editor_key = key_quit; } static char game_args[16] = "--game-args"; // @fixme @todo remove '_' special char to signal that ui_menu() is writeable (inputbox) if( ui_menu_editbox( game_args, 16 ) ) {} if( ui_menu( ICON_CANCEL "@Cancel" ) ) {} if( ui_menu( window_has_pause() ? ICON_PLAY "@Tap to Play Game" : ICON_PAUSE "@Tap to Pause Game" )) editor_key = key_pause; if( ui_menu( ICON_MD_SKIP_NEXT "@Next frame") ) {} if( ui_menu( ICON_MD_FAST_FORWARD "@Fast forward") ) {} //if( ui_menu( ICON_STOP "@Stop game" )) editor_key = key_stop; if( ui_menu( va(ICON_BUILD "@Build game"))) ui_notify("Build", ICON_WARNING " Not implemented."); if( ui_menu( va(ICON_MD_ROCKET_LAUNCH "@Launch game"))) ui_notify("Launch", ICON_WARNING " Not implemented."); // ICON_MD_TROUBLESHOOT -> PROFILER // ICON_MD_SCHEMA -> GRAPHNODES // ICON_MD_ACCOUNT_TREE -> GRAPHNODES // ICON_MD_TIPS_AND_UPDATES -> BULB // if( ui_menu( ICON_MD_MENU )) {} // if( ui_menu( ICON_BROWSER "@Content browser" )) editor_key = key_browser; // if( ui_menu( va(ICON_OUTLINER " %d/%d@World outliner", set_count(editor_selection), map_count(editor_state) ))) editor_key = key_outliner; if( ui_menu( ICON_SCREENSHOT "@Take Screenshot" )) editor_key = key_screenshot; // MD_SCREENSHOT_MONITOR if( ui_menu( record_active() ? ICON_CAMERA_OFF "@Stop video recording" : ICON_CAMERA_ON "@Start video recording" )) { if(record_active()) record_stop(); else editor_key = key_recording; } if( ui_menu( editor_gamepad ? ICON_GAMEPAD_ON "@Gamepad is enabled. Tap to disable." : ICON_GAMEPAD_OFF "@Gamepad is disabled. Tap to enable." )) editor_key = key_gamepad; if( ui_menu( audio_volume_master(-1) > 0 ? ICON_AUDIO_ON "@Audio is enabled. Tap to mute." : ICON_AUDIO_OFF "@Audio is muted. Tap to enable." )) editor_key = key_mute; if( ui_menu( window_has_fullscreen() ? ICON_WINDOWED "@Fullscreen. Tap to go Windowed." : ICON_FULLSCREEN "@Windowed. Tap to go Fullscreen." )) editor_key = key_fullscreen; if( ui_menu( editor_ddraw ? ICON_RENDER_BASIC "@Debug renderer. Tap to go Retail Renderer." : ICON_RENDER_FULL "@Retail renderer. Tap to go Debug Renderer." )) editor_key = key_ddraw; // ICON_MD_ADD_PHOTO_ALTERNATE if( ui_menu( editor_lit ? ICON_LIGHTS_ON "@Lit. Tap to disable lighting." : ICON_LIGHTS_OFF "@Unlit. Tap to enable lighting." )) editor_key = key_lit; // logic: either plug icon (power saving off) or one of the following ones (power saving on): // if 0% batt (no batt): battery alert // if discharging: battery levels [alert,0..6,full] // if charging: battery charging int battery_read = app_battery(); int battery_level = abs(battery_read); int battery_discharging = battery_read < 0 && battery_level < 100; const char *battery_levels[] = { // @todo: remap [7%..100%] -> [0..1] ? ICON_BATTERY_LEVELS }; if( ui_menu( !editor_power_saving ? ICON_POWER"@Full power. Tap to save power." : va("%s@Power saving. Tap to go full power. %3d%% battery.", battery_read == 0 ? battery_levels[0] : battery_discharging ? battery_levels[(int)((countof(battery_levels)-1)*clampf(battery_level/100.f,0,1))] : ICON_BATTERY_CHARGING, battery_level) )) editor_key = key_battery; // @todo: combine-in-1? cycle mem -> cpu/profiler -> network mon -> debugger // bug report, signal status, disk status, framerate status if( ui_menu(va(ICON_SIGNAL " 0/0KiB" ))) {} // SIGNAL_CELLULAR_1_BAR SIGNAL_CELLULAR_2_BAR if( ui_menu(va(ICON_DISK " %s", xstats() ))) {} // 012/136MB if( ui_menu(va(ICON_RATE " %5.2f/%d", window_fps(), (int)window_fps_target()))) editor_key = key_profiler; // 012/136MB // bug report, profile, warnings, time/chrono (@todo: alarm/snooze? calendar?) if( ui_menu( ICON_MD_BUG_REPORT /*"^"*/ "0" ) ) {} if( ui_menu( ICON_MD_FACE /*"^"*/ "3" ) ) {} // @todo: do both messaging/warnings + profile settings here { static double base = 0, tap = 0; if( tap == 0 ) tap = time_ss(); double delta = time_ss() - tap; tap = time_ss(); base += delta * !window_has_pause(); if( ui_menu( base == 0 ? va(ICON_CLOCK "%02d:%02d", (int)((date() / 10000) % 100), (int)((date() / 100) % 100)) : va(ICON_CHRONO "%03dm:%02ds:%02df@Tap to reset chrono.",((int)(base/60))%60,((int)base)%60,(int)((base - (int)base)*window_fps_target()))) || editor_key == key_stop ) { base = 0, tap = 0; } } for each_map_ptr(editor_state, void *, o, editor_state_t, ed) { profile_incstat("Editor.num_objects", +1); void *obj = *o; #if 1 #elif 0 // auto-load from disk during init. @fixme kvdb database if( array_count(ed->history) == 0 ) if( editor_load_disk(obj, editor_obj_string(obj, ".path")) ) {} // auto-save in-mem during first edit if( array_count(ed->history) == 0 ) editor_save_mem(obj); #endif // @todo: continue if obj not found in selection set if( obj != editor_selected_obj ) continue; if( editor_key == key_debugger ) { breakpoint("User requested breakpoint on this object"); } #if 1 #elif 0 if( editor_key == key_reset ) { const char *ok = editor_reset(obj) ? "ok" : "err"; EDITOR_PRINTF("reset: %s\n", ok); } if( editor_key == key_save_mem ) { const char *ok = editor_save_mem(obj) ? "ok" : "err"; EDITOR_PRINTF("mem saved: %s\n", ok); } if( editor_key == key_undo ) { const char *ok = editor_undo(obj) ? "ok" : "err"; EDITOR_PRINTF("undo: %s\n", ok); } if( editor_key == key_redo ) { const char *ok = editor_redo(obj) ? "ok" : "err"; EDITOR_PRINTF("redo: %s\n", ok); } if( editor_key == key_save_disk ) { const char *ok = editor_save_disk(obj, editor_obj_string(obj, ".path")) ? "ok" : "err"; EDITOR_PRINTF("save: %s\n", ok); } if( editor_key == key_load_disk ) { const char *ok = editor_load_disk(obj, editor_obj_string(obj, ".path")) ? "ok" : "err"; EDITOR_PRINTF("load: %s\n", ok); } #endif } char *name; switch( editor_key ) { default: break; case key_quit: record_stop(), exit(0); break; case key_stop: window_pause(1); break; case key_mute: audio_volume_master( 1 ^ !!audio_volume_master(-1) ); break; case key_pause: window_pause( window_has_pause() ^ 1 ); break; case key_reload: window_reload(); break; case key_battery: editor_power_saving ^= 1; break; case key_browser: ui_show("File Browser", ui_visible("File Browser") ^ true); break; case key_outliner: ui_show("Outliner", ui_visible("Outliner") ^ true); break; case key_recording: name = file_counter(va("%s.mp4",app_name())), window_record(name), ui_notify(va("Video capturing: %s", name), date_string()); break; case key_screenshot: name = file_counter(va("%s.png",app_name())), window_screenshot(name), ui_notify(va("Screenshot: %s", name), date_string()); break; case key_profiler: ui_show("Profiler", profiler_enable(ui_visible("Profiler") ^ true)); break; case key_fullscreen: record_stop(), window_fullscreen( window_has_fullscreen() ^ 1 ); // framebuffer resizing corrupts video stream, so stop any recording beforehand break; case key_gamepad: editor_gamepad ^= 1; break; case key_lit: editor_lit ^= 1; break; case key_ddraw: editor_ddraw ^= 1; } editor_key = 0; }