[imgui] 1. FileExplorer 구현
2023년 7월 5일
목표 #
- 디렉터리와 일반 파일들의 목록이 보이는 파일탐색기 구현
- 파일을 열거나 삭제하거나 파일명을 수정하는 등의 작업이 가능하도록 구현

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

최상단 메뉴를 그려주는 함수이다. 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 #

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

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

모든 파일의 확장자를 검사해서 몇개있는지 확인한다.
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}