Browse Source

Editor: Add image viewer

Panagiotis Christopoulos Charitos 2 months ago
parent
commit
a26641c59f

+ 18 - 3
AnKi/Editor/EditorUi.cpp

@@ -180,6 +180,15 @@ void EditorUi::draw(UiCanvas& canvas)
 	cVarsWindow();
 	cVarsWindow();
 	debugRtsWindow();
 	debugRtsWindow();
 
 
+	{
+		const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
+		const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
+		const Vec2 initialSize = Vec2(viewportSize.y() * 0.75f);
+		const Vec2 initialPos = (viewportSize - initialSize) / 2.0f;
+
+		m_imageViewer.drawWindow(canvas, initialPos, initialSize, 0);
+	}
+
 	ImGui::End();
 	ImGui::End();
 
 
 	ImGui::PopStyleVar();
 	ImGui::PopStyleVar();
@@ -1172,17 +1181,18 @@ void EditorUi::assetsWindow()
 								{
 								{
 									const AssetFile& file = *filteredFiles[idx];
 									const AssetFile& file = *filteredFiles[idx];
 
 
+									ImGui::PushID(idx);
 									if(file.m_type == AssetFileType::kMaterial)
 									if(file.m_type == AssetFileType::kMaterial)
 									{
 									{
 										ImTextureID id;
 										ImTextureID id;
 										id.m_texture = &m_materialIcon->getTexture();
 										id.m_texture = &m_materialIcon->getTexture();
-										ImGui::Image(id, Vec2(cellWidth));
+										ImGui::ImageButton("##", id, Vec2(cellWidth));
 									}
 									}
 									else if(file.m_type == AssetFileType::kMesh)
 									else if(file.m_type == AssetFileType::kMesh)
 									{
 									{
 										ImTextureID id;
 										ImTextureID id;
 										id.m_texture = &m_meshIcon->getTexture();
 										id.m_texture = &m_meshIcon->getTexture();
-										ImGui::Image(id, Vec2(cellWidth));
+										ImGui::ImageButton("##", id, Vec2(cellWidth));
 									}
 									}
 									else if(file.m_type == AssetFileType::kTexture)
 									else if(file.m_type == AssetFileType::kTexture)
 									{
 									{
@@ -1191,8 +1201,13 @@ void EditorUi::assetsWindow()
 										ImTextureID id;
 										ImTextureID id;
 										id.m_texture = &img->getTexture();
 										id.m_texture = &img->getTexture();
 										id.m_textureSubresource = TextureSubresourceDesc::all();
 										id.m_textureSubresource = TextureSubresourceDesc::all();
-										ImGui::Image(id, Vec2(cellWidth));
+										if(ImGui::ImageButton("##", id, Vec2(cellWidth)))
+										{
+											m_imageViewer.m_image = img;
+											m_imageViewer.m_open = true;
+										}
 									}
 									}
+									ImGui::PopID();
 
 
 									ImGui::TextWrapped("%s", file.m_basename.cstr());
 									ImGui::TextWrapped("%s", file.m_basename.cstr());
 									ImGui::SetItemTooltip("%s", file.m_filename.cstr());
 									ImGui::SetItemTooltip("%s", file.m_filename.cstr());

+ 3 - 0
AnKi/Editor/EditorUi.h

@@ -6,6 +6,7 @@
 #pragma once
 #pragma once
 
 
 #include <AnKi/Ui.h>
 #include <AnKi/Ui.h>
+#include <AnKi/Editor/ImageViewerUi.h>
 
 
 namespace std {
 namespace std {
 namespace filesystem {
 namespace filesystem {
@@ -91,6 +92,8 @@ private:
 	ImageResourcePtr m_materialIcon;
 	ImageResourcePtr m_materialIcon;
 	ImageResourcePtr m_meshIcon;
 	ImageResourcePtr m_meshIcon;
 
 
+	ImageViewerUi m_imageViewer;
+
 	class
 	class
 	{
 	{
 	public:
 	public:

+ 287 - 0
AnKi/Editor/ImageViewerUi.cpp

@@ -0,0 +1,287 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Editor/ImageViewerUi.h>
+#include <AnKi/Resource/ResourceManager.h>
+#include <AnKi/Resource/ImageResource.h>
+#include <AnKi/Window/Input.h>
+#include <ThirdParty/ImGui/Extra/IconsMaterialDesignIcons.h> // See all icons in https://pictogrammers.com/library/mdi/
+
+namespace anki {
+
+ImageViewerUi::ImageViewerUi()
+{
+	ANKI_CHECKF(ResourceManager::getSingleton().loadResource("ShaderBinaries/UiVisualizeImage.ankiprogbin", m_imageProgram));
+
+	for(U32 i = 0; i < 2; i++)
+	{
+		ShaderProgramResourceVariantInitInfo variantInit(m_imageProgram);
+		variantInit.addMutation("TEXTURE_TYPE", i);
+
+		const ShaderProgramResourceVariant* variant;
+		m_imageProgram->getOrCreateVariant(variantInit, variant);
+		m_imageGrPrograms[i].reset(&variant->getProgram());
+	}
+}
+
+void ImageViewerUi::drawWindow(UiCanvas& canvas, Vec2 initialPos, Vec2 initialSize, ImGuiWindowFlags windowFlags)
+{
+	if(!m_open)
+	{
+		return;
+	}
+
+	const Bool imageChanged = !!m_image && (m_imageUuid != m_image->getUuid());
+	if(imageChanged)
+	{
+		m_crntMip = 0;
+		m_zoom = 1.0f;
+		m_depth = 0.0f;
+		m_pointSampling = true;
+		m_colorChannel = {true, true, true, true};
+		m_maxColorValue = 1.0f;
+
+		m_imageUuid = m_image->getUuid();
+	}
+
+	if(ImGui::GetFrameCount() > 1)
+	{
+		ImGui::SetNextWindowPos(initialPos, ImGuiCond_FirstUseEver);
+		ImGui::SetNextWindowSize(initialSize, ImGuiCond_FirstUseEver);
+	}
+
+	if(ImGui::Begin("Image Viewer", &m_open, windowFlags))
+	{
+		if(ImGui::BeginChild("Toolbox", Vec2(0.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY,
+							 ImGuiWindowFlags_None))
+		{
+			// Texture info
+			{
+				ImGui::TextUnformatted(ICON_MDI_INFORMATION_SLAB_BOX);
+				if(m_image)
+				{
+					const Texture& tex = m_image->getTexture();
+					ImGui::SetItemTooltip("%u x %u x %u\nMips %u\nFormat %s", tex.getWidth(), tex.getHeight(), tex.getDepth(), tex.getMipmapCount(),
+										  getFormatInfo(tex.getFormat()).m_name);
+				}
+				ImGui::SameLine();
+			}
+
+			// Zoom
+			{
+				if(ImGui::Button(ICON_MDI_MINUS))
+				{
+					m_zoom -= 0.1f;
+				}
+				ImGui::SameLine();
+				ImGui::DragFloat("##Zoom", &m_zoom, 0.01f, 0.1f, 20.0f, "Zoom %.3f");
+				ImGui::SameLine();
+				if(ImGui::Button(ICON_MDI_PLUS))
+				{
+					m_zoom += 0.1f;
+				}
+				ImGui::SameLine();
+				ImGui::Spacing();
+				ImGui::SameLine();
+			}
+
+			// Sampling
+			{
+				ImGui::Checkbox("Point Sampling", &m_pointSampling);
+				ImGui::SameLine();
+				ImGui::Spacing();
+				ImGui::SameLine();
+			}
+
+			// Colors
+			{
+				ImGui::Checkbox("Red", &m_colorChannel[0]);
+				ImGui::SameLine();
+				ImGui::Checkbox("Green", &m_colorChannel[1]);
+				ImGui::SameLine();
+				ImGui::Checkbox("Blue", &m_colorChannel[2]);
+				ImGui::SameLine();
+				const U32 colorComponentCount = (!!m_image) ? getFormatInfo(m_image->getTexture().getFormat()).m_componentCount : 4;
+				if(colorComponentCount == 4)
+				{
+					ImGui::Checkbox("Alpha", &m_colorChannel[3]);
+					ImGui::SameLine();
+				}
+				ImGui::Spacing();
+			}
+
+			// Mips combo
+			if(m_image)
+			{
+				const U32 mipCount = m_image->getTexture().getMipmapCount();
+				UiStringList mipLabels;
+				for(U32 mip = 0; mip < mipCount; ++mip)
+				{
+					mipLabels.pushBackSprintf("Mip %u (%u x %u)", mip, m_image->getTexture().getWidth() >> mip,
+											  m_image->getTexture().getHeight() >> mip);
+				}
+
+				if(ImGui::BeginCombo("##Mipmap", (mipLabels.getBegin() + m_crntMip)->cstr(), ImGuiComboFlags_HeightLarge))
+				{
+					for(U32 mip = 0; mip < mipCount; ++mip)
+					{
+						const Bool isSelected = (m_crntMip == mip);
+						if(ImGui::Selectable((mipLabels.getBegin() + mip)->cstr(), isSelected))
+						{
+							m_crntMip = mip;
+						}
+
+						if(isSelected)
+						{
+							ImGui::SetItemDefaultFocus();
+						}
+					}
+					ImGui::EndCombo();
+				}
+
+				ImGui::SameLine();
+			}
+
+			// Depth
+			if(m_image && m_image->getTexture().getTextureType() == TextureType::k3D)
+			{
+				UiStringList labels;
+				for(U32 d = 0; d < m_image->getTexture().getDepth(); ++d)
+				{
+					labels.pushBackSprintf("Depth %u", d);
+				}
+
+				if(ImGui::BeginCombo("##Depth", (labels.getBegin() + U32(m_depth))->cstr(), ImGuiComboFlags_HeightLarge))
+				{
+					for(U32 d = 0; d < m_image->getTexture().getDepth(); ++d)
+					{
+						const Bool isSelected = (m_depth == F32(d));
+						if(ImGui::Selectable((labels.getBegin() + d)->cstr(), isSelected))
+						{
+							m_depth = F32(d);
+						}
+
+						if(isSelected)
+						{
+							ImGui::SetItemDefaultFocus();
+						}
+					}
+					ImGui::EndCombo();
+				}
+
+				ImGui::SameLine();
+			}
+
+			// Avg color
+			{
+				const Vec4 avgColor = (m_image) ? m_image->getAverageColor() : Vec4(0.0f);
+
+				ImGui::Text("Average Color %.2f %.2f %.2f %.2f", avgColor.x(), avgColor.y(), avgColor.z(), avgColor.w());
+				ImGui::SameLine();
+
+				ImGui::ColorButton("Average Color", avgColor);
+				ImGui::SameLine();
+			}
+
+			// Max color slider
+			ImGui::SliderFloat("##Max color", &m_maxColorValue, 0.0f, 5.0f, "Max Color = %.3f");
+		}
+		ImGui::EndChild();
+
+		ImGuiWindowFlags windowFlags = ImGuiWindowFlags_HorizontalScrollbar;
+		if(Input::getSingleton().getKey(KeyCode::kLeftCtrl) > 0)
+		{
+			windowFlags |= ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoMove;
+		}
+
+		if(ImGui::BeginChild("Image", Vec2(-1.0f, -1.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX, windowFlags))
+		{
+			if(m_image)
+			{
+				Texture& tex = m_image->getTexture();
+
+				// Center image
+				const Vec2 imageSize = Vec2(F32(tex.getWidth()), F32(tex.getHeight())) * m_zoom;
+
+				class ExtraPushConstants
+				{
+				public:
+					Vec4 m_colorScale;
+					Vec4 m_depth;
+				} pc;
+				pc.m_colorScale.x() = F32(m_colorChannel[0]) / m_maxColorValue;
+				pc.m_colorScale.y() = F32(m_colorChannel[1]) / m_maxColorValue;
+				pc.m_colorScale.z() = F32(m_colorChannel[2]) / m_maxColorValue;
+				pc.m_colorScale.w() = F32(m_colorChannel[3]);
+
+				pc.m_depth = Vec4((m_depth + 0.5f) / F32(tex.getDepth()));
+
+				ImTextureID texid;
+				texid.m_texture = &tex;
+				texid.m_textureSubresource = TextureSubresourceDesc::surface(m_crntMip, 0, 0, DepthStencilAspectBit::kNone);
+				texid.m_customProgram = m_imageGrPrograms[tex.getTextureType() != TextureType::k2D].get();
+				texid.m_extraFastConstantsSize = U8(sizeof(pc));
+				texid.setExtraFastConstants(&pc, sizeof(pc));
+				texid.m_pointSampling = m_pointSampling;
+				ImGui::Image(texid, imageSize);
+
+				if(ImGui::IsItemHovered())
+				{
+					if(Input::getSingleton().getKey(KeyCode::kLeftCtrl) > 0)
+					{
+						// Zoom
+						const F32 zoomSpeed = 0.05f;
+						if(Input::getSingleton().getMouseButton(MouseButton::kScrollDown) > 0)
+						{
+							m_zoom *= 1.0f - zoomSpeed;
+						}
+						else if(Input::getSingleton().getMouseButton(MouseButton::kScrollUp) > 0)
+						{
+							m_zoom *= 1.0f + zoomSpeed;
+						}
+
+						// Pan
+						if(Input::getSingleton().getMouseButton(MouseButton::kLeft) > 0)
+						{
+							auto toWindow = [&](Vec2 in) {
+								in = in * 0.5f + 0.5f;
+								in.y() = 1.0f - in.y();
+								in *= canvas.getSizef();
+								return in;
+							};
+
+							const Vec2 delta =
+								toWindow(Input::getSingleton().getMousePositionNdc()) - toWindow(Input::getSingleton().getMousePreviousPositionNdc());
+
+							if(delta.x() != 0.0f)
+							{
+								ImGui::SetScrollX(ImGui::GetScrollX() - delta.x());
+							}
+
+							if(delta.y() != 0.0f)
+							{
+								ImGui::SetScrollY(ImGui::GetScrollY() - delta.y());
+							}
+						}
+					}
+				}
+			}
+			else
+			{
+				ImGui::TextUnformatted("No image");
+			}
+		}
+		ImGui::EndChild();
+	}
+	ImGui::End();
+
+	if(!m_open)
+	{
+		// It was closed
+		m_image.reset(nullptr);
+	}
+}
+
+} // end namespace anki

+ 40 - 0
AnKi/Editor/ImageViewerUi.h

@@ -0,0 +1,40 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Ui.h>
+
+namespace anki {
+
+/// @addtogroup editor
+/// @{
+
+class ImageViewerUi
+{
+public:
+	ImageResourcePtr m_image;
+	Bool m_open = false;
+
+	ImageViewerUi();
+
+	void drawWindow(UiCanvas& canvas, Vec2 initialPos, Vec2 initialSize, ImGuiWindowFlags windowFlags = 0);
+
+private:
+	ShaderProgramResourcePtr m_imageProgram;
+	Array<ShaderProgramPtr, 2> m_imageGrPrograms;
+
+	U32 m_crntMip = 0;
+	F32 m_zoom = 1.0f;
+	F32 m_depth = 0.0f;
+	Bool m_pointSampling = true;
+	Array<Bool, 4> m_colorChannel = {true, true, true, true};
+	F32 m_maxColorValue = 1.0f;
+
+	U32 m_imageUuid = 0;
+};
+/// @}
+
+} // end namespace anki

+ 1 - 5
Tests/Framework/Framework.cpp

@@ -241,12 +241,8 @@ void deleteTesterSingleton()
 
 
 void initWindow()
 void initWindow()
 {
 {
-	NativeWindowInitInfo inf;
-	inf.m_width = g_cvarWindowWidth;
-	inf.m_height = g_cvarWindowHeight;
-	inf.m_title = "AnKi unit tests";
 	NativeWindow::allocateSingleton();
 	NativeWindow::allocateSingleton();
-	ANKI_TEST_EXPECT_NO_ERR(NativeWindow::getSingleton().init(inf));
+	ANKI_TEST_EXPECT_NO_ERR(NativeWindow::getSingleton().init(60, "AnKi unit tests"));
 }
 }
 
 
 void initGrManager()
 void initGrManager()

+ 5 - 220
Tools/Image/ImageViewerMain.cpp

@@ -10,7 +10,7 @@ using namespace anki;
 class TextureViewerUiNode : public SceneNode
 class TextureViewerUiNode : public SceneNode
 {
 {
 public:
 public:
-	ImageResourcePtr m_imageResource;
+	ImageViewerUi m_ui;
 
 
 	TextureViewerUiNode(CString name)
 	TextureViewerUiNode(CString name)
 		: SceneNode(name)
 		: SceneNode(name)
@@ -22,7 +22,7 @@ public:
 			},
 			},
 			this);
 			this);
 
 
-		ANKI_CHECK_AND_IGNORE(ResourceManager::getSingleton().loadResource("ShaderBinaries/UiVisualizeImage.ankiprogbin", m_imageProgram));
+		m_ui.m_open = true;
 	}
 	}
 
 
 	void frameUpdate([[maybe_unused]] Second prevUpdateTime, [[maybe_unused]] Second crntTime) override
 	void frameUpdate([[maybe_unused]] Second prevUpdateTime, [[maybe_unused]] Second crntTime) override
@@ -31,237 +31,22 @@ public:
 
 
 private:
 private:
 	ImFont* m_font = nullptr;
 	ImFont* m_font = nullptr;
-	ShaderProgramResourcePtr m_imageProgram;
-	ShaderProgramPtr m_imageGrProgram;
-
-	U32 m_crntMip = 0;
-	F32 m_zoom = 1.0f;
-	F32 m_depth = 0.0f;
-	Bool m_pointSampling = true;
-	Array<Bool, 4> m_colorChannel = {true, true, true, true};
-	F32 m_maxColorValue = 1.0f;
 
 
 	void draw(UiCanvas& canvas)
 	void draw(UiCanvas& canvas)
 	{
 	{
 		if(!m_font)
 		if(!m_font)
 		{
 		{
-			m_font = canvas.addFont("EngineAssets/UbuntuMonoRegular.ttf");
-		}
-
-		const Texture& grTex = m_imageResource->getTexture();
-		const U32 colorComponentCount = getFormatInfo(grTex.getFormat()).m_componentCount;
-		ANKI_ASSERT(grTex.getTextureType() == TextureType::k2D || grTex.getTextureType() == TextureType::k3D);
-
-		if(!m_imageGrProgram.isCreated())
-		{
-			ShaderProgramResourceVariantInitInfo variantInit(m_imageProgram);
-			variantInit.addMutation("TEXTURE_TYPE", (grTex.getTextureType() == TextureType::k2D) ? 0 : 1);
-
-			const ShaderProgramResourceVariant* variant;
-			m_imageProgram->getOrCreateVariant(variantInit, variant);
-			m_imageGrProgram.reset(&variant->getProgram());
+			m_font = canvas.addFont("EngineAssets/UbuntuRegular.ttf");
 		}
 		}
 
 
-		const ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove;
-		ImGui::Begin("Console", nullptr, windowFlags);
-
 		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(canvas.getSizef());
 		ImGui::SetWindowSize(canvas.getSizef());
 
 
-		ImGui::BeginChild("Tools", Vec2(-1.0f, 30.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX, ImGuiWindowFlags_None);
-
-		// Zoom
-		if(ImGui::Button("-"))
-		{
-			m_zoom -= 0.1f;
-		}
-		ImGui::SameLine();
-		ImGui::DragFloat("##Zoom", &m_zoom, 0.01f, 0.1f, 20.0f, "Zoom %.3f");
-		ImGui::SameLine();
-		if(ImGui::Button("+"))
-		{
-			m_zoom += 0.1f;
-		}
-		ImGui::SameLine();
-		ImGui::Spacing();
-		ImGui::SameLine();
-
-		// Sampling
-		ImGui::Checkbox("Point sampling", &m_pointSampling);
-		ImGui::SameLine();
-		ImGui::Spacing();
-		ImGui::SameLine();
-
-		// Colors
-		ImGui::Checkbox("Red", &m_colorChannel[0]);
-		ImGui::SameLine();
-		ImGui::Checkbox("Green", &m_colorChannel[1]);
-		ImGui::SameLine();
-		ImGui::Checkbox("Blue", &m_colorChannel[2]);
-		ImGui::SameLine();
-		if(colorComponentCount == 4)
-		{
-			ImGui::Checkbox("Alpha", &m_colorChannel[3]);
-			ImGui::SameLine();
-		}
-		ImGui::Spacing();
-		ImGui::SameLine();
-
-		// Mips combo
-		{
-			UiStringList mipLabels;
-			for(U32 mip = 0; mip < grTex.getMipmapCount(); ++mip)
-			{
-				mipLabels.pushBackSprintf("Mip %u (%u x %u)", mip, grTex.getWidth() >> mip, grTex.getHeight() >> mip);
-			}
-
-			if(ImGui::BeginCombo("##Mipmap", (mipLabels.getBegin() + m_crntMip)->cstr(), ImGuiComboFlags_HeightLarge))
-			{
-				for(U32 mip = 0; mip < grTex.getMipmapCount(); ++mip)
-				{
-					const Bool isSelected = (m_crntMip == mip);
-					if(ImGui::Selectable((mipLabels.getBegin() + mip)->cstr(), isSelected))
-					{
-						m_crntMip = mip;
-					}
-
-					if(isSelected)
-					{
-						ImGui::SetItemDefaultFocus();
-					}
-				}
-				ImGui::EndCombo();
-			}
-
-			ImGui::SameLine();
-		}
-
-		// Depth
-		if(grTex.getTextureType() == TextureType::k3D)
-		{
-			UiStringList labels;
-			for(U32 d = 0; d < grTex.getDepth(); ++d)
-			{
-				labels.pushBackSprintf("Depth %u", d);
-			}
-
-			if(ImGui::BeginCombo("##Depth", (labels.getBegin() + U32(m_depth))->cstr(), ImGuiComboFlags_HeightLarge))
-			{
-				for(U32 d = 0; d < grTex.getDepth(); ++d)
-				{
-					const Bool isSelected = (m_depth == F32(d));
-					if(ImGui::Selectable((labels.getBegin() + d)->cstr(), isSelected))
-					{
-						m_depth = F32(d);
-					}
-
-					if(isSelected)
-					{
-						ImGui::SetItemDefaultFocus();
-					}
-				}
-				ImGui::EndCombo();
-			}
-
-			ImGui::SameLine();
-		}
-
-		// Max color slider
-		ImGui::SliderFloat("##Max color", &m_maxColorValue, 0.0f, 5.0f, "Max color = %.3f");
-		ImGui::SameLine();
-
-		// Avg color
-		{
-			const Vec4 avgColor = m_imageResource->getAverageColor();
-
-			ImGui::Text("Average Color %.2f %.2f %.2f %.2f", avgColor.x(), avgColor.y(), avgColor.z(), avgColor.w());
-			ImGui::SameLine();
-
-			ImGui::ColorButton("Average color", avgColor);
-		}
-
-		// Next
-		ImGui::EndChild();
-
-		ImGui::BeginChild("Image", Vec2(-1.0f, -1.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX,
-						  ImGuiWindowFlags_HorizontalScrollbar
-							  | (Input::getSingleton().getKey(KeyCode::kLeftCtrl) > 0 ? ImGuiWindowFlags_NoScrollWithMouse : 0));
-
-		// Image
-		{
-			// Center image
-			const Vec2 imageSize = Vec2(F32(grTex.getWidth()), F32(grTex.getHeight())) * m_zoom;
-
-			class ExtraPushConstants
-			{
-			public:
-				Vec4 m_colorScale;
-				Vec4 m_depth;
-			} pc;
-			pc.m_colorScale.x() = F32(m_colorChannel[0]) / m_maxColorValue;
-			pc.m_colorScale.y() = F32(m_colorChannel[1]) / m_maxColorValue;
-			pc.m_colorScale.z() = F32(m_colorChannel[2]) / m_maxColorValue;
-			pc.m_colorScale.w() = F32(m_colorChannel[3]);
-
-			pc.m_depth = Vec4((m_depth + 0.5f) / F32(grTex.getDepth()));
-
-			ImTextureID texid;
-			texid.m_texture = &m_imageResource->getTexture();
-			texid.m_textureSubresource = TextureSubresourceDesc::surface(m_crntMip, 0, 0, DepthStencilAspectBit::kNone);
-			texid.m_customProgram = m_imageGrProgram.get();
-			texid.m_extraFastConstantsSize = U8(sizeof(pc));
-			texid.setExtraFastConstants(&pc, sizeof(pc));
-			texid.m_pointSampling = m_pointSampling;
-			ImGui::Image(texid, imageSize);
-
-			if(ImGui::IsItemHovered())
-			{
-				if(Input::getSingleton().getKey(KeyCode::kLeftCtrl) > 0)
-				{
-					// Zoom
-					const F32 zoomSpeed = 0.05f;
-					if(Input::getSingleton().getMouseButton(MouseButton::kScrollDown) > 0)
-					{
-						m_zoom *= 1.0f - zoomSpeed;
-					}
-					else if(Input::getSingleton().getMouseButton(MouseButton::kScrollUp) > 0)
-					{
-						m_zoom *= 1.0f + zoomSpeed;
-					}
-
-					// Pan
-					if(Input::getSingleton().getMouseButton(MouseButton::kLeft) > 0)
-					{
-						auto toWindow = [](Vec2 in) {
-							in = in * 0.5f + 0.5f;
-							in.y() = 1.0f - in.y();
-							in *= Vec2(UVec2(NativeWindow::getSingleton().getWidth(), NativeWindow::getSingleton().getHeight()));
-							return in;
-						};
-
-						const Vec2 delta =
-							toWindow(Input::getSingleton().getMousePositionNdc()) - toWindow(Input::getSingleton().getMousePreviousPositionNdc());
-
-						if(delta.x() != 0.0f)
-						{
-							ImGui::SetScrollX(ImGui::GetScrollX() - delta.x());
-						}
-
-						if(delta.y() != 0.0f)
-						{
-							ImGui::SetScrollY(ImGui::GetScrollY() - delta.y());
-						}
-					}
-				}
-			}
-		}
-
-		ImGui::EndChild();
+		m_ui.drawWindow(canvas, Vec2(0.0f), canvas.getSizef(), ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
 
 
 		ImGui::PopFont();
 		ImGui::PopFont();
-		ImGui::End();
 	}
 	}
 };
 };
 
 
@@ -307,7 +92,7 @@ public:
 
 
 		// Create the node
 		// Create the node
 		TextureViewerUiNode* node = SceneGraph::getSingleton().newSceneNode<TextureViewerUiNode>("TextureViewer");
 		TextureViewerUiNode* node = SceneGraph::getSingleton().newSceneNode<TextureViewerUiNode>("TextureViewer");
-		node->m_imageResource = std::move(image);
+		node->m_ui.m_image = std::move(image);
 
 
 		return Error::kNone;
 		return Error::kNone;
 	}
 	}