From cfe250979a444e02cec9e5d826ca266184ce144a Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Mon, 27 Apr 2026 16:48:44 +0300 Subject: [PATCH] File selector bottom edit action bar is in safe area --- .../app/src/main/AndroidManifest.xml | 1 + include/Helpers/StringHelpers.cpp | 15 +- include/Helpers/StringHelpers.hpp | 3 + src/CustomEvents.hpp | 1 + src/GUIStuff/ElementHelpers/LayoutHelpers.cpp | 8 +- src/Screens/FileSelectScreen.cpp | 212 +++++++++--------- src/Screens/PhoneDrawingProgramScreen.cpp | 3 + src/World.cpp | 16 +- src/World.hpp | 2 +- 9 files changed, 145 insertions(+), 116 deletions(-) diff --git a/android-project/app/src/main/AndroidManifest.xml b/android-project/app/src/main/AndroidManifest.xml index 0a5a9b0..c8db854 100644 --- a/android-project/app/src/main/AndroidManifest.xml +++ b/android-project/app/src/main/AndroidManifest.xml @@ -28,6 +28,7 @@ android:name="android.hardware.type.pc" android:required="false" /> + diff --git a/include/Helpers/StringHelpers.cpp b/include/Helpers/StringHelpers.cpp index b99114e..882141b 100644 --- a/include/Helpers/StringHelpers.cpp +++ b/include/Helpers/StringHelpers.cpp @@ -2,9 +2,7 @@ #include #include #include -#include -#include -#include +#include #include #include @@ -162,3 +160,14 @@ std::string sdl_time_to_nice_access_time(const SDL_DateTime& t, SDL_DateFormat d } return ""; } + +std::vector glob_path_as_string_list(const std::filesystem::path& folder, const char* globStr, SDL_GlobFlags globFlags, const std::function& pathToStr) { + int globCount; + char** filesInPath = SDL_GlobDirectory(folder.c_str(), globStr, globFlags, &globCount); + if(!filesInPath) + throw std::runtime_error("[glob_path_as_string_list] SDL_GlobDirectory failed"); + std::vector toRet(globCount); + for(int i = 0; i < globCount; i++) + toRet[i] = pathToStr(std::filesystem::path(filesInPath[i])); + return toRet; +} diff --git a/include/Helpers/StringHelpers.hpp b/include/Helpers/StringHelpers.hpp index b0552c6..2813894 100644 --- a/include/Helpers/StringHelpers.hpp +++ b/include/Helpers/StringHelpers.hpp @@ -1,8 +1,10 @@ #pragma once +#include #include #include #include #include +#include #include std::vector split_string_by_token(std::string str, std::string token); @@ -15,3 +17,4 @@ bool is_valid_http_url(const std::string& str); std::string remove_carriage_returns_from_str(std::string s); std::string ensure_string_unique(const std::vector& stringList, std::string str); std::string sdl_time_to_nice_access_time(const SDL_DateTime& t, SDL_DateFormat dF, SDL_TimeFormat tF); +std::vector glob_path_as_string_list(const std::filesystem::path& folder, const char* globStr, SDL_GlobFlags globFlags, const std::function& pathToStr); diff --git a/src/CustomEvents.hpp b/src/CustomEvents.hpp index 2f2d45a..1a24d2a 100644 --- a/src/CustomEvents.hpp +++ b/src/CustomEvents.hpp @@ -48,6 +48,7 @@ namespace CustomEvents { struct OpenInfiniPaintFileEvent { static uint32_t EVENT_NUM; bool isClient; + std::optional filePathEmptyAutoSaveDir; std::optional filePathSource; std::string netSource; std::string serverLocalID; diff --git a/src/GUIStuff/ElementHelpers/LayoutHelpers.cpp b/src/GUIStuff/ElementHelpers/LayoutHelpers.cpp index 8c91d8e..f8bb43b 100644 --- a/src/GUIStuff/ElementHelpers/LayoutHelpers.cpp +++ b/src/GUIStuff/ElementHelpers/LayoutHelpers.cpp @@ -194,22 +194,22 @@ void window_gap_side_bar(GUIManager& gui, const char* id, const WindowFillSideBa switch(dir) { case WindowFillSideBarConfig::Direction::TOP: CLAY_AUTO_ID({ - .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(gui.io.safeWindowRect.min.y())}} + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(gui.io.safeWindowRect.min.y())}} }) {} break; case WindowFillSideBarConfig::Direction::BOTTOM: CLAY_AUTO_ID({ - .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(gui.io.windowSize.y() - gui.io.safeWindowRect.max.y())}} + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(gui.io.windowSize.y() - gui.io.safeWindowRect.max.y())}} }) {} break; case WindowFillSideBarConfig::Direction::LEFT: CLAY_AUTO_ID({ - .layout = {.sizing = {.width = CLAY_SIZING_FIXED(gui.io.safeWindowRect.min.x()), .height = CLAY_SIZING_GROW(0)}} + .layout = {.sizing = {.width = CLAY_SIZING_FIT(gui.io.safeWindowRect.min.x()), .height = CLAY_SIZING_GROW(0)}} }) {} break; case WindowFillSideBarConfig::Direction::RIGHT: CLAY_AUTO_ID({ - .layout = {.sizing = {.width = CLAY_SIZING_FIXED(gui.io.windowSize.x() - gui.io.safeWindowRect.max.x()), .height = CLAY_SIZING_GROW(0)}} + .layout = {.sizing = {.width = CLAY_SIZING_FIT(gui.io.windowSize.x() - gui.io.safeWindowRect.max.x()), .height = CLAY_SIZING_GROW(0)}} }) {} break; } diff --git a/src/Screens/FileSelectScreen.cpp b/src/Screens/FileSelectScreen.cpp index 37c037f..eef1f54 100644 --- a/src/Screens/FileSelectScreen.cpp +++ b/src/Screens/FileSelectScreen.cpp @@ -28,6 +28,9 @@ FileSelectScreen::FileSelectScreen(MainProgram& m): Screen(m) { trashPath = main.conf.configPath / "trash"; trashInfoPath = main.conf.configPath / "trashInfo.json"; + SDL_CreateDirectory(savePath.string().c_str()); + SDL_CreateDirectory(trashPath.string().c_str()); + try { nlohmann::json j(nlohmann::json::parse(read_file_to_string(trashInfoPath))); j.get_to(trashInfo); @@ -60,7 +63,7 @@ void FileSelectScreen::update_file_list(std::vector& fL, const std::fi for(int i = 0; i < globCount; i++) { std::filesystem::path p = filesInPath[i]; std::string fExt = p.extension().string(); - if(fExt == "." + World::FILE_EXTENSION) { + if(fExt == World::DOT_FILE_EXTENSION) { std::filesystem::path fullPath = savePath / filesInPath[i]; FileInfo fileInfoToAdd; fileInfoToAdd.fileName = p.stem().string(); @@ -134,60 +137,52 @@ void FileSelectScreen::main_display() { .layoutDirection = CLAY_TOP_TO_BOTTOM, }, }) { + if(editMode) { + numberOfSelectedEntries = std::count_if(fileList.begin(), fileList.end(), [] (const FileInfo& f) { return f.selected; }); + if(numberOfSelectedEntries == 0) + actionBarOpenAnim->animation_trigger_reverse(); + else + actionBarOpenAnim->animation_trigger(); + edit_title_bar(); + } + else { + actionBarOpenAnim->animation_trigger_reverse(); + title_bar(); + } CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP }, - .layoutDirection = CLAY_TOP_TO_BOTTOM, + .layoutDirection = CLAY_LEFT_TO_RIGHT }, }) { - if(editMode) { - numberOfSelectedEntries = std::count_if(fileList.begin(), fileList.end(), [] (const FileInfo& f) { return f.selected; }); - if(numberOfSelectedEntries == 0) - actionBarOpenAnim->animation_trigger_reverse(); - else - actionBarOpenAnim->animation_trigger(); - edit_title_bar(); - } - else { - actionBarOpenAnim->animation_trigger_reverse(); - title_bar(); - } + window_gap_side_bar(gui, "main display left fill", WindowFillSideBarConfig::Direction::LEFT); CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .layoutDirection = CLAY_LEFT_TO_RIGHT + .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, }, }) { - window_gap_side_bar(gui, "main display left fill", WindowFillSideBarConfig::Direction::LEFT); - 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, - }, - }) { - switch(selectedMenu) { - case SelectedMenu::FILES: - file_view(); - if(!editMode) - create_file_button(); - break; - case SelectedMenu::TRASH: - file_view(); - break; - case SelectedMenu::SETTINGS: - break; - } + switch(selectedMenu) { + case SelectedMenu::FILES: + file_view(); + if(!editMode) + create_file_button(); + break; + case SelectedMenu::TRASH: + file_view(); + break; + case SelectedMenu::SETTINGS: + break; } - window_gap_side_bar(gui, "main display right fill", WindowFillSideBarConfig::Direction::RIGHT); } - window_gap_side_bar(gui, "main display bottom fill", WindowFillSideBarConfig::Direction::BOTTOM); - menu_black_box(); + window_gap_side_bar(gui, "main display right fill", WindowFillSideBarConfig::Direction::RIGHT); } + window_gap_side_bar(gui, "main display bottom fill", WindowFillSideBarConfig::Direction::BOTTOM); edit_action_bar(); + menu_black_box(); } } } @@ -211,7 +206,8 @@ void FileSelectScreen::create_file_button() { svg_icon_button(gui, "add button", "data/icons/plus.svg", { .onClick = [&] { main.create_new_tab({ - .isClient = false + .isClient = false, + .filePathEmptyAutoSaveDir = savePath }); main.screen = std::make_unique(main); } @@ -254,24 +250,18 @@ void FileSelectScreen::move_selected_files(const std::filesystem::path& fromPath 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 + try { + toFolderListNames = glob_path_as_string_list(toPath, "*", 0, [&](const auto& p){ return p.stem().string();}); + } catch(...) { 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 filePath = fromPath / (f.fileName + World::DOT_FILE_EXTENSION); std::filesystem::path thumbnailPath = fromPath / (f.fileName + ".jpg"); - std::filesystem::path newFilePath = toPath / (newFileName + "." + World::FILE_EXTENSION); + std::filesystem::path newFilePath = toPath / (newFileName + World::DOT_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()); @@ -293,24 +283,18 @@ void FileSelectScreen::move_selected_files(const std::filesystem::path& fromPath void FileSelectScreen::duplicate_selected_files(const std::filesystem::path& inPath) { std::vector toFolderListNames; - - int globCount; - char** filesInPath = SDL_GlobDirectory(inPath.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 + try { + toFolderListNames = glob_path_as_string_list(inPath, "*", 0, [&](const auto& p){ return p.stem().string();}); + } catch(...) { SDL_CreateDirectory(inPath.string().c_str()); + } for(const FileInfo& f : fileList) { if(f.selected) { std::string newFileName = ensure_string_unique(toFolderListNames, f.fileName); std::filesystem::path filePath = inPath / (f.fileName + World::DOT_FILE_EXTENSION); std::filesystem::path thumbnailPath = inPath / (f.fileName + ".jpg"); - std::filesystem::path newFilePath = inPath / (newFileName + "." + World::FILE_EXTENSION); + std::filesystem::path newFilePath = inPath / (newFileName + World::DOT_FILE_EXTENSION); std::filesystem::path newThumbnailPath = inPath / (newFileName + ".jpg"); SDL_CopyFile(filePath.string().c_str(), newFilePath.string().c_str()); SDL_CopyFile(thumbnailPath.string().c_str(), newThumbnailPath.string().c_str()); @@ -333,43 +317,67 @@ void FileSelectScreen::delete_selected_files_in_trash() { void FileSelectScreen::edit_action_bar() { auto& gui = main.g.gui; if(actionBarOpenAnim->get_val()) { - gui.element("edit action bar", [&](LayoutElement*, const Clay_ElementId& lId) { - CLAY(lId, { - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50 * actionBarOpenAnim->get_val())}, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, - .layoutDirection = CLAY_LEFT_TO_RIGHT, + gui.set_z_index(gui.get_z_index() + 3, [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .floating = { + .zIndex = gui.get_z_index(), + .attachPoints = { + .element = CLAY_ATTACH_POINT_LEFT_TOP, + .parent = CLAY_ATTACH_POINT_LEFT_TOP, }, - .backgroundColor = convert_vec4(gui.io.theme->backColor1), - }) { - if(actionBarOpenAnim->is_at_end()) { - 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; - }); - edit_action_bar_button("duplicate", "data/icons/RemixIcon/file-copy-line.svg", "Duplicate", [&] { - duplicate_selected_files(savePath); - 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; - }); - } - } + .attachTo = CLAY_ATTACH_TO_PARENT, } - }); + }) { + CLAY_AUTO_ID({ + .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}, + }) {} + window_fill_side_bar(gui, "edit action bar bottom fill", { + .dir = WindowFillSideBarConfig::Direction::BOTTOM, + .backgroundColor = gui.io.theme->backColor1 + }, [&] { + gui.element("edit action bar", [&](LayoutElement*, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50 * actionBarOpenAnim->get_val())}, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + } + }) { + if(actionBarOpenAnim->is_at_end()) { + 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; + }); + edit_action_bar_button("duplicate", "data/icons/RemixIcon/file-copy-line.svg", "Duplicate", [&] { + duplicate_selected_files(savePath); + 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; + }); + } + } + } + }); + }); + } + }); } } @@ -560,7 +568,7 @@ void FileSelectScreen::file_view() { std::filesystem::path folderPath = (selectedMenu == SelectedMenu::TRASH) ? trashPath : savePath; auto fileButton = [&] (size_t i, bool isList, const Vector2f& iconSize) { - std::filesystem::path filePath = folderPath / (fileList[i].fileName + "." + World::FILE_EXTENSION); + std::filesystem::path filePath = folderPath / (fileList[i].fileName + World::DOT_FILE_EXTENSION); gui.element("file button", SelectableButton::Data{ .isSelected = editMode && fileList[i].selected, .onClick = [&, filePath, i] { @@ -653,7 +661,7 @@ void FileSelectScreen::file_view() { .entryHeight = 150.0f, .entryCount = fileList.size(), .clipHorizontal = true, - .xElementSize = CLAY_SIZING_FIT(0), + .xElementSize = CLAY_SIZING_GROW(0), .elementContent = [&](size_t i) { fileButton(i, true, Vector2f{100.0f, 100.0f}); } diff --git a/src/Screens/PhoneDrawingProgramScreen.cpp b/src/Screens/PhoneDrawingProgramScreen.cpp index 659e50b..ae699cd 100644 --- a/src/Screens/PhoneDrawingProgramScreen.cpp +++ b/src/Screens/PhoneDrawingProgramScreen.cpp @@ -5,6 +5,7 @@ #include "../GUIStuff/Elements/LayoutElement.hpp" #include "../GUIStuff/ElementHelpers/ButtonHelpers.hpp" #include "../GUIStuff/ElementHelpers/LayoutHelpers.hpp" +#include "../GUIStuff/ElementHelpers/TextLabelHelpers.hpp" #include "FileSelectScreen.hpp" using namespace GUIStuff; @@ -55,10 +56,12 @@ void PhoneDrawingProgramScreen::top_toolbar() { svg_icon_button(gui, "back exit button", "data/icons/RemixIcon/arrow-left-s-line.svg", { .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, .onClick = [&] { + main.world->save_to_file(main.world->filePath); main.set_tab_to_close(main.world.get()); main.screen = std::make_unique(main); } }); + text_label(gui, main.world->name); } }); } diff --git a/src/World.cpp b/src/World.cpp index 0d567f3..3dcfb10 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -63,11 +63,11 @@ World::World(MainProgram& initMain, const CustomEvents::OpenInfiniPaintFileEvent init_client(worldInfo.netSource); else { init_client_data_list(); + set_name(name); if(worldInfo.filePathSource.has_value()) load_from_file(worldInfo.filePathSource.value(), worldInfo.fileDataBuffer); else - load_empty_canvas(); - set_name(name); + load_empty_canvas(worldInfo.filePathEmptyAutoSaveDir); #ifdef ENABLE_ORDERED_LIST_TEST listDebugTest = netObjMan.make_obj>(); #endif @@ -432,9 +432,10 @@ void World::start_hosting(const std::string& initNetSource, const std::string& s void World::autosave_to_directory(const std::filesystem::path& directoryToSaveAt) { std::vector strList; - for(auto& entry : std::filesystem::directory_iterator(directoryToSaveAt)) { - if(entry.path().has_stem() && entry.path().has_extension() && entry.path().extension() == ("." + FILE_EXTENSION)) - strList.emplace_back(entry.path().stem().string()); + try { + strList = glob_path_as_string_list(directoryToSaveAt, ("*" + World::DOT_FILE_EXTENSION).c_str(), 0, [&](const auto& p){ return p.stem().string();}); + } + catch(...) { } std::string nameToSaveUnder = ensure_string_unique(strList, name); save_to_file(directoryToSaveAt / std::filesystem::path(nameToSaveUnder + "." + FILE_EXTENSION)); @@ -482,11 +483,14 @@ void World::save_to_file(const std::filesystem::path& filePathToSaveAt) { } } -void World::load_empty_canvas() { +void World::load_empty_canvas(const std::optional& filePathEmptyAutoSaveDir) { gridMan.server_init_no_file(); bMan.server_init_no_file(); drawProg.server_init_no_file(); canvasTheme.server_init_no_file(); + + if(filePathEmptyAutoSaveDir.has_value()) + autosave_to_directory(filePathEmptyAutoSaveDir.value()); } void World::load_from_file(const std::filesystem::path& filePathToLoadFrom, std::string_view buffer) { diff --git a/src/World.hpp b/src/World.hpp index 6301e6a..732d5a2 100644 --- a/src/World.hpp +++ b/src/World.hpp @@ -110,7 +110,7 @@ class World { bool hasUnsavedLocalChanges = false; - void load_empty_canvas(); + void load_empty_canvas(const std::optional& filePathEmptyAutoSaveDir = std::nullopt); Vector3f get_random_cursor_color(); void init_client_data_list();