From 3c5e41af94e8e1a1cbcbfc7ce238849d2afe1b27 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sun, 5 Apr 2026 20:26:03 +0300 Subject: [PATCH] Readded file tabs --- src/CustomEvents.cpp | 36 ++++++- src/CustomEvents.hpp | 18 ++++ src/GUIHolder.cpp | 5 + src/GUIHolder.hpp | 1 + src/GUIStuff/Elements/MovableTabList.cpp | 128 +++++++++++++---------- src/GUIStuff/Elements/MovableTabList.hpp | 22 ++-- src/GUIStuff/Elements/SVGIcon.hpp | 2 +- src/GUIStuff/GUIManager.cpp | 11 -- src/InputManager.cpp | 5 + src/InputManager.hpp | 1 + src/MainProgram.cpp | 121 +++++++++++---------- src/MainProgram.hpp | 17 +-- src/Toolbar.cpp | 79 +++++++------- src/World.cpp | 23 ++-- src/World.hpp | 19 ++-- src/WorldUndoManager.cpp | 10 +- src/main.cpp | 12 ++- 17 files changed, 297 insertions(+), 213 deletions(-) diff --git a/src/CustomEvents.cpp b/src/CustomEvents.cpp index a9878f9..cbc7c7f 100644 --- a/src/CustomEvents.cpp +++ b/src/CustomEvents.cpp @@ -1,21 +1,26 @@ #include "CustomEvents.hpp" #include +#include #include namespace CustomEvents { bool alreadyInitialized = false; -uint32_t PASTE_EVENT; -std::queue pasteEventData; - void init() { if(!alreadyInitialized) { PASTE_EVENT = SDL_RegisterEvents(1); + OPEN_INFINIPAINT_FILE_EVENT = SDL_RegisterEvents(1); alreadyInitialized = true; } } + +// Paste event + +uint32_t PASTE_EVENT; +std::queue pasteEventData; + bool emit_paste_event(const PasteEventData& pasteData) { SDL_Event event; SDL_zero(event); @@ -35,4 +40,29 @@ void pop_paste_event_data() { pasteEventData.pop(); } + +// Open InfiniPaint file event + +uint32_t OPEN_INFINIPAINT_FILE_EVENT; +std::queue openInfiniPaintFileEventData; + +bool emit_open_infinipaint_file_event(const OpenInfiniPaintFileEventData& openData) { + SDL_Event event; + SDL_zero(event); + event.type = OPEN_INFINIPAINT_FILE_EVENT; + if(SDL_PushEvent(&event)) { + openInfiniPaintFileEventData.push(openData); + return true; + } + return false; +} + +const OpenInfiniPaintFileEventData& get_open_infinipaint_file_event_data() { + return openInfiniPaintFileEventData.front(); +} + +void pop_open_infinipaint_file_event_data() { + openInfiniPaintFileEventData.pop(); +} + } diff --git a/src/CustomEvents.hpp b/src/CustomEvents.hpp index 2075289..c8aee0b 100644 --- a/src/CustomEvents.hpp +++ b/src/CustomEvents.hpp @@ -7,6 +7,7 @@ namespace CustomEvents { void init(); + // Paste event enum class PasteEventDataType { TEXT, IMAGE @@ -23,4 +24,21 @@ namespace CustomEvents { void pop_paste_event_data(); extern uint32_t PASTE_EVENT; + + + // Open InfiniPaint file event + + struct OpenInfiniPaintFileEventData { + bool isClient; + std::optional filePathSource; + std::string netSource; + std::string serverLocalID; + std::string_view fileDataBuffer; + }; + + bool emit_open_infinipaint_file_event(const OpenInfiniPaintFileEventData& openData); + const OpenInfiniPaintFileEventData& get_open_infinipaint_file_event_data(); + void pop_open_infinipaint_file_event_data(); + + extern uint32_t OPEN_INFINIPAINT_FILE_EVENT; } diff --git a/src/GUIHolder.cpp b/src/GUIHolder.cpp index a4af0af..f241284 100644 --- a/src/GUIHolder.cpp +++ b/src/GUIHolder.cpp @@ -162,3 +162,8 @@ void GUIHolder::input_finger_touch_callback(const InputManager::FingerTouchCallb void GUIHolder::input_finger_motion_callback(const InputManager::FingerMotionCallbackArgs& motion) { } + +void GUIHolder::post_callback() { + gui.run_post_callback_func(); + gui.layout_if_necessary(); +} diff --git a/src/GUIHolder.hpp b/src/GUIHolder.hpp index cf50666..99b71fd 100644 --- a/src/GUIHolder.hpp +++ b/src/GUIHolder.hpp @@ -30,6 +30,7 @@ class GUIHolder { void input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel); void input_finger_touch_callback(const InputManager::FingerTouchCallbackArgs& touch); void input_finger_motion_callback(const InputManager::FingerMotionCallbackArgs& motion); + void post_callback(); float final_gui_scale(); private: diff --git a/src/GUIStuff/Elements/MovableTabList.cpp b/src/GUIStuff/Elements/MovableTabList.cpp index 8c12340..186c352 100644 --- a/src/GUIStuff/Elements/MovableTabList.cpp +++ b/src/GUIStuff/Elements/MovableTabList.cpp @@ -3,65 +3,83 @@ #include "../GUIManager.hpp" #include +#include "../ElementHelpers/ButtonHelpers.hpp" +#include "../ElementHelpers/TextLabelHelpers.hpp" +#include "SVGIcon.hpp" + namespace GUIStuff { +using namespace ElementHelpers; + MovableTabList::MovableTabList(GUIManager& gui): Element(gui) {} -void MovableTabList::layout(const Clay_ElementId& id, const Data& d) { - //CLAY_AUTO_ID({.layout = { - // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::BIG_BUTTON_SIZE)}, - // .padding = CLAY_PADDING_ALL(0), - // .childGap = 4, - // .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER }, - // .layoutDirection = CLAY_LEFT_TO_RIGHT - // }, - // .clip = {.horizontal = true, .childOffset = Clay_GetScrollOffset()} - //}) { - // for(size_t i = 0; i < d.tabNames.size(); i++) { - // CLAY_AUTO_ID({.layout = { - // .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_GROW(0)}, - // } - // }) { - // buttons[i].update(io, SelectableButton::DrawType::FILLED, [&](SelectionHelper& s, bool iS) { - // CLAY_AUTO_ID({.layout = { - // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - // .padding = CLAY_PADDING_ALL(0), - // .childGap = 4, - // .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, - // .layoutDirection = CLAY_LEFT_TO_RIGHT - // } - // }) { - // if(!tabNames[i].first.empty()) { - // CLAY_AUTO_ID({.layout = { - // .sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)}, - // .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, - // .layoutDirection = CLAY_LEFT_TO_RIGHT - // } - // }) { - // tabIcons[i].update(io, tabNames[i].first, iS, nullptr); - // } - // } - // CLAY_TEXT(io.strArena->std_str_to_clay_str(tabNames[i].second), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io.theme->frontColor1), .fontSize = io.fontSize })); - // if(s.clicked) - // selectedTab = i; - // CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} - // CLAY_AUTO_ID({.layout = { - // .sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)}, - // .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, - // .layoutDirection = CLAY_LEFT_TO_RIGHT - // } - // }) { - // closeButtons[i].update(io, SelectableButton::DrawType::TRANSPARENT_ALL, [&](SelectionHelper& s, bool iS) { - // if(s.clicked) - // closedTab = i; - // closeIcons[i].update(io, "data/icons/close.svg", s.held || s.hovered, nullptr); - // }, false); - // } - // } - // }, currentSelectedTab == i); - // } - // } - //} +void MovableTabList::layout(const Clay_ElementId& id, const MovableTabListData& newData) { + d = newData; + CLAY(id, {.layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE)}, + .padding = CLAY_PADDING_ALL(0), + .childGap = 4, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + .clip = {.horizontal = true, .childOffset = Clay_GetScrollOffset()} + }) { + for(size_t i = 0; i < d.tabNames.size(); i++) { + CLAY_AUTO_ID({.layout = { + .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_GROW(0)}, + } + }) { + gui.new_id(i, [&] { + gui.element("b", SelectableButton::Data{ + .drawType = SelectableButton::DrawType::FILLED, + .isSelected = d.selectedTab == i, + .onClick = [&, i] { + if(d.selectedTab != i && d.changeSelectedTab) d.changeSelectedTab(i); + }, + .innerContent = [&] (const SelectableButton::InnerContentCallbackParameters&) { + CLAY_AUTO_ID({.layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .padding = CLAY_PADDING_ALL(0), + .childGap = 4, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } + }) { + if(!d.tabNames[i].iconPath.empty()) { + CLAY_AUTO_ID({.layout = { + .sizing = {.width = CLAY_SIZING_FIXED(SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(SMALL_BUTTON_SIZE)}, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } + }) { + gui.element("icon", d.tabNames[i].iconPath); + } + } + text_label(gui, d.tabNames[i].name); + CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} + CLAY_AUTO_ID({.layout = { + .sizing = {.width = CLAY_SIZING_FIXED(SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(SMALL_BUTTON_SIZE)}, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } + }) { + gui.element("close button", SelectableButton::Data { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .onClick = [&, i] { + if(d.closeTab) d.closeTab(i); + }, + .innerContent = [&] (const SelectableButton::InnerContentCallbackParameters&) { + gui.element("close", "data/icons/close.svg"); + } + }); + } + } + } + }); + }); + } + } + } } } diff --git a/src/GUIStuff/Elements/MovableTabList.hpp b/src/GUIStuff/Elements/MovableTabList.hpp index 2f2f71d..aae4673 100644 --- a/src/GUIStuff/Elements/MovableTabList.hpp +++ b/src/GUIStuff/Elements/MovableTabList.hpp @@ -5,17 +5,23 @@ namespace GUIStuff { +struct MovableTabListData { + struct IconNamePair { + std::string iconPath; + std::string name; + }; + std::vector tabNames; + size_t selectedTab = 0; + std::function changeSelectedTab; + std::function closeTab; +}; + class MovableTabList : public Element { public: - struct Data { - std::vector> tabNames; - size_t selectedTab = 0; - std::function changeSelectedTab; - std::function closeTab; - }; - MovableTabList(GUIManager& gui); - void layout(const Clay_ElementId& id, const Data& d); + void layout(const Clay_ElementId& id, const MovableTabListData& newData = {}); + private: + MovableTabListData d; }; } diff --git a/src/GUIStuff/Elements/SVGIcon.hpp b/src/GUIStuff/Elements/SVGIcon.hpp index 239def7..3beb4fb 100644 --- a/src/GUIStuff/Elements/SVGIcon.hpp +++ b/src/GUIStuff/Elements/SVGIcon.hpp @@ -6,7 +6,7 @@ namespace GUIStuff { class SVGIcon : public Element { public: SVGIcon(GUIManager& gui); - void layout(const Clay_ElementId& id, const std::string& newSvgPath, bool newIsHighlighted); + void layout(const Clay_ElementId& id, const std::string& newSvgPath, bool newIsHighlighted = false); virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; private: bool highlighted; diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index a4c0bfb..1d744c0 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -347,29 +347,21 @@ bool GUIManager::cursor_obstructed() const { void GUIManager::input_paste_callback(const CustomEvents::PasteEventData& paste) { for(ElementContainer* e : orderedElements) e->elem->input_paste_callback(paste); - run_post_callback_func(); - layout_if_necessary(); } void GUIManager::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { for(ElementContainer* e : orderedElements) e->elem->input_text_key_callback(key); - run_post_callback_func(); - layout_if_necessary(); } void GUIManager::input_text_callback(const InputManager::TextCallbackArgs& text) { for(ElementContainer* e : orderedElements) e->elem->input_text_callback(text); - run_post_callback_func(); - layout_if_necessary(); } void GUIManager::input_key_callback(const InputManager::KeyCallbackArgs& key) { for(ElementContainer* e : orderedElements) e->elem->input_key_callback(key); - run_post_callback_func(); - layout_if_necessary(); } void GUIManager::input_mouse_button_callback(InputManager::MouseButtonCallbackArgs button) { @@ -411,9 +403,6 @@ void GUIManager::mouse_callback(const Vector2f& mousePos, const std::function #include @@ -69,27 +70,6 @@ void MainProgram::update() { input.update(); - for(auto& s : setTabsToClose) - std::erase(worlds, s.lock()); - - std::erase_if(worlds, [&](const std::shared_ptr& w) { - return w->setToDestroy; - }); - - if(worldIndex >= worlds.size()) - worldIndex = worlds.size() - 1; - if(worlds.size() == 0) - new_tab({ - .isClient = false - }, true); - - std::shared_ptr oldWorld = world; - world = worlds[worldIndex]; - if(oldWorld != world) { - if(oldWorld) - oldWorld->on_tab_out(); - } - deltaTime.update_time_since(); deltaTime.update_time_point(); @@ -103,13 +83,6 @@ void MainProgram::update() { g.update(); NetLibrary::update(); - - if(tabSetToOpen) { - tabSetToOpen = false; - new_tab_open(); - } - - return; } bool MainProgram::app_close_requested() { @@ -123,31 +96,6 @@ void MainProgram::update_display_names() { } } -void MainProgram::new_tab(const World::OpenWorldInfo& tabInfo, bool createSameThread) { - newTabToOpenInfo = tabInfo; - if(createSameThread) - new_tab_open(); - else - tabSetToOpen = true; -} - -void MainProgram::new_tab_open() { - std::unique_ptr newWorld; - try { - newWorld = std::make_unique(*this, newTabToOpenInfo); - } - catch(const std::runtime_error& e) { - Logger::get().log("WORLDFATAL", "Failed to open canvas: " + (newTabToOpenInfo.filePathSource.has_value() ? newTabToOpenInfo.filePathSource.value().string() : "NO PATH") + " with error: " + e.what()); - return; - } - worlds.emplace_back(std::move(newWorld)); - worldIndex = worlds.size() - 1; -} - -void MainProgram::set_tab_to_close(const std::weak_ptr& tabToClose) { - setTabsToClose.emplace_back(tabToClose); -} - void MainProgram::init_net_library() { NetLibrary::init(configPath / "p2p.json"); } @@ -317,10 +265,44 @@ sk_sp MainProgram::create_native_surface(Vector2i resolution, bool is return nullptr; } +void MainProgram::create_new_tab(const CustomEvents::OpenInfiniPaintFileEventData& openFile) { + std::shared_ptr newWorld; + try { + newWorld = std::make_shared(*this, openFile); + } + catch(const std::runtime_error& e) { + Logger::get().log("WORLDFATAL", "Failed to open canvas: " + (openFile.filePathSource.has_value() ? openFile.filePathSource.value().string() : "NO PATH") + " with error: " + e.what()); + return; + } + worlds.emplace_back(newWorld); + switch_to_tab(worlds.size() - 1); + g.gui.set_to_layout(); +} + +void MainProgram::set_tab_to_close(World* world) { + tabsToClose.emplace(world); +} + +void MainProgram::switch_to_tab(size_t wIndex) { + world = worlds[wIndex]; + worldIndex = wIndex; +} + +void MainProgram::post_callback() { + close_set_to_close_tabs(); + g.post_callback(); +} + +void MainProgram::input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEventData& openFile) { + create_new_tab(openFile); + post_callback(); +} + void MainProgram::input_paste_callback(const CustomEvents::PasteEventData& paste) { g.input_paste_callback(paste); if(world) world->input_paste_callback(paste); + post_callback(); } bool MainProgram::input_keybind_callback(const Vector2ui32& newKey) { @@ -345,10 +327,10 @@ void MainProgram::input_drop_file_callback(const InputManager::DropCallbackArgs& if(std::filesystem::is_regular_file(drop.data)) { std::filesystem::path droppedFilePath(drop.data); if(droppedFilePath.has_extension() && droppedFilePath.extension().string() == std::string("." + World::FILE_EXTENSION)) { - new_tab({ + CustomEvents::emit_open_infinipaint_file_event({ .isClient = false, .filePathSource = droppedFilePath - }, true); + }); return; } } @@ -378,36 +360,42 @@ void MainProgram::input_key_callback(const InputManager::KeyCallbackArgs& key) { g.input_key_callback(key); if(world) world->input_key_callback(key); + post_callback(); } void MainProgram::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { g.input_text_key_callback(key); if(world) world->input_text_key_callback(key); + post_callback(); } void MainProgram::input_text_callback(const InputManager::TextCallbackArgs& text) { g.input_text_callback(text); if(world) world->input_text_callback(text); + post_callback(); } void MainProgram::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { g.input_mouse_button_callback(button); if(world) world->input_mouse_button_callback(button); + post_callback(); } void MainProgram::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { g.input_mouse_motion_callback(motion); if(world) world->input_mouse_motion_callback(motion); + post_callback(); } void MainProgram::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel) { g.input_mouse_wheel_callback(wheel); if(world) world->input_mouse_wheel_callback(wheel); + post_callback(); } void MainProgram::input_pen_button_callback(const InputManager::PenButtonCallbackArgs& button) { @@ -446,6 +434,29 @@ void MainProgram::input_finger_touch_callback(const InputManager::FingerTouchCal void MainProgram::input_finger_motion_callback(const InputManager::FingerMotionCallbackArgs& motion) { } +void MainProgram::close_set_to_close_tabs() { + if(!tabsToClose.empty()) { + std::erase_if(worlds, [&](auto& w) { + if(tabsToClose.contains(w.get())) { + if(w == world) + world = nullptr; + return true; + } + return false; + }); + if(worlds.empty()) + create_new_tab({ + .isClient = false + }); + else if(world) + worldIndex = std::find(worlds.begin(), worlds.end(), world) - worlds.begin(); + else + switch_to_tab(0); + tabsToClose.clear(); + g.gui.set_to_layout(); + } +} + bool MainProgram::network_being_used() { for(auto& w : worlds) { if(w->netObjMan.is_connected()) diff --git a/src/MainProgram.hpp b/src/MainProgram.hpp index 3c3c166..d20b158 100644 --- a/src/MainProgram.hpp +++ b/src/MainProgram.hpp @@ -76,7 +76,6 @@ class MainProgram { TimePoint deltaTime; Toolbar toolbar; GUIHolder g; - std::shared_ptr world; ToolConfiguration toolConfig; std::ofstream* logFile; @@ -93,8 +92,6 @@ class MainProgram { void early_destroy(); - void new_tab(const World::OpenWorldInfo& tabInfo, bool createSameThread = false); - void set_tab_to_close(const std::weak_ptr& tabToClose); bool network_being_used(); bool net_server_hosted(); void update_display_names(); @@ -117,11 +114,13 @@ class MainProgram { bool drawGui = true; size_t worldIndex = 0; + std::shared_ptr world; std::vector> worlds; GlobalConfig conf; std::optional keybindWaiting; + void input_open_infinipaint_file_callback(const CustomEvents::OpenInfiniPaintFileEventData& openFile); void input_paste_callback(const CustomEvents::PasteEventData& paste); bool input_keybind_callback(const Vector2ui32& newKey); void input_drop_file_callback(const InputManager::DropCallbackArgs& drop); @@ -141,13 +140,15 @@ class MainProgram { void input_finger_touch_callback(const InputManager::FingerTouchCallbackArgs& touch); void input_finger_motion_callback(const InputManager::FingerMotionCallbackArgs& motion); + void create_new_tab(const CustomEvents::OpenInfiniPaintFileEventData& openFile); + void set_tab_to_close(World* world); + void switch_to_tab(size_t wIndex); + ~MainProgram(); private: - void new_tab_open(); - std::atomic tabSetToOpen = false; - World::OpenWorldInfo newTabToOpenInfo; - - std::vector> setTabsToClose; + std::unordered_set tabsToClose; + void close_set_to_close_tabs(); + void post_callback(); std::string gen_random_display_name(); diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index d32e23a..f72dcd8 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -1,4 +1,5 @@ #include "Toolbar.hpp" +#include "CustomEvents.hpp" #include "DrawingProgram/Tools/DrawingProgramToolBase.hpp" #include "DrawingProgram/Tools/ScreenshotTool.hpp" #include "FileHelpers.hpp" @@ -50,6 +51,7 @@ #include "GUIStuff/Elements/LayoutElement.hpp" #include "GUIStuff/Elements/DropDown.hpp" #include "GUIStuff/Elements/SVGIcon.hpp" +#include "GUIStuff/Elements/MovableTabList.hpp" #define UPDATE_DOWNLOAD_URL "https://infinipaint.com/download.html" #define UPDATE_NOTIFICATION_URL "https://infinipaint.com/updateNotificationVersion.txt" @@ -320,6 +322,7 @@ void Toolbar::add_world_to_close_popup_data(const std::shared_ptr& w) { }); if(it == closePopupData.worldsToClose.end()) closePopupData.worldsToClose.emplace_back(w, true); + main.g.gui.set_to_layout(); } void Toolbar::close_popup_gui() { @@ -388,7 +391,7 @@ void Toolbar::close_popup_gui() { else wLock->save_to_file(wLock->filePath); } - main.set_tab_to_close(w); + main.set_tab_to_close(wLock.get()); } closePopupData.worldsToClose.clear(); if(closePopupData.closeAppWhenDone) @@ -398,8 +401,11 @@ void Toolbar::close_popup_gui() { text_button(gui, "Discard All", "Discard All", { .wide = true, .onClick = [&] { - for(auto& [w, setToSave] : closePopupData.worldsToClose) - main.set_tab_to_close(w); + for(auto& [w, setToSave] : closePopupData.worldsToClose) { + auto wLock = w.lock(); + if(wLock) + main.set_tab_to_close(wLock.get()); + } closePopupData.worldsToClose.clear(); if(closePopupData.closeAppWhenDone) main.setToQuit = true; @@ -495,31 +501,26 @@ void Toolbar::top_toolbar() { menuPopUpOpen = !menuPopUpOpen; }); + std::vector tabNames; + for(size_t i = 0; i < main.worlds.size(); i++) { + auto& w = main.worlds[i]; + bool shouldAddStarNextToName = w->should_ask_before_closing() && !w->netServer && !w->netClient; + tabNames.emplace_back(w->netObjMan.is_connected() ? "data/icons/network.svg" : "", w->name + (shouldAddStarNextToName ? "*" : "")); + } - // Temporary gap between buttons while tab list is being fixed - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + gui.element("file tab list", MovableTabListData{ + .tabNames = tabNames, + .selectedTab = main.worldIndex, + .changeSelectedTab = [&] (size_t i) { + main.switch_to_tab(i); + }, + .closeTab = [&] (size_t i) { + if(main.worlds[i]->should_ask_before_closing()) + add_world_to_close_popup_data(main.worlds[i]); + else + main.set_tab_to_close(main.worlds[i].get()); } - }) { } - - - //std::vector> tabNames; - //for(size_t i = 0; i < main.worlds.size(); i++) { - // auto& w = main.worlds[i]; - // bool shouldAddStarNextToName = w->should_ask_before_closing() && !w->netServer && !w->netClient; - // tabNames.emplace_back(w->netObjMan.is_connected() ? "data/icons/network.svg" : "", w->name + (shouldAddStarNextToName ? "*" : "")); - //} - - //std::optional closedTab; - //gui.tab_list("file tab list", tabNames, main.worldIndex, closedTab); - //if(closedTab) { - // auto& closedWorldPtr = main.worlds[closedTab.value()]; - // if(closedWorldPtr->should_ask_before_closing()) - // add_world_to_close_popup_data(closedWorldPtr); - // else - // main.set_tab_to_close(closedWorldPtr); - //} + }); if(!main.world->clientStillConnecting) { if(main.world->netObjMan.is_connected()) { @@ -596,9 +597,9 @@ void Toolbar::top_toolbar() { }); }; menu_popup_text_button("new file local", "New File", [&] { - main.new_tab({ + CustomEvents::emit_open_infinipaint_file_event({ .isClient = false - }, true); + }); }); menu_popup_text_button("open file", "Open", [&] { open_world_file(false, "", ""); }); if(!main.world->clientStillConnecting) { @@ -1454,18 +1455,8 @@ void Toolbar::player_list() { auto& gui = main.g.gui; auto& io = gui.io; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(500), .height = CLAY_SIZING_FIT(0) }, - .padding = CLAY_PADDING_ALL(io.theme->padding1), - .childGap = io.theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - }, - .backgroundColor = convert_vec4(io.theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(io.theme->windowCorners1), - .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { + + center_obstructing_window_gui("player client list", CLAY_SIZING_FIT(500), CLAY_SIZING_FIT(0), [&] { gui.new_id("client list", [&] { text_label_centered(gui, "Player List"); if(!main.world->clientStillConnecting) { @@ -1505,7 +1496,7 @@ void Toolbar::player_list() { playerMenuOpen = false; }}); }); - } + }); } void Toolbar::open_world_file(bool isClient, const std::string& netSource, const std::string& serverLocalID2) { @@ -1534,7 +1525,7 @@ void Toolbar::open_world_file(bool isClient, const std::string& netSource, const }, &uploadData); #else open_file_selector("Open", {{"InfiniPaint Canvas", World::FILE_EXTENSION}, {"Any File", "*"}}, [&, isClient = isClient, netSource = netSource, serverLocalID2 = serverLocalID2](const std::filesystem::path& p, const auto& e) { - main.new_tab({ + CustomEvents::emit_open_infinipaint_file_event({ .isClient = isClient, .filePathSource = p, .netSource = netSource, @@ -1611,10 +1602,10 @@ void Toolbar::options_menu() { else if(serverToConnectTo.substr(0, NetLibrary::GLOBALID_LEN) == NetLibrary::get_global_id()) Logger::get().log("USERINFO", "Connect issue: Can't connect to your own address"); else { - main.new_tab({ + CustomEvents::emit_open_infinipaint_file_event({ .isClient = true, .netSource = serverToConnectTo - }, true); + }); optionsMenuOpen = false; } }); diff --git a/src/World.cpp b/src/World.cpp index 541df5b..c66be7e 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -38,7 +38,7 @@ #include #endif -World::World(MainProgram& initMain, OpenWorldInfo& worldInfo): +World::World(MainProgram& initMain, const CustomEvents::OpenInfiniPaintFileEventData& worldInfo): netObjMan(!worldInfo.isClient), main(initMain), undo(*this), @@ -151,12 +151,15 @@ void World::init_client(const std::string& serverFullID) { #endif clientStillConnecting = false; + drawProg.world.main.g.gui.set_to_layout(); }); netClient->add_recv_callback(CLIENT_UPDATE_NETWORK_OBJECT, [&](cereal::PortableBinaryInputArchive& message) { netObjMan.read_update_message(message, nullptr); + drawProg.world.main.g.gui.set_to_layout(); }); netClient->add_recv_callback(CLIENT_UPDATE_MANY_NETWORK_OBJECTS, [&](cereal::PortableBinaryInputArchive& message) { netObjMan.read_many_update_message(message, nullptr); + drawProg.world.main.g.gui.set_to_layout(); }); netClient->add_recv_callback(CLIENT_KEEP_ALIVE, [&](cereal::PortableBinaryInputArchive& message) { }); @@ -165,8 +168,7 @@ void World::init_client(const std::string& serverFullID) { } void World::focus_update() { - connection_update(); - if(setToDestroy) + if(!connection_update()) return; if(!clientStillConnecting) { @@ -190,7 +192,7 @@ void World::focus_update() { rMan.update(); } -void World::connection_update() { +bool World::connection_update() { if(netServer) { if(netServer->is_disconnected()) { Logger::get().log("USERINFO", "Host connection failed"); @@ -209,8 +211,9 @@ void World::connection_update() { else if(netClient) { if(netClient->is_disconnected()) { Logger::get().log("USERINFO", "Client connection failed"); - setToDestroy = true; netObjMan.disconnect(); + main.set_tab_to_close(this); + return false; } netClient->update(); if(std::chrono::steady_clock::now() - lastKeepAliveSent > std::chrono::seconds(2)) { @@ -218,6 +221,7 @@ void World::connection_update() { lastKeepAliveSent = std::chrono::steady_clock::now(); } } + return true; } bool World::is_focus() { @@ -236,8 +240,6 @@ void World::redo_with_checks() { void World::unfocus_update() { connection_update(); - if(setToDestroy) - return; } void World::on_tab_out() { @@ -582,6 +584,13 @@ bool World::should_ask_before_closing() { #endif } +void World::set_has_unsaved_local_changes(bool newHasUnsavedLocalChangesVal) { + bool oldHasUnsavedLocalChanges = hasUnsavedLocalChanges; + hasUnsavedLocalChanges = newHasUnsavedLocalChangesVal; + if(oldHasUnsavedLocalChanges != hasUnsavedLocalChanges) + main.g.gui.set_to_layout(); +} + #ifdef ENABLE_ORDERED_LIST_TEST void World::list_debug_test_update() { if(std::chrono::steady_clock::now() < (listDebugTestTimeStart + std::chrono::minutes(1))) { diff --git a/src/World.hpp b/src/World.hpp index 3791889..4147f47 100644 --- a/src/World.hpp +++ b/src/World.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include "CustomEvents.hpp" #include "Helpers/NetworkingObjects/NetObjUnorderedSet.hpp" #include "WorldUndoManager.hpp" #include "Bookmarks/BookmarkManager.hpp" @@ -24,15 +25,7 @@ class World { static constexpr std::string FILE_EXTENSION = "infpnt"; static constexpr size_t CHAT_SIZE = 10; - struct OpenWorldInfo { - bool isClient; - std::optional filePathSource; - std::string netSource; - std::string serverLocalID; - std::string_view fileDataBuffer; - }; - - World(MainProgram& initMain, OpenWorldInfo& worldInfo); + World(MainProgram& initMain, const CustomEvents::OpenInfiniPaintFileEventData& worldInfo); // NOTE: Keep at the very beginning so that it's destroyed last NetworkingObjects::NetObjManager netObjMan; @@ -87,10 +80,9 @@ class World { void scale_up_step(); bool should_ask_before_closing(); + void set_has_unsaved_local_changes(bool newHasUnsavedLocalChangesVal); bool is_focus(); - bool hasUnsavedLocalChanges = false; - bool setToDestroy = false; NetworkingObjects::DelayUpdateSerializedClassManager delayedUpdateObjectManager; std::shared_ptr netServer; @@ -112,6 +104,9 @@ class World { void input_multi_finger_touch_callback(const InputManager::MultiFingerTouchCallbackArgs& touch); void input_multi_finger_motion_callback(const InputManager::MultiFingerMotionCallbackArgs& motion); private: + + bool hasUnsavedLocalChanges = false; + void load_empty_canvas(); Vector3f get_random_cursor_color(); @@ -125,7 +120,7 @@ class World { void draw_other_player_cursors(SkCanvas* canvas, const DrawData& drawData); void ensure_display_name_unique(std::string& displayName); - void connection_update(); + bool connection_update(); TimePoint timeToSendCameraData; diff --git a/src/WorldUndoManager.cpp b/src/WorldUndoManager.cpp index 8e403bc..d365eb1 100644 --- a/src/WorldUndoManager.cpp +++ b/src/WorldUndoManager.cpp @@ -19,7 +19,7 @@ bool WorldUndoManager::can_redo() { void WorldUndoManager::push(std::unique_ptr undoAction) { push_undo(std::move(undoAction)); redoQueue.clear(); - world.hasUnsavedLocalChanges = true; + world.set_has_unsaved_local_changes(true); } void WorldUndoManager::push_undo(std::unique_ptr undoAction) { @@ -160,7 +160,7 @@ void WorldUndoManager::set_save_action() { undoActionSavedAt = nullptr; else undoActionSavedAt = undoQueue.back().get(); - world.hasUnsavedLocalChanges = false; + world.set_has_unsaved_local_changes(false); } void WorldUndoManager::set_world_has_unsaved_local_changes() { @@ -169,11 +169,11 @@ void WorldUndoManager::set_world_has_unsaved_local_changes() { // - nullptr: The file was saved when undoQueue was empty // - nullopt: The undo action the file was saved at was lost if(!undoActionSavedAt.has_value()) - world.hasUnsavedLocalChanges = true; + world.set_has_unsaved_local_changes(true); else { if(undoQueue.empty()) - world.hasUnsavedLocalChanges = undoActionSavedAt.value() != nullptr; + world.set_has_unsaved_local_changes(undoActionSavedAt.value() != nullptr); else - world.hasUnsavedLocalChanges = undoActionSavedAt.value() != undoQueue.back().get(); + world.set_has_unsaved_local_changes(undoActionSavedAt.value() != undoQueue.back().get()); } } diff --git a/src/main.cpp b/src/main.cpp index abd60da..7b33cd8 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -436,6 +436,8 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { initialize_sdl(mS, initWidth, initHeight); + CustomEvents::init(); + int32_t cursorData[2] = {0, 0}; mS.hiddenCursor = SDL_CreateCursor((Uint8 *)cursorData, (Uint8 *)cursorData, 8, 8, 4, 4); @@ -548,16 +550,16 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { resize_window(mS); if(listOfFilesToOpenFromCommand.empty()) { - mS.m->new_tab({ + mS.m->create_new_tab({ .isClient = false - }, true); + }); } else { for(std::filesystem::path& f : listOfFilesToOpenFromCommand) { - mS.m->new_tab({ + mS.m->create_new_tab({ .isClient = false, .filePathSource = f - }, true); + }); } } @@ -837,6 +839,8 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { default: { if(event->type == CustomEvents::PASTE_EVENT) mS.m->input.backend_paste_event(); + else if(event->type == CustomEvents::OPEN_INFINIPAINT_FILE_EVENT) + mS.m->input.backend_open_infinipaint_file_event(); break; } }