[imgui] 10. 통합 Desktop 구현

목표

  • Desktop에 지금까지 구현했던 프로그램들을 전부 로드
  • 사실 추가로 구현한거 없이 그냥 연결만 했다.

Final
Final

파일트리

지금까지 작업했던 파일들을 복사해서 옮겼다. Own이 붙은것만 직접 작업한 파일이다.

output
│  Calendar_Own.cpp
│  Calendar_Own.hpp
│  CsvTool_Own.cpp
│  CsvTool_Own.hpp
│  Desktop_Own.cpp
│  Desktop_Own.hpp
│  DiffViewer_Own.cpp
│  DiffViewer_Own.hpp
│  FileExplorer_Own.cpp
│  FileExplorer_Own.hpp
│  main.cpp
│  OtherTopics.cpp
│  OtherTopics.hpp
│  Paint_Own.cpp
│  Paint_Own.hpp
│  Plotter_Own.cpp
│  Plotter_Own.hpp
│  TextEditor_Own.cpp
│  TextEditor_Own.hpp
│  UiHelpers.cpp
│  UiHelpers.hpp
│  WallClock_Own.cpp
│  WallClock_Own.hpp
│  WindowBase.cpp
└─ WindowBase.hpp

추가작업

  1. CMakeLists.txt에 소스파일 추가하기 (external 폴더의 파일도)
  2. CMakeLists.txt에 이미지 로드 기능을 정의
  3. CMakeLists.txt에 헤더경로 지정
  4. 아이콘으로 띄울 프로그램들을 WindowBase를 상속받도록 구현
  5. 각 프로그램 연결

구현코드

github 링크

WindowBase

모든 어플리케이션의 부모클래스이며, 윈도우의 설정을 담당한다.

class WindowBase
{
public:
    constexpr static auto mainWindowFlags =
        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
        ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar;
    constexpr static auto mainWindowSize = ImVec2(1280.0F, 720.0F);
    constexpr static auto mainWindowPos = ImVec2(0.0F, 0.0F);

public:
    WindowBase() : ini(CSimpleIniA()){};
    virtual ~WindowBase(){};

    // 테마를 세팅, 저장, 로드할 수 있는 함수들
    void SettingsMenuBar();
    void LoadTheme();
    void SaveTheme();

    // 순수가상함수로, 상속받은 객체가 자신의 Draw함수를 구현해야함
    virtual void Draw(std::string_view label, bool *open = nullptr) = 0;

protected:
    void DrawColorsSettings(bool *open = nullptr);
    static ImGuiStyle DefaultColorStyle();

    CSimpleIniA ini;
};

Desktop::DrawDesktop

이전과 마찬가지로 그냥 가지고있는 모든 icon을 돌면서 Draw해준다.

void Desktop::DrawDesktop()
{
    for (auto &icon : icons)
        icon.Draw();
}

Desktop::Icon::Draw

아이콘의 Draw는 그냥 버튼을 클릭했을때 base의 Draw를 호출하면서 popupOpen 값을 true로 변경하고 Draw의 open 인자로 전달한다.

void Desktop::Icon::Draw()
{
    constexpr static auto flags =
        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse;

    ImGui::SetNextWindowPos(position, ImGuiCond_FirstUseEver);
    ImGui::Begin(fmt::format("###{}", label).data(), nullptr, flags);
    if (ImGui::Button(label.data(), ImVec2(100.0F, 50.0F)) || popupOpen)
    {
        popupOpen = true;
        base->Draw(label, &popupOpen);
    }
    ImGui::End();
}

TextEditor::Draw

윈도우의 사이즈, 위치는 WindowBase에 있는대로 초기화하고, Flags는 | 연산자로 추가가 가능하다.
기존 구현한 컨텐츠를 그대로 Draw해주면 된다.

void TextEditor::Draw(std::string_view label, bool *open)
{
    ImGui::SetNextWindowSize(mainWindowSize);
    ImGui::SetNextWindowPos(mainWindowPos);

    ImGui::Begin(label.data(), open, mainWindowFlags | ImGuiWindowFlags_MenuBar);

    SettingsMenuBar();

    DrawMenu();
    ImGui::Separator();
    DrawContent();
    ImGui::Separator();
    DrawInfo();

    ImGui::End();
}

ini 파일 Save/Load

설정을 좀더 쉽게 저장할 수 있게 ini 파일을 사용했다.
vpkg로 simpleini 패키지를 추가하고, CMakeLists.txt에서 include_directories에 ${SIMPLEINI_INCLUDE_DIRS} 를 추가해주면 SimpleIni.h를 사용할 수 있다.

실제 테마값이 저장된 test.ini 파일

[theme]
GlobalAlpha = 1.000000
WindowPaddingX = 8.000000
WindowPaddingY = 14.000000
WindowRounding = 0.000000
FramePaddingX = 4.000000
FramePaddingY = 3.000000
...

Save

test.ini 파일의 theme 섹션에 값을 저장하는 방법

    const char ini_filepath[] = "test.ini";
    const char section[] = "theme";

    if (!ImGui::GetCurrentContext())
        return;

    SI_Error rc = ini.LoadFile(ini_filepath);
    if (rc < 0)
        return;

    auto &style = ImGui::GetStyle();

    ini.SetDoubleValue(section, "GlobalAlpha", style.Alpha);
    ini.SetDoubleValue(section, "WindowPaddingX", style.WindowPadding.x);
    ini.SetDoubleValue(section, "WindowPaddingY", style.WindowPadding.y);

Load

    const char ini_filepath[] = "test.ini";
    const char section[] = "theme";

    // 파일이 없는경우 기본값 지정 후 파일 만들고 나가기
    if (!std::filesystem::exists(ini_filepath))
    {
        ImGui::GetStyle() = DefaultColorStyle();

        auto out = std::ofstream{ini_filepath};
        out.close();

        return;
    }

    ini.LoadFile(ini_filepath);
    auto &style = ImGui::GetStyle();
    style.WindowPadding.x = (float)ini.GetDoubleValue(section,
                                                      "WindowPaddingX",
                                                      style.WindowPadding.x);

이미지 로드

Desktop::DrawBackground

Desktop에 있는 백그라운드 그리기 함수이다.
커서의 포지션을 0, 0 으로 이동 후 이미지 경로를 전달해서 그린다.

void Desktop::DrawBackground()
{
    ImGui::SetCursorPos(ImVec2(0.0F, 0.0F));
    const auto image_filepath =
        fmt::format("{}{}", PROJECT_PATH, "/images/bg.png");
    LoadAndDisplayImage(image_filepath);
    ImGui::SetCursorPos(ImVec2(0.0F, 0.0F));
}

UiHelpers.cpp

백엔드에 의존하는 코드이기 때문에 구글링으로 내 백엔드에 맞는 코드를 찾아야한다.

loadTexture

백엔드 코드를 이용해서 텍스쳐를 로드한다. 아래는 OpenGL에서 사용하는 방법이다.

std::tuple<GLuint, std::uint32_t, std::uint32_t> loadTexture(
    const char *filename)
{
    std::vector<unsigned char> data;
    std::uint32_t width = 0U;
    std::uint32_t height = 0U;

    const auto error = lodepng::decode(data, width, height, filename);

    if (error)
        throw "Error loading image";

    GLuint texture = 0U;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 GL_RGBA,
                 width,
                 height,
                 0,
                 GL_RGBA,
                 GL_UNSIGNED_BYTE,
                 &data[0]);

    return std::make_tuple(texture, width, height);
}

LoadAndDisplayImage

이미지를 로드한 후 ImGui에 전달해서 실제로 이미지를 그려주는 작업을 한다.

void LoadAndDisplayImage(std::string_view image_filepath)
{
    try
    {
        const auto &[myImageTexture, imageWidth, imageHeight] =
            loadTexture(image_filepath.data());

        const auto imageSize = ImVec2(static_cast<float>(imageWidth),
                                      static_cast<float>(imageHeight));
        ImGui::Image(reinterpret_cast<ImTextureID>(myImageTexture), imageSize);
    }
    catch (const std::exception &e)
    {
        std::cerr << e.what() << '\n';
        exit(1);
    }
}

ETC

TreeNode

collapse가 가능하고, 활성화되면 내부의 요소들을 Tab으로 포커싱 전환을 할 수 있다.

TreeNode
TreeNode

CollapsingHeader

collapse가 가능하고, 헤더가 강조된 형식 (영상의 Help 콜랩스)

BeginTabBar

탭바의 시작함수 BeginTabItemEndTabItem 사이에서 요소를 추가한다.

SeparatorText

구분선 중간에 텍스트가 있는 형태

BeginMenuBar

드롭다운 메뉴바 형태

my_imgui_config.h

CMakeLists.txt에 추가된 코드가 있다.
IMGUI_USER_CONFIG 값을 my_imgui_config.h 파일로 절대경로를 지정했는데,
런타임 옵션을 변경할 수 있는 파일이다.
99% 상황에서는 필요없다고 한다.

add_compile_definitions(
    IMGUI_USER_CONFIG="${CMAKE_SOURCE_DIR}/external/my_imgui_config.h"
)

Comments

ESC
Type to search...