[imgui] 10. 통합 Desktop 구현

[imgui] 10. 통합 Desktop 구현

2023년 7월 11일
imgui

목표 #

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

Final

파일트리 #

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

 1output
 2│  Calendar_Own.cpp
 3│  Calendar_Own.hpp
 4│  CsvTool_Own.cpp
 5│  CsvTool_Own.hpp
 6│  Desktop_Own.cpp
 7│  Desktop_Own.hpp
 8│  DiffViewer_Own.cpp
 9│  DiffViewer_Own.hpp
10│  FileExplorer_Own.cpp
11│  FileExplorer_Own.hpp
12│  main.cpp
13│  OtherTopics.cpp
14│  OtherTopics.hpp
15│  Paint_Own.cpp
16│  Paint_Own.hpp
17│  Plotter_Own.cpp
18│  Plotter_Own.hpp
19│  TextEditor_Own.cpp
20│  TextEditor_Own.hpp
21│  UiHelpers.cpp
22│  UiHelpers.hpp
23│  WallClock_Own.cpp
24│  WallClock_Own.hpp
25│  WindowBase.cpp
26└─ WindowBase.hpp

추가작업 #

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

구현코드 #

github 링크

WindowBase #

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

 1class WindowBase
 2{
 3public:
 4    constexpr static auto mainWindowFlags =
 5        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
 6        ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar;
 7    constexpr static auto mainWindowSize = ImVec2(1280.0F, 720.0F);
 8    constexpr static auto mainWindowPos = ImVec2(0.0F, 0.0F);
 9
10public:
11    WindowBase() : ini(CSimpleIniA()){};
12    virtual ~WindowBase(){};
13
14    // 테마를 세팅, 저장, 로드할 수 있는 함수들
15    void SettingsMenuBar();
16    void LoadTheme();
17    void SaveTheme();
18
19    // 순수가상함수로, 상속받은 객체가 자신의 Draw함수를 구현해야함
20    virtual void Draw(std::string_view label, bool *open = nullptr) = 0;
21
22protected:
23    void DrawColorsSettings(bool *open = nullptr);
24    static ImGuiStyle DefaultColorStyle();
25
26    CSimpleIniA ini;
27};

Desktop::DrawDesktop #

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

1void Desktop::DrawDesktop()
2{
3    for (auto &icon : icons)
4        icon.Draw();
5}

Desktop::Icon::Draw #

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

 1void Desktop::Icon::Draw()
 2{
 3    constexpr static auto flags =
 4        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse;
 5
 6    ImGui::SetNextWindowPos(position, ImGuiCond_FirstUseEver);
 7    ImGui::Begin(fmt::format("###{}", label).data(), nullptr, flags);
 8    if (ImGui::Button(label.data(), ImVec2(100.0F, 50.0F)) || popupOpen)
 9    {
10        popupOpen = true;
11        base->Draw(label, &popupOpen);
12    }
13    ImGui::End();
14}

TextEditor::Draw #

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

 1void TextEditor::Draw(std::string_view label, bool *open)
 2{
 3    ImGui::SetNextWindowSize(mainWindowSize);
 4    ImGui::SetNextWindowPos(mainWindowPos);
 5
 6    ImGui::Begin(label.data(), open, mainWindowFlags | ImGuiWindowFlags_MenuBar);
 7
 8    SettingsMenuBar();
 9
10    DrawMenu();
11    ImGui::Separator();
12    DrawContent();
13    ImGui::Separator();
14    DrawInfo();
15
16    ImGui::End();
17}

ini 파일 Save/Load #

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

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

1[theme]
2GlobalAlpha = 1.000000
3WindowPaddingX = 8.000000
4WindowPaddingY = 14.000000
5WindowRounding = 0.000000
6FramePaddingX = 4.000000
7FramePaddingY = 3.000000
8...

Save #

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

 1    const char ini_filepath[] = "test.ini";
 2    const char section[] = "theme";
 3
 4    if (!ImGui::GetCurrentContext())
 5        return;
 6
 7    SI_Error rc = ini.LoadFile(ini_filepath);
 8    if (rc < 0)
 9        return;
10
11    auto &style = ImGui::GetStyle();
12
13    ini.SetDoubleValue(section, "GlobalAlpha", style.Alpha);
14    ini.SetDoubleValue(section, "WindowPaddingX", style.WindowPadding.x);
15    ini.SetDoubleValue(section, "WindowPaddingY", style.WindowPadding.y);

Load #

 1    const char ini_filepath[] = "test.ini";
 2    const char section[] = "theme";
 3
 4    // 파일이 없는경우 기본값 지정 후 파일 만들고 나가기
 5    if (!std::filesystem::exists(ini_filepath))
 6    {
 7        ImGui::GetStyle() = DefaultColorStyle();
 8
 9        auto out = std::ofstream{ini_filepath};
10        out.close();
11
12        return;
13    }
14
15    ini.LoadFile(ini_filepath);
16    auto &style = ImGui::GetStyle();
17    style.WindowPadding.x = (float)ini.GetDoubleValue(section,
18                                                      "WindowPaddingX",
19                                                      style.WindowPadding.x);

이미지 로드 #

Desktop::DrawBackground #

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

1void Desktop::DrawBackground()
2{
3    ImGui::SetCursorPos(ImVec2(0.0F, 0.0F));
4    const auto image_filepath =
5        fmt::format("{}{}", PROJECT_PATH, "/images/bg.png");
6    LoadAndDisplayImage(image_filepath);
7    ImGui::SetCursorPos(ImVec2(0.0F, 0.0F));
8}

UiHelpers.cpp #

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

loadTexture #

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

 1std::tuple<GLuint, std::uint32_t, std::uint32_t> loadTexture(
 2    const char *filename)
 3{
 4    std::vector<unsigned char> data;
 5    std::uint32_t width = 0U;
 6    std::uint32_t height = 0U;
 7
 8    const auto error = lodepng::decode(data, width, height, filename);
 9
10    if (error)
11        throw "Error loading image";
12
13    GLuint texture = 0U;
14    glGenTextures(1, &texture);
15    glBindTexture(GL_TEXTURE_2D, texture);
16    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
17    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
18    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
19    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
20
21    glTexImage2D(GL_TEXTURE_2D,
22                 0,
23                 GL_RGBA,
24                 width,
25                 height,
26                 0,
27                 GL_RGBA,
28                 GL_UNSIGNED_BYTE,
29                 &data[0]);
30
31    return std::make_tuple(texture, width, height);
32}

LoadAndDisplayImage #

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

 1void LoadAndDisplayImage(std::string_view image_filepath)
 2{
 3    try
 4    {
 5        const auto &[myImageTexture, imageWidth, imageHeight] =
 6            loadTexture(image_filepath.data());
 7
 8        const auto imageSize = ImVec2(static_cast<float>(imageWidth),
 9                                      static_cast<float>(imageHeight));
10        ImGui::Image(reinterpret_cast<ImTextureID>(myImageTexture), imageSize);
11    }
12    catch (const std::exception &e)
13    {
14        std::cerr << e.what() << '\n';
15        exit(1);
16    }
17}

ETC #

TreeNode #

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

CollapsingHeader #

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

BeginTabBar #

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

SeparatorText #

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

BeginMenuBar #

드롭다운 메뉴바 형태


my_imgui_config.h #

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

1add_compile_definitions(
2    IMGUI_USER_CONFIG="${CMAKE_SOURCE_DIR}/external/my_imgui_config.h"
3)
comments powered by Disqus