[imgui] 1. FileExplorer 구현

[imgui] 1. FileExplorer 구현

2023년 7월 5일
imgui

목표 #

  • 디렉터리와 일반 파일들의 목록이 보이는 파일탐색기 구현
  • 파일을 열거나 삭제하거나 파일명을 수정하는 등의 작업이 가능하도록 구현

fileexplorer

구현 코드 #

메인 함수 #

main.cpp의 프레임 반복문에서 호출되는 render 함수

1void render(WindowClass &window_obj)
2{
3    window_obj.Draw("FileExplorer_Own");
4}

FileExplorer의 구현코드

 1#include <cstdint>
 2#include <string_view>
 3#include <filesystem>       
 4// c++17의 filesystem 헤더 추가
 5
 6namespace fs = std::filesystem;
 7
 8class WindowClass
 9{
10public:
11    // 생성자. WindowClass가 생성되며 현재파일위치를 가져옴
12    WindowClass() : currentPath(fs::current_path()), selectedPath(fs::path{}) {};
13
14    void Draw(std::string_view label);
15
16private:
17    void DrawMenu();                  // 윈도우의 최상단 메뉴바 출력   
18    void DrawContent();               // 현재 디렉토리의 파일 리스트 출력
19    void DrawActions();               // 선택된 파일경로, open, rename, delete 버튼 출력
20    void DrawFilter();                // 확장자 필터링용 입력필드 출력
21
22    void renameFilePopup();           // 팝업 Dialog 로직
23    void deleteFilePopup();
24
25    // 실제 동작을 수행하는 함수
26    bool renameFile(const fs::path& oldPath, const fs::path& newPath); 
27    bool deleteFile(const fs::path& path);
28
29    void openFileWithDefaultEditor(); // 파일을 원하는 프로그램으로 열기 로직
30
31private:
32    fs::path currentPath;               // 현재 디렉토리
33    fs::path selectedPath;              // 선택된 디렉토리
34
35    bool renameDialogOpen = false;      // DialogOpen true/false
36    bool deleteDialogOpen = false;
37};
38
39void 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    // 윈도우의 시작지점
13    ImGui::Begin(label.data(), nullptr, window_flags);
14
15    DrawMenu();
16    ImGui::Separator();   // 메뉴마다 구분선 넣기
17
18    DrawContent();
19
20    // 다음 요소의 커서 위치 지정.
21    // (FileExplorer 윈도우크기 - 100px) 을 지정해서 bottom에서 100px 위쪽에 다음요소 출력
22    ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 100.0F);
23    ImGui::Separator();
24
25    DrawActions();
26    ImGui::Separator();
27
28    DrawFilter();
29
30    // 윈도우설정 끝
31    ImGui::End();
32}

DrawMenu #

Menu

최상단 메뉴를 그려주는 함수이다. Go Up 버튼으로 상위폴더로 이동할 수 있고, 현재경로를 출력한다.
상위폴더는

 1void WindowClass::DrawMenu()
 2{
 3    if (ImGui::Button("Go Up"))   // Go Up 레이블을 가진 버튼을 클릭하면 true가 리턴되어 내부 로직을 수행한다. 
 4    {
 5        if (currentPath.has_parent_path())            // 부모경로가 있다면 
 6            currentPath = currentPath.parent_path();  // 현재 경로를 부모경로로 업데이트
 7    }
 8
 9    ImGui::SameLine();    // 위의 요소와 아래 요소를 같은 줄에 그린다. 
10    // 현재 디렉토리 출력. printf의 가변인자처럼 문자열을 포매팅할 수 있다. 
11    ImGui::Text("Currenty directory: %s", currentPath.string().c_str());
12}

DrawContent #

Content

현재 경로의 파일들을 전부 가져와 이름들을 출력하고, 클릭하면 현재경로를 업데이트하거나 선택된경로를 업데이트한다.

 1void WindowClass::DrawContent()
 2{
 3    // 현재 경로의 디렉터리 이터레이터에서 요소를 하나씩 꺼낸다. 
 4    for (const auto& entry : fs::directory_iterator(currentPath))
 5    {
 6        const auto is_selected = entry.path() == selectedPath;    // 선택됐는지 확인 
 7        const auto is_directory = entry.is_directory();           // 현재 요소가 파일인지 디렉터리인지 확인
 8        const auto is_file = entry.is_regular_file();
 9        auto entry_name = entry.path().filename().string();       // 현재요소의 이름만
10
11        // 디렉터리, 일반파일인지에 따라 출력할 문자열 변경
12        if (is_directory)
13            entry_name = "[D] " + entry_name;
14        else if (is_file)
15            entry_name = "[F] " + entry_name;
16
17        // 선택 가능한 텍스트필드. Button처럼 클릭하면 true가 반환됨.
18        // is_selected를 전달해서 선택됐다면 배경색 변경
19        if (ImGui::Selectable(entry_name.c_str(), is_selected))
20        {
21            // 디렉토리인 경우 현재경로에 클릭한 파일명 추가
22            if (is_directory)
23                currentPath /= entry.path().filename();
24
25            // 현재요소의 전체경로를 가져와서 selectedPath 에 저장
26            selectedPath = entry.path();
27        }
28    }
29}

DrawActions #

Actions Modal

파일을 프로그램을 선택해서 열거나, 이름변경, 삭제하는 버튼을 그린다.
기능을 전부 구현해야해서 가장 기능이 크다.

DrawActions #

 1void WindowClass::DrawActions()
 2{
 3    // 선택된 파일 종류에 따라 출력문자열 변경
 4    if (fs::is_directory(selectedPath))
 5        ImGui::Text("Selected dir: %s", selectedPath.string().c_str());
 6    else if (fs::is_regular_file(selectedPath))
 7        ImGui::Text("Selected file: %s", selectedPath.string().c_str());
 8    else
 9    {
10        ImGui::Text("Nothing selected!");
11
12        // 선택되지 않은경우 버튼이 표시되지 않도록 버튼의 자리를 차지하는 투명버튼을 생성
13        // Alpha 값을 0으로 해서 PushStyleVar ~ PopStyleVar 사이의 요소를 투명화
14        ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
15                            ImGui::GetStyle().Alpha * 0.0f);
16
17        // 클릭여부에 따라 로직이 존재하지 않는 가짜버튼
18        ImGui::Button("Non-clickable button");
19        ImGui::PopStyleVar();
20
21        // 선택되지 않은경우 Open, Rename, Delete 버튼이 보이지 않도록 리턴
22        return; 
23    }
24
25    // 파일인경우 Open 버튼 추가렌더링.
26    // Open버튼 클릭 시 확장자에 맞는 프로그램으로 파일 열기
27    if (fs::is_regular_file(selectedPath) && ImGui::Button("Open"))
28        openFileWithDefaultEditor();
29
30    ImGui::SameLine();
31  
32    // Rename 버튼을 클릭한 경우
33    if (ImGui::Button("Rename"))
34    {
35        // "Rename File" 이라는 키를 가진 팝업을 오픈한다. 
36        renameDialogOpen = true;
37        ImGui::OpenPopup("Rename File");
38    }
39
40    ImGui::SameLine();
41
42    // Delete 버튼을 클릭한 경우
43    if (ImGui::Button("Delete"))
44    {
45        // "Delete File" 팝업 오픈. DialogOpen 값을 관리하지 않음
46        ImGui::OpenPopup("Delete File");
47    }
48
49    // 관련 팝업 정의 함수
50    renameFilePopup();
51    deleteFilePopup();
52}

system 함수로 운영체제에 맞는 명령을 호출해서 파일을 확장자에 해당하는 프로그램으로 열 수 있도록 한다.

 1void WindowClass::openFileWithDefaultEditor()
 2{
 3#ifdef _WIN32
 4    const auto command = "start \"\" \"" + selectedPath.string() + "\"";
 5#elif __APPLE__
 6    const auto command = "open \"" + selectedPath.string() + "\"";
 7#elif __linux__
 8    const auto command = "xdg-open \"" + selectedPath.string() + "\"";
 9#endif
10    std::system(command.c_str());
11}

Delete 로직과 비교해보면 모달 열고닫는 방식이 다르다.

 1bool WindowClass::renameFile(const fs::path& old_path, const fs::path& new_path)
 2{
 3    try
 4    {
 5        // filesystem의 rename 함수를 호출해서 실제 파일 이름변경
 6        fs::rename(old_path, new_path);
 7        return true;
 8    }
 9    catch (const std::exception& e)
10    {
11        std::cerr << e.what() << '\n';
12        return false;
13    }
14}
15
16void WindowClass::renameFilePopup()
17{
18    // "Rename File" 팝업을 오픈한 경우, renameDialogOpen 함수가 false가 될때까지 팝업오픈
19    if (ImGui::BeginPopupModal("Rename File", &renameDialogOpen))
20    {
21        // 팝업창 내부 
22        static char buffer_name[512] = {0, };
23
24        ImGui::Text("New name: ");
25        // 입력필드. label앞에 ###을 붙이면 label을 숨길 수 있다. 
26        // 버퍼사이즈까지 입력받을 수 있도록 함
27        ImGui::InputText("###newName", buffer_name, sizeof(buffer_name));
28
29        // Rename 버튼
30        if (ImGui::Button("Rename"))
31        {
32            // 선택된 경로의 부모경로(선택한 파일명제외)에 변경할 파일명을 추가
33            auto new_path = selectedPath.parent_path() / buffer_name;
34            if (renameFile(selectedPath, new_path))
35            {
36                selectedPath = new_path;                            // 새 경로로 선택경로 변경
37                renameDialogOpen = false;                           // Dialog 닫기
38                std::memset(buffer_name, 0, sizeof(buffer_name));   // 입력필드 버퍼 초기화
39            }
40        }
41
42        ImGui::SameLine();
43
44        // 취소 버튼을 누른경우
45        if (ImGui::Button("Cancel"))
46        {
47            renameDialogOpen = false;
48            std::memset(buffer_name, 0, sizeof(buffer_name));
49        }
50
51        // 팝업은 여기까지 
52        ImGui::EndPopup();
53    }
54}
 1bool WindowClass::deleteFile(const fs::path& path)
 2{
 3    try
 4    {
 5        // filesystem의 remote 함수 호출로 해당 경로의 파일 삭제
 6        fs::remove(path);
 7        return true;
 8    }
 9    catch (const std::exception& e)
10    {
11        std::cerr << e.what() << '\n';
12        return false;
13    }
14}
15
16void WindowClass::deleteFilePopup()
17{
18    // "Delete File" 팝업 정의 시작
19    if (ImGui::BeginPopupModal("Delete File"))
20    {
21        ImGui::Text("Are you sure you want to delete %s?",
22            selectedPath.string().c_str());
23
24        if (ImGui::Button("Yes"))
25        {
26            // 삭제가 성공한경우 selectedPath는 지워준다.
27            if (deleteFile(selectedPath))
28                selectedPath.clear();
29            deleteDialogOpen = false;
30
31            // 현재 팝업 닫기 
32            ImGui::CloseCurrentPopup();
33        }
34
35        ImGui::SameLine();
36
37        if (ImGui::Button("No"))
38        {
39            ImGui::CloseCurrentPopup();
40        }
41
42        ImGui::EndPopup();
43    }
44}

DrawFilter #

Actions

모든 파일의 확장자를 검사해서 몇개있는지 확인한다.

 1void WindowClass::DrawFilter()
 2{
 3    static char extension_filter[16] = { 0, };
 4    ImGui::Text("Filter by extension");
 5    ImGui::SameLine();
 6    ImGui::InputText("###inFilter", extension_filter, sizeof(extension_filter));
 7
 8    if (std::strlen(extension_filter) == 0)
 9        return;
10
11    auto filtered_file_count = 0;
12    // 전체 파일을 검사해서 확장자에 해당하는 파일이 몇개있는지 검사 
13    for (const auto& entry : fs::directory_iterator(currentPath))
14    {
15        if (!fs::is_regular_file(entry))
16            continue;
17
18        if (entry.path().extension().string() == extension_filter)
19            ++filtered_file_count;
20    }
21
22    ImGui::Text("filtered file count: %d", filtered_file_count);
23}
comments powered by Disqus