[imgui] 4. DiffViewer 구현

목표

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

구현 코드

메인함수

#include <cstdint>
#include <string_view>
#include <vector>
#include <string>

class WindowClass
{
public:
    // 파일의 내용은 vector에 스트링형태로 저장
    using FileContent = std::vector<std::string>;

public:
    WindowClass() : filePath1("text1.txt"), filePath2("text2.txt"),
        fileContent1({}), fileContent2({}),
          diffResult1({}), diffResult2({})
    {}

    void Draw(std::string_view label);

private:
    void DrawSelection();   // 파일 선택 + Compare버튼 메뉴바
    void DrawDiffView();    // 파일 로드 후 비교해서 보여주는 화면
    void DrawStats();       // 다른라인 수를 출력

    // 파일 로드, 세이브
    FileContent LoadFileContent(std::string_view file_path);
    void SaveFileContent(std::string_view file_path, FileContent fileContent);

    // 로드된 파일들의 차이점을 계산하는 함수
    void CreateDiff();

private:
    std::string filePath1;
    std::string filePath2;

    FileContent fileContent1;
    FileContent fileContent2;

    FileContent diffResult1; 
    FileContent diffResult2;
};

void render(WindowClass &window_obj);
void WindowClass::Draw(std::string_view label)
{
    constexpr static auto window_flags =
        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
        ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar;
    constexpr static auto window_size = ImVec2(1280.0F, 720.0F);
    constexpr static auto window_pos = ImVec2(0.0F, 0.0F);

    ImGui::SetNextWindowSize(window_size);
    ImGui::SetNextWindowPos(window_pos);

    ImGui::Begin(label.data(), nullptr, window_flags);

    DrawSelection();
    DrawDiffView();
    DrawStats();

    ImGui::End();
}

DrawSelection

WindowClass::FileContent WindowClass::LoadFileContent(std::string_view file_path)
{
    // FileContent 타입은 vector<string> 이다.
    auto content = FileContent();

    auto in = std::ifstream(file_path.data());
    if (in.is_open())
    {
        // 파일을 열고 개행기준으로 한줄씩 가져와서 vector에 push
        auto line = std::string{};
        while (std::getline(in, line))
            content.push_back(line);
    }
    in.close();
    return content;
}

void WindowClass::SaveFileContent(std::string_view file_path,
                                  FileContent fileContent)
{
    auto out = std::ofstream(file_path.data());
    if (out.is_open())
    {
        // 저장할땐 한줄씩 꺼내와서 개행까지 추가
        for (const auto& s : fileContent)
            out << s << '\n';
    }
    out.close();
}

void WindowClass::CreateDiff()
{
    diffResult1.clear();
    diffResult2.clear();

    const auto max_num_lines =
        std::max(fileContent1.size(), fileContent2.size());

    for (std::size_t i = 0; i < max_num_lines; ++i)
    {
        const auto line1 = i < fileContent1.size() ? fileContent1[i] : "EMPTY";
        const auto line2 = i < fileContent2.size() ? fileContent2[i] : "EMPTY";

        if (line1 != line2)
        {
            // 모든 줄을 비교해서 다른점이 있으면 diffResult에 하나씩 저장
            diffResult1.push_back(line1);
            diffResult2.push_back(line2);
        }
        else
        {
            // 같으면 빈 문자열 저장
            diffResult1.push_back("");
            diffResult2.push_back("");
        }
    }
}

void WindowClass::DrawSelection()
{
    ImGui::InputText("Left", &filePath1);
    ImGui::SameLine();

    // Right에도 Save 버튼이 있기 때문에 ### 로 숨겨진 글자를 추가해서 유니크한 label 생성
    if (ImGui::Button("Save###Left"))
        SaveFileContent(filePath1, fileContent1);
    ImGui::InputText("Right", &filePath2);
    ImGui::SameLine();
    if (ImGui::Button("Save###Right"))
        SaveFileContent(filePath2, fileContent2);

    if (ImGui::Button("Compare"))
    {
        fileContent1 = LoadFileContent(filePath1);
        fileContent2 = LoadFileContent(filePath2);

        CreateDiff();
    }
}

DrawDiffView


void WindowClass::DrawDiffView()
{
    constexpr static auto swap_width = 40.0f;
    const auto parent_size = ImVec2(ImGui::GetContentRegionAvail().x, 500.0f);
    const auto child_size = ImVec2(parent_size.x / 2.0f - swap_width, parent_size.y);

    const auto swap_size = ImVec2(swap_width, child_size.y);

    // 패딩 없애기
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));

    // Diff 화면 + Swap 버튼을 엮은 요소. 3번째 인자는 border를 넣을지?
    ImGui::BeginChild("Parent", parent_size, true);

    if (ImGui::BeginChild("Diff1", child_size, false))
    {
        for (std::size_t i = 0; i < fileContent1.size(); ++i)
        {
            // 다른점이 있다면 색을 지정한 텍스트를 출력. ImVec4(R,G,B,Alpha)
            if (!diffResult1[i].empty())
                ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f),
                                   "%s",
                                   fileContent1[i].data());
            else
                ImGui::Text("%s", fileContent1[i].data());
        }
    }
    ImGui::EndChild();
    ImGui::SameLine();

    const auto line_height = ImGui::GetTextLineHeightWithSpacing();
    const auto button_size = ImVec2(15.0f, line_height);

    if (ImGui::BeginChild("Swap", swap_size, true))
    {
        for (std::size_t i = 0; i < diffResult1.size(); ++i)
        {
            const auto left_label = fmt::format("<##{}", i);
            const auto right_label = fmt::format(">##{}", i);

            // 둘중 하나라도 다르면 Swap 버튼 구현
            if (!diffResult1[i].empty() || !diffResult2[i].empty())
            {
                if (ImGui::Button(left_label.data(), button_size))
                {
                    if (fileContent1.size() > i && fileContent2.size() > i)
                        fileContent1[i] = fileContent2[i];
                    else if (fileContent2.size() > i)
                        fileContent1.push_back(fileContent2[i]);
                    CreateDiff();
                }

                ImGui::SameLine();

                if (ImGui::Button(right_label.data(), button_size))
                {
                    if (fileContent2.size() > i && fileContent1.size() > i)
                        fileContent2[i] = fileContent1[i];
                    else if (fileContent1.size() > i)
                        fileContent2.push_back(fileContent1[i]);
                    CreateDiff();
                }
            }
            else
            {
                ImGui::SetCursorPosY(ImGui::GetCursorPosY() + line_height);
            }
        }
    }
    ImGui::EndChild();
    ImGui::SameLine();

    if (ImGui::BeginChild("Diff2"))
    {
        for (std::size_t i = 0; i < fileContent2.size(); ++i)
        {
            if (!diffResult2[i].empty())
                ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f),
                                   "%s",
                                   fileContent2[i].data());
            else
                ImGui::Text("%s", fileContent2[i].data());
        }
    }
    ImGui::EndChild();

    ImGui::EndChild();
    ImGui::PopStyleVar();
}

DrawStats

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

void WindowClass::DrawStats()
{
    auto diff_lines_count = std::size_t{0};
    for (const auto& line : diffResult1)
    {
        if (!line.empty())
            ++diff_lines_count;
    }

    // 현재 위치 기준으로 위치 조절
    // ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 20.0f);
    // 전체 윈도우 크기 기준으로 위치 조절
    ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 20.0f);
    ImGui::Text("Diff lines count: %u", diff_lines_count);
}

Comments

ESC
Type to search...