Implemented view change and trash in file selector, and moved more file IO to SDL file IO

This commit is contained in:
Yousef Khadadeh 2026-04-23 20:56:11 +03:00
parent b81b33660e
commit 9ea5a1eae9
15 changed files with 585 additions and 203 deletions

View file

@ -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"

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 3C10.9 3 10 3.9 10 5C10 6.1 10.9 7 12 7C13.1 7 14 6.1 14 5C14 3.9 13.1 3 12 3ZM12 17C10.9 17 10 17.9 10 19C10 20.1 10.9 21 12 21C13.1 21 14 20.1 14 19C14 17.9 13.1 17 12 17ZM12 10C10.9 10 10 10.9 10 12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12C14 10.9 13.1 10 12 10Z"></path></svg>

After

Width:  |  Height:  |  Size: 376 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5.46257 4.43262C7.21556 2.91688 9.5007 2 12 2C17.5228 2 22 6.47715 22 12C22 14.1361 21.3302 16.1158 20.1892 17.7406L17 12H20C20 7.58172 16.4183 4 12 4C9.84982 4 7.89777 4.84827 6.46023 6.22842L5.46257 4.43262ZM18.5374 19.5674C16.7844 21.0831 14.4993 22 12 22C6.47715 22 2 17.5228 2 12C2 9.86386 2.66979 7.88416 3.8108 6.25944L7 12H4C4 16.4183 7.58172 20 12 20C14.1502 20 16.1022 19.1517 17.5398 17.7716L18.5374 19.5674Z"></path></svg>

After

Width:  |  Height:  |  Size: 524 B

View file

@ -1,5 +1,4 @@
#include "NetLibrary.hpp"
#include <fstream>
#include <nlohmann/json.hpp>
#include <rtc/peerconnection.hpp>
#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() {

View file

@ -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<MutableTextLabel>(id, val, CLAY_TEXT_CONFIG({.textColor = convert_vec4<Clay_Color>(gui.io.theme->frontColor1), .fontSize = gui.io.fontSize }));
}
}}

View file

@ -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);
}}

View file

@ -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);
}
}
}

View file

@ -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;
};
}

View file

@ -17,15 +17,6 @@ void PositionAdjustingPopupMenu::layout(const Clay_ElementId& id, Vector2f popup
layoutElement = gui.element<LayoutElement>("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<Clay_Color>(gui.io.theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(gui.io.theme->windowCorners1),
.floating = {
.offset = {popupPos.x(), popupPos.y()},
.zIndex = gui.get_z_index(),

View file

@ -1,5 +1,6 @@
#include "GlobalConfig.hpp"
#include <Helpers/Random.hpp>
#include "Helpers/StringHelpers.hpp"
#include "ResourceDisplay/ImageResourceDisplay.hpp"
#include "DrawingProgram/DrawingProgramCache.hpp"
#include <SDL3/SDL_time.h>
@ -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<Palette> 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<Palette> palettes;
j.get_to(palettes);
palettes.insert(palettes.end(), palettes.begin(), palettes.end());
} catch(...) {}
}
void GlobalConfig::load_default_palette() {

View file

@ -42,7 +42,6 @@ MainProgram::MainProgram():
fonts(std::make_shared<FontData>()),
g(*this)
{
screen = std::make_unique<FileSelectScreen>(*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();
}

View file

@ -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 <SDL3/SDL_time.h>
#include <SDL3/SDL_timer.h>
#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<FileInfo> trashListTemp;
update_file_list(trashListTemp, trashPath, true);
}
void FileSelectScreen::gui_layout_run() {
main_display();
}
void FileSelectScreen::update_file_list(std::vector<FileInfo>& 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(&currentTime);
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<void()>& onClick) {
auto& gui = main.g.gui;
CLAY_AUTO_ID({
.layout = {.sizing = {.width = CLAY_SIZING_FIXED(70), .height = CLAY_SIZING_GROW(0)}}
}) {
gui.element<SelectableButton>(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<SVGIcon>("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(&currentTime);
std::vector<std::string> 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<LayoutElement>("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<Clay_Color>(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<PositionAdjustingPopupMenu>("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<Clay_Color>(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<void()>& 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<void()>& 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<LayoutElement>("main menu popup", [&] (LayoutElement*, const Clay_ElementId& lId) {
@ -89,41 +415,23 @@ void FileSelectScreen::main_menu() {
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(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<GridScrollArea>("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<SelectableButton>("file button", SelectableButton::Data{
.onClick = [&, filePath] {
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
.isClient = false,
.filePathSource = filePath
});
.isSelected = editMode && fileList[i].selected,
.onClick = [&, filePath, i] {
if(editMode)
fileList[i].selected = !fileList[i].selected;
else {
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
.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<ImageDisplay>("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::FileInfo>& FileSelectScreen::get_file_list(const std::string& folder) {
if(!fileListOptional.has_value()) {
std::filesystem::path savePath = main.conf.configPath / folder;
fileListOptional = std::vector<FileInfo>();
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());
}

View file

@ -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<std::string, TrashFile> 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<FileInfo>& get_file_list(const std::string& folder);
std::optional<std::vector<FileInfo>> fileListOptional;
void update_file_list(std::vector<FileInfo>& fL, const std::filesystem::path& savePath, bool trashUpdate);
TrashInfo trashInfo;
std::vector<FileInfo> 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<void()>& onClick);
void icon_text_transparent_option_selected_button(const char* id, const std::string& svgPath, const char* text, bool isSelected, const std::function<void()>& onClick);
void edit_action_bar_button(const char* id, const std::string& svgPath, const char* text, const std::function<void()>& 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;
};

View file

@ -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;

View file

@ -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 <SDL3/SDL_filesystem.h>
@ -535,6 +536,8 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
}
}
mS.m->screen = std::make_unique<FileSelectScreen>(*mS.m);
mS.m->update(); // Run update once to make sure that callbacks dont crash
#ifdef NDEBUG