[imgui] 4. DiffViewer 구현
2023년 7월 8일
목표 #
- 두 파일에서 다른 라인은 빨간색으로 표시
- 다른 라인 수에 대한 카운트
- 버튼으로 해당 라인 덮어쓰기

구현 코드 #
메인함수 #
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}