Readded file tabs

This commit is contained in:
Yousef Khadadeh 2026-04-05 20:26:03 +03:00
parent 26c9be8318
commit 3c5e41af94
17 changed files with 297 additions and 213 deletions

View file

@ -1,21 +1,26 @@
#include "CustomEvents.hpp"
#include <SDL3/SDL.h>
#include <SDL3/SDL_events.h>
#include <queue>
namespace CustomEvents {
bool alreadyInitialized = false;
uint32_t PASTE_EVENT;
std::queue<PasteEventData> 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> 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> 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();
}
}

View file

@ -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<std::filesystem::path> 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;
}

View file

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

View file

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

View file

@ -3,65 +3,83 @@
#include "../GUIManager.hpp"
#include <iostream>
#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<Clay_Color>(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<SelectableButton>("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<SVGIcon>("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<SelectableButton>("close button", SelectableButton::Data {
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
.onClick = [&, i] {
if(d.closeTab) d.closeTab(i);
},
.innerContent = [&] (const SelectableButton::InnerContentCallbackParameters&) {
gui.element<SVGIcon>("close", "data/icons/close.svg");
}
});
}
}
}
});
});
}
}
}
}
}

View file

@ -5,17 +5,23 @@
namespace GUIStuff {
struct MovableTabListData {
struct IconNamePair {
std::string iconPath;
std::string name;
};
std::vector<IconNamePair> tabNames;
size_t selectedTab = 0;
std::function<void(size_t)> changeSelectedTab;
std::function<void(size_t)> closeTab;
};
class MovableTabList : public Element {
public:
struct Data {
std::vector<std::pair<std::string, std::string>> tabNames;
size_t selectedTab = 0;
std::function<void(size_t)> changeSelectedTab;
std::function<void(size_t)> 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;
};
}

View file

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

View file

@ -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<vo
for(ElementContainer* e : orderedElements)
f(e);
run_post_callback_func();
layout_if_necessary();
}
}

View file

@ -362,6 +362,11 @@ uint32_t InputManager::make_generic_key_mod(SDL_Keymod m) {
return toRet;
}
void InputManager::backend_open_infinipaint_file_event() {
main.input_open_infinipaint_file_callback(CustomEvents::get_open_infinipaint_file_event_data());
CustomEvents::pop_open_infinipaint_file_event_data();
}
void InputManager::backend_paste_event() {
main.input_paste_callback(CustomEvents::get_paste_event_data());
CustomEvents::pop_paste_event_data();

View file

@ -262,6 +262,7 @@ struct InputManager {
void touch_finger_do_mouse_up(const SDL_TouchFingerEvent& f);
void touch_finger_do_mouse_motion(const SDL_TouchFingerEvent& f);
void backend_open_infinipaint_file_event();
void backend_paste_event();
void backend_input_text_event(const std::string& str);
void backend_drop_file_event(const SDL_DropEvent& e);

View file

@ -1,4 +1,5 @@
#include "MainProgram.hpp"
#include "CustomEvents.hpp"
#include "VersionConstants.hpp"
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_video.h>
@ -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<World>& w) {
return w->setToDestroy;
});
if(worldIndex >= worlds.size())
worldIndex = worlds.size() - 1;
if(worlds.size() == 0)
new_tab({
.isClient = false
}, true);
std::shared_ptr<World> 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<World> newWorld;
try {
newWorld = std::make_unique<World>(*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<World>& tabToClose) {
setTabsToClose.emplace_back(tabToClose);
}
void MainProgram::init_net_library() {
NetLibrary::init(configPath / "p2p.json");
}
@ -317,10 +265,44 @@ sk_sp<SkSurface> MainProgram::create_native_surface(Vector2i resolution, bool is
return nullptr;
}
void MainProgram::create_new_tab(const CustomEvents::OpenInfiniPaintFileEventData& openFile) {
std::shared_ptr<World> newWorld;
try {
newWorld = std::make_shared<World>(*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())

View file

@ -76,7 +76,6 @@ class MainProgram {
TimePoint deltaTime;
Toolbar toolbar;
GUIHolder g;
std::shared_ptr<World> 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<World>& 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> world;
std::vector<std::shared_ptr<World>> worlds;
GlobalConfig conf;
std::optional<unsigned> 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<bool> tabSetToOpen = false;
World::OpenWorldInfo newTabToOpenInfo;
std::vector<std::weak_ptr<World>> setTabsToClose;
std::unordered_set<World*> tabsToClose;
void close_set_to_close_tabs();
void post_callback();
std::string gen_random_display_name();

View file

@ -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<World>& 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<MovableTabListData::IconNamePair> 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<MovableTabList>("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<std::pair<std::string, std::string>> 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<size_t> 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<Clay_Color>(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;
}
});

View file

@ -38,7 +38,7 @@
#include <EmscriptenHelpers/emscripten_browser_file.h>
#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))) {

View file

@ -1,6 +1,7 @@
#pragma once
#include <Helpers/NetworkingObjects/DelayUpdateSerializedClassManager.hpp>
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
#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<std::filesystem::path> 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> 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;

View file

@ -19,7 +19,7 @@ bool WorldUndoManager::can_redo() {
void WorldUndoManager::push(std::unique_ptr<WorldUndoAction> 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<WorldUndoAction> 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());
}
}

View file

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