[DirectX] IMGUI의 클래스 화 2


지난 포스트에 이어서 Inspector UI를 구성해보자.

InspectorUI 구성하기


ComponentUI 클래스의 구성


TransformUI와 MeshRenderUI는 공통된 부분이 있으므로 상속을 이용할 것이다.
이를 위해서 이제는 ComponentUI에 구성을 한 뒤 상속시키는 방법을 이용하겠다.

// 헤더 파일

#pragma once
#include "UI.h"

#include <Engine\ptr.h>
#include <Engine\CRes.h>

class CGameObject;

class ComponentUI :
    public UI
{
private:
    CGameObject*            m_Target;
    const COMPONENT_TYPE    m_Type;

public:
    void SetTarget(CGameObject* _Target);
    
    CGameObject* GetTarget() { return m_Target; }

    COMPONENT_TYPE GetComponentType() { return m_Type; }

    void GetResKey(Ptr<CRes> _Res, char* _Buff, size_t _BufferSize);

public:
    virtual int render_update() override;


public:
    ComponentUI(const string& _Name, COMPONENT_TYPE _Type);
    ~ComponentUI();
};



// cpp 파일

#include "pch.h"
#include "ComponentUI.h"

#include <Engine\CGameObject.h>

ComponentUI::ComponentUI(const string& _Name, COMPONENT_TYPE _Type)
	: UI(_Name)
	, m_Type(_Type)
{
}

ComponentUI::~ComponentUI()
{
	
}

int ComponentUI::render_update()
{
	if (nullptr == m_Target || nullptr == m_Target->GetComponent(m_Type))
		return FALSE;

	ImGui::PushID(0);
	ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(0.f / 7.0f, 0.6f, 0.6f));
	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(0.f / 7.0f, 0.6f, 0.6f));
	ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(0.f / 7.0f, 0.6f, 0.6f));
	ImGui::Button(GetName().c_str());
	ImGui::PopStyleColor(3);
	ImGui::PopID();	

	return TRUE;
}


void ComponentUI::SetTarget(CGameObject* _Target)
{
	m_Target = _Target;

	if (nullptr == m_Target->GetComponent(m_Type))
	{
		SetActive(false);
	}
	else
	{
		SetActive(true);
	}
}

void ComponentUI::GetResKey(Ptr<CRes> _Res, char* _Buff, size_t _BufferSize)
{
	memset(_Buff, 0, sizeof(char) * _BufferSize);

	if (nullptr == _Res)			
		return;	

	wstring wstrKey = _Res->GetKey();
	string	strKey = string(wstrKey.begin(), wstrKey.end());
	memcpy(_Buff, strKey.data(), sizeof(char) * strKey.length());
}

컴포넌트 타입의 m_Type 변수는 컴포넌트 UI가 어떤 컴포넌트를 담당하는지를 기록하는 역할을 한다.

그리고 자식 클래스에 어떤 컴포넌트인지 전달을 하기 위해서 자식 생성자의 인자에 COMPONENT_TYPE가 들어간다.

그리고, 리소스의 키 값을 받아올 수 있도록 GetResKey함수를 만든다.

InspectorUI.cpp 중에서 (생성자)

InspectorUI::InspectorUI()
	: UI("Inspector")
	, m_pTarget(nullptr)
	, m_TransformUI(nullptr)
{
	m_TransformUI = new TransformUI;
	//m_arrComUI[(UINT)COMPONENT_TYPE::TRANSFORM] = new TransformUI;
	AddChildUI(m_TransformUI);

	m_MeshRenderUI = new MeshRenderUI;
	AddChildUI(m_MeshRenderUI);
}

TransformUI 클래스의 구성


// 헤더파일

#pragma once
#include "UI.h"

class CGameObject;

class TransformUI :
    public UI
{
private:
    CGameObject*    m_Target;

public:
    virtual void render_update() override;

public:
    void SetTarget(CGameObject* _Target) { m_Target = _Target; }

public:
    TransformUI();
    ~TransformUI();
};


// cpp 파일

#include "pch.h"
#include "TransformUI.h"

#include <Engine\CGameObject.h>
#include <Engine\CTransform.h>

TransformUI::TransformUI()
	: UI("Transform")
	, m_Target(nullptr)
{
}

TransformUI::~TransformUI()
{
}

void TransformUI::render_update()
{
	if (nullptr == m_Target)
		return;

	Vec3 vPos = m_Target->Transform()->GetRelativePos();
	Vec3 vScale = m_Target->Transform()->GetRelativeScale();
	Vec3 vRotation = m_Target->Transform()->GetRelativeRot();
	vRotation = (vRotation / XM_PI) * 180.f;

	ImGui::Text("Position");
	ImGui::SameLine();
	ImGui::InputFloat3("##Relative Position", vPos);

	ImGui::Text("Scale   ");
	ImGui::SameLine();
	ImGui::InputFloat3("##Relative Scale", vScale);

	ImGui::Text("Rotation");
	ImGui::SameLine();
	ImGui::InputFloat3("##Relative Rotation", vRotation);

	m_Target->Transform()->SetRelativePos(vPos);
	m_Target->Transform()->SetRelativeScale(vScale);

	vRotation = (vRotation / 180.f) * XM_PI;
	m_Target->Transform()->SetRelativeRot(vRotation);
}


추가로, 텍스트만 제목으로 해놓으면 밋밋하니 버튼을 이용해서 제목을 붙여보자.

// TransformUI.cpp 파일 중에서


	if (FALSE == ComponentUI::render_update())
		return FALSE;

	Vec3 vPos = GetTarget()->Transform()->GetRelativePos();
	Vec3 vScale = GetTarget()->Transform()->GetRelativeScale();
	Vec3 vRotation = GetTarget()->Transform()->GetRelativeRot();
	vRotation = (vRotation / XM_PI) * 180.f;

	ImGui::Text("Position");
	ImGui::SameLine();
	ImGui::InputFloat3("##Relative Position", vPos);

	ImGui::Text("Scale   ");
	ImGui::SameLine();
	ImGui::InputFloat3("##Relative Scale", vScale);

	ImGui::Text("Rotation");
	ImGui::SameLine();
	ImGui::InputFloat3("##Relative Rotation", vRotation);

아래 사항을 이용한다.

  • 버튼의 기능이 없다.
  • 버튼이 hover되어있건, 평소 상태이건 색깔을 같게 만든다.


MeshRenderUI 클래스의 구성


오브젝트가 보유하고 있는 렌더 정보를 출력하도록 한다.

// 헤더파일

#pragma once
#include "ComponentUI.h"

class MeshRenderUI :
    public ComponentUI
{

public:
    virtual int render_update() override;

public:
    MeshRenderUI();
    ~MeshRenderUI();
};
// cpp 파일

#include "pch.h"
#include "MeshRenderUI.h"

#include <Engine\CMeshRender.h>

MeshRenderUI::MeshRenderUI()
	: ComponentUI("MeshRender", COMPONENT_TYPE::MESHRENDER)	
{
}

MeshRenderUI::~MeshRenderUI()
{
}


int MeshRenderUI::render_update()
{
	if (FALSE == ComponentUI::render_update())
		return FALSE;

	char szBuff[50] = {};

	Ptr<CMesh> pMesh = GetTarget()->MeshRender()->GetMesh();
	Ptr<CMaterial> pMtrl = GetTarget()->MeshRender()->GetMaterial();
		
	ImGui::Text("Mesh    ");
	ImGui::SameLine();	
	GetResKey(pMesh.Get(), szBuff, 50);
	ImGui::InputText("##MeshName", szBuff, 50, ImGuiInputTextFlags_ReadOnly);
	ImGui::SameLine();
	ImGui::Button("##MeshSelectBtn", ImVec2(18, 18));
	
	ImGui::Text("Material");
	ImGui::SameLine();
	GetResKey(pMtrl.Get(), szBuff, 50);
	ImGui::InputText("##MtrlName", szBuff, 50, ImGuiInputTextFlags_ReadOnly);
	ImGui::SameLine();
	ImGui::Button("##MtrlSelectBtn", ImVec2(18, 18));

	return TRUE;
}


ImGuiInputTextFlags_ReadOnly로 설정함으로써 메쉬의 이름을 바꾸지 못하도록 지정할 수 있다.

그리고 담당 컴포넌트에게 메쉬와 재질에 대한 정보를 받아오도록 조치한다.

그리고 메쉬, 재질 정보를 담은 목록을 보여주는 UI를 보여줄 수 있도록 버튼도 만들어 줄 수 있다.

ListUI


한편, 리스트를 담은 UI는 InspectorUI가 아닌 완전 별도의 UI이므로 새로운 클래스를 생성해주어야 한다.

// 헤더파일

#pragma once
#include "UI.h"

class ListUI :
    public UI
{
private:
    vector<string>          m_vecStrData; 
    int                     m_iSelectedIdx;

public:
    virtual int render_update() override;

public:
    void AddItem(const string& _strItem) {  m_vecStrData.push_back(_strItem); }
    void Clear() { m_vecStrData.clear(); m_iSelectedIdx = -1; }

public:
    ListUI();
    ~ListUI();
};
// cpp 파일

#include "pch.h"
#include "ListUI.h"

ListUI::ListUI()
    : UI("##List")
    , m_iSelectedIdx(0)
{
}

ListUI::~ListUI()
{
}

int ListUI::render_update()
{    
    // 최근 UI 의 작업영역 사이즈를 알아낸다.
    ImVec2 ListUIContentSize = ImGui::GetContentRegionAvail();
        
    if (ImGui::BeginListBox("##list", ListUIContentSize))
    {
        for (int i = 0; i < m_vecStrData.size(); i++)
        {
            const bool is_selected = (m_iSelectedIdx == i);

            // 리스트 아이템 출력, 반환값은 클릭 True/ False
            if (ImGui::Selectable(m_vecStrData[i].c_str(), is_selected))
            {
                m_iSelectedIdx = i;
            }             

            // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
            if (is_selected)
                ImGui::SetItemDefaultFocus();
        }
        ImGui::EndListBox();
    }
       

    return TRUE;
}

메쉬에 있는 버튼을 누르면 메쉬 리스트를, 재질에 있는 버튼을 누르면 재질 리스트를 보여줄 수 있도록 한다.

List를 출력할 땐 BeginListbox를 이용해볼 수 있다.

// imgui_demo.cpp 파일 중에서

const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };

static int item_current_idx = 0; // Here we store our selection data as an index.
if (ImGui::BeginListBox("listbox 1"))
{
	for (int n = 0; n < IM_ARRAYSIZE(items); n++)
	{
		const bool is_selected = (item_current_idx == n);
		if (ImGui::Selectable(items[n], is_selected))
			item_current_idx = n;

		// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
		if (is_selected)
			ImGui::SetItemDefaultFocus();
	}
	ImGui::EndListBox();
}


위의 포맷을 갖다 쓸 것이다.

// ListUI.cpp 중에서 render_update 함수

int ListUI::render_update()
{    
    // 최근 UI 의 작업영역 사이즈를 알아낸다.
    ImVec2 ListUIContentSize = ImGui::GetContentRegionAvail();
        
    if (ImGui::BeginListBox("##list", ListUIContentSize))
    {
        for (int i = 0; i < m_vecStrData.size(); i++)
        {
            const bool is_selected = (m_iSelectedIdx == i);

            // 리스트 아이템 출력, 반환값은 클릭 True/ False
            if (ImGui::Selectable(m_vecStrData[i].c_str(), is_selected))
            {
                m_iSelectedIdx = i;
            }             

            // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
            if (is_selected)
                ImGui::SetItemDefaultFocus();
        }
        ImGui::EndListBox();
    }
       

    return TRUE;
}
  • GetWindowSize()함수를 통해 ListUISize에 값을 저장해서
  • GetContentRegionAvail()함수를 통해 ListUIContentSize에 값을 저장해서

List의 크기가 부모 윈도우 UI의 크기와 일치하도록 조정할 수 있다.

차이점은 리스트와 윈도우 사이에 패딩이 있는가 없는가 여부이다.

그리고 리스트에 값을 저장하는 데는 vector 자료구조를 사용할 것이다.

// ListUI.h 헤더파일 중에서

private:
	vector<string>		m_vecStrData;

위와 같이 변수를 생성해준다.

그리고 문자열 데이터를 추가하는 과정을 아래와 같이 생성한다.

// ListUI.h 중에서

public:
    void AddItem(const string& _strItem) {  m_vecStrData.push_back(_strItem); }


메쉬 목록을 List로 가져오기


우선, 리소스 매니저로부터 메쉬 목록을 가져오기 위해서 함수를 아래와 같이 생성한다.

이때, map에서 키값만을 가져올 것이므로 데이터 보존을 위해서 const reference 방식을 사용한다.

// CResMgr.h 중에서

public:
    const map<wstring, Ptr<CRes>>& GetResources(RES_TYPE _Type) { return m_arrRes[(UINT)_Type]; }

그리고 ImGuiMgr.cpp 중에서 UI를 반환하는 함수(FindUI)를 아래와 같이 추가한다.

// ImGuiMgr.cpp 중에서

UI* ImGuiMgr::FindUI(const string& _UIName)
{
    map<string, UI*>::iterator iter = m_mapUI.find(_UIName);

    if(iter == m_mapUI.end())
        return nullptr;

    return iter->second;
}

그리고 아래 코드를 통해서 MeshRenderUI에서 리스트를 가져온다.

// MeshRenderUI.cpp 중에서

int MeshRenderUI::render_update()
{
	if (FALSE == ComponentUI::render_update())
		return FALSE;

	char szBuff[50] = {};

	Ptr<CMesh> pMesh = GetTarget()->MeshRender()->GetMesh();
	Ptr<CMaterial> pMtrl = GetTarget()->MeshRender()->GetMaterial();
		
	ImGui::Text("Mesh    ");
	ImGui::SameLine();	
	GetResKey(pMesh.Get(), szBuff, 50);
	ImGui::InputText("##MeshName", szBuff, 50, ImGuiInputTextFlags_ReadOnly);
	ImGui::SameLine();
	if (ImGui::Button("##MeshSelectBtn", ImVec2(18, 18)))
	{
		const map<wstring, Ptr<CRes>>& mapMesh = CResMgr::GetInst()->GetResources(RES_TYPE::MESH);

		ListUI* pListUI = (ListUI*)ImGuiMgr::GetInst()->FindUI("##List");

		pListUI->SetName("Mesh List");
		pListUI->Clear();
		for (const auto& pair : mapMesh)
		{			
			pListUI->AddItem(string(pair.first.begin(), pair.first.end()));
		}
		pListUI->SetActive(true);
	}
	
	ImGui::Text("Material");
	ImGui::SameLine();
	GetResKey(pMtrl.Get(), szBuff, 50);
	ImGui::InputText("##MtrlName", szBuff, 50, ImGuiInputTextFlags_ReadOnly);
	ImGui::SameLine();
	if (ImGui::Button("##MtrlSelectBtn", ImVec2(18, 18)))
	{
		const map<wstring, Ptr<CRes>>& mapMtrl = CResMgr::GetInst()->GetResources(RES_TYPE::MATERIAL);

		ListUI* pListUI = (ListUI*)ImGuiMgr::GetInst()->FindUI("##List");

		pListUI->SetName("Material List");
		pListUI->Clear();
		for (const auto& pair : mapMtrl)
		{
			pListUI->AddItem(string(pair.first.begin(), pair.first.end()));
		}
		pListUI->SetActive(true);
	}

	return TRUE;
}


Modal방식 및 BeginPopupModal


모달방식은 어떤 UI가 켜져있을 때 다른 UI나 다른 부분이 클릭이 되지 않도록 만드는 기능이다.

IMGUI는 이 기능을 지원하는데, 특정한 UI를 모달방식을 사용하도록 하고 싶으면 BeginPopupModal을 사용하면 된다.

UI 클래스 에서 모달 방식여부에 따라서 분기를 설정해줄 필요가 있다.

// 헤더파일

bool	m_Modal;
// UI.cpp의 finaltick함수 중에서

void UI::finaltick()
{
	if (!m_Active)
		return;

	// 부모 UI
	if (nullptr == m_ParentUI)
	{
		// 모달리스
		if (!m_Modal)
		{
			ImGui::Begin(m_strName.c_str(), &m_Active);

			render_update();

			for (size_t i = 0; i < m_vecChildUI.size(); ++i)
			{
				// 자식UI 가 비활성화 상태면 건너뛴다.
				if (!m_vecChildUI[i]->IsActive())
					continue;

				m_vecChildUI[i]->finaltick();

				// 자식 UI 간의 구분선
				if (i != m_vecChildUI.size() - 1)
					ImGui::Separator();
			}

			ImGui::End();
		}

		// 모달
		else
		{
			ImGui::OpenPopup(m_strName.c_str());
			if (ImGui::BeginPopupModal(m_strName.c_str(), &m_Active))
			{
				render_update();

				for (size_t i = 0; i < m_vecChildUI.size(); ++i)
				{
					// 자식UI 가 비활성화 상태면 건너뛴다.
					if (!m_vecChildUI[i]->IsActive())
						continue;

					m_vecChildUI[i]->finaltick();

					// 자식 UI 간의 구분선
					if (i != m_vecChildUI.size() - 1)
						ImGui::Separator();
				}

				ImGui::EndPopup();
			}
		}
	}

	// 자식 UI
	else
	{
		ImGui::BeginChild(m_strName.c_str(), m_vSize);

		render_update();

		for (size_t i = 0; i < m_vecChildUI.size(); ++i)
		{
			m_vecChildUI[i]->finaltick();

			if (i != m_vecChildUI.size() - 1)
				ImGui::Separator();
		}

		ImGui::EndChild();
	}
}


다음 내용은 다음 포스트에 이어서 포스팅 하곘다.


© 2022.07. by Wookey_Kim

Powered by Hydejack v7.5.2