[imgui] 4. DiffViewer 구현

[imgui] 4. DiffViewer 구현

2023년 7월 8일
imgui

목표 #

  • 두 파일에서 다른 라인은 빨간색으로 표시
  • 다른 라인 수에 대한 카운트
  • 버튼으로 해당 라인 덮어쓰기 DiffViewer

구현 코드 #

메인함수 #

 1#include <cstdint>
 2#include <string_view>
 3#include <vector>
 4#include <string>
 5
 6class WindowClass
 7{
 8public:
 9    // 파일의 내용은 vector에 스트링형태로 저장
10    using FileContent = std::vector<std::string>;
11
12public:
13    WindowClass() : filePath1("text1.txt"), filePath2("text2.txt"),
14        fileContent1({}), fileContent2({}),
15          diffResult1({}), diffResult2({})
16    {}
17
18    void Draw(std::string_view label);
19
20private:
21    void DrawSelection();   // 파일 선택 + Compare버튼 메뉴바
22    void DrawDiffView();    // 파일 로드 후 비교해서 보여주는 화면
23    void DrawStats();       // 다른라인 수를 출력
24
25    // 파일 로드, 세이브
26    FileContent LoadFileContent(std::string_view file_path);
27    void SaveFileContent(std::string_view file_path, FileContent fileContent);
28
29    // 로드된 파일들의 차이점을 계산하는 함수
30    void CreateDiff();
31
32private:
33    std::string filePath1;
34    std::string filePath2;
35
36    FileContent fileContent1;
37    FileContent fileContent2;
38
39    FileContent diffResult1; 
40    FileContent diffResult2;
41};
42
43void render(WindowClass &window_obj);
 1void WindowClass::Draw(std::string_view label)
 2{
 3    constexpr static auto window_flags =
 4        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
 5        ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar;
 6    constexpr static auto window_size = ImVec2(1280.0F, 720.0F);
 7    constexpr static auto window_pos = ImVec2(0.0F, 0.0F);
 8
 9    ImGui::SetNextWindowSize(window_size);
10    ImGui::SetNextWindowPos(window_pos);
11
12    ImGui::Begin(label.data(), nullptr, window_flags);
13
14    DrawSelection();
15    DrawDiffView();
16    DrawStats();
17
18    ImGui::End();
19}

DrawSelection #

 1WindowClass::FileContent WindowClass::LoadFileContent(std::string_view file_path)
 2{
 3    // FileContent 타입은 vector<string> 이다.
 4    auto content = FileContent();
 5
 6    auto in = std::ifstream(file_path.data());
 7    if (in.is_open())
 8    {
 9        // 파일을 열고 개행기준으로 한줄씩 가져와서 vector에 push
10        auto line = std::string{};
11        while (std::getline(in, line))
12            content.push_back(line);
13    }
14    in.close();
15    return content;
16}
17
18void WindowClass::SaveFileContent(std::string_view file_path,
19                                  FileContent fileContent)
20{
21    auto out = std::ofstream(file_path.data());
22    if (out.is_open())
23    {
24        // 저장할땐 한줄씩 꺼내와서 개행까지 추가
25        for (const auto& s : fileContent)
26            out << s << '\n';
27    }
28    out.close();
29}
30
31void WindowClass::CreateDiff()
32{
33    diffResult1.clear();
34    diffResult2.clear();
35
36    const auto max_num_lines =
37        std::max(fileContent1.size(), fileContent2.size());
38
39    for (std::size_t i = 0; i < max_num_lines; ++i)
40    {
41        const auto line1 = i < fileContent1.size() ? fileContent1[i] : "EMPTY";
42        const auto line2 = i < fileContent2.size() ? fileContent2[i] : "EMPTY";
43
44        if (line1 != line2)
45        {
46            // 모든 줄을 비교해서 다른점이 있으면 diffResult에 하나씩 저장
47            diffResult1.push_back(line1);
48            diffResult2.push_back(line2);
49        }
50        else
51        {
52            // 같으면 빈 문자열 저장
53            diffResult1.push_back("");
54            diffResult2.push_back("");
55        }
56    }
57}
58
59void WindowClass::DrawSelection()
60{
61    ImGui::InputText("Left", &filePath1);
62    ImGui::SameLine();
63
64    // Right에도 Save 버튼이 있기 때문에 ### 로 숨겨진 글자를 추가해서 유니크한 label 생성
65    if (ImGui::Button("Save###Left"))
66        SaveFileContent(filePath1, fileContent1);
67    ImGui::InputText("Right", &filePath2);
68    ImGui::SameLine();
69    if (ImGui::Button("Save###Right"))
70        SaveFileContent(filePath2, fileContent2);
71
72    if (ImGui::Button("Compare"))
73    {
74        fileContent1 = LoadFileContent(filePath1);
75        fileContent2 = LoadFileContent(filePath2);
76
77        CreateDiff();
78    }
79}

DrawDiffView #

 1
 2void WindowClass::DrawDiffView()
 3{
 4    constexpr static auto swap_width = 40.0f;
 5    const auto parent_size = ImVec2(ImGui::GetContentRegionAvail().x, 500.0f);
 6    const auto child_size = ImVec2(parent_size.x / 2.0f - swap_width, parent_size.y);
 7
 8    const auto swap_size = ImVec2(swap_width, child_size.y);
 9
10    // 패딩 없애기
11    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
12
13    // Diff 화면 + Swap 버튼을 엮은 요소. 3번째 인자는 border를 넣을지?
14    ImGui::BeginChild("Parent", parent_size, true);
15
16    if (ImGui::BeginChild("Diff1", child_size, false))
17    {
18        for (std::size_t i = 0; i < fileContent1.size(); ++i)
19        {
20            // 다른점이 있다면 색을 지정한 텍스트를 출력. ImVec4(R,G,B,Alpha)
21            if (!diffResult1[i].empty())
22                ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f),
23                                   "%s",
24                                   fileContent1[i].data());
25            else
26                ImGui::Text("%s", fileContent1[i].data());
27        }
28    }
29    ImGui::EndChild();
30    ImGui::SameLine();
31
32    const auto line_height = ImGui::GetTextLineHeightWithSpacing();
33    const auto button_size = ImVec2(15.0f, line_height);
34
35    if (ImGui::BeginChild("Swap", swap_size, true))
36    {
37        for (std::size_t i = 0; i < diffResult1.size(); ++i)
38        {
39            const auto left_label = fmt::format("<##{}", i);
40            const auto right_label = fmt::format(">##{}", i);
41
42            // 둘중 하나라도 다르면 Swap 버튼 구현
43            if (!diffResult1[i].empty() || !diffResult2[i].empty())
44            {
45                if (ImGui::Button(left_label.data(), button_size))
46                {
47                    if (fileContent1.size() > i && fileContent2.size() > i)
48                        fileContent1[i] = fileContent2[i];
49                    else if (fileContent2.size() > i)
50                        fileContent1.push_back(fileContent2[i]);
51                    CreateDiff();
52                }
53
54                ImGui::SameLine();
55
56                if (ImGui::Button(right_label.data(), button_size))
57                {
58                    if (fileContent2.size() > i && fileContent1.size() > i)
59                        fileContent2[i] = fileContent1[i];
60                    else if (fileContent1.size() > i)
61                        fileContent2.push_back(fileContent1[i]);
62                    CreateDiff();
63                }
64            }
65            else
66            {
67                ImGui::SetCursorPosY(ImGui::GetCursorPosY() + line_height);
68            }
69        }
70    }
71    ImGui::EndChild();
72    ImGui::SameLine();
73
74    if (ImGui::BeginChild("Diff2"))
75    {
76        for (std::size_t i = 0; i < fileContent2.size(); ++i)
77        {
78            if (!diffResult2[i].empty())
79                ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f),
80                                   "%s",
81                                   fileContent2[i].data());
82            else
83                ImGui::Text("%s", fileContent2[i].data());
84        }
85    }
86    ImGui::EndChild();
87
88    ImGui::EndChild();
89    ImGui::PopStyleVar();
90}

DrawStats #

File1 기준으로 다른 라인 수 출력

 1void WindowClass::DrawStats()
 2{
 3    auto diff_lines_count = std::size_t{0};
 4    for (const auto& line : diffResult1)
 5    {
 6        if (!line.empty())
 7            ++diff_lines_count;
 8    }
 9
10    // 현재 위치 기준으로 위치 조절
11    // ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 20.0f);
12    // 전체 윈도우 크기 기준으로 위치 조절
13    ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 20.0f);
14    ImGui::Text("Diff lines count: %u", diff_lines_count);
15}
comments powered by Disqus