diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e8e9d3..88379c6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ set(sources "src/main.cpp" "src/GUIStuff/Elements/GUIStuffHelpers.cpp" "src/GUIStuff/Elements/Element.cpp" "src/GUIStuff/Elements/ImageDisplay.cpp" + "src/GUIStuff/Elements/MutableTextLabel.cpp" "src/GUIStuff/Elements/SVGIcon.cpp" "src/GUIStuff/Elements/RadioButton.cpp" "src/GUIStuff/Elements/CheckBox.cpp" diff --git a/assets/data/icons/RemixIcon/more-2-fill.svg b/assets/data/icons/RemixIcon/more-2-fill.svg new file mode 100644 index 0000000..91266e3 --- /dev/null +++ b/assets/data/icons/RemixIcon/more-2-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/data/icons/RemixIcon/refresh-line.svg b/assets/data/icons/RemixIcon/refresh-line.svg new file mode 100644 index 0000000..05cf986 --- /dev/null +++ b/assets/data/icons/RemixIcon/refresh-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/include/Helpers/Networking/NetLibrary.cpp b/include/Helpers/Networking/NetLibrary.cpp index bbcef33..bb6d1fd 100644 --- a/include/Helpers/Networking/NetLibrary.cpp +++ b/include/Helpers/Networking/NetLibrary.cpp @@ -1,5 +1,4 @@ #include "NetLibrary.hpp" -#include #include #include #include "../Logger.hpp" @@ -175,29 +174,23 @@ void NetLibrary::init(const std::filesystem::path& p2pConfigPath) { } void NetLibrary::copy_default_p2p_config_to_path(const std::filesystem::path& newP2PConfigPath) { - std::ifstream checkIfFileAlreadyExists(newP2PConfigPath); - if(checkIfFileAlreadyExists.is_open()) { // Don't copy if P2P config exists already + try { + std::string readLocalP2PConfig = read_file_to_string(newP2PConfigPath); // If it doesnt fail, then file exists, so dont copy p2p config Logger::get().log("INFO", "Local P2P configuration already exists"); - checkIfFileAlreadyExists.close(); return; - } - std::ifstream defaultP2PConfig("data/config/default_p2p.json"); - if(defaultP2PConfig.is_open()) { - nlohmann::json j; - defaultP2PConfig >> j; - defaultP2PConfig.close(); + } catch(...) {} - std::ofstream newFile(newP2PConfigPath); - if(newFile.is_open()) { - newFile << std::setw(4) << j; - newFile.close(); + try { + nlohmann::json j(nlohmann::json::parse(read_file_to_string("data/config/default_p2p.json"))); + std::stringstream newFileSS; + newFileSS << std::setw(4) << j; + if(SDL_SaveFile(newP2PConfigPath.string().c_str(), newFileSS.view().data(), newFileSS.view().size())) Logger::get().log("INFO", "New P2P configuration created"); - } else Logger::get().log("INFO", "[NetLibrary::copy_default_p2p_config_to_path] Could not open new p2p.json to write to"); - } - else + } catch(...) { Logger::get().log("INFO", "[NetLibrary::copy_default_p2p_config_to_path] Could not open default_p2p.json"); + } } std::string NetLibrary::get_random_server_local_id() { diff --git a/src/GUIStuff/ElementHelpers/TextLabelHelpers.cpp b/src/GUIStuff/ElementHelpers/TextLabelHelpers.cpp index cf0892e..88b821a 100644 --- a/src/GUIStuff/ElementHelpers/TextLabelHelpers.cpp +++ b/src/GUIStuff/ElementHelpers/TextLabelHelpers.cpp @@ -1,4 +1,5 @@ #include "TextLabelHelpers.hpp" +#include "../Elements/MutableTextLabel.hpp" namespace GUIStuff { namespace ElementHelpers { @@ -40,4 +41,8 @@ void text_label_light_centered(GUIManager& gui, std::string_view val) { } } +void mutable_text_label(GUIManager& gui, const char* id, const std::string& val) { + gui.element(id, val, CLAY_TEXT_CONFIG({.textColor = convert_vec4(gui.io.theme->frontColor1), .fontSize = gui.io.fontSize })); +} + }} diff --git a/src/GUIStuff/ElementHelpers/TextLabelHelpers.hpp b/src/GUIStuff/ElementHelpers/TextLabelHelpers.hpp index 9a58d98..8a8d3e9 100644 --- a/src/GUIStuff/ElementHelpers/TextLabelHelpers.hpp +++ b/src/GUIStuff/ElementHelpers/TextLabelHelpers.hpp @@ -10,4 +10,6 @@ void text_label(GUIManager& gui, std::string_view val); void text_label_light_centered(GUIManager& gui, std::string_view val); void text_label_centered(GUIManager& gui, std::string_view val); +void mutable_text_label(GUIManager& gui, const char* id, const std::string& val); + }} diff --git a/src/GUIStuff/Elements/MutableTextLabel.cpp b/src/GUIStuff/Elements/MutableTextLabel.cpp new file mode 100644 index 0000000..b30669e --- /dev/null +++ b/src/GUIStuff/Elements/MutableTextLabel.cpp @@ -0,0 +1,20 @@ +#include "MutableTextLabel.hpp" +#include "../GUIManager.hpp" + +namespace GUIStuff { + +MutableTextLabel::MutableTextLabel(GUIManager& gui): Element(gui) {} + +void MutableTextLabel::layout(const Clay_ElementId& id, const std::string& text, Clay_TextElementConfig* textConfig) { + if(text != oldText) { + oldText = text; + gui.invalidate_draw_element(this); + } + CLAY(id, { + .layout = {.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0)}} + }) { + CLAY_TEXT(gui.strArena.std_str_to_clay_str(text), textConfig); + } +} + +} diff --git a/src/GUIStuff/Elements/MutableTextLabel.hpp b/src/GUIStuff/Elements/MutableTextLabel.hpp new file mode 100644 index 0000000..7593a93 --- /dev/null +++ b/src/GUIStuff/Elements/MutableTextLabel.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "Element.hpp" +#include "Helpers/ConvertVec.hpp" + +namespace GUIStuff { + +class MutableTextLabel : public Element { + public: + MutableTextLabel(GUIManager& gui); + void layout(const Clay_ElementId& id, const std::string& text, Clay_TextElementConfig* textConfig); + private: + std::string oldText; +}; + +} diff --git a/src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp b/src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp index cd9ebbf..56cf994 100644 --- a/src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp +++ b/src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp @@ -17,15 +17,6 @@ void PositionAdjustingPopupMenu::layout(const Clay_ElementId& id, Vector2f popup layoutElement = gui.element("popup", [&] (LayoutElement*, const Clay_ElementId& lId) { CLAY(lId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(100), .height = CLAY_SIZING_FIT(0)}, - .padding = CLAY_PADDING_ALL(gui.io.theme->padding1), - .childGap = 1, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - }, - .backgroundColor = convert_vec4(gui.io.theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(gui.io.theme->windowCorners1), .floating = { .offset = {popupPos.x(), popupPos.y()}, .zIndex = gui.get_z_index(), diff --git a/src/GlobalConfig.cpp b/src/GlobalConfig.cpp index 05863fc..2fc6fab 100644 --- a/src/GlobalConfig.cpp +++ b/src/GlobalConfig.cpp @@ -1,5 +1,6 @@ #include "GlobalConfig.hpp" #include +#include "Helpers/StringHelpers.hpp" #include "ResourceDisplay/ImageResourceDisplay.hpp" #include "DrawingProgram/DrawingProgramCache.hpp" #include @@ -132,32 +133,25 @@ void GlobalConfig::set_config_json(InputManager& input, const nlohmann::json& j, } void GlobalConfig::save_palettes() { - std::ofstream f(configPath / "palettes.json"); - if(f.is_open()) { - using json = nlohmann::json; - json j; - auto palettesToSave = palettes; - palettesToSave.erase(palettesToSave.begin()); - j = palettesToSave; - f << j; - f.close(); - } + using json = nlohmann::json; + json j; + auto palettesToSave = palettes; + palettesToSave.erase(palettesToSave.begin()); + nlohmann::to_json(j, palettesToSave); + std::stringstream f; + f << j; + SDL_SaveFile((configPath / "palettes.json").string().c_str(), f.view().data(), f.view().size()); } void GlobalConfig::load_palettes() { - std::ifstream f(configPath / "palettes.json"); load_default_palette(); - if(f.is_open()) { - using json = nlohmann::json; - try { - json j; - f >> j; - std::vector palettes; - j.get_to(palettes); - palettes.insert(palettes.end(), palettes.begin(), palettes.end()); - } catch(...) {} - f.close(); - } + using json = nlohmann::json; + try { + json j(nlohmann::json::parse(read_file_to_string(configPath / "palettes.json"))); + std::vector palettes; + j.get_to(palettes); + palettes.insert(palettes.end(), palettes.begin(), palettes.end()); + } catch(...) {} } void GlobalConfig::load_default_palette() { diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index ff9dcf9..32578a5 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -42,7 +42,6 @@ MainProgram::MainProgram(): fonts(std::make_shared()), g(*this) { - screen = std::make_unique(*this); Logger::get().add_log("WORLDFATAL", [&](const std::string& text) { *logFile << "[WORLDFATAL] " << text << std::endl; std::cout << "[WORLDFATAL] " << text << std::endl; @@ -95,74 +94,67 @@ void MainProgram::init_net_library() { } void MainProgram::save_config() { - std::ofstream f(conf.configPath / "config.json"); - if(f.is_open()) { - using json = nlohmann::json; - json j; + using json = nlohmann::json; + json j; - j["version"] = VersionConstants::CURRENT_VERSION_STRING; - j["settings"] = conf.get_config_json(input); - j["window"]["pos"] = window.writtenPos; - j["window"]["size"] = window.writtenSize; - j["window"]["maximized"] = window.maximized; - j["window"]["fullscreen"] = window.fullscreen; - j["fileselectorpath"] = conf.currentSearchPath; - j["toolConfig"] = toolConfig; + j["version"] = VersionConstants::CURRENT_VERSION_STRING; + j["settings"] = conf.get_config_json(input); + j["window"]["pos"] = window.writtenPos; + j["window"]["size"] = window.writtenSize; + j["window"]["maximized"] = window.maximized; + j["window"]["fullscreen"] = window.fullscreen; + j["fileselectorpath"] = conf.currentSearchPath; + j["toolConfig"] = toolConfig; - f << std::setw(4) << j; - f.close(); - } + std::stringstream f; + f << std::setw(4) << j; + SDL_SaveFile((conf.configPath / "config.json").string().c_str(), f.view().data(), f.view().size()); conf.save_palettes(); } void MainProgram::load_config() { - std::ifstream f(conf.configPath / "config.json"); conf.currentSearchPath = homePath; - if(f.is_open()) { - using json = nlohmann::json; + using json = nlohmann::json; + + try { + json j(nlohmann::json::parse(read_file_to_string(conf.configPath / "config.json"))); + + VersionNumber version(0, 0, 1); + try { + std::string versionStr; + j.at("version").get_to(versionStr); + auto optVersion = version_str_to_version_numbers(versionStr); + if(optVersion.has_value()) + version = optVersion.value(); + } + catch(...) {} try { - json j; - f >> j; - - VersionNumber version(0, 0, 1); - try { - std::string versionStr; - j.at("version").get_to(versionStr); - auto optVersion = version_str_to_version_numbers(versionStr); - if(optVersion.has_value()) - version = optVersion.value(); - } - catch(...) {} - - try { - conf.set_config_json(input, j["settings"], version); - } - catch(...) {} + conf.set_config_json(input, j["settings"], version); + } + catch(...) {} #ifndef __EMSCRIPTEN__ - try { - j.at("window").at("size").get_to(window.size); - window.writtenSize = window.size; - j.at("window").at("pos").get_to(window.pos); - window.writtenPos = window.pos; - j.at("window").at("maximized").get_to(window.maximized); - j.at("window").at("fullscreen").get_to(window.fullscreen); - } - catch(...) {} - try { - j.at("fileselectorpath").get_to(conf.currentSearchPath); - } - catch(...) { - conf.currentSearchPath = homePath; - } + try { + j.at("window").at("size").get_to(window.size); + window.writtenSize = window.size; + j.at("window").at("pos").get_to(window.pos); + window.writtenPos = window.pos; + j.at("window").at("maximized").get_to(window.maximized); + j.at("window").at("fullscreen").get_to(window.fullscreen); + } + catch(...) {} + try { + j.at("fileselectorpath").get_to(conf.currentSearchPath); + } + catch(...) { + conf.currentSearchPath = homePath; + } #endif - try { - j.at("toolConfig").get_to(toolConfig); - } - catch(...) {} - f.close(); - } catch(...) {} - } + try { + j.at("toolConfig").get_to(toolConfig); + } + catch(...) {} + } catch(...) {} #ifdef __EMSCRIPTEN__ else conf.viewWebVersionWelcome = true; @@ -465,5 +457,6 @@ void MainProgram::early_destroy() { } MainProgram::~MainProgram() { + screen = nullptr; // Destroy screen first to let destructor run NetLibrary::destroy(); } diff --git a/src/Screens/FileSelectScreen.cpp b/src/Screens/FileSelectScreen.cpp index 6938054..415ffdb 100644 --- a/src/Screens/FileSelectScreen.cpp +++ b/src/Screens/FileSelectScreen.cpp @@ -7,9 +7,12 @@ #include "../GUIStuff/Elements/GridScrollArea.hpp" #include "../GUIStuff/Elements/ImageDisplay.hpp" #include "../GUIStuff/Elements/LayoutElement.hpp" +#include "../GUIStuff/Elements/SVGIcon.hpp" +#include "../GUIStuff/Elements/PositionAdjustingPopupMenu.hpp" #include "../World.hpp" #include "Helpers/StringHelpers.hpp" #include "DrawingProgramScreen.hpp" +#include #include #define MAIN_MENU_SIZE 300 @@ -17,9 +20,94 @@ using namespace GUIStuff; using namespace ElementHelpers; -FileSelectScreen::FileSelectScreen(MainProgram& m): Screen(m) {} +FileSelectScreen::FileSelectScreen(MainProgram& m): Screen(m) { + savePath = main.conf.configPath / "saves"; + trashPath = main.conf.configPath / "trash"; + trashInfoPath = main.conf.configPath / "trashInfo.json"; + + try { + nlohmann::json j(nlohmann::json::parse(read_file_to_string(trashInfoPath))); + j.get_to(trashInfo); + } + catch(...) {} + + update_file_list(fileList, savePath, false); + + // Update trash list once to get trash info up to date + std::vector trashListTemp; + update_file_list(trashListTemp, trashPath, true); +} void FileSelectScreen::gui_layout_run() { + main_display(); +} + +void FileSelectScreen::update_file_list(std::vector& fL, const std::filesystem::path& savePath, bool trashUpdate) { + constexpr SDL_Time NS_IN_DAY = SDL_SECONDS_TO_NS(24 * 3600); + + SDL_Time currentTime; + SDL_GetCurrentTime(¤tTime); + + SDL_Time afterThirtyDays = currentTime + NS_IN_DAY * 30; + + fL.clear(); + int globCount; + char** filesInPath = SDL_GlobDirectory(savePath.string().c_str(), "*", 0, &globCount); + if(filesInPath) { + for(int i = 0; i < globCount; i++) { + std::filesystem::path p = filesInPath[i]; + std::string fExt = p.extension().string(); + if(fExt == "." + World::FILE_EXTENSION) { + std::filesystem::path fullPath = savePath / filesInPath[i]; + FileInfo fileInfoToAdd; + fileInfoToAdd.fileName = p.stem().string(); + + if(trashUpdate) { + auto it = trashInfo.files.find(fileInfoToAdd.fileName); + if(it == trashInfo.files.end()) { + trashInfo.files[fileInfoToAdd.fileName].trashTime = currentTime; + fileInfoToAdd.lastModifyTime = currentTime; + } + else + fileInfoToAdd.lastModifyTime = it->second.trashTime; + + if(fileInfoToAdd.lastModifyTime >= afterThirtyDays) { + // Remove file and thumbnail + SDL_RemovePath(fullPath.string().c_str()); + SDL_RemovePath((savePath / fileInfoToAdd.fileName / ".jpg").string().c_str()); + trashInfo.files.erase(it); + continue; + } + + int64_t daysToDeletion = (afterThirtyDays - fileInfoToAdd.lastModifyTime) / NS_IN_DAY; + + if(daysToDeletion == 0) + fileInfoToAdd.lastModifyDate = "Today"; + else if(daysToDeletion == 1) + fileInfoToAdd.lastModifyDate = "Tomorrow"; + else + fileInfoToAdd.lastModifyDate = std::to_string(daysToDeletion) + " days"; + } + else { + SDL_PathInfo pathInfo; + if(SDL_GetPathInfo(fullPath.string().c_str(), &pathInfo)) { + SDL_DateTime pathDt; + fileInfoToAdd.lastModifyTime = pathInfo.modify_time; + if(SDL_TimeToDateTime(pathInfo.modify_time, &pathDt, true)) { + fileInfoToAdd.lastModifyDate = sdl_time_to_nice_access_time(pathDt, main.conf.dateFormat, main.conf.timeFormat); + } + } + } + + fL.emplace_back(fileInfoToAdd); + } + } + } + else + SDL_CreateDirectory(savePath.string().c_str()); +} + +void FileSelectScreen::main_display() { auto& gui = main.g.gui; CLAY_AUTO_ID({ .layout = { @@ -33,26 +121,39 @@ void FileSelectScreen::gui_layout_run() { CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(gui.io.windowSize.x()), .height = CLAY_SIZING_GROW(0)}, - .padding = CLAY_PADDING_ALL(gui.io.theme->padding1), - .childGap = gui.io.theme->childGap1, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP }, .layoutDirection = CLAY_TOP_TO_BOTTOM, }, }) { - switch(selectedMenu) { - case SelectedMenu::FILES: - title_bar("Files"); - file_view(); - break; - case SelectedMenu::TRASH: - title_bar("Trash"); - file_view(); - break; - case SelectedMenu::SETTINGS: - title_bar("Settings"); - break; + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .padding = CLAY_PADDING_ALL(gui.io.theme->padding1), + .childGap = gui.io.theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + }, + }) { + if(editMode) { + numberOfSelectedEntries = std::count_if(fileList.begin(), fileList.end(), [] (const FileInfo& f) { return f.selected; }); + edit_title_bar(); + } + else + title_bar(); + switch(selectedMenu) { + case SelectedMenu::FILES: + file_view(); + break; + case SelectedMenu::TRASH: + file_view(); + break; + case SelectedMenu::SETTINGS: + break; + } + menu_black_box(); } - menu_black_box(); + if(editMode && numberOfSelectedEntries != 0) + edit_action_bar(); } } } @@ -62,11 +163,147 @@ bool FileSelectScreen::main_menu_fills_screen() { return main.g.gui.io.windowSize.x() < MAIN_MENU_SIZE + 50; } -void FileSelectScreen::title_bar(std::string_view title) { +void FileSelectScreen::edit_action_bar_button(const char* id, const std::string& svgPath, const char* text, const std::function& onClick) { + auto& gui = main.g.gui; + CLAY_AUTO_ID({ + .layout = {.sizing = {.width = CLAY_SIZING_FIXED(70), .height = CLAY_SIZING_GROW(0)}} + }) { + gui.element(id, SelectableButton::Data{ + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .onClick = onClick, + .innerContent = [&](const SelectableButton::InnerContentCallbackParameters&) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0)}, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + } + }) { + CLAY_AUTO_ID({ + .layout = {.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}} + }) { + gui.element("icon", svgPath, false); + } + text_label(gui, text); + } + } + }); + } +} + +void FileSelectScreen::move_selected_files(const std::filesystem::path& fromPath, const std::filesystem::path& toPath, TrashMoveType trashMoveType) { + SDL_Time currentTime; + SDL_GetCurrentTime(¤tTime); + + std::vector toFolderListNames; + + int globCount; + char** filesInPath = SDL_GlobDirectory(toPath.c_str(), "*", 0, &globCount); + if(filesInPath) { + for(int i = 0; i < globCount; i++) { + std::filesystem::path p(filesInPath[i]); + toFolderListNames.emplace_back(p.filename().stem()); + } + } + else + SDL_CreateDirectory(toPath.string().c_str()); + + for(const FileInfo& f : fileList) { + if(f.selected) { + std::string newFileName = ensure_string_unique(toFolderListNames, f.fileName); + std::filesystem::path filePath = fromPath / (f.fileName + "." + World::FILE_EXTENSION); + std::filesystem::path thumbnailPath = fromPath / (f.fileName + ".jpg"); + std::filesystem::path newFilePath = toPath / (newFileName + "." + World::FILE_EXTENSION); + std::filesystem::path newThumbnailPath = toPath / (newFileName + ".jpg"); + SDL_RenamePath(filePath.string().c_str(), newFilePath.string().c_str()); + SDL_RenamePath(thumbnailPath.string().c_str(), newThumbnailPath.string().c_str()); + switch(trashMoveType) { + case TrashMoveType::NONE: + break; + case TrashMoveType::MOVE_TO_TRASH: + trashInfo.files[newFileName] = TrashInfo::TrashFile{ + .trashTime = currentTime + }; + break; + case TrashMoveType::MOVE_OUT_OF_TRASH: + trashInfo.files.erase(f.fileName); + break; + } + } + } +} + +void FileSelectScreen::delete_selected_files_in_trash() { + for(const FileInfo& f : fileList) { + if(f.selected) { + std::filesystem::path filePath = trashPath / (f.fileName + World::DOT_FILE_EXTENSION); + std::filesystem::path thumbnailPath = trashPath / (f.fileName + ".jpg"); + SDL_RemovePath(filePath.string().c_str()); + SDL_RemovePath(thumbnailPath.string().c_str()); + trashInfo.files.erase(f.fileName); + } + } +} + +void FileSelectScreen::edit_action_bar() { + auto& gui = main.g.gui; + gui.element("edit action bar", [&](LayoutElement*, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + }, + .backgroundColor = convert_vec4(gui.io.theme->backColor2), + }) { + if(selectedMenu == SelectedMenu::FILES) { + edit_action_bar_button("trash", "data/icons/trash.svg", "Trash", [&] { + move_selected_files(savePath, trashPath, TrashMoveType::MOVE_TO_TRASH); + update_file_list(fileList, savePath, false); + editMode = false; + }); + } + else if(selectedMenu == SelectedMenu::TRASH) { + edit_action_bar_button("restore", "data/icons/RemixIcon/refresh-line.svg", "Restore", [&] { + move_selected_files(trashPath, savePath, TrashMoveType::MOVE_OUT_OF_TRASH); + update_file_list(fileList, trashPath, true); + editMode = false; + }); + edit_action_bar_button("delete", "data/icons/trash.svg", "Delete", [&] { + delete_selected_files_in_trash(); + update_file_list(fileList, trashPath, true); + editMode = false; + }); + } + } + }); +} + +void FileSelectScreen::edit_title_bar() { auto& gui = main.g.gui; CLAY_AUTO_ID({ .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(40)}, + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, + .childGap = gui.io.theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + } + }) { + svg_icon_button(gui, "close edit mode button", "data/icons/close.svg", { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .onClick = [&] { + editMode = false; + } + }); + mutable_text_label(gui, "select files label", (numberOfSelectedEntries == 0) ? "Select files" : (std::to_string(numberOfSelectedEntries) + " selected")); + } +} + +void FileSelectScreen::title_bar() { + auto& gui = main.g.gui; + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, .childGap = gui.io.theme->childGap1, .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER }, .layoutDirection = CLAY_LEFT_TO_RIGHT, @@ -78,10 +315,99 @@ void FileSelectScreen::title_bar(std::string_view title) { mainMenuOpen = true; } }); - text_label(gui, title); + switch(selectedMenu) { + case SelectedMenu::FILES: + mutable_text_label(gui, "main screen header text", "Files"); + break; + case SelectedMenu::TRASH: + mutable_text_label(gui, "main screen header text", "Trash"); + break; + case SelectedMenu::SETTINGS: + mutable_text_label(gui, "main screen header text", "Settings"); + break; + } + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + } + }) {} + if(selectedMenu == SelectedMenu::FILES || selectedMenu == SelectedMenu::TRASH) { + SelectableButton* moreButton = svg_icon_button(gui, "more settings button", "data/icons/RemixIcon/more-2-fill.svg", { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .onClick = [&] { + moreOptionsMenu = MoreOptionsMenu::MAIN; + } + }); + if(moreButton->get_bb().has_value() && moreOptionsMenu != MoreOptionsMenu::CLOSED) { + gui.set_z_index(gui.get_z_index() + 10, [&] { + gui.element("more options popup", moreButton->get_bb().value().top_right(), [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_FIT(0)}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(gui.io.theme->backColor2), + .cornerRadius = CLAY_CORNER_RADIUS(4) + }) { + if(moreOptionsMenu == MoreOptionsMenu::MAIN) { + text_transparent_option_button("Edit", "Edit", [&] { + moreOptionsMenu = MoreOptionsMenu::CLOSED; + start_edit_mode(); + }); + text_transparent_option_button("View", "View", [&] { + moreOptionsMenu = MoreOptionsMenu::VIEW; + }); + } + else if(moreOptionsMenu == MoreOptionsMenu::VIEW) { + text_transparent_option_button("Large Grid", "Large Grid", [&] { + fileViewType = FileViewType::LARGE_GRID; + moreOptionsMenu = MoreOptionsMenu::CLOSED; + }); + text_transparent_option_button("Medium Grid", "Medium Grid", [&] { + fileViewType = FileViewType::MEDIUM_GRID; + moreOptionsMenu = MoreOptionsMenu::CLOSED; + }); + text_transparent_option_button("Small Grid", "Small Grid", [&] { + fileViewType = FileViewType::SMALL_GRID; + moreOptionsMenu = MoreOptionsMenu::CLOSED; + }); + } + } + }, LayoutElement::Callbacks{ + .mouseButton = [&](LayoutElement* l, const InputManager::MouseButtonCallbackArgs& m) { + if(!l->mouseHovering && m.down) { + moreOptionsMenu = MoreOptionsMenu::CLOSED; + main.g.gui.set_to_layout(); + } + } + }); + }); + } + } } } +void FileSelectScreen::text_transparent_option_button(const char* id, const char* text, const std::function& onClick) { + auto& gui = main.g.gui; + text_button(gui, id, text, { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .wide = true, + .centered = false, + .onClick = onClick + }); +} + +void FileSelectScreen::icon_text_transparent_option_selected_button(const char* id, const std::string& svgPath, const char* text, bool isSelected, const std::function& onClick) { + auto& gui = main.g.gui; + text_button_with_icon(gui, id, svgPath, text, { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .isSelected = isSelected, + .wide = true, + .centered = false, + .onClick = onClick + }); +} + void FileSelectScreen::main_menu() { auto& gui = main.g.gui; gui.element("main menu popup", [&] (LayoutElement*, const Clay_ElementId& lId) { @@ -89,41 +415,23 @@ void FileSelectScreen::main_menu() { .layout = { .sizing = {.width = CLAY_SIZING_GROW(static_cast(main_menu_fills_screen() ? 0 : 300)), .height = CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(gui.io.theme->padding1), - .childGap = gui.io.theme->childGap1, + .childGap = 0, .layoutDirection = CLAY_TOP_TO_BOTTOM, }, }) { - text_button_with_icon(gui, "Files", "data/icons/folder.svg", "Files", { - .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, - .isSelected = selectedMenu == SelectedMenu::FILES, - .wide = true, - .centered = false, - .onClick = [&] { - selectedMenu = SelectedMenu::FILES; - fileListOptional.reset(); - mainMenuOpen = false; - } + icon_text_transparent_option_selected_button("Files", "data/icons/folder.svg", "Files", selectedMenu == SelectedMenu::FILES, [&] { + selectedMenu = SelectedMenu::FILES; + update_file_list(fileList, savePath, false); + mainMenuOpen = false; }); - text_button_with_icon(gui, "Trash", "data/icons/trash.svg", "Trash", { - .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, - .isSelected = selectedMenu == SelectedMenu::TRASH, - .wide = true, - .centered = false, - .onClick = [&] { - selectedMenu = SelectedMenu::TRASH; - fileListOptional.reset(); - mainMenuOpen = false; - } + icon_text_transparent_option_selected_button("Trash", "data/icons/trash.svg", "Trash", selectedMenu == SelectedMenu::TRASH, [&] { + selectedMenu = SelectedMenu::TRASH; + update_file_list(fileList, trashPath, true); + mainMenuOpen = false; }); - text_button_with_icon(gui, "Settings", "data/icons/RemixIcon/settings-3-line.svg", "Settings", { - .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, - .isSelected = selectedMenu == SelectedMenu::SETTINGS, - .wide = true, - .centered = false, - .onClick = [&] { - selectedMenu = SelectedMenu::SETTINGS; - mainMenuOpen = false; - } + icon_text_transparent_option_selected_button("Settings", "data/icons/RemixIcon/settings-3-line.svg", "Settings", selectedMenu == SelectedMenu::SETTINGS, [&] { + selectedMenu = SelectedMenu::SETTINGS; + mainMenuOpen = false; }); } }); @@ -131,22 +439,43 @@ void FileSelectScreen::main_menu() { void FileSelectScreen::file_view() { auto& gui = main.g.gui; - bool isTrash = selectedMenu == SelectedMenu::TRASH; - std::string folder = isTrash ? "trash" : "saves"; - auto& fileList = get_file_list(folder); + std::filesystem::path folderPath = (selectedMenu == SelectedMenu::TRASH) ? trashPath : savePath; + + Vector2f entrySize{0.0f, 0.0f}; + Vector2f iconSize{0.0f, 0.0f}; + switch(fileViewType) { + case FileViewType::LARGE_GRID: + entrySize = {200.0f, 220.0f}; + iconSize = {150.0f, 150.0f}; + break; + case FileViewType::MEDIUM_GRID: + entrySize = {170.0f, 190.0f}; + iconSize = {120.0f, 120.0f}; + break; + case FileViewType::SMALL_GRID: + entrySize = {140.0f, 160.0f}; + iconSize = {90.0f, 90.0f}; + break; + } + gui.element("File selector grid", GridScrollArea::Options{ - .entryWidth = 180.0f, + .entryWidth = entrySize.x(), .childAlignmentX = CLAY_ALIGN_X_LEFT, - .entryHeight = 200.0f, + .entryHeight = entrySize.y(), .entryCount = fileList.size(), .elementContent = [&](size_t i) { - std::filesystem::path filePath = main.conf.configPath / folder / (fileList[i].fileName + ".infpnt"); + std::filesystem::path filePath = folderPath / (fileList[i].fileName + "." + World::FILE_EXTENSION); gui.element("file button", SelectableButton::Data{ - .onClick = [&, filePath] { - CustomEvents::emit_event({ - .isClient = false, - .filePathSource = filePath - }); + .isSelected = editMode && fileList[i].selected, + .onClick = [&, filePath, i] { + if(editMode) + fileList[i].selected = !fileList[i].selected; + else { + CustomEvents::emit_event({ + .isClient = false, + .filePathSource = filePath + }); + } }, .innerContent = [&](const SelectableButton::InnerContentCallbackParameters& c){ CLAY_AUTO_ID({ @@ -159,10 +488,10 @@ void FileSelectScreen::file_view() { } }) { CLAY_AUTO_ID({ - .layout = { .sizing = {.width = CLAY_SIZING_FIXED(130), .height = CLAY_SIZING_FIXED(130) }}, + .layout = { .sizing = {.width = CLAY_SIZING_FIXED(iconSize.x()), .height = CLAY_SIZING_FIXED(iconSize.y()) }}, }) { gui.element("ico", ImageDisplay::Data{ - .imgPath = main.conf.configPath / folder / (fileList[i].fileName + ".jpg"), + .imgPath = folderPath / (fileList[i].fileName + ".jpg"), .radius = 20 }); } @@ -207,37 +536,10 @@ void FileSelectScreen::menu_black_box() { } } -const std::vector& FileSelectScreen::get_file_list(const std::string& folder) { - if(!fileListOptional.has_value()) { - std::filesystem::path savePath = main.conf.configPath / folder; - fileListOptional = std::vector(); - auto& fL = fileListOptional.value(); - int globCount; - char** filesInPath = SDL_GlobDirectory(savePath.string().c_str(), "*", 0, &globCount); - if(filesInPath) { - for(int i = 0; i < globCount; i++) { - std::filesystem::path p = filesInPath[i]; - std::string fExt = p.extension().string(); - if(fExt == "." + World::FILE_EXTENSION) { - std::filesystem::path fullPath = savePath / filesInPath[i]; - FileInfo fileInfoToAdd; - fileInfoToAdd.fileName = p.stem().string(); - - SDL_PathInfo pathInfo; - if(SDL_GetPathInfo(fullPath.string().c_str(), &pathInfo)) { - SDL_DateTime pathDt; - fileInfoToAdd.lastModifyTime = pathInfo.modify_time; - if(SDL_TimeToDateTime(pathInfo.modify_time, &pathDt, true)) { - fileInfoToAdd.lastModifyDate = sdl_time_to_nice_access_time(pathDt, main.conf.dateFormat, main.conf.timeFormat); - } - } - - fL.emplace_back(fileInfoToAdd); - } - } - } - } - return fileListOptional.value(); +void FileSelectScreen::start_edit_mode() { + for(auto& f : fileList) + f.selected = false; + editMode = true; } void FileSelectScreen::input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEvent& openFile) { @@ -248,3 +550,10 @@ void FileSelectScreen::input_open_infinipaint_file_callback(const CustomEvents:: void FileSelectScreen::draw(SkCanvas* canvas) { canvas->clear(main.g.gui.io.theme->backColor1); } + +FileSelectScreen::~FileSelectScreen() { + nlohmann::json j; + nlohmann::to_json(j, trashInfo); + std::string saveJson = nlohmann::to_string(j); + SDL_SaveFile(trashInfoPath.string().c_str(), saveJson.c_str(), saveJson.size()); +} diff --git a/src/Screens/FileSelectScreen.hpp b/src/Screens/FileSelectScreen.hpp index 0dfd56c..20c96c4 100644 --- a/src/Screens/FileSelectScreen.hpp +++ b/src/Screens/FileSelectScreen.hpp @@ -1,5 +1,7 @@ #pragma once #include "Screen.hpp" +#include "nlohmann/json.hpp" + class FileSelectScreen : public Screen { public: @@ -7,25 +9,76 @@ class FileSelectScreen : public Screen { virtual void gui_layout_run() override; virtual void draw(SkCanvas* canvas) override; virtual void input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEvent& openFile) override; + + struct TrashInfo { + struct TrashFile { + SDL_Time trashTime; + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(TrashFile, trashTime) + }; + std::unordered_map files; + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(TrashInfo, files) + }; + ~FileSelectScreen(); + private: + std::filesystem::path savePath; + std::filesystem::path trashPath; + std::filesystem::path trashInfoPath; + struct FileInfo { std::string fileName; SDL_Time lastModifyTime; std::string lastModifyDate; + bool selected = false; }; - const std::vector& get_file_list(const std::string& folder); - std::optional> fileListOptional; + void update_file_list(std::vector& fL, const std::filesystem::path& savePath, bool trashUpdate); + + TrashInfo trashInfo; + std::vector fileList; + + void main_display(); void main_menu(); - void file_view(); bool main_menu_fills_screen(); + void file_view(); + void file_view_edit(); void menu_black_box(); - void title_bar(std::string_view title); + void edit_action_bar(); + void edit_title_bar(); + void title_bar(); + void text_transparent_option_button(const char* id, const char* text, const std::function& onClick); + void icon_text_transparent_option_selected_button(const char* id, const std::string& svgPath, const char* text, bool isSelected, const std::function& onClick); + void edit_action_bar_button(const char* id, const std::string& svgPath, const char* text, const std::function& onClick); + + enum class TrashMoveType { + NONE, + MOVE_TO_TRASH, + MOVE_OUT_OF_TRASH + }; + void move_selected_files(const std::filesystem::path& fromPath, const std::filesystem::path& toPath, TrashMoveType trashMoveType); + void delete_selected_files_in_trash(); + + enum class FileViewType { + LARGE_GRID, + MEDIUM_GRID, + SMALL_GRID + } fileViewType = FileViewType::LARGE_GRID; + + enum class MoreOptionsMenu { + CLOSED, + MAIN, + VIEW + } moreOptionsMenu = MoreOptionsMenu::CLOSED; enum class SelectedMenu { FILES, TRASH, SETTINGS } selectedMenu = SelectedMenu::FILES; + + void start_edit_mode(); + size_t numberOfSelectedEntries; + bool editMode = false; + bool mainMenuOpen = false; }; diff --git a/src/World.hpp b/src/World.hpp index bcc5c83..6301e6a 100644 --- a/src/World.hpp +++ b/src/World.hpp @@ -22,6 +22,7 @@ class MainProgram; class World { public: + static constexpr std::string DOT_FILE_EXTENSION = ".infpnt"; static constexpr std::string FILE_EXTENSION = "infpnt"; static constexpr size_t CHAT_SIZE = 10; diff --git a/src/main.cpp b/src/main.cpp index e4d3065..77977c6 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include "Helpers/SCollision.hpp" #include "Helpers/StringHelpers.hpp" #include "Helpers/FileDownloader.hpp" +#include "Screens/FileSelectScreen.hpp" #include "VersionConstants.hpp" #include "include/gpu/GpuTypes.h" #include @@ -535,6 +536,8 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { } } + mS.m->screen = std::make_unique(*mS.m); + mS.m->update(); // Run update once to make sure that callbacks dont crash #ifdef NDEBUG