<commit_before>#include <imgui/imgui.h>
#include <imgui/IconsFontAwesome4.h>

#include "studio_app.h"
#include "asset_browser.h"
#include "audio/audio_scene.h"
#include "editor/asset_compiler.h"
#include "editor/file_system_watcher.h"
#include "editor/gizmo.h"
#include "editor/prefab_system.h"
#include "editor/render_interface.h"
#include "editor/world_editor.h"
#include "engine/command_line_parser.h"
#include "engine/crc32.h"
#include "engine/debug.h"
#include "engine/allocator.h"
#include "engine/engine.h"
#include "engine/file_system.h"
#include "engine/geometry.h"
#include "engine/input_system.h"
#include "engine/job_system.h"
#include "engine/log.h"
#include "engine/lua_wrapper.h"
#include "engine/mt/thread.h"
#include "engine/os.h"
#include "engine/path_utils.h"
#include "engine/plugin_manager.h"
#include "engine/profiler.h"
#include "engine/reflection.h"
#include "engine/resource_manager.h"
#include "engine/universe/universe.h"
#include "log_ui.h"
#include "profiler_ui.h"
#include "property_grid.h"
#include "settings.h"
#include "utils.h"

#ifdef _WIN32
	#include "engine/win/simple_win.h"
#endif

namespace Lumix
{
	

struct TarHeader {
	char name[100];
	char mode[8];
	char uid[8];
	char gid[8];
	char size[12];
	char mtime[12];
	char chksum[8];
	char typeflag;
	char linkname[100];
	char magic[6];
	char version[2];
	char uname[32];
	char gname[32];
	char devmajor[8];
	char devminor[8];
	char prefix[155];   
};


#define NO_ICON "       "


struct LuaPlugin : public StudioApp::GUIPlugin
{
	LuaPlugin(StudioApp& app, const char* src, const char* filename)
		: editor(app.getWorldEditor())
	{
		L = lua_newthread(editor.getEngine().getState());						 // [thread]
		thread_ref = luaL_ref(editor.getEngine().getState(), LUA_REGISTRYINDEX); // []

		lua_newtable(L);						  // [env]
												  // reference environment
		lua_pushvalue(L, -1);					  // [env, env]
		env_ref = luaL_ref(L, LUA_REGISTRYINDEX); // [env]

		// environment's metatable & __index
		lua_pushvalue(L, -1);	// [env, env]
		lua_setmetatable(L, -2); // [env]
		lua_pushvalue(L, LUA_GLOBALSINDEX);
		lua_setfield(L, -2, "__index"); // [env]

		bool errors = luaL_loadbuffer(L, src, stringLength(src), filename) != 0; // [env, func]

		lua_pushvalue(L, -2); // [env, func, env]
		lua_setfenv(L, -2);   // function's environment [env, func]

		errors = errors || lua_pcall(L, 0, 0, 0) != 0; // [env]
		if (errors)
		{
			logError("Editor") << filename << ": " << lua_tostring(L, -1);
			lua_pop(L, 1);
		}

		lua_getfield(L, -1, "plugin_name"); // [env, plugin_name]
		const char* name = "LuaPlugin";
		if (lua_type(L, -1) == LUA_TSTRING) {
			name = lua_tostring(L, -1);
		}

		lua_pop(L, 2); // []

		Action* action = LUMIX_NEW(editor.getAllocator(), Action)(name, name, name);
		action->func.bind<&LuaPlugin::onAction>(this);
		app.addWindowAction(action);
		m_is_open = false;

		lua_pop(L, 1); // plugin_name
	}


	~LuaPlugin()
	{
		lua_State* L = editor.getEngine().getState();
		luaL_unref(L, LUA_REGISTRYINDEX, env_ref);
		luaL_unref(L, LUA_REGISTRYINDEX, thread_ref);
	}


	const char* getName() const override { return "lua_script"; }


	void onAction() { m_is_open = !m_is_open; }


	void onWindowGUI() override
	{
		if (!m_is_open) return;

		lua_rawgeti(L, LUA_REGISTRYINDEX, env_ref); // [env]
		lua_getfield(L, -1, "onGUI"); // [env, onGUI]
		if (lua_type(L, -1) == LUA_TFUNCTION) {
			if (lua_pcall(L, 0, 0, 0) != 0) {
				logError("Editor") << "LuaPlugin:" << lua_tostring(L, -1);
				lua_pop(L, 1);
			}
			lua_pop(L, 1);
		}
		else {
			lua_pop(L, 2);
		}
	}

	WorldEditor& editor;
	lua_State* L;
	int thread_ref;
	int env_ref;
	bool m_is_open;
};


class StudioAppImpl final : public StudioApp
{
public:
	StudioAppImpl()
		: m_is_entity_list_open(true)
		, m_is_save_as_dialog_open(false)
		, m_finished(false)
		, m_deferred_game_mode_exit(false)
		, m_profiler_ui(nullptr)
		, m_asset_browser(nullptr)
		, m_asset_compiler(nullptr)
		, m_property_grid(nullptr)
		, m_actions(m_allocator)
		, m_window_actions(m_allocator)
		, m_toolbar_actions(m_allocator)
		, m_is_welcome_screen_open(true)
		, m_is_pack_data_dialog_open(false)
		, m_editor(nullptr)
		, m_settings(*this)
		, m_gui_plugins(m_allocator)
		, m_plugins(m_allocator)
		, m_add_cmp_plugins(m_allocator)
		, m_component_labels(m_allocator)
		, m_confirm_load(false)
		, m_confirm_new(false)
		, m_confirm_exit(false)
		, m_exit_code(0)
		, m_allocator(m_main_allocator)
		, m_universes(m_allocator)
		, m_events(m_allocator)
		, m_windows(m_allocator)
	{
		JobSystem::init(MT::getCPUsCount(), m_allocator);
	}


	void onEvent(const OS::Event& event) override
	{
		const bool handle_input = isFocused();
		m_events.push(event);
		switch (event.type) {
			case OS::Event::Type::FOCUS: break;
			case OS::Event::Type::MOUSE_BUTTON: {
				ImGuiIO& io = ImGui::GetIO();
				m_editor->setToggleSelection(io.KeyCtrl);
				m_editor->setSnapMode(io.KeyShift, io.KeyCtrl);
				if (handle_input || !event.mouse_button.down) {
					io.MouseDown[(int)event.mouse_button.button] = event.mouse_button.down;
				}
				break;
			}
			case OS::Event::Type::MOUSE_WHEEL:
				if (handle_input) {
					ImGuiIO& io = ImGui::GetIO();
					io.MouseWheel = event.mouse_wheel.amount;
				}
				break;
			case OS::Event::Type::MOUSE_MOVE:
				if (handle_input) {
					m_mouse_move += {(float)event.mouse_move.xrel, (float)event.mouse_move.yrel};
				}
				break;
			case OS::Event::Type::WINDOW_SIZE:
				if (ImGui::GetCurrentContext()) {
					ImGuiViewport* vp = ImGui::FindViewportByPlatformHandle(event.window);
					if (vp) vp->PlatformRequestResize = true;
				}
				if (event.window == m_main_window && event.win_size.h > 0 && event.win_size.w > 0) {
					m_settings.m_window.w = event.win_size.w;
					m_settings.m_window.h = event.win_size.h;
					m_settings.m_is_maximized = OS::isMaximized(m_main_window);
				}
				break;
			case OS::Event::Type::WINDOW_MOVE:
				if (ImGui::GetCurrentContext()) {
					ImGuiViewport* vp = ImGui::FindViewportByPlatformHandle(event.window);
					if (vp) vp->PlatformRequestMove = true;
				}
				if (event.window == m_main_window) {
					m_settings.m_window.x = event.win_move.x;
					m_settings.m_window.y = event.win_move.y;
					m_settings.m_is_maximized = OS::isMaximized(m_main_window);
				}
				break;
			case OS::Event::Type::WINDOW_CLOSE: {
				ImGuiViewport* vp = ImGui::FindViewportByPlatformHandle(event.window);
				if (vp) vp->PlatformRequestClose = true;
				if (event.window == m_main_window) exit();
				break;
			}
			case OS::Event::Type::QUIT:
				exit();
				break;
			case OS::Event::Type::CHAR:
				if (handle_input) {
					ImGuiIO& io = ImGui::GetIO();
					char utf8[5];
					OS::UTF32ToUTF8(event.text_input.utf32, utf8);
					utf8[4] = 0;
					io.AddInputCharactersUTF8(utf8);
				}
				break;
			case OS::Event::Type::KEY:
				if (handle_input) {
					ImGuiIO& io = ImGui::GetIO();
					io.KeysDown[(int)event.key.keycode] = event.key.down;
					io.KeyShift = OS::isKeyDown(OS::Keycode::SHIFT);
					io.KeyCtrl = OS::isKeyDown(OS::Keycode::CTRL);
					io.KeyAlt = OS::isKeyDown(OS::Keycode::MENU);
					checkShortcuts();
				}
				break;
			case OS::Event::Type::DROP_FILE:
				for(int i = 0, c = OS::getDropFileCount(event); i < c; ++i) {
					char tmp[MAX_PATH_LENGTH];
					OS::getDropFile(event, i, Span(tmp));
					if (!m_asset_browser->onDropFile(tmp)) {
						for (GUIPlugin* plugin : m_gui_plugins) {
							if (plugin->onDropFile(tmp)) break;
						}
					}
				}
				break;
			default: ASSERT(false); break;
		}
	}

	bool isFocused() const {
		const OS::WindowHandle focused = OS::getFocused();
		const int idx = m_windows.find([focused](OS::WindowHandle w){ return w == focused; });
		return idx >= 0;
	}

	void onIdle() override
	{
		update();

		if (m_sleep_when_inactive && !isFocused()) {
			const float frame_time = m_inactive_fps_timer.tick();
			const float wanted_fps = 5.0f;

			if (frame_time < 1 / wanted_fps) {
				PROFILE_BLOCK("sleep");
				MT::sleep(u32(1000 / wanted_fps - frame_time * 1000));
			}
			m_inactive_fps_timer.tick();
		}

		Profiler::frame();
		m_events.clear();
	}


	void run() override
	{
		JobSystem::SignalHandle finished = JobSystem::INVALID_HANDLE;
		JobSystem::runEx(this, [](void* data) {
			Lumix::OS::run(*(StudioAppImpl*)data);
		}, &finished, JobSystem::INVALID_HANDLE, 0);
		Profiler::setThreadName("Main thread");
		JobSystem::wait(finished);
	}

	
	static void* imguiAlloc(size_t size, void* user_data) {
		StudioAppImpl* app = (StudioAppImpl*)user_data;
		return app->m_allocator.allocate(size);
	}


	static void imguiFree(void* ptr, void* user_data) {
		StudioAppImpl* app = (StudioAppImpl*)user_data;
		return app->m_allocator.deallocate(ptr);
	}


	void onInit() override
	{
		OS::Timer init_timer;

		m_add_cmp_root.label[0] = '\0';
		m_template_name[0] = '\0';
		m_open_filter[0] = '\0';

		checkWorkingDirectory();

		char saved_data_dir[MAX_PATH_LENGTH] = {};
		OS::InputFile cfg_file;
		if (cfg_file.open(".lumixuser")) {
			cfg_file.read(saved_data_dir, minimum(lengthOf(saved_data_dir), (int)cfg_file.size()));
			cfg_file.close();
		}

		char current_dir[MAX_PATH_LENGTH];
		OS::getCurrentDirectory(Span(current_dir));

		char data_dir[MAX_PATH_LENGTH] = {};
		checkDataDirCommandLine(data_dir, lengthOf(data_dir));
		m_engine = Engine::create(data_dir[0] ? data_dir : (saved_data_dir[0] ? saved_data_dir : current_dir)
			, m_allocator);
		createLua();

		extractBundled();

		OS::InitWindowArgs create_win_args;
		create_win_args.handle_file_drops = true;
		create_win_args.name = "Lumix Studio";
		m_main_window = OS::createWindow(create_win_args);
		m_windows.push(m_main_window);

		const char* plugins[] = { ""
			#ifdef LUMIXENGINE_PLUGINS
				, LUMIXENGINE_PLUGINS
			#endif
		};

		PluginManager& plugin_manager = m_engine->getPluginManager();
		for (auto* plugin_name : plugins) {
			if (plugin_name[0] && !plugin_manager.load(plugin_name)) {
				logInfo("Editor") << plugin_name << " plugin has not been loaded";
			}
		}

		Engine::PlatformData platform_data = {};
		platform_data.window_handle = m_main_window;
		m_engine->setPlatformData(platform_data);

		plugin_manager.initPlugins();

		m_editor = WorldEditor::create(current_dir, *m_engine, m_allocator);
		m_settings.m_editor = m_editor;
		scanUniverses();
		loadUserPlugins();
		addActions();

		m_asset_compiler = AssetCompiler::create(*this);
		m_asset_browser = LUMIX_NEW(m_allocator, AssetBrowser)(*this);
		m_property_grid = LUMIX_NEW(m_allocator, PropertyGrid)(*this);
		m_profiler_ui = ProfilerUI::create(*m_engine);
		m_log_ui = LUMIX_NEW(m_allocator, LogUI)(m_editor->getAllocator());

		ImGui::SetAllocatorFunctions(imguiAlloc, imguiFree, this);
		ImGui::CreateContext();
		loadSettings();
		initIMGUI();

		m_custom_pivot_action = LUMIX_NEW(m_editor->getAllocator(), Action)("Set Custom Pivot",
			"Set Custom Pivot",
			"set_custom_pivot",
			OS::Keycode::K,
			OS::Keycode::INVALID,
			OS::Keycode::INVALID);
		m_custom_pivot_action->is_global = false;
		addAction(m_custom_pivot_action);

		setStudioApp();
		loadIcons();
		loadSettings();
		loadUniverseFromCommandLine();
		findLuaPlugins("plugins/lua/");

		m_asset_compiler->onInitFinished();
		m_sleep_when_inactive = shouldSleepWhenInactive();

		checkScriptCommandLine();

		logInfo("Editor") << "Startup took " << init_timer.getTimeSinceStart() << " s"; 
	}


	~StudioAppImpl()
	{
		ImGuiIO& io = ImGui::GetIO();
		if (io.WantSaveIniSettings) {
			size_t size;
			const char* data = ImGui::SaveIniSettingsToMemory(&size);
			FileSystem& fs = m_editor->getEngine().getFileSystem();
			OS::OutputFile file;
			if (fs.open("imgui.ini", Ref(file))) {
				file.write(data ,size);
				file.close();
			}
		}

		if (m_watched_plugin.watcher) FileSystemWatcher::destroy(m_watched_plugin.watcher);

		saveSettings();
		unloadIcons();

		while (m_editor->getEngine().getFileSystem().hasWork())
		{
			m_editor->getEngine().getFileSystem().processCallbacks();
		}

		m_editor->newUniverse();

		destroyAddCmpTreeNode(m_add_cmp_root.child);

		for (auto* i : m_plugins)
		{
			LUMIX_DELETE(m_editor->getAllocator(), i);
		}
		m_plugins.clear();
		PrefabSystem::destroyEditorPlugins(*this);
		ASSERT(m_gui_plugins.empty());

		for (auto* i : m_add_cmp_plugins)
		{
			LUMIX_DELETE(m_editor->getAllocator(), i);
		}
		m_add_cmp_plugins.clear();

		for (auto* a : m_actions)
		{
			LUMIX_DELETE(m_editor->getAllocator(), a);
		}
		m_actions.clear();

		ProfilerUI::destroy(*m_profiler_ui);
		LUMIX_DELETE(m_allocator, m_asset_browser);
		LUMIX_DELETE(m_allocator, m_property_grid);
		LUMIX_DELETE(m_allocator, m_log_ui);
		AssetCompiler::destroy(*m_asset_compiler);
		WorldEditor::destroy(m_editor, m_allocator);
		Engine::destroy(m_engine, m_allocator);
		m_engine = nullptr;
		m_editor = nullptr;
		
		OS::destroyWindow(m_main_window);
		JobSystem::shutdown();
	}


	bool makeFile(const char* path, const char* content) override
	{
		OS::OutputFile file;
		if (!m_engine->getFileSystem().open(path, Ref(file))) return false;
		file << content;
		file.close();
		return file.isError();
	}


	void destroyAddCmpTreeNode(AddCmpTreeNode* node)
	{
		if (!node) return;
		destroyAddCmpTreeNode(node->child);
		destroyAddCmpTreeNode(node->next);
		LUMIX_DELETE(m_allocator, node);
	}


	const char* getComponentTypeName(ComponentType cmp_type) const override
	{
		auto iter = m_component_labels.find(cmp_type);
		if (iter == m_component_labels.end()) return "Unknown";
		return iter.value().c_str();
	}


	const AddCmpTreeNode& getAddComponentTreeRoot() const override { return m_add_cmp_root; }


	void addPlugin(IAddComponentPlugin& plugin)
	{
		int i = 0;
		while (i < m_add_cmp_plugins.size() && compareString(plugin.getLabel(), m_add_cmp_plugins[i]->getLabel()) > 0)
		{
			++i;
		}
		m_add_cmp_plugins.insert(i, &plugin);

		auto* node = LUMIX_NEW(m_allocator, AddCmpTreeNode);
		copyString(node->label, plugin.getLabel());
		node->plugin = &plugin;
		insertAddCmpNode(m_add_cmp_root, node);
	}


	static void insertAddCmpNodeOrdered(AddCmpTreeNode& parent, AddCmpTreeNode* node)
	{
		if (!parent.child)
		{
			parent.child = node;
			return;
		}
		if (compareString(parent.child->label, node->label) > 0)
		{
			node->next = parent.child;
			parent.child = node;
			return;
		}
		auto* i = parent.child;
		while (i->next && compareString(i->next->label, node->label) < 0)
		{
			i = i->next;
		}
		node->next = i->next;
		i->next = node;
	}


	void insertAddCmpNode(AddCmpTreeNode& parent, AddCmpTreeNode* node)
	{
		for (auto* i = parent.child; i; i = i->next)
		{
			if (!i->plugin && startsWith(node->label, i->label))
			{
				insertAddCmpNode(*i, node);
				return;
			}
		}
		const char* rest = node->label + stringLength(parent.label);
		if (parent.label[0] != '\0') ++rest; // include '/'
		const char* slash = findSubstring(rest, "/");
		if (!slash)
		{
			insertAddCmpNodeOrdered(parent, node);
			return;
		}
		auto* new_group = LUMIX_NEW(m_allocator, AddCmpTreeNode);
		copyNString(Span(new_group->label), node->label, int(slash - node->label));
		insertAddCmpNodeOrdered(parent, new_group);
		insertAddCmpNode(*new_group, node);
	}


	void registerComponentWithResource(const char* type,
		const char* label,
		ResourceType resource_type,
		const Reflection::PropertyBase& property) override
	{
		struct Plugin final : public IAddComponentPlugin
		{
			void onGUI(bool create_entity, bool from_filter) override
			{
				ImGui::SetNextWindowSize(ImVec2(300, 300));
				const char* last = reverseFind(label, nullptr, '/');
				last = last && !from_filter ? last + 1 : label;
				if (last[0] == ' ') ++last;
				if (!ImGui::BeginMenu(last)) return;
				char buf[MAX_PATH_LENGTH];
				bool create_empty = ImGui::MenuItem("Empty");
				static u32 selected_res_hash = 0;
				if (asset_browser->resourceList(Span(buf), Ref(selected_res_hash), resource_type, 0, true) || create_empty)
				{
					if (create_entity)
					{
						EntityRef entity = editor->addEntity();
						editor->selectEntities(&entity, 1, false);
					}

					const Array<EntityRef>& selected_entites = editor->getSelectedEntities();
					editor->addComponent(selected_entites, type);
					if (!create_empty)
					{
						editor->setProperty(type,
							-1,
							*property,
							&editor->getSelectedEntities()[0],
							editor->getSelectedEntities().size(),
							buf,
							stringLength(buf) + 1);
					}
					ImGui::CloseCurrentPopup();
				}
				ImGui::EndMenu();
			}


			const char* getLabel() const override { return label; }

			PropertyGrid* property_grid;
			AssetBrowser* asset_browser;
			WorldEditor* editor;
			ComponentType type;
			ResourceType resource_type;
			const Reflection::PropertyBase* property;
			char label[50];
		};

		auto& allocator = m_editor->getAllocator();
		auto* plugin = LUMIX_NEW(allocator, Plugin);
		plugin->property_grid = m_property_grid;
		plugin->asset_browser = m_asset_browser;
		plugin->type = Reflection::getComponentType(type);
		plugin->editor = m_editor;
		plugin->property = &property;
		plugin->resource_type = resource_type;
		copyString(plugin->label, label);
		addPlugin(*plugin);

		m_component_labels.insert(plugin->type, String(label, m_allocator));
	}


	void registerComponent(const char* id, IAddComponentPlugin& plugin) override
	{
		addPlugin(plugin);
		m_component_labels.insert(Reflection::getComponentType(id), String(plugin.getLabel(), m_allocator));
	}


	void registerComponent(const char* type, const char* label) override
	{
		struct Plugin final : public IAddComponentPlugin
		{
			void onGUI(bool create_entity, bool from_filter) override
			{
				const char* last = reverseFind(label, nullptr, '/');
				last = last && !from_filter ? last + 1 : label;
				if (last[0] == ' ') ++last;
				if (ImGui::MenuItem(last))
				{
					if (create_entity)
					{
						EntityRef entity = editor->addEntity();
						editor->selectEntities(&entity, 1, false);
					}

					editor->addComponent(editor->getSelectedEntities(), type);
				}
			}


			const char* getLabel() const override { return label; }

			WorldEditor* editor;
			PropertyGrid* property_grid;
			ComponentType type;
			char label[64];
		};

		auto& allocator = m_editor->getAllocator();
		auto* plugin = LUMIX_NEW(allocator, Plugin);
		plugin->property_grid = m_property_grid;
		plugin->editor = m_editor;
		plugin->type = Reflection::getComponentType(type);
		copyString(plugin->label, label);
		addPlugin(*plugin);

		m_component_labels.insert(plugin->type, String(label, m_allocator));
	}


	const Array<Action*>& getActions() override { return m_actions; }


	Array<Action*>& getToolbarActions() override { return m_toolbar_actions; }


	void guiBeginFrame() const
	{
		PROFILE_FUNCTION();

		ImGuiIO& io = ImGui::GetIO();

		updateIMGUIMonitors();
		const OS::Rect rect = OS::getWindowClientRect(m_main_window);
		if (rect.width > 0 && rect.height > 0) {
			io.DisplaySize = ImVec2(float(rect.width), float(rect.height));
		}
		else if(io.DisplaySize.x <= 0) {
			io.DisplaySize.x = 800;
			io.DisplaySize.y = 600;
		}
		io.DeltaTime = m_engine->getLastTimeDelta();
		io.KeyShift = OS::isKeyDown(OS::Keycode::SHIFT);
		io.KeyCtrl = OS::isKeyDown(OS::Keycode::CTRL);
		io.KeyAlt = OS::isKeyDown(OS::Keycode::MENU);

		const OS::Point cp = OS::getMouseScreenPos();
		io.MousePos.x = (float)cp.x;
		io.MousePos.y = (float)cp.y;

		ImGui::NewFrame();
		ImGui::PushFont(m_font);
	}


	float showMainToolbar(float menu_height)
	{
		if (m_toolbar_actions.empty())
		{
			ImGui::SetCursorPosY(menu_height);
			return menu_height;
		}

		auto frame_padding = ImGui::GetStyle().FramePadding;
		float padding = frame_padding.y * 2;
		ImVec2 toolbar_size(ImGui::GetIO().DisplaySize.x, 24 + padding);
		ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
		const ImVec2 pos = ImGui::GetMainViewport()->Pos;
		if (ImGui::BeginToolbar("main_toolbar", ImVec2(pos.x + 1, pos.y + menu_height), toolbar_size))
		{
			for (auto* action : m_toolbar_actions)
			{
				action->toolbarButton();
			}
		}
		ImGui::EndToolbar();
		ImGui::PopStyleVar();
		return menu_height + 24 + padding;
	}


	void guiEndFrame()
	{
		if (m_is_welcome_screen_open)
		{
			ImGuiID dockspace_id = ImGui::GetID("MyDockspace");
			ImGui::DockSpace(dockspace_id, ImVec2(0, 0), ImGuiDockNodeFlags_KeepAliveOnly);
			showWelcomeScreen();
		}
		else
		{
			if (ImGui::GetIO().DisplaySize.y > 0)
			{
				auto pos = ImVec2(0, 0);
				auto size = ImGui::GetIO().DisplaySize;
				size.y -= pos.y;
				ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
										 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
										 ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | 
										 ImGuiWindowFlags_NoDocking;
				ImGui::SetNextWindowSize(size);
				ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos, ImGuiCond_FirstUseEver);
				if (ImGui::Begin("MainDockspace", nullptr, flags))
				{
					float menu_height = showMainMenu();
					showMainToolbar(menu_height);
					ImGuiID dockspace_id = ImGui::GetID("MyDockspace");
					ImGui::Dummy(ImVec2(2, 2));
					ImGui::DockSpace(dockspace_id, ImVec2(0, 0));
				}
				ImGui::End();
			}
			m_profiler_ui->onGUI();
			m_asset_browser->onGUI();
			m_log_ui->onGUI();
			m_asset_compiler->onGUI();
			m_property_grid->onGUI();
			onEntityListGUI();
			onEditCameraGUI();
			onSaveAsDialogGUI();
			for (auto* plugin : m_gui_plugins)
			{
				plugin->onWindowGUI();
			}
			m_settings.onGUI();
			onPackDataGUI();
		}
		ImGui::PopFont();
		ImGui::Render();
		ImGui::UpdatePlatformWindows();
<commit_msg>no debug window in welcome screen<commit_after>#include <imgui/imgui.h>
#include <imgui/IconsFontAwesome4.h>

#include "studio_app.h"
#include "asset_browser.h"
#include "audio/audio_scene.h"
#include "editor/asset_compiler.h"
#include "editor/file_system_watcher.h"
#include "editor/gizmo.h"
#include "editor/prefab_system.h"
#include "editor/render_interface.h"
#include "editor/world_editor.h"
#include "engine/command_line_parser.h"
#include "engine/crc32.h"
#include "engine/debug.h"
#include "engine/allocator.h"
#include "engine/engine.h"
#include "engine/file_system.h"
#include "engine/geometry.h"
#include "engine/input_system.h"
#include "engine/job_system.h"
#include "engine/log.h"
#include "engine/lua_wrapper.h"
#include "engine/mt/thread.h"
#include "engine/os.h"
#include "engine/path_utils.h"
#include "engine/plugin_manager.h"
#include "engine/profiler.h"
#include "engine/reflection.h"
#include "engine/resource_manager.h"
#include "engine/universe/universe.h"
#include "log_ui.h"
#include "profiler_ui.h"
#include "property_grid.h"
#include "settings.h"
#include "utils.h"

#ifdef _WIN32
	#include "engine/win/simple_win.h"
#endif

namespace Lumix
{
	

struct TarHeader {
	char name[100];
	char mode[8];
	char uid[8];
	char gid[8];
	char size[12];
	char mtime[12];
	char chksum[8];
	char typeflag;
	char linkname[100];
	char magic[6];
	char version[2];
	char uname[32];
	char gname[32];
	char devmajor[8];
	char devminor[8];
	char prefix[155];   
};


#define NO_ICON "       "


struct LuaPlugin : public StudioApp::GUIPlugin
{
	LuaPlugin(StudioApp& app, const char* src, const char* filename)
		: editor(app.getWorldEditor())
	{
		L = lua_newthread(editor.getEngine().getState());						 // [thread]
		thread_ref = luaL_ref(editor.getEngine().getState(), LUA_REGISTRYINDEX); // []

		lua_newtable(L);						  // [env]
												  // reference environment
		lua_pushvalue(L, -1);					  // [env, env]
		env_ref = luaL_ref(L, LUA_REGISTRYINDEX); // [env]

		// environment's metatable & __index
		lua_pushvalue(L, -1);	// [env, env]
		lua_setmetatable(L, -2); // [env]
		lua_pushvalue(L, LUA_GLOBALSINDEX);
		lua_setfield(L, -2, "__index"); // [env]

		bool errors = luaL_loadbuffer(L, src, stringLength(src), filename) != 0; // [env, func]

		lua_pushvalue(L, -2); // [env, func, env]
		lua_setfenv(L, -2);   // function's environment [env, func]

		errors = errors || lua_pcall(L, 0, 0, 0) != 0; // [env]
		if (errors)
		{
			logError("Editor") << filename << ": " << lua_tostring(L, -1);
			lua_pop(L, 1);
		}

		lua_getfield(L, -1, "plugin_name"); // [env, plugin_name]
		const char* name = "LuaPlugin";
		if (lua_type(L, -1) == LUA_TSTRING) {
			name = lua_tostring(L, -1);
		}

		lua_pop(L, 2); // []

		Action* action = LUMIX_NEW(editor.getAllocator(), Action)(name, name, name);
		action->func.bind<&LuaPlugin::onAction>(this);
		app.addWindowAction(action);
		m_is_open = false;

		lua_pop(L, 1); // plugin_name
	}


	~LuaPlugin()
	{
		lua_State* L = editor.getEngine().getState();
		luaL_unref(L, LUA_REGISTRYINDEX, env_ref);
		luaL_unref(L, LUA_REGISTRYINDEX, thread_ref);
	}


	const char* getName() const override { return "lua_script"; }


	void onAction() { m_is_open = !m_is_open; }


	void onWindowGUI() override
	{
		if (!m_is_open) return;

		lua_rawgeti(L, LUA_REGISTRYINDEX, env_ref); // [env]
		lua_getfield(L, -1, "onGUI"); // [env, onGUI]
		if (lua_type(L, -1) == LUA_TFUNCTION) {
			if (lua_pcall(L, 0, 0, 0) != 0) {
				logError("Editor") << "LuaPlugin:" << lua_tostring(L, -1);
				lua_pop(L, 1);
			}
			lua_pop(L, 1);
		}
		else {
			lua_pop(L, 2);
		}
	}

	WorldEditor& editor;
	lua_State* L;
	int thread_ref;
	int env_ref;
	bool m_is_open;
};


class StudioAppImpl final : public StudioApp
{
public:
	StudioAppImpl()
		: m_is_entity_list_open(true)
		, m_is_save_as_dialog_open(false)
		, m_finished(false)
		, m_deferred_game_mode_exit(false)
		, m_profiler_ui(nullptr)
		, m_asset_browser(nullptr)
		, m_asset_compiler(nullptr)
		, m_property_grid(nullptr)
		, m_actions(m_allocator)
		, m_window_actions(m_allocator)
		, m_toolbar_actions(m_allocator)
		, m_is_welcome_screen_open(true)
		, m_is_pack_data_dialog_open(false)
		, m_editor(nullptr)
		, m_settings(*this)
		, m_gui_plugins(m_allocator)
		, m_plugins(m_allocator)
		, m_add_cmp_plugins(m_allocator)
		, m_component_labels(m_allocator)
		, m_confirm_load(false)
		, m_confirm_new(false)
		, m_confirm_exit(false)
		, m_exit_code(0)
		, m_allocator(m_main_allocator)
		, m_universes(m_allocator)
		, m_events(m_allocator)
		, m_windows(m_allocator)
	{
		JobSystem::init(MT::getCPUsCount(), m_allocator);
	}


	void onEvent(const OS::Event& event) override
	{
		const bool handle_input = isFocused();
		m_events.push(event);
		switch (event.type) {
			case OS::Event::Type::FOCUS: break;
			case OS::Event::Type::MOUSE_BUTTON: {
				ImGuiIO& io = ImGui::GetIO();
				m_editor->setToggleSelection(io.KeyCtrl);
				m_editor->setSnapMode(io.KeyShift, io.KeyCtrl);
				if (handle_input || !event.mouse_button.down) {
					io.MouseDown[(int)event.mouse_button.button] = event.mouse_button.down;
				}
				break;
			}
			case OS::Event::Type::MOUSE_WHEEL:
				if (handle_input) {
					ImGuiIO& io = ImGui::GetIO();
					io.MouseWheel = event.mouse_wheel.amount;
				}
				break;
			case OS::Event::Type::MOUSE_MOVE:
				if (handle_input) {
					m_mouse_move += {(float)event.mouse_move.xrel, (float)event.mouse_move.yrel};
				}
				break;
			case OS::Event::Type::WINDOW_SIZE:
				if (ImGui::GetCurrentContext()) {
					ImGuiViewport* vp = ImGui::FindViewportByPlatformHandle(event.window);
					if (vp) vp->PlatformRequestResize = true;
				}
				if (event.window == m_main_window && event.win_size.h > 0 && event.win_size.w > 0) {
					m_settings.m_window.w = event.win_size.w;
					m_settings.m_window.h = event.win_size.h;
					m_settings.m_is_maximized = OS::isMaximized(m_main_window);
				}
				break;
			case OS::Event::Type::WINDOW_MOVE:
				if (ImGui::GetCurrentContext()) {
					ImGuiViewport* vp = ImGui::FindViewportByPlatformHandle(event.window);
					if (vp) vp->PlatformRequestMove = true;
				}
				if (event.window == m_main_window) {
					m_settings.m_window.x = event.win_move.x;
					m_settings.m_window.y = event.win_move.y;
					m_settings.m_is_maximized = OS::isMaximized(m_main_window);
				}
				break;
			case OS::Event::Type::WINDOW_CLOSE: {
				ImGuiViewport* vp = ImGui::FindViewportByPlatformHandle(event.window);
				if (vp) vp->PlatformRequestClose = true;
				if (event.window == m_main_window) exit();
				break;
			}
			case OS::Event::Type::QUIT:
				exit();
				break;
			case OS::Event::Type::CHAR:
				if (handle_input) {
					ImGuiIO& io = ImGui::GetIO();
					char utf8[5];
					OS::UTF32ToUTF8(event.text_input.utf32, utf8);
					utf8[4] = 0;
					io.AddInputCharactersUTF8(utf8);
				}
				break;
			case OS::Event::Type::KEY:
				if (handle_input) {
					ImGuiIO& io = ImGui::GetIO();
					io.KeysDown[(int)event.key.keycode] = event.key.down;
					io.KeyShift = OS::isKeyDown(OS::Keycode::SHIFT);
					io.KeyCtrl = OS::isKeyDown(OS::Keycode::CTRL);
					io.KeyAlt = OS::isKeyDown(OS::Keycode::MENU);
					checkShortcuts();
				}
				break;
			case OS::Event::Type::DROP_FILE:
				for(int i = 0, c = OS::getDropFileCount(event); i < c; ++i) {
					char tmp[MAX_PATH_LENGTH];
					OS::getDropFile(event, i, Span(tmp));
					if (!m_asset_browser->onDropFile(tmp)) {
						for (GUIPlugin* plugin : m_gui_plugins) {
							if (plugin->onDropFile(tmp)) break;
						}
					}
				}
				break;
			default: ASSERT(false); break;
		}
	}

	bool isFocused() const {
		const OS::WindowHandle focused = OS::getFocused();
		const int idx = m_windows.find([focused](OS::WindowHandle w){ return w == focused; });
		return idx >= 0;
	}

	void onIdle() override
	{
		update();

		if (m_sleep_when_inactive && !isFocused()) {
			const float frame_time = m_inactive_fps_timer.tick();
			const float wanted_fps = 5.0f;

			if (frame_time < 1 / wanted_fps) {
				PROFILE_BLOCK("sleep");
				MT::sleep(u32(1000 / wanted_fps - frame_time * 1000));
			}
			m_inactive_fps_timer.tick();
		}

		Profiler::frame();
		m_events.clear();
	}


	void run() override
	{
		JobSystem::SignalHandle finished = JobSystem::INVALID_HANDLE;
		JobSystem::runEx(this, [](void* data) {
			Lumix::OS::run(*(StudioAppImpl*)data);
		}, &finished, JobSystem::INVALID_HANDLE, 0);
		Profiler::setThreadName("Main thread");
		JobSystem::wait(finished);
	}

	
	static void* imguiAlloc(size_t size, void* user_data) {
		StudioAppImpl* app = (StudioAppImpl*)user_data;
		return app->m_allocator.allocate(size);
	}


	static void imguiFree(void* ptr, void* user_data) {
		StudioAppImpl* app = (StudioAppImpl*)user_data;
		return app->m_allocator.deallocate(ptr);
	}


	void onInit() override
	{
		OS::Timer init_timer;

		m_add_cmp_root.label[0] = '\0';
		m_template_name[0] = '\0';
		m_open_filter[0] = '\0';

		checkWorkingDirectory();

		char saved_data_dir[MAX_PATH_LENGTH] = {};
		OS::InputFile cfg_file;
		if (cfg_file.open(".lumixuser")) {
			cfg_file.read(saved_data_dir, minimum(lengthOf(saved_data_dir), (int)cfg_file.size()));
			cfg_file.close();
		}

		char current_dir[MAX_PATH_LENGTH];
		OS::getCurrentDirectory(Span(current_dir));

		char data_dir[MAX_PATH_LENGTH] = {};
		checkDataDirCommandLine(data_dir, lengthOf(data_dir));
		m_engine = Engine::create(data_dir[0] ? data_dir : (saved_data_dir[0] ? saved_data_dir : current_dir)
			, m_allocator);
		createLua();

		extractBundled();

		OS::InitWindowArgs create_win_args;
		create_win_args.handle_file_drops = true;
		create_win_args.name = "Lumix Studio";
		m_main_window = OS::createWindow(create_win_args);
		m_windows.push(m_main_window);

		const char* plugins[] = { ""
			#ifdef LUMIXENGINE_PLUGINS
				, LUMIXENGINE_PLUGINS
			#endif
		};

		PluginManager& plugin_manager = m_engine->getPluginManager();
		for (auto* plugin_name : plugins) {
			if (plugin_name[0] && !plugin_manager.load(plugin_name)) {
				logInfo("Editor") << plugin_name << " plugin has not been loaded";
			}
		}

		Engine::PlatformData platform_data = {};
		platform_data.window_handle = m_main_window;
		m_engine->setPlatformData(platform_data);

		plugin_manager.initPlugins();

		m_editor = WorldEditor::create(current_dir, *m_engine, m_allocator);
		m_settings.m_editor = m_editor;
		scanUniverses();
		loadUserPlugins();
		addActions();

		m_asset_compiler = AssetCompiler::create(*this);
		m_asset_browser = LUMIX_NEW(m_allocator, AssetBrowser)(*this);
		m_property_grid = LUMIX_NEW(m_allocator, PropertyGrid)(*this);
		m_profiler_ui = ProfilerUI::create(*m_engine);
		m_log_ui = LUMIX_NEW(m_allocator, LogUI)(m_editor->getAllocator());

		ImGui::SetAllocatorFunctions(imguiAlloc, imguiFree, this);
		ImGui::CreateContext();
		loadSettings();
		initIMGUI();

		m_custom_pivot_action = LUMIX_NEW(m_editor->getAllocator(), Action)("Set Custom Pivot",
			"Set Custom Pivot",
			"set_custom_pivot",
			OS::Keycode::K,
			OS::Keycode::INVALID,
			OS::Keycode::INVALID);
		m_custom_pivot_action->is_global = false;
		addAction(m_custom_pivot_action);

		setStudioApp();
		loadIcons();
		loadSettings();
		loadUniverseFromCommandLine();
		findLuaPlugins("plugins/lua/");

		m_asset_compiler->onInitFinished();
		m_sleep_when_inactive = shouldSleepWhenInactive();

		checkScriptCommandLine();

		logInfo("Editor") << "Startup took " << init_timer.getTimeSinceStart() << " s"; 
	}


	~StudioAppImpl()
	{
		ImGuiIO& io = ImGui::GetIO();
		if (io.WantSaveIniSettings) {
			size_t size;
			const char* data = ImGui::SaveIniSettingsToMemory(&size);
			FileSystem& fs = m_editor->getEngine().getFileSystem();
			OS::OutputFile file;
			if (fs.open("imgui.ini", Ref(file))) {
				file.write(data ,size);
				file.close();
			}
		}

		if (m_watched_plugin.watcher) FileSystemWatcher::destroy(m_watched_plugin.watcher);

		saveSettings();
		unloadIcons();

		while (m_editor->getEngine().getFileSystem().hasWork())
		{
			m_editor->getEngine().getFileSystem().processCallbacks();
		}

		m_editor->newUniverse();

		destroyAddCmpTreeNode(m_add_cmp_root.child);

		for (auto* i : m_plugins)
		{
			LUMIX_DELETE(m_editor->getAllocator(), i);
		}
		m_plugins.clear();
		PrefabSystem::destroyEditorPlugins(*this);
		ASSERT(m_gui_plugins.empty());

		for (auto* i : m_add_cmp_plugins)
		{
			LUMIX_DELETE(m_editor->getAllocator(), i);
		}
		m_add_cmp_plugins.clear();

		for (auto* a : m_actions)
		{
			LUMIX_DELETE(m_editor->getAllocator(), a);
		}
		m_actions.clear();

		ProfilerUI::destroy(*m_profiler_ui);
		LUMIX_DELETE(m_allocator, m_asset_browser);
		LUMIX_DELETE(m_allocator, m_property_grid);
		LUMIX_DELETE(m_allocator, m_log_ui);
		AssetCompiler::destroy(*m_asset_compiler);
		WorldEditor::destroy(m_editor, m_allocator);
		Engine::destroy(m_engine, m_allocator);
		m_engine = nullptr;
		m_editor = nullptr;
		
		OS::destroyWindow(m_main_window);
		JobSystem::shutdown();
	}


	bool makeFile(const char* path, const char* content) override
	{
		OS::OutputFile file;
		if (!m_engine->getFileSystem().open(path, Ref(file))) return false;
		file << content;
		file.close();
		return file.isError();
	}


	void destroyAddCmpTreeNode(AddCmpTreeNode* node)
	{
		if (!node) return;
		destroyAddCmpTreeNode(node->child);
		destroyAddCmpTreeNode(node->next);
		LUMIX_DELETE(m_allocator, node);
	}


	const char* getComponentTypeName(ComponentType cmp_type) const override
	{
		auto iter = m_component_labels.find(cmp_type);
		if (iter == m_component_labels.end()) return "Unknown";
		return iter.value().c_str();
	}


	const AddCmpTreeNode& getAddComponentTreeRoot() const override { return m_add_cmp_root; }


	void addPlugin(IAddComponentPlugin& plugin)
	{
		int i = 0;
		while (i < m_add_cmp_plugins.size() && compareString(plugin.getLabel(), m_add_cmp_plugins[i]->getLabel()) > 0)
		{
			++i;
		}
		m_add_cmp_plugins.insert(i, &plugin);

		auto* node = LUMIX_NEW(m_allocator, AddCmpTreeNode);
		copyString(node->label, plugin.getLabel());
		node->plugin = &plugin;
		insertAddCmpNode(m_add_cmp_root, node);
	}


	static void insertAddCmpNodeOrdered(AddCmpTreeNode& parent, AddCmpTreeNode* node)
	{
		if (!parent.child)
		{
			parent.child = node;
			return;
		}
		if (compareString(parent.child->label, node->label) > 0)
		{
			node->next = parent.child;
			parent.child = node;
			return;
		}
		auto* i = parent.child;
		while (i->next && compareString(i->next->label, node->label) < 0)
		{
			i = i->next;
		}
		node->next = i->next;
		i->next = node;
	}


	void insertAddCmpNode(AddCmpTreeNode& parent, AddCmpTreeNode* node)
	{
		for (auto* i = parent.child; i; i = i->next)
		{
			if (!i->plugin && startsWith(node->label, i->label))
			{
				insertAddCmpNode(*i, node);
				return;
			}
		}
		const char* rest = node->label + stringLength(parent.label);
		if (parent.label[0] != '\0') ++rest; // include '/'
		const char* slash = findSubstring(rest, "/");
		if (!slash)
		{
			insertAddCmpNodeOrdered(parent, node);
			return;
		}
		auto* new_group = LUMIX_NEW(m_allocator, AddCmpTreeNode);
		copyNString(Span(new_group->label), node->label, int(slash - node->label));
		insertAddCmpNodeOrdered(parent, new_group);
		insertAddCmpNode(*new_group, node);
	}


	void registerComponentWithResource(const char* type,
		const char* label,
		ResourceType resource_type,
		const Reflection::PropertyBase& property) override
	{
		struct Plugin final : public IAddComponentPlugin
		{
			void onGUI(bool create_entity, bool from_filter) override
			{
				ImGui::SetNextWindowSize(ImVec2(300, 300));
				const char* last = reverseFind(label, nullptr, '/');
				last = last && !from_filter ? last + 1 : label;
				if (last[0] == ' ') ++last;
				if (!ImGui::BeginMenu(last)) return;
				char buf[MAX_PATH_LENGTH];
				bool create_empty = ImGui::MenuItem("Empty");
				static u32 selected_res_hash = 0;
				if (asset_browser->resourceList(Span(buf), Ref(selected_res_hash), resource_type, 0, true) || create_empty)
				{
					if (create_entity)
					{
						EntityRef entity = editor->addEntity();
						editor->selectEntities(&entity, 1, false);
					}

					const Array<EntityRef>& selected_entites = editor->getSelectedEntities();
					editor->addComponent(selected_entites, type);
					if (!create_empty)
					{
						editor->setProperty(type,
							-1,
							*property,
							&editor->getSelectedEntities()[0],
							editor->getSelectedEntities().size(),
							buf,
							stringLength(buf) + 1);
					}
					ImGui::CloseCurrentPopup();
				}
				ImGui::EndMenu();
			}


			const char* getLabel() const override { return label; }

			PropertyGrid* property_grid;
			AssetBrowser* asset_browser;
			WorldEditor* editor;
			ComponentType type;
			ResourceType resource_type;
			const Reflection::PropertyBase* property;
			char label[50];
		};

		auto& allocator = m_editor->getAllocator();
		auto* plugin = LUMIX_NEW(allocator, Plugin);
		plugin->property_grid = m_property_grid;
		plugin->asset_browser = m_asset_browser;
		plugin->type = Reflection::getComponentType(type);
		plugin->editor = m_editor;
		plugin->property = &property;
		plugin->resource_type = resource_type;
		copyString(plugin->label, label);
		addPlugin(*plugin);

		m_component_labels.insert(plugin->type, String(label, m_allocator));
	}


	void registerComponent(const char* id, IAddComponentPlugin& plugin) override
	{
		addPlugin(plugin);
		m_component_labels.insert(Reflection::getComponentType(id), String(plugin.getLabel(), m_allocator));
	}


	void registerComponent(const char* type, const char* label) override
	{
		struct Plugin final : public IAddComponentPlugin
		{
			void onGUI(bool create_entity, bool from_filter) override
			{
				const char* last = reverseFind(label, nullptr, '/');
				last = last && !from_filter ? last + 1 : label;
				if (last[0] == ' ') ++last;
				if (ImGui::MenuItem(last))
				{
					if (create_entity)
					{
						EntityRef entity = editor->addEntity();
						editor->selectEntities(&entity, 1, false);
					}

					editor->addComponent(editor->getSelectedEntities(), type);
				}
			}


			const char* getLabel() const override { return label; }

			WorldEditor* editor;
			PropertyGrid* property_grid;
			ComponentType type;
			char label[64];
		};

		auto& allocator = m_editor->getAllocator();
		auto* plugin = LUMIX_NEW(allocator, Plugin);
		plugin->property_grid = m_property_grid;
		plugin->editor = m_editor;
		plugin->type = Reflection::getComponentType(type);
		copyString(plugin->label, label);
		addPlugin(*plugin);

		m_component_labels.insert(plugin->type, String(label, m_allocator));
	}


	const Array<Action*>& getActions() override { return m_actions; }


	Array<Action*>& getToolbarActions() override { return m_toolbar_actions; }


	void guiBeginFrame() const
	{
		PROFILE_FUNCTION();

		ImGuiIO& io = ImGui::GetIO();

		updateIMGUIMonitors();
		const OS::Rect rect = OS::getWindowClientRect(m_main_window);
		if (rect.width > 0 && rect.height > 0) {
			io.DisplaySize = ImVec2(float(rect.width), float(rect.height));
		}
		else if(io.DisplaySize.x <= 0) {
			io.DisplaySize.x = 800;
			io.DisplaySize.y = 600;
		}
		io.DeltaTime = m_engine->getLastTimeDelta();
		io.KeyShift = OS::isKeyDown(OS::Keycode::SHIFT);
		io.KeyCtrl = OS::isKeyDown(OS::Keycode::CTRL);
		io.KeyAlt = OS::isKeyDown(OS::Keycode::MENU);

		const OS::Point cp = OS::getMouseScreenPos();
		io.MousePos.x = (float)cp.x;
		io.MousePos.y = (float)cp.y;

		ImGui::NewFrame();
		ImGui::PushFont(m_font);
	}


	float showMainToolbar(float menu_height)
	{
		if (m_toolbar_actions.empty())
		{
			ImGui::SetCursorPosY(menu_height);
			return menu_height;
		}

		auto frame_padding = ImGui::GetStyle().FramePadding;
		float padding = frame_padding.y * 2;
		ImVec2 toolbar_size(ImGui::GetIO().DisplaySize.x, 24 + padding);
		ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
		const ImVec2 pos = ImGui::GetMainViewport()->Pos;
		if (ImGui::BeginToolbar("main_toolbar", ImVec2(pos.x + 1, pos.y + menu_height), toolbar_size))
		{
			for (auto* action : m_toolbar_actions)
			{
				action->toolbarButton();
			}
		}
		ImGui::EndToolbar();
		ImGui::PopStyleVar();
		return menu_height + 24 + padding;
	}


	void guiEndFrame()
	{
		if (m_is_welcome_screen_open)
		{
			ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
										ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
										ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | 
										ImGuiWindowFlags_NoDocking;
			ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
			ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos, ImGuiCond_FirstUseEver);
			if (ImGui::Begin("MainDockspace", nullptr, flags))
			{
				ImGuiID dockspace_id = ImGui::GetID("MyDockspace");
				ImGui::DockSpace(dockspace_id, ImVec2(0, 0), ImGuiDockNodeFlags_KeepAliveOnly);
			}
			ImGui::End();
			showWelcomeScreen();
		}
		else
		{
			if (ImGui::GetIO().DisplaySize.y > 0)
			{
				ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
										 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
										 ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | 
										 ImGuiWindowFlags_NoDocking;
				ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
				ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos, ImGuiCond_FirstUseEver);
				if (ImGui::Begin("MainDockspace", nullptr, flags))
				{
					float menu_height = showMainMenu();
					showMainToolbar(menu_height);
					ImGuiID dockspace_id = ImGui::GetID("MyDockspace");
					ImGui::Dummy(ImVec2(2, 2));
					ImGui::DockSpace(dockspace_id, ImVec2(0, 0));
				}
				ImGui::End();
			}
			m_profiler_ui->onGUI();
			m_asset_browser->onGUI();
			m_log_ui->onGUI();
			m_asset_compiler->onGUI();
			m_property_grid->onGUI();
			onEntityListGUI();
			onEditCameraGUI();
			onSaveAsDialogGUI();
			for (auto* plugin : m_gui_plugins)
			{
				plugin->onWindowGUI();
			}
			m_settings.onGUI();
			onPackDataGUI();
		}
		ImGui::PopFont();
		ImGui::Render();
		ImGui::UpdatePlatformWindows();

{'fraction_non_alphanumeric': 0.14708037554385162, 'fraction_numerical': 0.0073505839248912295, 'mean_word_length': 4.3341883473799925, 'pattern_counts': {'":': 2, '<': 69, '<?xml version=': 0, '>': 202, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '631815', 'n_tokens_mistral': 18554, 'n_tokens_neox': 16039, 'n_words': 2709}