Browse Source

More editor code

Panagiotis Christopoulos Charitos 2 months ago
parent
commit
fafec05155

+ 518 - 42
AnKi/Editor/EditorUi.cpp

@@ -4,20 +4,58 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include <AnKi/Editor/EditorUi.h>
 #include <AnKi/Editor/EditorUi.h>
-#include <AnKi/Scene/SceneGraph.h>
-#include <AnKi/Scene/SceneNode.h>
+#include <AnKi/Scene.h>
 #include <AnKi/Resource/ResourceManager.h>
 #include <AnKi/Resource/ResourceManager.h>
 #include <AnKi/Resource/ImageResource.h>
 #include <AnKi/Resource/ImageResource.h>
+#include <AnKi/Script/ScriptManager.h>
+#include <AnKi/Window/Input.h>
 #include <ThirdParty/ImGui/Extra/IconsMaterialDesignIcons.h> // See all icons in https://pictogrammers.com/library/mdi/
 #include <ThirdParty/ImGui/Extra/IconsMaterialDesignIcons.h> // See all icons in https://pictogrammers.com/library/mdi/
 
 
 namespace anki {
 namespace anki {
 
 
+template<typename TFunc>
+class DeferredPop
+{
+public:
+	TFunc m_func;
+
+	DeferredPop(TFunc func)
+		: m_func(func)
+	{
+	}
+
+	~DeferredPop()
+	{
+		m_func();
+	}
+};
+
+#define ANKI_IMGUI_PUSH_POP(func, ...) \
+	ImGui::Push##func(__VA_ARGS__); \
+	DeferredPop ANKI_CONCATENATE(pop, __LINE__)([] { \
+		ImGui::Pop##func(); \
+	})
+
 EditorUi::EditorUi()
 EditorUi::EditorUi()
 {
 {
+	Logger::getSingleton().addMessageHandler(this, loggerMessageHandler);
 }
 }
 
 
 EditorUi::~EditorUi()
 EditorUi::~EditorUi()
 {
 {
+	Logger::getSingleton().removeMessageHandler(this, loggerMessageHandler);
+}
+
+void EditorUi::loggerMessageHandler(void* ud, const LoggerMessageInfo& info)
+{
+	EditorUi& self = *static_cast<EditorUi*>(ud);
+	String logEntry;
+	logEntry.sprintf("[%s][%-4s] %s [%s:%d][%s][%s]\n", kLoggerMessageTypeText[info.m_type], info.m_subsystem ? info.m_subsystem : "N/A", info.m_msg,
+					 info.m_file, info.m_line, info.m_func, info.m_threadName);
+
+	LockGuard lock(self.m_consoleWindow.m_logMtx);
+	self.m_consoleWindow.m_log.emplaceBack(std::pair(info.m_type, logEntry));
+	self.m_consoleWindow.m_forceLogScrollDown = true;
 }
 }
 
 
 void EditorUi::draw(UiCanvas& canvas)
 void EditorUi::draw(UiCanvas& canvas)
@@ -30,7 +68,13 @@ void EditorUi::draw(UiCanvas& canvas)
 		m_font = canvas.addFonts(fnames);
 		m_font = canvas.addFonts(fnames);
 	}
 	}
 
 
+	if(!m_monospaceFont)
+	{
+		m_monospaceFont = canvas.addFont("EngineAssets/UbuntuMonoRegular.ttf");
+	}
+
 	ImGui::PushFont(m_font, m_fontSize);
 	ImGui::PushFont(m_font, m_fontSize);
+	ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 20.0f);
 
 
 	const Vec4 oldWindowColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
 	const Vec4 oldWindowColor = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
 	ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.0f;
 	ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.0f;
@@ -50,10 +94,14 @@ void EditorUi::draw(UiCanvas& canvas)
 
 
 	buildSceneHierarchyWindow();
 	buildSceneHierarchyWindow();
 	buildSceneNodePropertiesWindow();
 	buildSceneNodePropertiesWindow();
+	buildConsoleWindow();
+	buildAssetsWindow();
 
 
 	buildCVarsEditorWindow();
 	buildCVarsEditorWindow();
 
 
 	ImGui::End();
 	ImGui::End();
+
+	ImGui::PopStyleVar();
 	ImGui::PopFont();
 	ImGui::PopFont();
 
 
 	m_canvas = nullptr;
 	m_canvas = nullptr;
@@ -65,52 +113,68 @@ void EditorUi::buildMainMenu()
 	{
 	{
 		if(ImGui::BeginMenu(ICON_MDI_FOLDER_OUTLINE " File"))
 		if(ImGui::BeginMenu(ICON_MDI_FOLDER_OUTLINE " File"))
 		{
 		{
-			if(ImGui::MenuItem(ICON_MDI_CLOSE_OUTLINE " Quit"))
+			if(ImGui::MenuItem(ICON_MDI_CLOSE_CIRCLE " Quit", "CTRL+Q"))
 			{
 			{
 				m_quit = true;
 				m_quit = true;
 			}
 			}
 			ImGui::EndMenu();
 			ImGui::EndMenu();
 		}
 		}
 
 
-		if(ImGui::BeginMenu(ICON_MDI_EYE " View"))
+		if(ImGui::BeginMenu(ICON_MDI_EYE " Windows"))
 		{
 		{
+			if(ImGui::MenuItem(ICON_MDI_EYE " Console"))
+			{
+				m_showConsoleWindow = true;
+			}
+
+			if(ImGui::MenuItem(ICON_MDI_EYE " SceneNode Props"))
+			{
+				m_showSceneNodePropsWindow = true;
+			}
+
+			if(ImGui::MenuItem(ICON_MDI_EYE " Scene Hierarchy"))
+			{
+				m_showSceneHierarcyWindow = true;
+			}
+
+			if(ImGui::MenuItem(ICON_MDI_EYE " Assets"))
+			{
+				m_showAssetsWindow = true;
+			}
+
 			if(ImGui::MenuItem(ICON_MDI_EYE " CVars Editor"))
 			if(ImGui::MenuItem(ICON_MDI_EYE " CVars Editor"))
 			{
 			{
 				m_showCVarEditorWindow = true;
 				m_showCVarEditorWindow = true;
 			}
 			}
+
 			ImGui::EndMenu();
 			ImGui::EndMenu();
 		}
 		}
 
 
 		// Quit bnt
 		// Quit bnt
 		{
 		{
-			const Char* text = ICON_MDI_CLOSE_OUTLINE;
+			const Char* text = ICON_MDI_CLOSE_CIRCLE;
 			const Vec2 textSize = ImGui::CalcTextSize(text);
 			const Vec2 textSize = ImGui::CalcTextSize(text);
 
 
-			m_mainMenu.m_quitBtnHovered = false;
 			const F32 menuBarWidth = ImGui::GetWindowWidth();
 			const F32 menuBarWidth = ImGui::GetWindowWidth();
 			ImGui::SameLine(menuBarWidth - textSize.x() - ImGui::GetStyle().FramePadding.x * 2.0f - kMargin);
 			ImGui::SameLine(menuBarWidth - textSize.x() - ImGui::GetStyle().FramePadding.x * 2.0f - kMargin);
 
 
-			const Bool changeColor = m_mainMenu.m_quitBtnHovered;
-			if(changeColor)
-			{
-				ImGui::PushStyleColor(ImGuiCol_Text, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
-			}
+			ImGui::PushStyleColor(ImGuiCol_ButtonHovered, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
 
 
 			if(ImGui::Button(text))
 			if(ImGui::Button(text))
 			{
 			{
 				m_quit = true;
 				m_quit = true;
 			}
 			}
 
 
-			m_mainMenu.m_quitBtnHovered = ImGui::IsItemHovered();
-
-			if(changeColor)
-			{
-				ImGui::PopStyleColor();
-			}
+			ImGui::PopStyleColor();
 		}
 		}
 
 
 		ImGui::EndMainMenuBar();
 		ImGui::EndMainMenuBar();
 	}
 	}
+
+	if(Input::getSingleton().getKey(KeyCode::kLeftCtrl) > 0 && Input::getSingleton().getKey(KeyCode::kQ) > 0)
+	{
+		m_quit = true;
+	}
 }
 }
 
 
 void EditorUi::buildSceneNode(SceneNode& node)
 void EditorUi::buildSceneNode(SceneNode& node)
@@ -156,16 +220,21 @@ void EditorUi::buildSceneNode(SceneNode& node)
 
 
 void EditorUi::buildSceneHierarchyWindow()
 void EditorUi::buildSceneHierarchyWindow()
 {
 {
+	if(!m_showSceneHierarcyWindow)
+	{
+		return;
+	}
+
 	if(ImGui::GetFrameCount() > 1)
 	if(ImGui::GetFrameCount() > 1)
 	{
 	{
 		// Viewport is one frame delay so do that when frame >1
 		// Viewport is one frame delay so do that when frame >1
 		const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
 		const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
 		const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
 		const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
-		ImGui::SetNextWindowPos(viewportPos + kMargin, ImGuiCond_FirstUseEver);
-		ImGui::SetNextWindowSize(Vec2(400.0f, viewportSize.y() - kMargin * 2.0f), ImGuiCond_FirstUseEver);
+		ImGui::SetNextWindowPos(viewportPos, ImGuiCond_FirstUseEver);
+		ImGui::SetNextWindowSize(Vec2(400.0f, viewportSize.y() - kConsoleHeight), ImGuiCond_FirstUseEver);
 	}
 	}
 
 
-	if(ImGui::Begin("Scene Hierarchy", nullptr, 0))
+	if(ImGui::Begin("Scene Hierarchy", &m_showSceneHierarcyWindow, 0))
 	{
 	{
 		buildFilter(m_sceneHierarchyWindow.m_filter);
 		buildFilter(m_sceneHierarchyWindow.m_filter);
 
 
@@ -191,55 +260,298 @@ void EditorUi::buildSceneHierarchyWindow()
 
 
 void EditorUi::buildSceneNodePropertiesWindow()
 void EditorUi::buildSceneNodePropertiesWindow()
 {
 {
+	if(!m_showSceneNodePropsWindow)
+	{
+		return;
+	}
+
+	auto& state = m_sceneNodePropsWindow;
+
 	if(ImGui::GetFrameCount() > 1)
 	if(ImGui::GetFrameCount() > 1)
 	{
 	{
 		// Viewport is one frame delay so do that when frame >1
 		// Viewport is one frame delay so do that when frame >1
 		const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
 		const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
 		const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
 		const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
 		const F32 initialWidth = 400.0f;
 		const F32 initialWidth = 400.0f;
-		ImGui::SetNextWindowPos(Vec2(viewportSize.x() - kMargin - initialWidth, viewportPos.y() + kMargin), ImGuiCond_FirstUseEver);
-		ImGui::SetNextWindowSize(Vec2(initialWidth, viewportSize.y() - 2.0f * kMargin), ImGuiCond_FirstUseEver);
+		ImGui::SetNextWindowPos(Vec2(viewportSize.x() - initialWidth, viewportPos.y()), ImGuiCond_FirstUseEver);
+		ImGui::SetNextWindowSize(Vec2(initialWidth, viewportSize.y() - kConsoleHeight), ImGuiCond_FirstUseEver);
 	}
 	}
 
 
-	if(ImGui::Begin("Scene Node Props", nullptr, 0) && m_sceneHierarchyWindow.m_visibleNode)
+	if(ImGui::Begin("SceneNode Props", &m_showSceneNodePropsWindow, 0) && m_sceneHierarchyWindow.m_visibleNode)
 	{
 	{
+		U32 id = 0;
 		SceneNode& node = *m_sceneHierarchyWindow.m_visibleNode;
 		SceneNode& node = *m_sceneHierarchyWindow.m_visibleNode;
 
 
+		if(state.m_currentSceneNodeUuid != node.getUuid())
+		{
+			// Node changed, reset a few things
+			state.m_currentSceneNodeUuid = node.getUuid();
+			state.m_textEditorOpen = false;
+			state.m_scriptComponentThatHasTheTextEditorOpen = 0;
+			state.m_textEditorTxt.destroy();
+		}
+
+		auto dummyButton = [&]() {
+			ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.0f);
+			ImGui::PushID(id++);
+			ImGui::Button(ICON_MDI_LOCK);
+			ImGui::PopID();
+			ImGui::PopStyleVar();
+			ImGui::SameLine();
+		};
+
+		const F32 labelWidthBase = ImGui::GetFontSize() * 12; // Some amount of width for label, based on font size.
+		const F32 labelWidthMax = ImGui::GetContentRegionAvail().x * 0.40f; // ...but always leave some room for framed widgets.
+		ANKI_IMGUI_PUSH_POP(ItemWidth, -min(labelWidthBase, labelWidthMax));
+
 		// Name
 		// Name
-		Array<Char, 256> name;
-		std::strncpy(name.getBegin(), node.getName().getBegin(), name.getSize());
-		if(ImGui::InputText("Name", name.getBegin(), name.getSize(), ImGuiInputTextFlags_EnterReturnsTrue))
 		{
 		{
-			node.setName(&name[0]);
+			dummyButton();
+
+			Array<Char, 256> name;
+			std::strncpy(name.getBegin(), node.getName().getBegin(), name.getSize());
+			if(ImGui::InputText("Name", name.getBegin(), name.getSize(), ImGuiInputTextFlags_EnterReturnsTrue))
+			{
+				node.setName(&name[0]);
+			}
 		}
 		}
 
 
 		// Local transform
 		// Local transform
-		F32 localOrigin[3] = {node.getLocalOrigin().x(), node.getLocalOrigin().y(), node.getLocalOrigin().z()};
-		if(ImGui::DragFloat3(ICON_MDI_ARROW_ALL " Origin", localOrigin, 0.25f, -1000000.0f, 1000000.0f))
 		{
 		{
-			node.setLocalOrigin(Vec3(&localOrigin[0]));
+			dummyButton();
+
+			F32 localOrigin[3] = {node.getLocalOrigin().x(), node.getLocalOrigin().y(), node.getLocalOrigin().z()};
+			if(ImGui::DragFloat3(ICON_MDI_AXIS_ARROW " Origin", localOrigin, 0.025f, -1000000.0f, 1000000.0f))
+			{
+				node.setLocalOrigin(Vec3(&localOrigin[0]));
+			}
 		}
 		}
 
 
 		// Local scale
 		// Local scale
-		F32 localScale[3] = {node.getLocalScale().x(), node.getLocalScale().y(), node.getLocalScale().z()};
-		if(ImGui::DragFloat3(ICON_MDI_ARROW_EXPAND_ALL " Scale", localScale, 0.25f, -1000000.0f, 1000000.0f))
 		{
 		{
-			node.setLocalScale(Vec3(&localScale[0]));
+			ImGui::PushID(id++);
+			if(ImGui::Button(state.m_uniformScale ? ICON_MDI_LOCK : ICON_MDI_LOCK_OPEN))
+			{
+				state.m_uniformScale = !state.m_uniformScale;
+			}
+			ImGui::SetItemTooltip("Uniform/Non-uniform scale");
+			ImGui::PopID();
+			ImGui::SameLine();
+
+			F32 localScale[3] = {node.getLocalScale().x(), node.getLocalScale().y(), node.getLocalScale().z()};
+			if(ImGui::DragFloat3(ICON_MDI_ARROW_EXPAND_ALL " Scale", localScale, 0.0025f, 0.01f, 1000000.0f))
+			{
+				if(!state.m_uniformScale)
+				{
+					node.setLocalScale(Vec3(&localScale[0]));
+				}
+				else
+				{
+					// The component that have changed wins
+					F32 scale = scale = localScale[2];
+					if(localScale[0] != node.getLocalScale().x())
+					{
+						scale = localScale[0];
+					}
+					else if(localScale[1] != node.getLocalScale().y())
+					{
+						scale = localScale[1];
+					}
+					node.setLocalScale(Vec3(scale));
+				}
+			}
 		}
 		}
 
 
 		// Local rotation
 		// Local rotation
-		const Euler rot(node.getLocalRotation());
-		F32 localRotation[3] = {toDegrees(rot.x()), toDegrees(rot.y()), toDegrees(rot.z())};
-		if(ImGui::DragFloat3(ICON_MDI_ROTATE_ORBIT " Rotation", localRotation, 0.25f, -360.0f, 360.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp))
 		{
 		{
-			const Euler rot(toRad(localRotation[0]), toRad(localRotation[1]), toRad(localRotation[2]));
-			node.setLocalRotation(Mat3(rot));
+			dummyButton();
+
+			const Euler rot(node.getLocalRotation());
+			F32 localRotation[3] = {toDegrees(rot.x()), toDegrees(rot.y()), toDegrees(rot.z())};
+			if(ImGui::DragFloat3(ICON_MDI_ROTATE_ORBIT " Rotation", localRotation, 0.25f, -360.0f, 360.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp))
+			{
+				const Euler rot(toRad(localRotation[0]), toRad(localRotation[1]), toRad(localRotation[2]));
+				node.setLocalRotation(Mat3(rot));
+			}
+
+			ImGui::Text(" ");
+		}
+
+		// Component controls
+		{
+			if(ImGui::Button(ICON_MDI_PLUS_BOX))
+			{
+				switch(SceneComponentType(state.m_selectedSceneComponentType))
+				{
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) \
+	case SceneComponentType::k##name: \
+		node.newComponent<name##Component>(); \
+		break;
+#include <AnKi/Scene/Components/SceneComponentClasses.def.h>
+				default:
+					ANKI_ASSERT(0);
+				}
+			}
+			ImGui::SetItemTooltip("Add new component");
+
+			ImGui::SameLine();
+			ImGui::SetNextItemWidth(-1.0f);
+
+			I32 n = 0;
+			if(ImGui::BeginCombo(" ", kSceneComponentTypeName[state.m_selectedSceneComponentType]))
+			{
+				for(const Char* name : kSceneComponentTypeName)
+				{
+					const Bool isSelected = (state.m_selectedSceneComponentType == n);
+					if(ImGui::Selectable(name, isSelected))
+					{
+						state.m_selectedSceneComponentType = n;
+					}
+
+					// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
+					if(isSelected)
+					{
+						ImGui::SetItemDefaultFocus();
+					}
+
+					++n;
+				}
+				ImGui::EndCombo();
+			}
+
+			ImGui::Text(" ");
+		}
+
+		// Components
+		{
+			ANKI_IMGUI_PUSH_POP(StyleVar, ImGuiStyleVar_SeparatorTextAlign, Vec2(0.5f));
+
+			U32 count = 0;
+			node.iterateComponents([&](SceneComponent& comp) {
+				ANKI_IMGUI_PUSH_POP(ID, comp.getUuid());
+				const F32 alpha = 0.1f;
+				ANKI_IMGUI_PUSH_POP(StyleColor, ImGuiCol_ChildBg, (count & 1) ? Vec4(0.0, 0.0f, 1.0f, alpha) : Vec4(1.0, 0.0f, 0.0f, alpha));
+
+				if(ImGui::BeginChild("Child", Vec2(0.0f), ImGuiChildFlags_AutoResizeY))
+				{
+					// Find the icon
+					CString icon = ICON_MDI_TOY_BRICK;
+					switch(comp.getType())
+					{
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon_) \
+	case SceneComponentType::k##name: \
+		icon = ANKI_CONCATENATE(ICON_MDI_, icon_); \
+		break;
+#include <AnKi/Scene/Components/SceneComponentClasses.def.h>
+					}
+
+					// Header
+					{
+						String label;
+						label.sprintf(" %s %s (%u)", icon.cstr(), kSceneComponentTypeName[comp.getType()], comp.getUuid());
+						ImGui::SeparatorText(label.cstr());
+
+						if(ImGui::Button(ICON_MDI_DELETE))
+						{
+						}
+						ImGui::SetItemTooltip("Delete Component");
+						ImGui::SameLine();
+
+						if(ImGui::Button(ICON_MDI_EYE))
+						{
+						}
+						ImGui::SetItemTooltip("Disable component");
+					}
+
+					switch(comp.getType())
+					{
+					case SceneComponentType::kMove:
+					case SceneComponentType::kUi:
+						// Nothing
+						break;
+					case SceneComponentType::kScript:
+						buildScriptComponent(static_cast<ScriptComponent&>(comp));
+						break;
+					default:
+						ImGui::Text("TODO");
+					}
+				}
+				ImGui::EndChild();
+				ImGui::Text(" ");
+				++count;
+			});
 		}
 		}
 	}
 	}
 
 
 	ImGui::End();
 	ImGui::End();
 }
 }
 
 
+void EditorUi::buildScriptComponent(ScriptComponent& comp)
+{
+	auto& state = m_sceneNodePropsWindow;
+
+	// Clear button
+	{
+		if(ImGui::Button(ICON_MDI_DELETE "##ScriptComponentResourceFilename"))
+		{
+			comp.setScriptResourceFilename("");
+		}
+		ImGui::SetItemTooltip("Clear");
+		ImGui::SameLine();
+	}
+
+	// Filename input
+	{
+		Char rsrcTxt[kMaxTextInputLen] = "";
+		if(comp.hasScriptResource())
+		{
+			std::strncpy(rsrcTxt, comp.getScriptResourceFilename().cstr(), sizeof(rsrcTxt));
+		}
+		ImGui::SetNextItemWidth(-1.0f);
+		ImGui::InputTextWithHint("##", "Script Filename", rsrcTxt, sizeof(rsrcTxt));
+	}
+
+	ImGui::Text(" -- or --");
+
+	// Clear button
+	{
+		if(ImGui::Button(ICON_MDI_DELETE "##ScriptComponentScriptText"))
+		{
+			comp.setScriptText("");
+		}
+		ImGui::SetItemTooltip("Clear");
+		ImGui::SameLine();
+	}
+
+	// Button
+	{
+		String buttonTxt;
+		buttonTxt.sprintf(ICON_MDI_LANGUAGE_LUA " Embedded Script%s", (comp.hasScriptText()) ? "" : " *Empty*");
+		const Bool showEditor = ImGui::Button(buttonTxt.cstr(), Vec2(-1.0f, 0.0f));
+		if(showEditor && (state.m_scriptComponentThatHasTheTextEditorOpen == 0 || state.m_scriptComponentThatHasTheTextEditorOpen == comp.getUuid()))
+		{
+			state.m_textEditorOpen = true;
+			state.m_scriptComponentThatHasTheTextEditorOpen = comp.getUuid();
+			state.m_textEditorTxt = (comp.hasScriptText()) ? comp.getScriptText() : "";
+		}
+	}
+
+	if(state.m_textEditorOpen && state.m_scriptComponentThatHasTheTextEditorOpen == comp.getUuid())
+	{
+		if(buildTextEditorWindow(String().sprintf("ScriptComponent %u", comp.getUuid()), &state.m_textEditorOpen, state.m_textEditorTxt))
+		{
+			ANKI_LOGV("Updating ScriptComponent");
+			comp.setScriptText(state.m_textEditorTxt);
+		}
+
+		if(!state.m_textEditorOpen)
+		{
+			state.m_scriptComponentThatHasTheTextEditorOpen = 0;
+			state.m_textEditorTxt.destroy();
+		}
+	}
+}
+
 void EditorUi::buildCVarsEditorWindow()
 void EditorUi::buildCVarsEditorWindow()
 {
 {
 	if(!m_showCVarEditorWindow)
 	if(!m_showCVarEditorWindow)
@@ -358,19 +670,183 @@ void EditorUi::buildCVarsEditorWindow()
 	ImGui::End();
 	ImGui::End();
 }
 }
 
 
-void EditorUi::buildFilter(ImGuiTextFilter& filter)
+void EditorUi::buildConsoleWindow()
 {
 {
-	ImGui::Text(ICON_MDI_MAGNIFY);
-	ImGui::SameLine();
+	if(!m_showConsoleWindow)
+	{
+		return;
+	}
 
 
+	auto& state = m_consoleWindow;
+
+	if(ImGui::GetFrameCount() > 1)
+	{
+		// Viewport is one frame delay so do that when frame >1
+		const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
+		const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
+		const Vec2 initialSize = Vec2(viewportSize.x() / 2.0f, kConsoleHeight);
+		const Vec2 initialPos = Vec2(0.0f, viewportPos.y() + viewportSize.y() - initialSize.y());
+		ImGui::SetNextWindowSize(initialSize, ImGuiCond_FirstUseEver);
+		ImGui::SetNextWindowPos(initialPos, ImGuiCond_FirstUseEver);
+	}
+
+	if(ImGui::Begin("Console", &m_showConsoleWindow, 0))
+	{
+		// Log controls
+		{
+			if(ImGui::Button(ICON_MDI_DELETE))
+			{
+				state.m_log.destroy();
+			}
+			ImGui::SetItemTooltip("Clear log");
+			ImGui::SameLine();
+		}
+
+		// Lua input
+		{
+			Char consoleTxt[kMaxTextInputLen] = "";
+			ImGui::SetNextItemWidth(-1.0f);
+			if(ImGui::InputTextWithHint(" ", ICON_MDI_LANGUAGE_LUA " LUA console", consoleTxt, sizeof(consoleTxt),
+										ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_EscapeClearsAll))
+			{
+				[[maybe_unused]] const Error err = ScriptManager::getSingleton().evalString(consoleTxt);
+				ImGui::SetKeyboardFocusHere(-1); // On enter it loses focus so call this to fix it
+			}
+		}
+
+		// Log
+		{
+			if(ImGui::BeginChild("Log", Vec2(0.0f), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened))
+			{
+				ImGui::PushFont(m_monospaceFont, 0.0f);
+
+				if(ImGui::BeginTable("##bg", 1, ImGuiTableFlags_RowBg))
+				{
+					LockGuard lock(state.m_logMtx);
+
+					for(const auto& logEntry : state.m_log)
+					{
+						ImGui::TableNextRow();
+						ImGui::TableNextColumn();
+						constexpr Array<Vec3, U(LoggerMessageType::kCount)> colors = {Vec3(0.074f, 0.631f, 0.054f), Vec3(0.074f, 0.354f, 0.631f),
+																					  Vec3(1.0f, 0.0f, 0.0f), Vec3(0.756f, 0.611f, 0.0f),
+																					  Vec3(1.0f, 0.0f, 0.0f)};
+						ImGui::PushStyleColor(ImGuiCol_Text, colors[logEntry.first].xyz1());
+						ImGui::TextUnformatted(logEntry.second.cstr());
+						ImGui::PopStyleColor();
+					}
+
+					if(state.m_forceLogScrollDown)
+					{
+						ImGui::SetScrollHereY(1.0f);
+						state.m_forceLogScrollDown = false;
+					}
+
+					ImGui::EndTable();
+				}
+
+				ImGui::PopFont();
+			}
+			ImGui::EndChild();
+		}
+	}
+
+	ImGui::End();
+}
+
+void EditorUi::buildAssetsWindow()
+{
+	if(!m_showAssetsWindow)
+	{
+		return;
+	}
+
+	if(ImGui::GetFrameCount() > 1)
+	{
+		// Viewport is one frame delay so do that when frame >1
+		const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
+		const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
+		const Vec2 initialSize = Vec2(viewportSize.x() / 2.0f, kConsoleHeight);
+		const Vec2 initialPos = Vec2(viewportSize.x() / 2.0f, viewportPos.y() + viewportSize.y() - initialSize.y());
+		ImGui::SetNextWindowSize(initialSize, ImGuiCond_FirstUseEver);
+		ImGui::SetNextWindowPos(initialPos, ImGuiCond_FirstUseEver);
+	}
+
+	if(ImGui::Begin("Assets", &m_showAssetsWindow, 0))
+	{
+	}
+
+	ImGui::End();
+}
+
+void EditorUi::buildFilter(ImGuiTextFilter& filter)
+{
 	ImGui::SetNextItemWidth(-FLT_MIN);
 	ImGui::SetNextItemWidth(-FLT_MIN);
 	ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
 	ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
 	ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
 	ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
-	if(ImGui::InputTextWithHint("##Filter", "incl,-excl", filter.InputBuf, IM_ARRAYSIZE(filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll))
+	if(ImGui::InputTextWithHint("##Filter", ICON_MDI_MAGNIFY " Search incl,-excl", filter.InputBuf, IM_ARRAYSIZE(filter.InputBuf),
+								ImGuiInputTextFlags_EscapeClearsAll))
 	{
 	{
 		filter.Build();
 		filter.Build();
 	}
 	}
 	ImGui::PopItemFlag();
 	ImGui::PopItemFlag();
 }
 }
 
 
+Bool EditorUi::buildTextEditorWindow(CString extraWindowTitle, Bool* pOpen, String& inout)
+{
+	Bool save = false;
+
+	ImGui::SetNextWindowSize(Vec2(600.0f, 800.0f), ImGuiCond_FirstUseEver);
+	const String title = String().sprintf("Text Editor: %s", extraWindowTitle.cstr());
+	if(ImGui::Begin(title.cstr(), pOpen))
+	{
+		if(ImGui::Button(ICON_MDI_CONTENT_SAVE " Save"))
+		{
+			save = true;
+		}
+		ImGui::SameLine();
+		if(pOpen && ImGui::Button(ICON_MDI_CLOSE " Close"))
+		{
+			*pOpen = false;
+		}
+
+		if(ImGui::IsWindowFocused() && Input::getSingleton().getKey(KeyCode::kEscape) && pOpen)
+		{
+			*pOpen = false;
+		}
+
+		DynamicArray<Char> buffer;
+		buffer.resize(max(inout.getLength() + 1, 1024_U32), '\0');
+
+		if(inout.getLength())
+		{
+			std::strncpy(buffer.getBegin(), inout.cstr(), inout.getLength());
+		}
+
+		auto replaceTabCallback = [](ImGuiInputTextCallbackData* data) -> int {
+			if(data->CursorPos > 0 && data->Buf[data->CursorPos - 1] == '\t')
+			{
+				data->DeleteChars(data->CursorPos - 1, 1);
+				data->InsertChars(data->CursorPos, "    ");
+				return 0;
+			}
+			else
+			{
+				return 0;
+			}
+		};
+
+		ImGui::PushFont(m_monospaceFont, 0.0f);
+		if(ImGui::InputTextMultiline("##", buffer.getBegin(), buffer.getSize(), Vec2(-1.0f),
+									 ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_CallbackEdit, replaceTabCallback))
+		{
+			inout = buffer.getBegin();
+		}
+		ImGui::PopFont();
+	}
+	ImGui::End();
+
+	return save;
+}
+
 } // end namespace anki
 } // end namespace anki

+ 37 - 2
AnKi/Editor/EditorUi.h

@@ -10,11 +10,12 @@
 namespace anki {
 namespace anki {
 
 
 class SceneNode;
 class SceneNode;
+class ScriptComponent;
 
 
 /// @addtogroup editor
 /// @addtogroup editor
 /// @{
 /// @{
 
 
-/// XXX
+/// A class that builds the editor UI and manipulates the scene directly.
 class EditorUi
 class EditorUi
 {
 {
 public:
 public:
@@ -28,18 +29,24 @@ public:
 
 
 private:
 private:
 	static constexpr F32 kMargin = 4.0f;
 	static constexpr F32 kMargin = 4.0f;
+	static constexpr F32 kConsoleHeight = 300.0f;
+	static constexpr U32 kMaxTextInputLen = 256;
 
 
 	UiCanvas* m_canvas = nullptr;
 	UiCanvas* m_canvas = nullptr;
 
 
 	ImFont* m_font = nullptr;
 	ImFont* m_font = nullptr;
+	ImFont* m_monospaceFont = nullptr;
 	F32 m_fontSize = 22.0f;
 	F32 m_fontSize = 22.0f;
 
 
 	Bool m_showCVarEditorWindow = false;
 	Bool m_showCVarEditorWindow = false;
+	Bool m_showConsoleWindow = true;
+	Bool m_showSceneNodePropsWindow = true;
+	Bool m_showSceneHierarcyWindow = true;
+	Bool m_showAssetsWindow = true;
 
 
 	class
 	class
 	{
 	{
 	public:
 	public:
-		Bool m_quitBtnHovered = false;
 	} m_mainMenu;
 	} m_mainMenu;
 
 
 	class
 	class
@@ -55,17 +62,45 @@ private:
 		ImGuiTextFilter m_filter;
 		ImGuiTextFilter m_filter;
 	} m_cvarsEditorWindow;
 	} m_cvarsEditorWindow;
 
 
+	class
+	{
+	public:
+		List<std::pair<LoggerMessageType, String>> m_log;
+		Bool m_forceLogScrollDown = true;
+		SpinLock m_logMtx;
+	} m_consoleWindow;
+
+	class
+	{
+	public:
+		I32 m_selectedSceneComponentType = 0;
+		Bool m_uniformScale = false;
+
+		U32 m_scriptComponentThatHasTheTextEditorOpen = 0;
+		Bool m_textEditorOpen = false;
+		String m_textEditorTxt;
+
+		U32 m_currentSceneNodeUuid = 0;
+	} m_sceneNodePropsWindow;
+
 	void buildMainMenu();
 	void buildMainMenu();
 
 
 	void buildSceneHierarchyWindow();
 	void buildSceneHierarchyWindow();
 	void buildSceneNode(SceneNode& node);
 	void buildSceneNode(SceneNode& node);
 
 
 	void buildSceneNodePropertiesWindow();
 	void buildSceneNodePropertiesWindow();
+	void buildScriptComponent(ScriptComponent& comp);
 
 
 	void buildCVarsEditorWindow();
 	void buildCVarsEditorWindow();
+	void buildConsoleWindow();
+	void buildAssetsWindow();
 
 
 	// Utils
 	// Utils
 	void buildFilter(ImGuiTextFilter& filter);
 	void buildFilter(ImGuiTextFilter& filter);
+	Bool buildTextEditorWindow(CString extraWindowTitle, Bool* pOpen, String& inout);
+
+	// Misc
+	static void loggerMessageHandler(void* ud, const LoggerMessageInfo& info);
 };
 };
 /// @}
 /// @}
 
 

+ 11 - 4
AnKi/Scene/Components/SceneComponent.h

@@ -19,7 +19,7 @@ namespace anki {
 /// @memberof SceneComponent
 /// @memberof SceneComponent
 enum class SceneComponentType : U8
 enum class SceneComponentType : U8
 {
 {
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight) k##name,
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) k##name,
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 
 
 	kCount,
 	kCount,
@@ -32,7 +32,7 @@ enum class SceneComponentTypeMask : U32
 {
 {
 	kNone = 0,
 	kNone = 0,
 
 
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight) k##name = 1 << U32(SceneComponentType::k##name),
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) k##name = 1 << U32(SceneComponentType::k##name),
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 };
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(SceneComponentTypeMask)
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(SceneComponentTypeMask)
@@ -40,7 +40,7 @@ ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(SceneComponentTypeMask)
 class SceneComponentType2
 class SceneComponentType2
 {
 {
 public:
 public:
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight) static constexpr SceneComponentType k##name##Component = SceneComponentType::k##name;
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) static constexpr SceneComponentType k##name##Component = SceneComponentType::k##name;
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 };
 };
 
 
@@ -50,6 +50,13 @@ public: \
 \
 \
 private:
 private:
 
 
+/// Component names
+inline Array<const Char*, U32(SceneComponentType::kCount)> kSceneComponentTypeName = {
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) ANKI_STRINGIZE(name)
+#define ANKI_SCENE_COMPONENT_SEPARATOR ,
+#include <AnKi/Scene/Components/SceneComponentClasses.def.h>
+};
+
 /// Passed to SceneComponent::update.
 /// Passed to SceneComponent::update.
 /// @memberof SceneComponent
 /// @memberof SceneComponent
 class SceneComponentUpdateInfo
 class SceneComponentUpdateInfo
@@ -159,7 +166,7 @@ private:
 	U32 m_type : 8 = 0; ///< Cache the type ID.
 	U32 m_type : 8 = 0; ///< Cache the type ID.
 
 
 	static constexpr Array<F32, U32(SceneComponentType::kCount)> m_updateOrderWeights = {
 	static constexpr Array<F32, U32(SceneComponentType::kCount)> m_updateOrderWeights = {
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight) weight
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) weight
 #define ANKI_SCENE_COMPONENT_SEPARATOR ,
 #define ANKI_SCENE_COMPONENT_SEPARATOR ,
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 	};
 	};

+ 19 - 19
AnKi/Scene/Components/SceneComponentClasses.def.h

@@ -7,49 +7,49 @@
 #	define ANKI_SCENE_COMPONENT_SEPARATOR
 #	define ANKI_SCENE_COMPONENT_SEPARATOR
 #endif
 #endif
 
 
-ANKI_DEFINE_SCENE_COMPONENT(Script, 0.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Script, 0.0f, LANGUAGE_LUA)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
 
 
-ANKI_DEFINE_SCENE_COMPONENT(Body, 10.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Body, 10.0f, CUBE_SEND)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(PlayerController, 10.0f)
+ANKI_DEFINE_SCENE_COMPONENT(PlayerController, 10.0f, HUMAN)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
 
 
-ANKI_DEFINE_SCENE_COMPONENT(Move, 30.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Move, 30.0f, AXIS_ARROW)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Skin, 30.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Skin, 30.0f, BONE)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
 
 
-ANKI_DEFINE_SCENE_COMPONENT(Joint, 35.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Joint, 35.0f, CONNECTION)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
 
 
-ANKI_DEFINE_SCENE_COMPONENT(Trigger, 40.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Trigger, 40.0f, LIGHT_SWITCH_OFF)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
 
 
-ANKI_DEFINE_SCENE_COMPONENT(Mesh, 50.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Mesh, 50.0f, VECTOR_POLYGON)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
 
 
-ANKI_DEFINE_SCENE_COMPONENT(Material, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Material, 100.0f, TEXTURE_BOX)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter, 100.0f, FOUNTAIN)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Decal, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Decal, 100.0f, LIQUID_SPOT)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Camera, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Camera, 100.0f, CAMERA)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(FogDensity, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(FogDensity, 100.0f, CLOUD)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(GlobalIlluminationProbe, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(GlobalIlluminationProbe, 100.0f, SPHERE)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(ReflectionProbe, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(ReflectionProbe, 100.0f, SPHERE)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Skybox, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Skybox, 100.0f, EARTH)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Ui, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Ui, 100.0f, WINDOW_RESTORE)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(LensFlare, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(LensFlare, 100.0f, FLARE)
 ANKI_SCENE_COMPONENT_SEPARATOR
 ANKI_SCENE_COMPONENT_SEPARATOR
-ANKI_DEFINE_SCENE_COMPONENT(Light, 100.0f)
+ANKI_DEFINE_SCENE_COMPONENT(Light, 100.0f, LIGHTBULB)
 
 
 #undef ANKI_DEFINE_SCENE_COMPONENT
 #undef ANKI_DEFINE_SCENE_COMPONENT
 #undef ANKI_SCENE_COMPONENT_SEPARATOR
 #undef ANKI_SCENE_COMPONENT_SEPARATOR

+ 71 - 8
AnKi/Scene/Components/ScriptComponent.cpp

@@ -20,11 +20,20 @@ ScriptComponent::ScriptComponent(SceneNode* node)
 
 
 ScriptComponent::~ScriptComponent()
 ScriptComponent::~ScriptComponent()
 {
 {
-	deleteInstance(SceneMemoryPool::getSingleton(), m_env);
+	deleteInstance(SceneMemoryPool::getSingleton(), m_environments[0]);
+	deleteInstance(SceneMemoryPool::getSingleton(), m_environments[1]);
 }
 }
 
 
-void ScriptComponent::loadScriptResource(CString fname)
+void ScriptComponent::setScriptResourceFilename(CString fname)
 {
 {
+	if(fname.isEmpty())
+	{
+		deleteInstance(SceneMemoryPool::getSingleton(), m_environments[1]);
+		m_environments[1] = nullptr;
+		m_resource.reset(nullptr);
+		return;
+	}
+
 	// Load
 	// Load
 	ScriptResourcePtr rsrc;
 	ScriptResourcePtr rsrc;
 	Error err = ResourceManager::getSingleton().loadResource(fname, rsrc);
 	Error err = ResourceManager::getSingleton().loadResource(fname, rsrc);
@@ -39,9 +48,51 @@ void ScriptComponent::loadScriptResource(CString fname)
 	// Exec the script
 	// Exec the script
 	if(!err)
 	if(!err)
 	{
 	{
-		err = newEnv->evalString(m_script->getSource());
+		err = newEnv->evalString(m_resource->getSource());
+	}
+
+	// Error
+	if(err)
+	{
+		ANKI_SCENE_LOGE("Failed to load the script");
+		deleteInstance(SceneMemoryPool::getSingleton(), newEnv);
+	}
+	else
+	{
+		m_resource = std::move(rsrc);
+		deleteInstance(SceneMemoryPool::getSingleton(), m_environments[1]);
+		m_environments[1] = newEnv;
+	}
+}
+
+CString ScriptComponent::getScriptResourceFilename() const
+{
+	if(ANKI_EXPECT(m_resource.isCreated()))
+	{
+		return m_resource->getFilename();
+	}
+	else
+	{
+		return "*Error*";
+	}
+}
+
+void ScriptComponent::setScriptText(CString text)
+{
+	if(text.isEmpty())
+	{
+		deleteInstance(SceneMemoryPool::getSingleton(), m_environments[0]);
+		m_environments[0] = nullptr;
+		m_text.destroy();
+		return;
 	}
 	}
 
 
+	// Create the env
+	ScriptEnvironment* newEnv = newInstance<ScriptEnvironment>(SceneMemoryPool::getSingleton());
+
+	// Exec the script
+	const Error err = newEnv->evalString(text);
+
 	// Error
 	// Error
 	if(err)
 	if(err)
 	{
 	{
@@ -50,21 +101,33 @@ void ScriptComponent::loadScriptResource(CString fname)
 	}
 	}
 	else
 	else
 	{
 	{
-		m_script = std::move(rsrc);
-		deleteInstance(SceneMemoryPool::getSingleton(), m_env);
-		m_env = newEnv;
+		m_text = text;
+		deleteInstance(SceneMemoryPool::getSingleton(), m_environments[0]);
+		m_environments[0] = newEnv;
+	}
+}
+
+CString ScriptComponent::getScriptText() const
+{
+	if(ANKI_EXPECT(m_text))
+	{
+		return m_text;
+	}
+	else
+	{
+		return "*Error*";
 	}
 	}
 }
 }
 
 
 void ScriptComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 void ScriptComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
 {
 {
 	updated = false;
 	updated = false;
-	if(m_env == nullptr)
+	if(!m_environments[0] && !m_environments[1])
 	{
 	{
 		return;
 		return;
 	}
 	}
 
 
-	lua_State* lua = &m_env->getLuaState();
+	lua_State* lua = (m_environments[0]) ? &m_environments[0]->getLuaState() : &m_environments[1]->getLuaState();
 
 
 	// Push function name
 	// Push function name
 	lua_getglobal(lua, "update");
 	lua_getglobal(lua, "update");

+ 22 - 5
AnKi/Scene/Components/ScriptComponent.h

@@ -14,7 +14,7 @@ namespace anki {
 /// @addtogroup scene
 /// @addtogroup scene
 /// @{
 /// @{
 
 
-/// Component of scripts.
+/// Component of scripts. It can point to a resource with the script code or have the script code embedded to it.
 class ScriptComponent : public SceneComponent
 class ScriptComponent : public SceneComponent
 {
 {
 	ANKI_SCENE_COMPONENT(ScriptComponent)
 	ANKI_SCENE_COMPONENT(ScriptComponent)
@@ -24,16 +24,33 @@ public:
 
 
 	~ScriptComponent();
 	~ScriptComponent();
 
 
-	void loadScriptResource(CString fname);
+	void setScriptResourceFilename(CString fname);
+
+	CString getScriptResourceFilename() const;
+
+	void setScriptText(CString text);
+
+	CString getScriptText() const;
+
+	Bool hasScriptText() const
+	{
+		return !m_text.isEmpty();
+	}
+
+	Bool hasScriptResource() const
+	{
+		return m_resource.isCreated();
+	}
 
 
 	Bool isEnabled() const
 	Bool isEnabled() const
 	{
 	{
-		return m_script.isCreated();
+		return m_environments[0] || m_environments[1];
 	}
 	}
 
 
 private:
 private:
-	ScriptResourcePtr m_script;
-	ScriptEnvironment* m_env = nullptr;
+	ScriptResourcePtr m_resource;
+	SceneString m_text;
+	Array<ScriptEnvironment*, 2> m_environments = {};
 
 
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
 	void update(SceneComponentUpdateInfo& info, Bool& updated) override;
 };
 };

+ 1 - 1
AnKi/Scene/Forward.h

@@ -9,7 +9,7 @@ namespace anki {
 
 
 // Components
 // Components
 class SceneComponent;
 class SceneComponent;
-#define ANKI_DEFINE_SCENE_COMPONENT(name, updateOrder) class name##Component;
+#define ANKI_DEFINE_SCENE_COMPONENT(name, updateOrder, icon) class name##Component;
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 
 
 // Nodes
 // Nodes

+ 2 - 2
AnKi/Scene/SceneGraph.h

@@ -38,7 +38,7 @@ ANKI_CVAR(NumericCVar<U32>, Scene, MinGpuSceneRenderables, 10 * 1024, 8, 100 * 1
 class SceneComponentArrays
 class SceneComponentArrays
 {
 {
 public:
 public:
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) \
 	SceneBlockArray<name##Component>& get##name##s() \
 	SceneBlockArray<name##Component>& get##name##s() \
 	{ \
 	{ \
 		return m_##name##Array; \
 		return m_##name##Array; \
@@ -46,7 +46,7 @@ public:
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 
 
 private:
 private:
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight) SceneBlockArray<name##Component> m_##name##Array;
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) SceneBlockArray<name##Component> m_##name##Array;
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 #include <AnKi/Scene/Components/SceneComponentClasses.def.h>
 };
 };
 
 

+ 2 - 2
AnKi/Scene/SceneNode.cpp

@@ -31,7 +31,7 @@
 namespace anki {
 namespace anki {
 
 
 // Specialize newComponent(). Do that first
 // Specialize newComponent(). Do that first
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) \
 	template<> \
 	template<> \
 	name##Component* SceneNode::newComponent<name##Component>() \
 	name##Component* SceneNode::newComponent<name##Component>() \
 	{ \
 	{ \
@@ -62,7 +62,7 @@ SceneNode::~SceneNode()
 
 
 		switch(comp->getType())
 		switch(comp->getType())
 		{
 		{
-#define ANKI_DEFINE_SCENE_COMPONENT(name, weight) \
+#define ANKI_DEFINE_SCENE_COMPONENT(name, weight, icon) \
 	case SceneComponentType::k##name: \
 	case SceneComponentType::k##name: \
 		SceneGraph::getSingleton().getComponentArrays().get##name##s().erase(comp->getArrayIndex()); \
 		SceneGraph::getSingleton().getComponentArrays().get##name##s().erase(comp->getArrayIndex()); \
 		break;
 		break;

+ 54 - 28
AnKi/Ui/UiCanvas.cpp

@@ -318,7 +318,7 @@ void UiCanvas::handleInput()
 
 
 // Handle keyboard
 // Handle keyboard
 #define ANKI_HANDLE(ak, imgui) \
 #define ANKI_HANDLE(ak, imgui) \
-	if(in.getKey(ak) > 1) \
+	if(in.getKey(ak) > 0) \
 	{ \
 	{ \
 		io.AddKeyEvent(imgui, true); \
 		io.AddKeyEvent(imgui, true); \
 	} \
 	} \
@@ -351,7 +351,22 @@ void UiCanvas::handleInput()
 	ANKI_HANDLE(KeyCode::kZ, ImGuiKey_Z)
 	ANKI_HANDLE(KeyCode::kZ, ImGuiKey_Z)
 	ANKI_HANDLE(KeyCode::kLeftCtrl, ImGuiKey_LeftCtrl)
 	ANKI_HANDLE(KeyCode::kLeftCtrl, ImGuiKey_LeftCtrl)
 	ANKI_HANDLE(KeyCode::kRightCtrl, ImGuiKey_RightCtrl)
 	ANKI_HANDLE(KeyCode::kRightCtrl, ImGuiKey_RightCtrl)
+#undef ANKI_HANDLE
 
 
+#define ANKI_HANDLE(ak, imgui) \
+	if(in.getKey(ak) > 0) \
+	{ \
+		io.AddKeyEvent(imgui, true); \
+	} \
+	else if(in.getKey(ak) < 0) \
+	{ \
+		io.AddKeyEvent(imgui, false); \
+	}
+
+	ANKI_HANDLE(KeyCode::kLeftCtrl, ImGuiMod_Ctrl)
+	ANKI_HANDLE(KeyCode::kLeftShift, ImGuiMod_Shift)
+	ANKI_HANDLE(KeyCode::kLeftAlt, ImGuiMod_Alt);
+	ANKI_HANDLE(KeyCode::kReturn, ImGuiMod_Super);
 #undef ANKI_HANDLE
 #undef ANKI_HANDLE
 
 
 	io.AddInputCharactersUTF8(in.getTextInput().cstr());
 	io.AddInputCharactersUTF8(in.getTextInput().cstr());
@@ -395,12 +410,14 @@ void UiCanvas::endBuilding()
 	{
 	{
 		for(ImTextureData* tex : *drawData.Textures)
 		for(ImTextureData* tex : *drawData.Textures)
 		{
 		{
-			if(tex->Status == ImTextureStatus_OK)
+			const ImTextureStatus status = tex->Status;
+
+			if(status == ImTextureStatus_OK)
 			{
 			{
 				continue;
 				continue;
 			}
 			}
 
 
-			if(tex->Status == ImTextureStatus_WantCreate)
+			if(status == ImTextureStatus_WantCreate)
 			{
 			{
 				TextureInitInfo init("ImGui requested");
 				TextureInitInfo init("ImGui requested");
 				init.m_width = tex->Width;
 				init.m_width = tex->Width;
@@ -414,26 +431,47 @@ void UiCanvas::endBuilding()
 				id.m_texture->retain();
 				id.m_texture->retain();
 				id.m_textureIsRefcounted = true;
 				id.m_textureIsRefcounted = true;
 				tex->SetTexID(id);
 				tex->SetTexID(id);
+
+				tex->SetStatus(ImTextureStatus_OK);
 			}
 			}
 
 
-			if(tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
+			if(status == ImTextureStatus_WantCreate || status == ImTextureStatus_WantUpdates)
 			{
 			{
-				m_texturesPendingUpload.emplaceBack(std::pair(tex, tex->Status == ImTextureStatus_WantCreate));
+				const Bool isNew = status == ImTextureStatus_WantCreate;
+				ImTextureID id = tex->GetTexID();
+
+				UploadRequest req;
+				req.m_rect.m_offsetX = isNew ? 0 : tex->UpdateRect.x;
+				req.m_rect.m_offsetY = isNew ? 0 : tex->UpdateRect.y;
+				req.m_rect.m_width = isNew ? tex->Width : tex->UpdateRect.w;
+				req.m_rect.m_height = isNew ? tex->Height : tex->UpdateRect.h;
+				req.m_texture = id.m_texture;
+				req.m_isNew = isNew;
+
+				const U32 uploadPitch = req.m_rect.m_width * U32(tex->BytesPerPixel);
+				const U32 copyBufferSize = req.m_rect.m_height * uploadPitch;
+				req.m_data.resize(copyBufferSize);
+				for(U32 y = 0; y < req.m_rect.m_height; y++)
+				{
+					memcpy(&req.m_data[uploadPitch * y], tex->GetPixelsAt(req.m_rect.m_offsetX, req.m_rect.m_offsetY + y), uploadPitch);
+				}
+
+				m_texturesPendingUpload.emplaceBack(std::move(req));
+
+				tex->SetStatus(ImTextureStatus_OK);
 			}
 			}
-			else if(tex->Status == ImTextureStatus_Destroyed)
+			else if(status == ImTextureStatus_WantDestroy && tex->UnusedFrames >= kMaxFramesInFlight + 2) // kMaxFramesInFlight + 2 to be 100% sure
 			{
 			{
 				ImTextureID id = tex->GetTexID();
 				ImTextureID id = tex->GetTexID();
 				ANKI_ASSERT(id.m_textureIsRefcounted);
 				ANKI_ASSERT(id.m_textureIsRefcounted);
 
 
 				TexturePtr pTex(id.m_texture);
 				TexturePtr pTex(id.m_texture);
 				id.m_texture->release();
 				id.m_texture->release();
-			}
-			else
-			{
-				ANKI_ASSERT(0);
-			}
 
 
-			tex->Status = ImTextureStatus_OK;
+				tex->SetTexID(ImTextureID(ImTextureID_Invalid));
+
+				tex->SetStatus(ImTextureStatus_Destroyed);
+			}
 		}
 		}
 	}
 	}
 
 
@@ -442,25 +480,13 @@ void UiCanvas::endBuilding()
 
 
 void UiCanvas::appendNonGraphicsCommands(CommandBuffer& cmdb) const
 void UiCanvas::appendNonGraphicsCommands(CommandBuffer& cmdb) const
 {
 {
-	for(const auto& pair : m_texturesPendingUpload)
+	for(const UploadRequest& req : m_texturesPendingUpload)
 	{
 	{
-		const ImTextureData* tex = pair.first;
-
-		const UVec2 uploadOffset(tex->UpdateRect.x, tex->UpdateRect.y);
-		const UVec2 uploadSize(tex->UpdateRect.w, tex->UpdateRect.h);
-		const U32 uploadPitch = uploadSize.x() * U32(tex->BytesPerPixel);
-		const U32 copyBufferSize = uploadSize.y() * uploadPitch;
-
 		WeakArray<U8> mappedMem;
 		WeakArray<U8> mappedMem;
-		const BufferView copyBuffer = RebarTransientMemoryPool::getSingleton().allocateCopyBuffer(copyBufferSize, mappedMem);
-
-		for(U32 y = 0; y < uploadSize.y(); y++)
-		{
-			memcpy(&mappedMem[uploadPitch * y], tex->GetPixelsAt(uploadOffset.x(), uploadOffset.y() + y), uploadPitch);
-		}
+		const BufferView copyBuffer = RebarTransientMemoryPool::getSingleton().allocateCopyBuffer(req.m_data.getSize(), mappedMem);
+		memcpy(mappedMem.getBegin(), req.m_data.getBegin(), req.m_data.getSizeInBytes());
 
 
-		cmdb.copyBufferToTexture(copyBuffer, TextureView(tex->GetTexID().m_texture, TextureSubresourceDesc::all()),
-								 {uploadOffset.x(), uploadOffset.y(), 0, uploadSize.x(), uploadSize.y(), 1});
+		cmdb.copyBufferToTexture(copyBuffer, TextureView(req.m_texture, TextureSubresourceDesc::all()), req.m_rect);
 	}
 	}
 }
 }
 
 

+ 12 - 3
AnKi/Ui/UiCanvas.h

@@ -58,9 +58,9 @@ public:
 	template<typename TFunc>
 	template<typename TFunc>
 	void visitTexturesForUpdate(TFunc func)
 	void visitTexturesForUpdate(TFunc func)
 	{
 	{
-		for(const auto& pair : m_texturesPendingUpload)
+		for(const UploadRequest& req : m_texturesPendingUpload)
 		{
 		{
-			func(*pair.first->GetTexID().m_texture, pair.second);
+			func(*req.m_texture, req.m_isNew);
 		}
 		}
 	}
 	}
 
 
@@ -95,7 +95,16 @@ private:
 
 
 	UiHashMap<U64, FontCacheEntry> m_fontCache;
 	UiHashMap<U64, FontCacheEntry> m_fontCache;
 
 
-	UiDynamicArray<std::pair<const ImTextureData*, Bool>> m_texturesPendingUpload;
+	class UploadRequest
+	{
+	public:
+		Texture* m_texture = nullptr;
+		TextureRect m_rect;
+		UiDynamicArray<U8> m_data;
+		Bool m_isNew = true;
+	};
+
+	UiDynamicArray<UploadRequest> m_texturesPendingUpload;
 
 
 	Error init(UVec2 size);
 	Error init(UVec2 size);
 };
 };

+ 4 - 6
AnKi/Util/Logger.cpp

@@ -19,8 +19,6 @@
 
 
 namespace anki {
 namespace anki {
 
 
-inline constexpr Array<const Char*, U(LoggerMessageType::kCount)> kMessageTypeTxt = {"I", "V", "E", "W", "F"};
-
 Logger::Logger()
 Logger::Logger()
 {
 {
 	addMessageHandler(this, &defaultSystemMessageHandler);
 	addMessageHandler(this, &defaultSystemMessageHandler);
@@ -189,7 +187,7 @@ void Logger::defaultSystemMessageHandler(void*, const LoggerMessageInfo& info)
 		endTerminalColor = "";
 		endTerminalColor = "";
 	}
 	}
 
 
-	fprintf(out, "%s[%s][%-4s]%s%s %s [%s:%d][%s][%s]%s\n", terminalColorBg, kMessageTypeTxt[U(info.m_type)],
+	fprintf(out, "%s[%s][%-4s]%s%s %s [%s:%d][%s][%s]%s\n", terminalColorBg, kLoggerMessageTypeText[U(info.m_type)],
 			info.m_subsystem ? info.m_subsystem : "N/A ", endTerminalColor, terminalColor, info.m_msg, info.m_file, info.m_line, info.m_func,
 			info.m_subsystem ? info.m_subsystem : "N/A ", endTerminalColor, terminalColor, info.m_msg, info.m_file, info.m_line, info.m_func,
 			info.m_threadName, endTerminalColor);
 			info.m_threadName, endTerminalColor);
 #elif ANKI_OS_WINDOWS
 #elif ANKI_OS_WINDOWS
@@ -236,7 +234,7 @@ void Logger::defaultSystemMessageHandler(void*, const LoggerMessageInfo& info)
 		SetConsoleTextAttribute(consoleHandle, attribs);
 		SetConsoleTextAttribute(consoleHandle, attribs);
 
 
 		// Print
 		// Print
-		fprintf(out, "[%s][%-4s] %s [%s:%d][%s][%s]\n", kMessageTypeTxt[info.m_type], info.m_subsystem ? info.m_subsystem : "N/A", info.m_msg,
+		fprintf(out, "[%s][%-4s] %s [%s:%d][%s][%s]\n", kLoggerMessageTypeText[info.m_type], info.m_subsystem ? info.m_subsystem : "N/A", info.m_msg,
 				info.m_file, info.m_line, info.m_func, info.m_threadName);
 				info.m_file, info.m_line, info.m_func, info.m_threadName);
 
 
 		// Restore state
 		// Restore state
@@ -265,7 +263,7 @@ void Logger::defaultSystemMessageHandler(void*, const LoggerMessageInfo& info)
 	}
 	}
 
 
 	static_assert(Thread::kThreadNameMaxLength == 15, "See file");
 	static_assert(Thread::kThreadNameMaxLength == 15, "See file");
-	__android_log_print(andMsgType, "AnKi", "[%s][%-4s] %s [%s:%d][%s][%s]\n", kMessageTypeTxt[info.m_type],
+	__android_log_print(andMsgType, "AnKi", "[%s][%-4s] %s [%s:%d][%s][%s]\n", kLoggerMessageTypeText[info.m_type],
 						info.m_subsystem ? info.m_subsystem : "N/A ", info.m_msg, info.m_file, info.m_line, info.m_func, info.m_threadName);
 						info.m_subsystem ? info.m_subsystem : "N/A ", info.m_msg, info.m_file, info.m_line, info.m_func, info.m_threadName);
 #else
 #else
 #	error "Not implemented"
 #	error "Not implemented"
@@ -276,7 +274,7 @@ void Logger::fileMessageHandler(void* pfile, const LoggerMessageInfo& info)
 {
 {
 	File* file = reinterpret_cast<File*>(pfile);
 	File* file = reinterpret_cast<File*>(pfile);
 
 
-	Error err = file->writeTextf("[%s] %s (%s:%d %s)\n", kMessageTypeTxt[info.m_type], info.m_msg, info.m_file, info.m_line, info.m_func);
+	Error err = file->writeTextf("[%s] %s (%s:%d %s)\n", kLoggerMessageTypeText[info.m_type], info.m_msg, info.m_file, info.m_line, info.m_func);
 
 
 	if(!err)
 	if(!err)
 	{
 	{

+ 2 - 0
AnKi/Util/Logger.h

@@ -29,6 +29,8 @@ enum class LoggerMessageType : U8
 	kCount
 	kCount
 };
 };
 
 
+inline constexpr Array<const Char*, U(LoggerMessageType::kCount)> kLoggerMessageTypeText = {"I", "V", "E", "W", "F"};
+
 /// Used as parammeter when emitting the signal.
 /// Used as parammeter when emitting the signal.
 /// @memberof Logger
 /// @memberof Logger
 class LoggerMessageInfo
 class LoggerMessageInfo

+ 1 - 1
Tools/Editor/EditorMain.cpp

@@ -97,7 +97,7 @@ public:
 	{
 	{
 		Input& in = Input::getSingleton();
 		Input& in = Input::getSingleton();
 		SceneGraph& scene = SceneGraph::getSingleton();
 		SceneGraph& scene = SceneGraph::getSingleton();
-		if(in.getKey(KeyCode::kEscape) > 0 || m_editorUiNode->m_editorUi.m_quit)
+		if(m_editorUiNode->m_editorUi.m_quit)
 		{
 		{
 			quit = true;
 			quit = true;
 		}
 		}

+ 1 - 1
Tools/Image/ImageViewerMain.cpp

@@ -68,7 +68,7 @@ private:
 		ImGui::PushFont(m_font, 16.0f);
 		ImGui::PushFont(m_font, 16.0f);
 
 
 		ImGui::SetWindowPos(Vec2(0.0f, 0.0f));
 		ImGui::SetWindowPos(Vec2(0.0f, 0.0f));
-		ImGui::SetWindowSize(Vec2(F32(canvas.getWidth()), F32(canvas.getHeight())));
+		ImGui::SetWindowSize(canvas.getSizef());
 
 
 		ImGui::BeginChild("Tools", Vec2(-1.0f, 30.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX, ImGuiWindowFlags_None);
 		ImGui::BeginChild("Tools", Vec2(-1.0f, 30.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX, ImGuiWindowFlags_None);