[imgui] 7. CSVEditor

[imgui] 7. CSVEditor

2023년 7월 10일
imgui

목표 #

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

CSVEditor

구현코드 #

github 링크

메인함수 #

 1class WindowClass
 2{
 3public:
 4    static constexpr auto popUpFlags =
 5        ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
 6        ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar;
 7    static constexpr auto popUpSize = ImVec2(300.0f, 100.0f);
 8    static constexpr auto popUpButtonSize = ImVec2(120.0f, 0.0f);
 9    static constexpr auto popUpPos = ImVec2(1280.0f / 2.0f - popUpSize.x / 2.0f,
10                                            720.0f / 2.0f - popUpSize.y / 2.0f);
11
12    // 행,열 최대길이
13    static constexpr auto maxNumRows = 30;
14    static constexpr auto maxNumCols = 8;
15
16public:
17    WindowClass()
18        : colSize(0), rowSize(0), data({}), filenameBuffer("test.csv"){};
19    void Draw(std::string_view label);
20
21private:
22    void DrawSizeButtons();       // 행, 열 길이조절하는 슬라이더, 버튼
23    void DrawIoButtons();         // Save, Load, Clear
24    void DrawTable();             // 실제 테이블이 구현되는곳
25
26    void DrawSavePopup();
27    void DrawLoadPopup();
28
29    // 셀 클릭했을때 값 지정할 수 있는 팝업
30    void DrawValuePopup(const int row, const int col);
31
32    void SaveToCsvFile(std::string_view filename);
33    void LoadFromCsvFile(std::string_view filename);
34
35    // 셀에서 value를 format에 맞게 데이터 출력
36    template <typename T>
37    void PlotCellValue(std::string_view formatter, const T value);
38
39    // 팝업 레이아웃 설정 모아둔 함수
40    void SetPopupLayout();
41
42private:
43    std::int32_t colSize;
44    std::int32_t rowSize;
45    std::vector<std::vector<float>> data;   // 실제 테이블의 셀 데이터
46
47    char filenameBuffer[256];
48};
 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    DrawSizeButtons();
15    ImGui::Separator();
16    DrawIoButtons();
17    ImGui::Separator();
18    DrawTable();
19
20    ImGui::End();
21}

DrawSizeButton #

 1void WindowClass::DrawSizeButtons()
 2{
 3    auto user_control_rows = false;
 4    auto user_control_cols = false;
 5
 6    auto slider_value_rows = rowSize;
 7    auto slider_value_cols = colSize;
 8
 9    ImGui::Text("Num Rows: ");
10    ImGui::SameLine();
11
12    // 정수형 슬라이더 구현.
13    // 슬라이더와 버튼이 조작됐는지 체크. 이후 조작여부에 따라 테이블 수정해야되기 때문
14    if (ImGui::SliderInt("##numRows", &slider_value_rows, 0, maxNumRows))
15    {
16        user_control_rows = true;
17        rowSize = slider_value_rows;
18    }
19    ImGui::SameLine();
20    if (ImGui::Button("Add Row") && rowSize < maxNumRows)
21    {
22        ++rowSize;
23        user_control_rows = true;
24    }
25    ImGui::SameLine();
26    if (ImGui::Button("Drop Row") && rowSize > 0)
27    {
28        --rowSize;
29        user_control_rows = true;
30    }
31
32    ImGui::Text("Num Cols: ");
33    /* ... Column 도 Row 처럼 버튼 컨트롤 ... */
34
35    if (user_control_rows)
36    {
37        // rows 가 변경된 경우, row 추가
38        data.resize(rowSize, std::vector<float>(colSize, 0.0f));
39    }
40    else if (user_control_cols)
41    {
42        for (auto row = 0; row < rowSize; ++row)
43        {
44            data[row].resize(colSize, 0.0f);
45        }
46    }
47}

DrawIoButtons #

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

 1void WindowClass::DrawIoButtons()
 2{
 3    if (ImGui::Button("Save"))
 4        ImGui::OpenPopup("Save File");
 5    ImGui::SameLine();
 6    if (ImGui::Button("Load"))
 7        ImGui::OpenPopup("Load File");
 8    ImGui::SameLine();
 9    if (ImGui::Button("Clear"))
10    {
11        data.clear();
12        rowSize = 0;
13        colSize = 0;
14    }
15    DrawSavePopup();
16    DrawLoadPopup();
17}

Save / Load 로직 #

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

 1void WindowClass::SaveToCsvFile(std::string_view filename)
 2{
 3    auto out = std::ofstream{filename.data()};
 4    if (!out.is_open())
 5        return;
 6
 7    for (std::int32_t row = 0; row < rowSize; ++row)
 8    {
 9        for (std::int32_t col = 0; col < colSize; ++col)
10        {
11            out << data[row][col] << ",";
12        }
13        out << "\n";
14    }
15    out.close();
16}

DrawTable #

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

 1// 데이터를 포맷에 맞게 text 요소를 그려주는 함수.
 2// 테이블을 생성하고 나면 TableNextColumn() 를 호출해야 테이블에 칸이 생성되고
 3// 칸이 생성된 상태에서 내부에 Text 요소를 넣는것이다. 
 4template <typename T>
 5void WindowClass::PlotCellValue(std::string_view formatter, const T value)
 6{
 7    ImGui::TableNextColumn();
 8    ImGui::Text(formatter.data(), value);
 9}
10
11void WindowClass::DrawTable()
12{
13    constexpr static auto table_flags =
14        ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuter;
15
16    static auto row_clicked = 0;
17    static auto col_clicked = 0;
18
19    if (colSize == 0)
20        return;
21
22    // 테이블의 시작. colSize가 1일때부터 테이블이 그려진다. 
23    ImGui::BeginTable("Table", colSize, table_flags);
24
25    // 열 이름 설정. 테이블이 그려지는데는 상관없지만, 각 열에대한 스타일링도 따로 할수도 있다.  
26    for (std::int32_t col = 0; col < colSize; ++col)
27    {
28        const auto col_name = fmt::format("{}", 'A' + col);
29        ImGui::TableSetupColumn(col_name.data(),
30                                ImGuiTableColumnFlags_WidthFixed,
31                                1280.0f / static_cast<float>(colSize));
32    }
33
34    // 열 하나를 그리는데, Headers 플래그를 넣어서 강조. 그리고 내부에는 열이름 데이터를 채움
35    ImGui::TableNextRow(ImGuiTableRowFlags_Headers);    
36    for (std::int32_t col = 0; col < colSize; ++col)
37        PlotCellValue("%c", 'A' + col);
38
39    for (std::int32_t row = 0; row < rowSize; ++row)
40    {
41        for (std::int32_t col = 0; col < colSize; ++col)
42        {
43            PlotCellValue("%f", data[row][col]);
44            if (ImGui::IsItemClicked())
45            {
46                ImGui::OpenPopup("Change Value");
47                row_clicked = row;
48                col_clicked = col;
49            }
50            else if (ImGui::IsItemHovered())
51            {
52                // 마우스만 올려둔 경우 행, 열의 위치를 출력하는 툴팁 그리기
53                ImGui::SetTooltip("Cell: (%d, %d)", row, col);
54            }
55        }
56        ImGui::TableNextRow();
57    }
58    DrawValuePopup(row_clicked, col_clicked);
59    ImGui::EndTable();
60}

Change Value Popup #

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

 1void WindowClass::DrawValuePopup(const int row, const int col)
 2{
 3    static char buffer[64] = { 0, };
 4    const auto esc_pressed =
 5        ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape));
 6
 7    SetPopupLayout();
 8    if (ImGui::BeginPopupModal("Change Value", nullptr, popUpFlags))
 9    {
10        const auto label = fmt::format("##{}_{}", row, col);
11        ImGui::InputText(label.data(), buffer, sizeof(buffer));
12
13        if (ImGui::Button("Save", popUpButtonSize))
14        {
15            // 값을 data 벡터에 집어넣는다. 
16            data[row][col] = std::stof(buffer);
17            ImGui::CloseCurrentPopup();
18        }
19
20        ImGui::SameLine();
21
22        if (ImGui::Button("Cancel", popUpButtonSize) || esc_pressed)
23        {
24            ImGui::CloseCurrentPopup();
25        }
26        ImGui::EndPopup();
27    }
28}
comments powered by Disqus