[imgui] 7. CSVEditor

목표

  • float 데이터를 저장하는 테이블 구현
  • csv 형태로 저장, 로드
  • 슬라이더로 행, 열 크기 조절가능

CSVEditor
CSVEditor

구현코드

github 링크

메인함수

class WindowClass
{
public:
    static constexpr auto popUpFlags =
        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
        ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar;
    static constexpr auto popUpSize = ImVec2(300.0f, 100.0f);
    static constexpr auto popUpButtonSize = ImVec2(120.0f, 0.0f);
    static constexpr auto popUpPos = ImVec2(1280.0f / 2.0f - popUpSize.x / 2.0f,
                                            720.0f / 2.0f - popUpSize.y / 2.0f);

    // 행,열 최대길이
    static constexpr auto maxNumRows = 30;
    static constexpr auto maxNumCols = 8;

public:
    WindowClass()
        : colSize(0), rowSize(0), data({}), filenameBuffer("test.csv"){};
    void Draw(std::string_view label);

private:
    void DrawSizeButtons();       // 행, 열 길이조절하는 슬라이더, 버튼
    void DrawIoButtons();         // Save, Load, Clear
    void DrawTable();             // 실제 테이블이 구현되는곳

    void DrawSavePopup();
    void DrawLoadPopup();

    // 셀 클릭했을때 값 지정할 수 있는 팝업
    void DrawValuePopup(const int row, const int col);

    void SaveToCsvFile(std::string_view filename);
    void LoadFromCsvFile(std::string_view filename);

    // 셀에서 value를 format에 맞게 데이터 출력
    template <typename T>
    void PlotCellValue(std::string_view formatter, const T value);

    // 팝업 레이아웃 설정 모아둔 함수
    void SetPopupLayout();

private:
    std::int32_t colSize;
    std::int32_t rowSize;
    std::vector<std::vector<float>> data;   // 실제 테이블의 셀 데이터

    char filenameBuffer[256];
};
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);

    DrawSizeButtons();
    ImGui::Separator();
    DrawIoButtons();
    ImGui::Separator();
    DrawTable();

    ImGui::End();
}

DrawSizeButton

void WindowClass::DrawSizeButtons()
{
    auto user_control_rows = false;
    auto user_control_cols = false;

    auto slider_value_rows = rowSize;
    auto slider_value_cols = colSize;

    ImGui::Text("Num Rows: ");
    ImGui::SameLine();

    // 정수형 슬라이더 구현.
    // 슬라이더와 버튼이 조작됐는지 체크. 이후 조작여부에 따라 테이블 수정해야되기 때문
    if (ImGui::SliderInt("##numRows", &slider_value_rows, 0, maxNumRows))
    {
        user_control_rows = true;
        rowSize = slider_value_rows;
    }
    ImGui::SameLine();
    if (ImGui::Button("Add Row") && rowSize < maxNumRows)
    {
        ++rowSize;
        user_control_rows = true;
    }
    ImGui::SameLine();
    if (ImGui::Button("Drop Row") && rowSize > 0)
    {
        --rowSize;
        user_control_rows = true;
    }

    ImGui::Text("Num Cols: ");
    /* ... Column 도 Row 처럼 버튼 컨트롤 ... */

    if (user_control_rows)
    {
        // rows 가 변경된 경우, row 추가
        data.resize(rowSize, std::vector<float>(colSize, 0.0f));
    }
    else if (user_control_cols)
    {
        for (auto row = 0; row < rowSize; ++row)
        {
            data[row].resize(colSize, 0.0f);
        }
    }
}

DrawIoButtons

그냥 버튼에 따라 팝업을 호출하는 함수이다.

void WindowClass::DrawIoButtons()
{
    if (ImGui::Button("Save"))
        ImGui::OpenPopup("Save File");
    ImGui::SameLine();
    if (ImGui::Button("Load"))
        ImGui::OpenPopup("Load File");
    ImGui::SameLine();
    if (ImGui::Button("Clear"))
    {
        data.clear();
        rowSize = 0;
        colSize = 0;
    }
    DrawSavePopup();
    DrawLoadPopup();
}

Save / Load 로직

csv 파일이기 때문에 저장이나 로드할때 , 구분자를 이용해서 데이터를 구분했다.

void WindowClass::SaveToCsvFile(std::string_view filename)
{
    auto out = std::ofstream{filename.data()};
    if (!out.is_open())
        return;

    for (std::int32_t row = 0; row < rowSize; ++row)
    {
        for (std::int32_t col = 0; col < colSize; ++col)
        {
            out << data[row][col] << ",";
        }
        out << "\n";
    }
    out.close();
}

DrawTable

데이터를 테이블 형태로 그려주는 함수이다.

// 데이터를 포맷에 맞게 text 요소를 그려주는 함수.
// 테이블을 생성하고 나면 TableNextColumn() 를 호출해야 테이블에 칸이 생성되고
// 칸이 생성된 상태에서 내부에 Text 요소를 넣는것이다. 
template <typename T>
void WindowClass::PlotCellValue(std::string_view formatter, const T value)
{
    ImGui::TableNextColumn();
    ImGui::Text(formatter.data(), value);
}

void WindowClass::DrawTable()
{
    constexpr static auto table_flags =
        ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuter;

    static auto row_clicked = 0;
    static auto col_clicked = 0;

    if (colSize == 0)
        return;

    // 테이블의 시작. colSize가 1일때부터 테이블이 그려진다. 
    ImGui::BeginTable("Table", colSize, table_flags);

    // 열 이름 설정. 테이블이 그려지는데는 상관없지만, 각 열에대한 스타일링도 따로 할수도 있다.  
    for (std::int32_t col = 0; col < colSize; ++col)
    {
        const auto col_name = fmt::format("{}", 'A' + col);
        ImGui::TableSetupColumn(col_name.data(),
                                ImGuiTableColumnFlags_WidthFixed,
                                1280.0f / static_cast<float>(colSize));
    }

    // 열 하나를 그리는데, Headers 플래그를 넣어서 강조. 그리고 내부에는 열이름 데이터를 채움
    ImGui::TableNextRow(ImGuiTableRowFlags_Headers);    
    for (std::int32_t col = 0; col < colSize; ++col)
        PlotCellValue("%c", 'A' + col);

    for (std::int32_t row = 0; row < rowSize; ++row)
    {
        for (std::int32_t col = 0; col < colSize; ++col)
        {
            PlotCellValue("%f", data[row][col]);
            if (ImGui::IsItemClicked())
            {
                ImGui::OpenPopup("Change Value");
                row_clicked = row;
                col_clicked = col;
            }
            else if (ImGui::IsItemHovered())
            {
                // 마우스만 올려둔 경우 행, 열의 위치를 출력하는 툴팁 그리기
                ImGui::SetTooltip("Cell: (%d, %d)", row, col);
            }
        }
        ImGui::TableNextRow();
    }
    DrawValuePopup(row_clicked, col_clicked);
    ImGui::EndTable();
}

Change Value Popup

얘는 좀 코드가 달라서 추가했다. 비슷하긴함

void WindowClass::DrawValuePopup(const int row, const int col)
{
    static char buffer[64] = { 0, };
    const auto esc_pressed =
        ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape));

    SetPopupLayout();
    if (ImGui::BeginPopupModal("Change Value", nullptr, popUpFlags))
    {
        const auto label = fmt::format("##{}_{}", row, col);
        ImGui::InputText(label.data(), buffer, sizeof(buffer));

        if (ImGui::Button("Save", popUpButtonSize))
        {
            // 값을 data 벡터에 집어넣는다. 
            data[row][col] = std::stof(buffer);
            ImGui::CloseCurrentPopup();
        }

        ImGui::SameLine();

        if (ImGui::Button("Cancel", popUpButtonSize) || esc_pressed)
        {
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    }
}

Comments

ESC
Type to search...