2149 lines
107 KiB
C++
2149 lines
107 KiB
C++
#include "Toolbar.hpp"
|
|
#include "CustomEvents.hpp"
|
|
#include "DrawingProgram/Tools/DrawingProgramToolBase.hpp"
|
|
#include "DrawingProgram/Tools/ScreenshotTool.hpp"
|
|
#include "FileHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/CheckBoxHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/LayoutHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/PopupHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/RadioButtonHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/ScrollAreaHelpers.hpp"
|
|
#include "Helpers/ConvertVec.hpp"
|
|
#include "Helpers/FileDownloader.hpp"
|
|
#include "Helpers/MathExtras.hpp"
|
|
#include "Helpers/Networking/NetLibrary.hpp"
|
|
#include "Helpers/NetworkingObjects/NetObjGenericSerializedClass.hpp"
|
|
#include "MainProgram.hpp"
|
|
#include "InputManager.hpp"
|
|
#include "ResourceDisplay/ImageResourceDisplay.hpp"
|
|
#include "RichText/TextStyleModifier.hpp"
|
|
#include "VersionConstants.hpp"
|
|
#include "World.hpp"
|
|
#include <SDL3/SDL_dialog.h>
|
|
#include <SDL3/SDL_filesystem.h>
|
|
#include <SDL3/SDL_render.h>
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <optional>
|
|
#include <Helpers/Logger.hpp>
|
|
#include <Helpers/StringHelpers.hpp>
|
|
#include <Helpers/VersionNumber.hpp>
|
|
|
|
#include <modules/skparagraph/src/ParagraphBuilderImpl.h>
|
|
#include <modules/skparagraph/include/ParagraphStyle.h>
|
|
#include <modules/skparagraph/include/FontCollection.h>
|
|
#include <modules/skparagraph/include/TextStyle.h>
|
|
#include <include/core/SkFontStyle.h>
|
|
#include <modules/skunicode/include/SkUnicode_icu.h>
|
|
|
|
#include <modules/svg/include/SkSVGNode.h>
|
|
#include <include/core/SkStream.h>
|
|
|
|
#include "GUIStuff/GUIManager.hpp"
|
|
#include "GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/ButtonHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/PopupHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/TextBoxHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/ColorPickerHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/RadioButtonHelpers.hpp"
|
|
#include "GUIStuff/ElementHelpers/NumberSliderHelpers.hpp"
|
|
#include "GUIStuff/Elements/ScrollArea.hpp"
|
|
#include "GUIStuff/Elements/LayoutElement.hpp"
|
|
#include "GUIStuff/Elements/DropDown.hpp"
|
|
#include "GUIStuff/Elements/SVGIcon.hpp"
|
|
#include "GUIStuff/Elements/MovableTabList.hpp"
|
|
#include "GUIStuff/Elements/TextParagraph.hpp"
|
|
|
|
#define UPDATE_DOWNLOAD_URL "https://infinipaint.com/download.html"
|
|
#define UPDATE_NOTIFICATION_URL "https://infinipaint.com/updateNotificationVersion.txt"
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
#include <EmscriptenHelpers/emscripten_browser_file.h>
|
|
#endif
|
|
|
|
Toolbar::NativeFilePicker Toolbar::nativeFilePicker;
|
|
|
|
using namespace GUIStuff;
|
|
using namespace ElementHelpers;
|
|
|
|
Toolbar::Toolbar(MainProgram& initMain):
|
|
main(initMain)
|
|
{}
|
|
|
|
// You can't trust that filter wont be -1 on the platform youre on, so dont use the extension from the callback
|
|
void Toolbar::sdl_open_file_dialog_callback(void* userData, const char * const * fileList, int filter) {
|
|
if(!fileList) {
|
|
nativeFilePicker.isOpen = false;
|
|
return;
|
|
}
|
|
if(!(*fileList)) {
|
|
nativeFilePicker.isOpen = false;
|
|
return;
|
|
}
|
|
ExtensionFilter e;
|
|
if(filter >= 0)
|
|
e = nativeFilePicker.extensionFiltersComplete[filter];
|
|
nativeFilePicker.postSelectionFunc(fileList[0], e);
|
|
nativeFilePicker.isOpen = false;
|
|
}
|
|
|
|
void Toolbar::open_file_selector(const std::string& filePickerName, const std::vector<ExtensionFilter>& extensionFilters, OpenFileSelectorCallback postSelectionFunc, const std::string& fileName, bool isSaving) {
|
|
if(main.conf.useNativeFilePicker) {
|
|
if(!nativeFilePicker.isOpen) {
|
|
nativeFilePicker.postSelectionFunc = postSelectionFunc;
|
|
nativeFilePicker.extensionFiltersComplete = extensionFilters;
|
|
nativeFilePicker.sdlFileFilters.clear();
|
|
for(auto& e : nativeFilePicker.extensionFiltersComplete)
|
|
nativeFilePicker.sdlFileFilters.emplace_back(e.name.c_str(), e.extensions.c_str());
|
|
if(isSaving)
|
|
SDL_ShowSaveFileDialog(sdl_open_file_dialog_callback, nullptr, main.window.sdlWindow, nativeFilePicker.sdlFileFilters.data(), nativeFilePicker.sdlFileFilters.size(), nullptr);
|
|
else
|
|
SDL_ShowOpenFileDialog(sdl_open_file_dialog_callback, nullptr, main.window.sdlWindow, nativeFilePicker.sdlFileFilters.data(), nativeFilePicker.sdlFileFilters.size(), nullptr, false);
|
|
}
|
|
}
|
|
else {
|
|
filePicker.isOpen = true;
|
|
filePicker.extensionFiltersComplete = extensionFilters;
|
|
filePicker.extensionFilters.clear();
|
|
for(auto& [name, exList] : extensionFilters)
|
|
filePicker.extensionFilters.emplace_back(exList);
|
|
filePicker.extensionSelected = extensionFilters.size() - 1;
|
|
filePicker.filePickerWindowName = filePickerName;
|
|
filePicker.postSelectionFunc = postSelectionFunc;
|
|
filePicker.fileName = "";
|
|
filePicker.isSaving = isSaving;
|
|
filePicker.entriesScrollArea = nullptr;
|
|
file_picker_gui_refresh_entries();
|
|
}
|
|
}
|
|
|
|
void Toolbar::color_button_left(const char* id, Vector4f* color, const ColorSelectorButtonData& colorSelectorData) {
|
|
auto& gui = main.g.gui;
|
|
color_button(gui, id, color, {
|
|
.isSelected = colorLeft == color,
|
|
.onClickButton = [&, colorSelectorData, color] (SelectableButton* b) {
|
|
if(colorSelectorData.onSelectorButtonClick) colorSelectorData.onSelectorButtonClick();
|
|
color_selector_left(b, color, {
|
|
.onChange = colorSelectorData.onChange,
|
|
.onSelect = colorSelectorData.onSelect,
|
|
.onDeselect = colorSelectorData.onDeselect,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
void Toolbar::color_button_right(const char* id, Vector4f* color, const ColorSelectorButtonData& colorSelectorData) {
|
|
auto& gui = main.g.gui;
|
|
color_button(gui, id, color, {
|
|
.isSelected = colorRight == color,
|
|
.onClickButton = [&, colorSelectorData, color] (SelectableButton* b) {
|
|
if(colorSelectorData.onSelectorButtonClick) colorSelectorData.onSelectorButtonClick();
|
|
color_selector_right(b, color, {
|
|
.onChange = colorSelectorData.onChange,
|
|
.onSelect = colorSelectorData.onSelect,
|
|
.onDeselect = colorSelectorData.onDeselect,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
void Toolbar::color_selector_left(Element* button, Vector4f* color, const ColorSelectorData& colorSelectorData) {
|
|
if(colorLeft != color) {
|
|
colorLeft = color;
|
|
colorLeftData = colorSelectorData;
|
|
colorLeftButton = button;
|
|
}
|
|
else
|
|
colorLeft = nullptr;
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
|
|
void Toolbar::color_selector_right(Element* button, Vector4f* color, const ColorSelectorData& colorSelectorData) {
|
|
if(colorRight != color) {
|
|
colorRight = color;
|
|
colorRightData = colorSelectorData;
|
|
colorRightButton = button;
|
|
}
|
|
else
|
|
colorRight = nullptr;
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
|
|
void Toolbar::update() {
|
|
std::erase_if(main.logMessages, [&](auto& logM) {
|
|
logM.time.update_time_since();
|
|
if(logM.time > UserLogMessage::FADE_START_TIME) {
|
|
main.g.gui.set_to_layout();
|
|
return logM.time >= UserLogMessage::DISPLAY_TIME;
|
|
}
|
|
return false;
|
|
});
|
|
if(!chatboxOpen) {
|
|
for(auto& chatMessage : main.world->chatMessages) {
|
|
bool wasShown = chatMessage.time < ChatMessage::DISPLAY_TIME;
|
|
chatMessage.time.update_time_since();
|
|
bool isShown = chatMessage.time < ChatMessage::DISPLAY_TIME;
|
|
bool isFading = chatMessage.time >= ChatMessage::FADE_START_TIME;
|
|
if((isFading && isShown) || (wasShown && !isShown))
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Toolbar::open_chatbox() {
|
|
if(!chatboxOpen) {
|
|
chatMessageInput.clear();
|
|
chatboxOpen = true;
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
}
|
|
|
|
void Toolbar::close_chatbox() {
|
|
if(chatboxOpen) {
|
|
chatboxOpen = false;
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
}
|
|
|
|
void Toolbar::layout_run() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
update_notification_check();
|
|
#endif
|
|
|
|
if(drawGui) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(gui.io.windowSize.x()), .height = CLAY_SIZING_FIT(gui.io.windowSize.y())},
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
}
|
|
}) {
|
|
top_toolbar();
|
|
if(!main.world->clientStillConnecting)
|
|
drawing_program_gui();
|
|
if(!closePopupData.worldsToClose.empty())
|
|
close_popup_gui();
|
|
if(main.conf.viewWebVersionWelcome)
|
|
web_version_welcome();
|
|
#ifndef __EMSCRIPTEN__
|
|
else if(updateCheckerData.showGui)
|
|
update_notification_gui();
|
|
#endif
|
|
else if(filePicker.isOpen)
|
|
file_picker_gui();
|
|
else if(optionsMenuOpen)
|
|
options_menu();
|
|
else if(main.world->clientStillConnecting)
|
|
still_connecting_center_message();
|
|
else if(playerMenuOpen)
|
|
player_list();
|
|
else if(!main.world->drawProg.layerMan.is_a_layer_being_edited())
|
|
no_layers_being_edited_message();
|
|
|
|
if(showPerformance)
|
|
performance_metrics();
|
|
}
|
|
if(!main.world->clientStillConnecting)
|
|
chat_box();
|
|
}
|
|
else {
|
|
if(!closePopupData.worldsToClose.empty()) // Should still show close popup if gui is disabled
|
|
close_popup_gui();
|
|
}
|
|
|
|
if(!main.world->clientStillConnecting)
|
|
main.world->drawProg.right_click_popup_gui(*this);
|
|
}
|
|
|
|
bool Toolbar::app_close_requested() {
|
|
for(auto& w : main.worlds) {
|
|
if(w->should_ask_before_closing())
|
|
add_world_to_close_popup_data(w);
|
|
}
|
|
closePopupData.closeAppWhenDone = true;
|
|
main.g.gui.set_to_layout();
|
|
return closePopupData.worldsToClose.empty();
|
|
}
|
|
|
|
void Toolbar::add_world_to_close_popup_data(const std::shared_ptr<World>& w) {
|
|
auto it = std::find_if(closePopupData.worldsToClose.begin(), closePopupData.worldsToClose.end(), [&](auto& wStruct) {
|
|
return wStruct.w.lock() == w;
|
|
});
|
|
if(it == closePopupData.worldsToClose.end())
|
|
closePopupData.worldsToClose.emplace_back(w, true);
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
|
|
void Toolbar::close_popup_gui() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
std::erase_if(closePopupData.worldsToClose, [](auto& wPair) {
|
|
return wPair.w.expired();
|
|
});
|
|
center_obstructing_window_gui("Close program popup GUI", CLAY_SIZING_FIT(0), CLAY_SIZING_FIT(0, 600), [&] {
|
|
text_label(gui, "Files may contain unsaved changes");
|
|
gui.clipping_element<ScrollArea>("close file popup gui scroll area", ScrollArea::Options{
|
|
.scrollVertical = true,
|
|
.clipVertical = true,
|
|
.showScrollbarY = true,
|
|
.innerContent = [&](const ScrollArea::InnerContentParameters&) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
}
|
|
}) {
|
|
size_t i = 0;
|
|
for(auto& [w, setToSave] : closePopupData.worldsToClose) {
|
|
auto wLock = w.lock();
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .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_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(io.theme->backColor2),
|
|
.cornerRadius = CLAY_CORNER_RADIUS(io.theme->windowCorners1),
|
|
}) {
|
|
gui.new_id(i, [&] {
|
|
checkbox_boolean(gui, "set to save checkbox", &setToSave);
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) },
|
|
.childGap = 0,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
}
|
|
}) {
|
|
text_label(gui, wLock->name);
|
|
text_label_light(gui, wLock->filePath.empty() ? "Autosave in " + main.documentsPath.string() : wLock->filePath.string());
|
|
}
|
|
});
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
text_button(gui, "Save", "Save", {
|
|
.wide = true,
|
|
.onClick = [&] {
|
|
for(auto& [w, setToSave] : closePopupData.worldsToClose) {
|
|
auto wLock = w.lock();
|
|
if(setToSave) {
|
|
if(wLock->filePath.empty())
|
|
wLock->autosave_to_directory(main.documentsPath);
|
|
else
|
|
wLock->save_to_file(wLock->filePath);
|
|
}
|
|
main.set_tab_to_close(wLock.get());
|
|
}
|
|
closePopupData.worldsToClose.clear();
|
|
if(closePopupData.closeAppWhenDone)
|
|
main.setToQuit = true;
|
|
}
|
|
});
|
|
text_button(gui, "Discard All", "Discard All", {
|
|
.wide = true,
|
|
.onClick = [&] {
|
|
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;
|
|
}
|
|
});
|
|
text_button(gui, "Cancel", "Cancel", {
|
|
.wide = true,
|
|
.onClick = [&] {
|
|
closePopupData.worldsToClose.clear();
|
|
closePopupData.closeAppWhenDone = false;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void Toolbar::save_func() {
|
|
if(main.world->filePath == std::filesystem::path())
|
|
save_as_func();
|
|
else
|
|
main.world->save_to_file(main.world->filePath);
|
|
}
|
|
|
|
void Toolbar::save_as_func() {
|
|
#ifdef __EMSCRIPTEN__
|
|
optionsMenuOpen = true;
|
|
optionsMenuType = SET_DOWNLOAD_NAME;
|
|
#else
|
|
open_file_selector("Save", {{"InfiniPaint Canvas", World::FILE_EXTENSION}}, [w = make_weak_ptr(main.world)](const std::filesystem::path& p, const auto& e) {
|
|
auto world = w.lock();
|
|
if(world)
|
|
world->save_to_file(p);
|
|
}, "", true);
|
|
#endif
|
|
}
|
|
|
|
void Toolbar::paint_popup(Vector2f popupPos) {
|
|
using namespace GUIStuff;
|
|
auto& gui = main.g.gui;
|
|
|
|
std::shared_ptr<double> newRotationAngle = std::make_shared<double>(main.world->drawData.cam.c.rotation);
|
|
|
|
gui.set_z_index(-1, [&] {
|
|
paint_circle_popup_menu(gui, "paint circle popup", popupPos, {
|
|
.rotationAngle = newRotationAngle.get(),
|
|
.selectedColor = main.world->drawProg.get_foreground_color_ptr(),
|
|
.palette = main.conf.palettes[paletteData.selectedPalette].colors,
|
|
.onRotate = [&, newRotationAngle] {
|
|
main.world->drawData.cam.c.rotate_about(main.world->drawData.cam.c.from_space(main.window.size.cast<float>() * 0.5f), *newRotationAngle - main.world->drawData.cam.c.rotation);
|
|
*newRotationAngle = main.world->drawData.cam.c.rotation;
|
|
gui.set_to_layout();
|
|
},
|
|
.onPaletteClick = [&] {
|
|
gui.set_to_layout();
|
|
},
|
|
.mouseButton = [&](const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
|
|
if(!mouseHovering && button.down && button.button != InputManager::MouseButton::RIGHT)
|
|
main.world->drawProg.clear_right_click_popup();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void Toolbar::top_toolbar() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
gui.element<LayoutElement>("top menu bar", [&] (LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(static_cast<uint16_t>(io.theme->padding1 / 2)),
|
|
.childGap = static_cast<uint16_t>(io.theme->childGap1 / 2),
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(io.theme->backColor1),
|
|
.cornerRadius = CLAY_CORNER_RADIUS(io.theme->windowCorners1)
|
|
}) {
|
|
global_log();
|
|
|
|
auto icon_button_top_toolbar = [&](const char* id, const std::string& svgPath, bool isSelected, const std::function<void()>& onClick) {
|
|
return svg_icon_button(gui, id, svgPath, {
|
|
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
|
.isSelected = isSelected,
|
|
.onClick = onClick
|
|
});
|
|
};
|
|
|
|
Element* mainMenuButton = icon_button_top_toolbar("Main Menu Button", "data/icons/menu.svg", menuPopUpOpen, [&] {
|
|
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 ? "*" : ""));
|
|
}
|
|
|
|
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());
|
|
}
|
|
});
|
|
|
|
if(!main.world->clientStillConnecting) {
|
|
if(main.world->netObjMan.is_connected()) {
|
|
icon_button_top_toolbar("Player List Toggle Button", "data/icons/list.svg", playerMenuOpen, [&] {
|
|
playerMenuOpen = !playerMenuOpen;
|
|
});
|
|
}
|
|
icon_button_top_toolbar("Menu Undo Button", "data/icons/undo.svg", false, [&] {
|
|
main.world->undo_with_checks();
|
|
});
|
|
icon_button_top_toolbar("Menu Redo Button", "data/icons/redo.svg", false, [&] {
|
|
main.world->redo_with_checks();
|
|
});
|
|
Element* gridMenuButton = icon_button_top_toolbar("Grids Button", "data/icons/grid.svg", gridMenu.popupOpen, [&] {
|
|
if(gridMenu.popupOpen)
|
|
stop_displaying_grid_menu();
|
|
else
|
|
gridMenu.popupOpen = true;
|
|
});
|
|
Element* layerMenuButton = icon_button_top_toolbar("Layer Menu Button", "data/icons/layer.svg", layerMenuPopupOpen, [&] {
|
|
if(layerMenuPopupOpen)
|
|
stop_displaying_layer_menu();
|
|
else
|
|
layerMenuPopupOpen = true;
|
|
});
|
|
Element* bookmarkMenuButton = icon_button_top_toolbar("Bookmark Menu Button", "data/icons/bookmark.svg", bookmarkMenuPopupOpen, [&] {
|
|
if(bookmarkMenuPopupOpen)
|
|
stop_displaying_bookmark_menu();
|
|
else
|
|
bookmarkMenuPopupOpen = true;
|
|
});
|
|
|
|
if(gridMenu.popupOpen)
|
|
grid_menu(gridMenuButton);
|
|
if(bookmarkMenuPopupOpen)
|
|
bookmark_menu(bookmarkMenuButton);
|
|
if(layerMenuPopupOpen)
|
|
layer_menu(layerMenuButton);
|
|
}
|
|
if(menuPopUpOpen) {
|
|
gui.set_z_index(5, [&] {
|
|
gui.element<LayoutElement>("main menu popup", [&] (LayoutElement*, const Clay_ElementId& id) {
|
|
CLAY(id, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(100), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(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>(io.theme->backColor1),
|
|
.cornerRadius = CLAY_CORNER_RADIUS(io.theme->windowCorners1),
|
|
.floating = {.offset = {.x = 0, .y = static_cast<float>(io.theme->padding1)}, .zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_TOP, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
auto menu_popup_text_button = [&](const char* id, const char* str, const std::function<void()>& onClick) {
|
|
text_button(gui, id, str, {
|
|
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
|
.wide = true,
|
|
.centered = false,
|
|
.onClick = [&, onClick]{
|
|
onClick();
|
|
menuPopUpOpen = false;
|
|
}
|
|
});
|
|
};
|
|
menu_popup_text_button("new file local", "New File", [&] {
|
|
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
|
|
.isClient = false
|
|
});
|
|
});
|
|
menu_popup_text_button("open file", "Open", [&] { open_world_file(false, "", ""); });
|
|
if(!main.world->clientStillConnecting) {
|
|
menu_popup_text_button("save file", "Save", [&] { save_func(); });
|
|
menu_popup_text_button("save as file", "Save As", [&] { save_as_func(); });
|
|
menu_popup_text_button("screenshot", "Screenshot", [&] { main.world->drawProg.switch_to_tool(DrawingProgramToolType::SCREENSHOT); });
|
|
menu_popup_text_button("add image or file to canvas", "Add Image/File to Canvas", [&] {
|
|
#ifdef __EMSCRIPTEN__
|
|
emscripten_browser_file::upload("*", [](std::string const& fileName, std::string const& mimeType, std::string_view buffer, void* callbackData) {
|
|
if(!buffer.empty()) {
|
|
CustomEvents::emit_event<CustomEvents::AddFileToCanvasEvent>({
|
|
.type = CustomEvents::AddFileToCanvasEvent::Type::BUFFER,
|
|
.name = fileName,
|
|
.buffer = std::string(buffer)
|
|
});
|
|
}
|
|
}, &main);
|
|
#else
|
|
open_file_selector("Open File", {{"Any File", "*"}}, [&](const std::filesystem::path& p, const auto& e) {
|
|
CustomEvents::emit_event<CustomEvents::AddFileToCanvasEvent>({
|
|
.type = CustomEvents::AddFileToCanvasEvent::Type::PATH,
|
|
.filePath = p,
|
|
.pos = main.window.size.cast<float>() / 2.0f
|
|
});
|
|
});
|
|
#endif
|
|
});
|
|
if(main.world->netObjMan.is_connected()) {
|
|
menu_popup_text_button("lobby info", "Lobby Info", [&] {
|
|
optionsMenuOpen = true;
|
|
optionsMenuType = LOBBY_INFO_MENU;
|
|
});
|
|
}
|
|
menu_popup_text_button("start hosting", "Host", [&] {
|
|
serverLocalID = NetLibrary::get_random_server_local_id();
|
|
serverToConnectTo = NetLibrary::get_global_id() + serverLocalID;
|
|
optionsMenuOpen = true;
|
|
optionsMenuType = HOST_MENU;
|
|
});
|
|
menu_popup_text_button("canvas specific settings", "Canvas Settings", [&] {
|
|
optionsMenuOpen = true;
|
|
optionsMenuType = CANVAS_SETTINGS_MENU;
|
|
});
|
|
}
|
|
menu_popup_text_button("start connecting", "Connect", [&] {
|
|
serverToConnectTo.clear();
|
|
optionsMenuOpen = true;
|
|
optionsMenuType = CONNECT_MENU;
|
|
});
|
|
menu_popup_text_button("open options", "Settings", [&] {
|
|
optionsMenuOpen = true;
|
|
optionsMenuType = GENERAL_SETTINGS_MENU;
|
|
});
|
|
menu_popup_text_button("about menu button", "About", [&] {
|
|
optionsMenuOpen = true;
|
|
optionsMenuType = ABOUT_MENU;
|
|
});
|
|
#ifndef __EMSCRIPTEN__
|
|
menu_popup_text_button("quit button", "Quit", [&] {
|
|
if(main.app_close_requested())
|
|
main.setToQuit = true;
|
|
});
|
|
#endif
|
|
}
|
|
}, LayoutElement::Callbacks {
|
|
.mouseButton = [&, mainMenuButton](LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) {
|
|
if(!l->mouseHovering && button.down && !mainMenuButton->mouseHovering) {
|
|
menuPopUpOpen = false;
|
|
gui.set_to_layout();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void Toolbar::web_version_welcome() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIXED(700), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .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}
|
|
}) {
|
|
gui.new_id("web version welcome gui", [&] {
|
|
text_label_centered(gui, "Welcome to the web version of InfiniPaint!");
|
|
text_label(gui,
|
|
R"(This version contains more known issues than the native version of the app. This includes:
|
|
- Rare crashes
|
|
- If this browser tab is unfocused, or the window is minimized, any InfiniPaint tabs connected online (whether host or client) will be disconnected
|
|
- 4GB memory limit. Might be a problem if you're uploading many files/images
|
|
- Not multithreaded
|
|
- Can't access local fonts
|
|
|
|
If you like this app, consider downloading the native version for your system)");
|
|
text_button(gui, "got it", "Got It", {
|
|
.wide = true,
|
|
.onClick = [&] {
|
|
main.conf.viewWebVersionWelcome = false;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
void Toolbar::still_connecting_center_message() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .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}
|
|
}) {
|
|
text_label(gui, "Connecting to server...");
|
|
}
|
|
}
|
|
|
|
void Toolbar::no_layers_being_edited_message() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .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}
|
|
}) {
|
|
text_label(gui, "Select a layer to edit");
|
|
}
|
|
}
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
void Toolbar::update_notification_check() {
|
|
if(!updateCheckerData.updateCheckDone) {
|
|
if(main.conf.checkForUpdates) {
|
|
if(!updateCheckerData.versionFile)
|
|
updateCheckerData.versionFile = FileDownloader::download_data_from_url(UPDATE_NOTIFICATION_URL);
|
|
else {
|
|
switch(updateCheckerData.versionFile->status) {
|
|
case FileDownloader::DownloadData::Status::IN_PROGRESS:
|
|
break;
|
|
case FileDownloader::DownloadData::Status::SUCCESS: {
|
|
updateCheckerData.updateCheckDone = true;
|
|
std::optional<VersionNumber> newVersion = version_str_to_version_numbers(updateCheckerData.versionFile->str);
|
|
std::optional<VersionNumber> currentVersion = VersionConstants::CURRENT_VERSION_NUMBER;
|
|
if(newVersion.has_value() && currentVersion.has_value()) {
|
|
VersionNumber& newV = newVersion.value();
|
|
VersionNumber& currentV = currentVersion.value();
|
|
updateCheckerData.newVersionStr = version_numbers_to_version_str(newV);
|
|
Logger::get().log("INFO", "Latest online version is v" + updateCheckerData.newVersionStr);
|
|
if(newV > currentV) {
|
|
updateCheckerData.showGui = true;
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
else if(newV == currentV)
|
|
Logger::get().log("INFO", "Current version is up to date");
|
|
else
|
|
Logger::get().log("INFO", "Local version has larger version number than the latest online one");
|
|
}
|
|
else
|
|
Logger::get().log("INFO", "Update notification file couldn't be converted to version numbers");
|
|
updateCheckerData.versionFile = nullptr;
|
|
break;
|
|
}
|
|
case FileDownloader::DownloadData::Status::FAILURE:
|
|
Logger::get().log("INFO", "Failed to check for updates");
|
|
updateCheckerData.updateCheckDone = true;
|
|
updateCheckerData.versionFile = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
updateCheckerData.updateCheckDone = true;
|
|
}
|
|
}
|
|
|
|
void Toolbar::update_notification_gui() {
|
|
auto& gui = main.g.gui;
|
|
|
|
center_obstructing_window_gui("Update notifications GUI", CLAY_SIZING_FIXED(700), CLAY_SIZING_FIT(0), [&] {
|
|
gui.new_id("update notification gui", [&] {
|
|
text_label_centered(gui, "Update v" + updateCheckerData.newVersionStr + " available!");
|
|
text_button(gui, "download", "Open download page in web browser", {
|
|
.wide = true,
|
|
.onClick = [&]{
|
|
SDL_OpenURL(UPDATE_DOWNLOAD_URL);
|
|
updateCheckerData.showGui = false;
|
|
}
|
|
});
|
|
text_button(gui, "ignore forever", "Ignore and don't notify again (can be changed in settings)", {
|
|
.wide = true,
|
|
.onClick = [&]{
|
|
main.conf.checkForUpdates = false;
|
|
updateCheckerData.showGui = false;
|
|
}
|
|
});
|
|
text_button(gui, "ignore for now", "Ignore for now", {
|
|
.wide = true,
|
|
.onClick = [&]{
|
|
updateCheckerData.showGui = false;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
#endif
|
|
|
|
void Toolbar::grid_menu(Element* gridMenuButton) {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
if(main.world->gridMan.grids) {
|
|
gui.set_z_index(gui.get_z_index() + 1, [&] {
|
|
gui.element<LayoutElement>("grid menu", [&] (LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .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 = {.offset = {.x = 0, .y = static_cast<float>(io.theme->padding1)}, .zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
text_label_centered(gui, "Grids");
|
|
float ENTRY_HEIGHT = 25.0f;
|
|
if(main.world->gridMan.grids->empty())
|
|
text_label_centered(gui, "No grids yet...");
|
|
scroll_area_many_entries(gui, "grid menu entries", {
|
|
.entryHeight = ENTRY_HEIGHT,
|
|
.entryCount = main.world->gridMan.grids->size(),
|
|
.clipHorizontal = true,
|
|
.growing = true,
|
|
.elementContent = [&](size_t i) {
|
|
auto& grid = main.world->gridMan.grids->at(i)->obj;
|
|
bool selectedEntry = gridMenu.selectedGrid == i;
|
|
gui.element<LayoutElement>("elem", [&] (LayoutElement*, const Clay_ElementId& lId2) {
|
|
CLAY(lId2, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)},
|
|
.childGap = 2,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
},
|
|
.backgroundColor = selectedEntry ? convert_vec4<Clay_Color>(io.theme->backColor1) : convert_vec4<Clay_Color>(io.theme->backColor2)
|
|
}) {
|
|
text_label(gui, grid->get_display_name());
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
|
|
.childGap = 1,
|
|
.childAlignment = {.x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
}
|
|
}) {
|
|
auto list_button = [&](const char* id, const char* svgPath, const std::function<void()>& onClick) {
|
|
gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] {
|
|
svg_icon_button(gui, id, svgPath, {
|
|
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
|
.size = ENTRY_HEIGHT,
|
|
.onClick = onClick
|
|
});
|
|
});
|
|
};
|
|
list_button("visibility eye", grid->visible ? "data/icons/eyeopen.svg" : "data/icons/eyeclose.svg", [&] {
|
|
grid->visible = !grid->visible;
|
|
NetworkingObjects::generic_serialized_class_send_update_to_all<WorldGrid>(grid);
|
|
});
|
|
list_button("edit pencil", "data/icons/pencil.svg", [&] {
|
|
main.world->drawProg.modify_grid(grid);
|
|
stop_displaying_grid_menu();
|
|
});
|
|
list_button("delete trash", "data/icons/trash.svg", [&, i] {
|
|
main.world->gridMan.remove_grid(i);
|
|
});
|
|
}
|
|
}
|
|
}, LayoutElement::Callbacks {
|
|
.mouseButton = [&, i] (LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) {
|
|
if(l->mouseHovering && button.down && button.button == InputManager::MouseButton::LEFT) {
|
|
gridMenu.selectedGrid = i;
|
|
if(button.clicks == 2) {
|
|
main.world->drawProg.modify_grid(grid);
|
|
stop_displaying_grid_menu();
|
|
}
|
|
gui.set_to_layout();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
left_to_right_line_layout(gui, [&]() {
|
|
input_text(gui, "grid text input", &gridMenu.newName, {
|
|
.onEnter = [&]() { add_grid(); }
|
|
});
|
|
svg_icon_button(gui, "grid add button", "data/icons/plusbold.svg", {
|
|
.size = SMALL_BUTTON_SIZE,
|
|
.onClick = [&] { add_grid(); }
|
|
});
|
|
});
|
|
}
|
|
}, LayoutElement::Callbacks {
|
|
.mouseButton = [&, gridMenuButton] (LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) {
|
|
if(!l->mouseHovering && !l->childMouseHovering && !gridMenuButton->mouseHovering && button.down)
|
|
stop_displaying_grid_menu();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
void Toolbar::add_grid() {
|
|
if(!gridMenu.newName.empty()) {
|
|
main.world->gridMan.add_default_grid(gridMenu.newName);
|
|
main.world->drawProg.modify_grid(main.world->gridMan.grids->at(main.world->gridMan.grids->size() - 1)->obj);
|
|
stop_displaying_grid_menu();
|
|
}
|
|
}
|
|
|
|
void Toolbar::stop_displaying_grid_menu() {
|
|
gridMenu.newName.clear();
|
|
gridMenu.popupOpen = false;
|
|
gridMenu.selectedGrid = std::numeric_limits<uint32_t>::max();
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
|
|
void Toolbar::stop_displaying_bookmark_menu() {
|
|
main.world->bMan.refresh_gui_data();
|
|
bookmarkMenuPopupOpen = false;
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
|
|
void Toolbar::stop_displaying_layer_menu() {
|
|
main.world->drawProg.layerMan.listGUI.refresh_gui_data();
|
|
layerMenuPopupOpen = false;
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
|
|
void Toolbar::bookmark_menu(Element* bookmarkMenuButton) {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
gui.set_z_index(gui.get_z_index() + 1, [&] {
|
|
gui.element<LayoutElement>("bookmark menu", [&] (LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .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 = {.offset = {.x = 0, .y = static_cast<float>(io.theme->padding1)}, .zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
text_label_centered(gui, "Bookmarks");
|
|
main.world->bMan.setup_list_gui();
|
|
}
|
|
}, LayoutElement::Callbacks {
|
|
.mouseButton = [&, bookmarkMenuButton] (LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) {
|
|
if(!l->mouseHovering && !l->childMouseHovering && !bookmarkMenuButton->mouseHovering && button.down)
|
|
stop_displaying_bookmark_menu();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void Toolbar::layer_menu(Element* layerMenuButton) {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
gui.set_z_index(gui.get_z_index() + 1, [&] {
|
|
gui.element<LayoutElement>("layer menu", [&] (LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .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 = {.offset = {.x = 0, .y = static_cast<float>(io.theme->padding1)}, .zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
text_label_centered(gui, "Layers");
|
|
main.world->drawProg.layerMan.listGUI.setup_list_gui();
|
|
}
|
|
}, LayoutElement::Callbacks {
|
|
.mouseButton = [&, layerMenuButton] (LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) {
|
|
if(!l->mouseHovering && !l->childMouseHovering && !layerMenuButton->mouseHovering && button.down)
|
|
stop_displaying_layer_menu();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
RichText::TextData Toolbar::build_paragraph_from_chat_message(const ChatMessage& message, float alpha) {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
RichText::TextData toRet;
|
|
|
|
auto& par = toRet.paragraphs.emplace_back();
|
|
|
|
RichText::PositionedTextStyleMod& positionedModInit = toRet.tStyleMods.emplace_back();
|
|
positionedModInit.pos = {0, 0};
|
|
positionedModInit.mods[RichText::TextStyleModifier::ModifierType::WEIGHT] = std::make_shared<RichText::WeightTextStyleModifier>(SkFontStyle::Weight::kBold_Weight);
|
|
positionedModInit.mods[RichText::TextStyleModifier::ModifierType::COLOR] = std::make_shared<RichText::ColorTextStyleModifier>(convert_vec4<Vector4f>(color_mul_alpha(message.type == ChatMessage::JOIN ? io.theme->warningColor : io.theme->frontColor1, alpha)));
|
|
if(message.type == ChatMessage::JOIN)
|
|
par.text += message.name + " ";
|
|
else
|
|
par.text += "[" + message.name + "] ";
|
|
|
|
RichText::PositionedTextStyleMod& positionedModMessage = toRet.tStyleMods.emplace_back();
|
|
positionedModMessage.pos = {0, par.text.size()};
|
|
positionedModMessage.mods[RichText::TextStyleModifier::ModifierType::WEIGHT] = std::make_shared<RichText::WeightTextStyleModifier>(SkFontStyle::Weight::kNormal_Weight);
|
|
par.text += message.message;
|
|
|
|
return toRet;
|
|
}
|
|
|
|
void Toolbar::chat_box() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
constexpr float CHATBOX_WIDTH = 700;
|
|
if(main.world->netObjMan.is_connected()) {
|
|
gui.element<LayoutElement>("Infinipaint chat box open button", [&](LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
},
|
|
.floating = {.offset = {static_cast<float>(io.theme->padding1), -static_cast<float>(io.theme->padding1)}, .zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_BOTTOM, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
svg_icon_button(gui, "Chat open button", "data/icons/chat.svg", {
|
|
.onClick = [&] {
|
|
chatboxOpen = !chatboxOpen;
|
|
chatMessageInput.clear();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
gui.element<LayoutElement>("Infinipaint chat box", [&](LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIXED(CHATBOX_WIDTH), .height = CLAY_SIZING_FIT(0) },
|
|
.childGap = 0,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_BOTTOM},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.floating = {.offset = {60 + static_cast<float>(io.theme->padding1), -static_cast<float>(io.theme->padding1)}, .zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_BOTTOM, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
if(chatboxOpen) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(0),
|
|
.childGap = 0,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_BOTTOM},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(io.theme->backColor1)
|
|
}) {
|
|
int id = 0;
|
|
for(auto& chatMessage : main.world->chatMessages | std::views::reverse) {
|
|
gui.new_id(id++, [&] {
|
|
gui.element<TextParagraph>("text", TextParagraph::Data{
|
|
.text = build_paragraph_from_chat_message(chatMessage, 1.0f),
|
|
.maxGrowX = CHATBOX_WIDTH,
|
|
.ellipsis = false
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
left_to_right_line_layout(gui, [&] {
|
|
input_text(gui, "message input", &chatMessageInput, {
|
|
.onEnter = [&] {
|
|
if(!chatMessageInput.empty())
|
|
main.world->send_chat_message(chatMessageInput);
|
|
chatboxOpen = false;
|
|
},
|
|
.onDeselect = [&] {
|
|
chatboxOpen = false;
|
|
gui.set_to_layout();
|
|
}
|
|
})->select();
|
|
text_button(gui, "send button", "Send", {
|
|
.instantResponse = true,
|
|
.onClick = [&] {
|
|
if(!chatMessageInput.empty())
|
|
main.world->send_chat_message(chatMessageInput);
|
|
chatboxOpen = false;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
else {
|
|
gui.new_id("Message popups", [&] {
|
|
int id = 0;
|
|
for(auto& chatMessage : main.world->chatMessages | std::views::reverse) {
|
|
chatMessage.time.update_time_since();
|
|
if(chatMessage.time < ChatMessage::DISPLAY_TIME) {
|
|
gui.new_id(id++, [&] {
|
|
float a = 1.0f - lerp_time<float>(chatMessage.time, ChatMessage::DISPLAY_TIME, ChatMessage::FADE_START_TIME);
|
|
gui.element<LayoutElement>("Chat message", [&](LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(0),
|
|
.childGap = 0,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(color_mul_alpha(io.theme->backColor1, a)),
|
|
}) {
|
|
gui.element<TextParagraph>("message", TextParagraph::Data{
|
|
.text = build_paragraph_from_chat_message(chatMessage, a),
|
|
.maxGrowX = CHATBOX_WIDTH,
|
|
.ellipsis = false
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void Toolbar::global_log() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
gui.new_id("Global log popup list", [&] {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIT(0) },
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.floating = {.offset = {0, 10}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
for(size_t i = 0; i < main.logMessages.size(); i++) {
|
|
auto& logM = main.logMessages[i];
|
|
logM.time.update_time_since();
|
|
if(logM.time < UserLogMessage::DISPLAY_TIME) {
|
|
float a = 1.0f - lerp_time<float>(logM.time, UserLogMessage::DISPLAY_TIME, UserLogMessage::FADE_START_TIME);
|
|
gui.new_id(i, [&] {
|
|
gui.element<LayoutElement>("Global log message", [&] (LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = 0,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(color_mul_alpha(io.theme->backColor1, a)),
|
|
.cornerRadius = CLAY_CORNER_RADIUS(io.theme->windowCorners1)
|
|
}) {
|
|
SkColor4f c{0, 0, 0, 0};
|
|
switch(logM.color) {
|
|
case UserLogMessage::COLOR_NORMAL:
|
|
c = io.theme->frontColor1;
|
|
break;
|
|
case UserLogMessage::COLOR_ERROR:
|
|
c = io.theme->errorColor;
|
|
break;
|
|
}
|
|
text_label_color(gui, logM.text, color_mul_alpha(c, a));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void Toolbar::drawing_program_gui() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) },
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
},
|
|
}) {
|
|
main.world->drawProg.toolbar_gui(*this);
|
|
|
|
if(colorLeft)
|
|
color_picker_window("Drawing program gui color picker left", &colorLeft, colorLeftButton, colorLeftData);
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}
|
|
}
|
|
}) {}
|
|
if(colorRight)
|
|
color_picker_window("Drawing program gui color picker right", &colorRight, colorRightButton, colorRightData);
|
|
|
|
main.world->drawProg.tool_options_gui(*this);
|
|
}
|
|
}
|
|
|
|
void Toolbar::color_picker_window(const char* id, Vector4f** color, GUIStuff::Element* b, const ColorSelectorData& colorSelectorData) {
|
|
auto& gui = main.g.gui;
|
|
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.padding = {.top = 40, .bottom = 40}
|
|
}
|
|
}) {
|
|
main.g.gui.element<LayoutElement>(id, [&](LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(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
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(gui.io.theme->backColor1),
|
|
.cornerRadius = CLAY_CORNER_RADIUS(gui.io.theme->windowCorners1)
|
|
}) {
|
|
color_picker_items(gui, "colorpicker", *color, {
|
|
.onEdit = colorSelectorData.onChange,
|
|
.onSelect = colorSelectorData.onSelect,
|
|
.onDeselect = colorSelectorData.onDeselect,
|
|
});
|
|
color_palette("colorpickerpalette", *color, [colorSelectorData] {
|
|
if(colorSelectorData.onSelect) colorSelectorData.onSelect();
|
|
if(colorSelectorData.onChange) colorSelectorData.onChange();
|
|
if(colorSelectorData.onDeselect) colorSelectorData.onDeselect();
|
|
});
|
|
}
|
|
}, LayoutElement::Callbacks{
|
|
.mouseButton = [&, b, color](LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) {
|
|
if(!l->mouseHovering && !l->childMouseHovering && !b->mouseHovering && button.down) {
|
|
*color = nullptr;
|
|
main.g.gui.set_to_layout();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void Toolbar::color_palette(const char* id, Vector4f* color, const std::function<void()>& onChange) {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
gui.new_id(id, [&] {
|
|
auto& palette = main.conf.palettes[paletteData.selectedPalette].colors;
|
|
|
|
gui.element<ScrollArea>("color palette scroll area", ScrollArea::Options{
|
|
.scrollVertical = true,
|
|
.clipVertical = true,
|
|
.showScrollbarY = true,
|
|
.innerContent = [&](const ScrollArea::InnerContentParameters&) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
|
|
.childGap = io.theme->childGap1,
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
}
|
|
}) {
|
|
size_t i = 0;
|
|
size_t nextID = 0;
|
|
while(i < palette.size()) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE)},
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
}
|
|
}) {
|
|
while(i < palette.size()) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE)}
|
|
}
|
|
}) {
|
|
auto newC = std::make_shared<Vector3f>(palette[i].x(), palette[i].y(), palette[i].z());
|
|
gui.new_id(nextID++, [&] {
|
|
color_button(gui, "c", newC.get(), {
|
|
.isSelected = newC->x() == color->x() && newC->y() == color->y() && newC->z() == color->z(),
|
|
.hasAlpha = false,
|
|
.onClick = [newC, color, onChange] {
|
|
// We want to keep the old color's alpha
|
|
color->x() = newC->x();
|
|
color->y() = newC->y();
|
|
color->z() = newC->z();
|
|
if(onChange) onChange();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
i++;
|
|
if(i % 6 == 0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if(paletteData.selectedPalette != 0) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)},
|
|
.padding = {.top = 3, .bottom = 3},
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
}
|
|
}) {
|
|
svg_icon_button(gui, "addcolor", "data/icons/plus.svg", {
|
|
.onClick = [&, color] {
|
|
std::erase(palette, Vector3f{color->x(), color->y(), color->z()});
|
|
palette.emplace_back(color->x(), color->y(), color->z());
|
|
}
|
|
});
|
|
svg_icon_button(gui, "deletecolor", "data/icons/close.svg", {
|
|
.onClick = [&, color] {
|
|
std::erase(palette, Vector3f{color->x(), color->y(), color->z()});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)},
|
|
.padding = {.top = 3, .bottom = 3},
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
}
|
|
}) {
|
|
std::vector<std::string> paletteNames;
|
|
for(auto& p : main.conf.palettes)
|
|
paletteNames.emplace_back(p.name);
|
|
gui.element<DropDown<size_t>>("paletteselector", &paletteData.selectedPalette, paletteNames, DropdownOptions{
|
|
.onClick = [&] { gui.set_to_layout(); }
|
|
});
|
|
svg_icon_button(gui, "paletteadd", "data/icons/plus.svg", {
|
|
.size = 25.0f,
|
|
.onClick = [&] {
|
|
paletteData.addingPalette = !paletteData.addingPalette;
|
|
}
|
|
});
|
|
svg_icon_button(gui, "paletteremove", "data/icons/close.svg", {
|
|
.size = 25.0f,
|
|
.onClick = [&] {
|
|
main.conf.palettes.erase(main.conf.palettes.begin() + paletteData.selectedPalette);
|
|
paletteData.selectedPalette = 0;
|
|
}
|
|
});
|
|
}
|
|
if(paletteData.addingPalette) {
|
|
input_text_field(gui, "paletteinputname", "Name", &paletteData.newPaletteStr);
|
|
text_button_wide("addpalettebutton", "Create", [&] {
|
|
if(!paletteData.newPaletteStr.empty()) {
|
|
main.conf.palettes.emplace_back();
|
|
main.conf.palettes.back().name = paletteData.newPaletteStr;
|
|
paletteData.selectedPalette = main.conf.palettes.size() - 1;
|
|
paletteData.addingPalette = false;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
void Toolbar::performance_metrics() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(0), .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_BOTTOM},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
},
|
|
.floating = {.offset = {-10, -10}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_BOTTOM, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(color_mul_alpha(io.theme->backColor1, 0.7f)),
|
|
}) {
|
|
text_label(gui, "Undo queue");
|
|
std::vector<std::string> undoList = main.world->undo.get_front_undo_queue_names(10);
|
|
for(const std::string& u : undoList)
|
|
text_label(gui, u);
|
|
}
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(color_mul_alpha(io.theme->backColor1, 0.7f)),
|
|
}) {
|
|
std::stringstream a;
|
|
a << "FPS: " << std::fixed << std::setprecision(0) << (1.0 / main.deltaTime);
|
|
text_label(gui, a.str());
|
|
text_label(gui, "Item Count: " + std::to_string(main.world->drawProg.layerMan.total_component_count()));
|
|
std::stringstream b;
|
|
b << "Coord: " << main.world->drawData.cam.c.pos.x().display_int_str(5) << ", " << main.world->drawData.cam.c.pos.y().display_int_str(5);
|
|
text_label(gui, b.str());
|
|
std::stringstream c;
|
|
c << "Zoom: " << main.world->drawData.cam.c.inverseScale.display_int_str(5);
|
|
text_label(gui, c.str());
|
|
std::stringstream d;
|
|
d << "Rotation: " << main.world->drawData.cam.c.rotation;
|
|
text_label(gui, d.str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Toolbar::player_list() {
|
|
auto& gui = main.g.gui;
|
|
|
|
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) {
|
|
left_to_right_line_layout(gui, [&]() {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(SkColor4f{main.world->ownClientData->get_cursor_color().x(), main.world->ownClientData->get_cursor_color().y(), main.world->ownClientData->get_cursor_color().z(), 1.0f}),
|
|
.cornerRadius = CLAY_CORNER_RADIUS(3)
|
|
}) {}
|
|
text_label(gui, main.world->ownClientData->get_display_name());
|
|
});
|
|
size_t num = 0;
|
|
for(auto& client : main.world->clients->get_data()) {
|
|
if(client != main.world->ownClientData) {
|
|
gui.new_id(num++, [&] {
|
|
left_to_right_line_layout(gui, [&]() {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(SkColor4f{client->get_cursor_color().x(), client->get_cursor_color().y(), client->get_cursor_color().z(), 1.0f}),
|
|
.cornerRadius = CLAY_CORNER_RADIUS(3)
|
|
}) {}
|
|
text_label(gui, client->get_display_name());
|
|
CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
|
|
text_button(gui, "teleport button", "Jump To", { .onClick = [&] {
|
|
main.world->drawData.cam.smooth_move_to(*main.world, client->get_cam_coords(), client->get_window_size());
|
|
}});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
text_button(gui, "close list", "Done", { .wide = true, .onClick = [&] {
|
|
playerMenuOpen = false;
|
|
}});
|
|
});
|
|
});
|
|
}
|
|
|
|
void Toolbar::open_world_file(bool isClient, const std::string& netSource, const std::string& serverLocalID2) {
|
|
#ifdef __EMSCRIPTEN__
|
|
static struct UploadData {
|
|
bool iC;
|
|
std::string nS;
|
|
std::string sLID;
|
|
MainProgram* main;
|
|
} uploadData;
|
|
uploadData.iC = isClient;
|
|
uploadData.nS = netSource;
|
|
uploadData.sLID = serverLocalID2;
|
|
uploadData.main = &main;
|
|
emscripten_browser_file::upload("." + World::FILE_EXTENSION, [](std::string const& fileName, std::string const& mimeType, std::string_view buffer, void* callbackData) {
|
|
if(!buffer.empty()) {
|
|
UploadData* uD = (UploadData*)callbackData;
|
|
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
|
|
.isClient = uD->iC,
|
|
.filePathSource = std::filesystem::path(fileName),
|
|
.netSource = uD->nS,
|
|
.serverLocalID = uD->sLID,
|
|
.fileDataBuffer = buffer
|
|
});
|
|
}
|
|
}, &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) {
|
|
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
|
|
.isClient = isClient,
|
|
.filePathSource = p,
|
|
.netSource = netSource,
|
|
.serverLocalID = serverLocalID2
|
|
});
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void Toolbar::center_obstructing_window_gui(const char* id, Clay_SizingAxis x, Clay_SizingAxis y, const std::function<void()>& innerContent) {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
gui.set_z_index(100, [&] {
|
|
gui.element<LayoutElement>(id, [&] (LayoutElement*, const Clay_ElementId& id) {
|
|
CLAY(id, {
|
|
.layout = {
|
|
.sizing = {.width = x, .height = y },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .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 = {.zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
|
}) {
|
|
innerContent();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void Toolbar::text_button_wide(const char* id, const char* str, const std::function<void()>& onClick) {
|
|
auto& gui = main.g.gui;
|
|
|
|
text_button(gui, id, str, {
|
|
.wide = true,
|
|
.onClick = onClick
|
|
});
|
|
}
|
|
|
|
void Toolbar::options_menu() {
|
|
auto& gui = main.g.gui;
|
|
|
|
switch(optionsMenuType) {
|
|
case HOST_MENU: {
|
|
center_obstructing_window_gui("host menu", CLAY_SIZING_FIT(650), CLAY_SIZING_FIT(0), [&] {
|
|
input_text_field(gui, "lobby", "Lobby", &serverToConnectTo);
|
|
left_to_right_line_layout(gui, [&]() {
|
|
text_button_wide("copy lobby address", "Copy Lobby Address", [&] {
|
|
main.input.set_clipboard_str(serverToConnectTo);
|
|
});
|
|
text_button_wide("host file", "Host", [&] {
|
|
main.world->start_hosting(serverToConnectTo, serverLocalID);
|
|
optionsMenuOpen = false;
|
|
});
|
|
text_button_wide("cancel", "Cancel", [&] {
|
|
optionsMenuOpen = false;
|
|
});
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case CONNECT_MENU: {
|
|
center_obstructing_window_gui("connect menu", CLAY_SIZING_FIT(650), CLAY_SIZING_FIT(0), [&] {
|
|
input_text_field(gui, "lobby", "Lobby", &serverToConnectTo);
|
|
left_to_right_line_layout(gui, [&]() {
|
|
text_button_wide("connect", "Connect", [&] {
|
|
if(serverToConnectTo.length() != (NetLibrary::LOCALID_LEN + NetLibrary::GLOBALID_LEN))
|
|
Logger::get().log("USERINFO", "Connect issue: Incorrect address length");
|
|
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 {
|
|
CustomEvents::emit_event<CustomEvents::OpenInfiniPaintFileEvent>({
|
|
.isClient = true,
|
|
.netSource = serverToConnectTo
|
|
});
|
|
optionsMenuOpen = false;
|
|
}
|
|
});
|
|
text_button_wide("cancel", "Cancel", [&] {
|
|
optionsMenuOpen = false;
|
|
});
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case GENERAL_SETTINGS_MENU: {
|
|
center_obstructing_window_gui("gsettings", CLAY_SIZING_FIXED(600), CLAY_SIZING_FIXED(500), [&] {
|
|
general_settings_inner_gui();
|
|
});
|
|
break;
|
|
}
|
|
case LOBBY_INFO_MENU: {
|
|
center_obstructing_window_gui("lobby info menu", CLAY_SIZING_FIT(650), CLAY_SIZING_FIT(0), [&] {
|
|
input_text_field(gui, "lobby", "Lobby", &main.world->netSource);
|
|
left_to_right_line_layout(gui, [&]() {
|
|
text_button_wide("copy lobby address", "Copy Lobby Address", [&] {
|
|
main.input.set_clipboard_str(main.world->netSource);
|
|
});
|
|
text_button_wide("done", "Done", [&] {
|
|
optionsMenuOpen = false;
|
|
});
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case CANVAS_SETTINGS_MENU: {
|
|
center_obstructing_window_gui("canvas settings menu", CLAY_SIZING_FIT(500), CLAY_SIZING_FIT(0), [&] {
|
|
auto newColorToSet = std::make_shared<SkColor4f>(main.world->canvasTheme.get_back_color());
|
|
color_picker_button_field(gui, "canvasColor", "Canvas Color", newColorToSet.get(), {
|
|
.hasAlpha = false,
|
|
.onEdit = [&, newColorToSet] {
|
|
main.world->canvasTheme.set_back_color(convert_vec3<Vector3f>(*newColorToSet));
|
|
}
|
|
});
|
|
text_button_wide("done", "Done", [&] {
|
|
optionsMenuOpen = false;
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case SET_DOWNLOAD_NAME: {
|
|
center_obstructing_window_gui("set download name menu", CLAY_SIZING_FIT(500), CLAY_SIZING_FIT(0), [&] {
|
|
input_text_field(gui, "file name", "File Name", &downloadNameSet);
|
|
left_to_right_line_layout(gui, [&]() {
|
|
text_button_wide("download save button", "Save", [&] {
|
|
main.world->save_to_file(downloadNameSet);
|
|
optionsMenuOpen = false;
|
|
});
|
|
text_button_wide("cancel", "Cancel", [&] {
|
|
optionsMenuOpen = false;
|
|
});
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case ABOUT_MENU: {
|
|
center_obstructing_window_gui("about menu", CLAY_SIZING_FIXED(650), CLAY_SIZING_FIXED(500), [&] {
|
|
about_menu_inner_gui();
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Toolbar::general_settings_inner_gui() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(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_LEFT_TO_RIGHT
|
|
}
|
|
}) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(150), .height = CLAY_SIZING_GROW(0) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childGap = 2,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
}
|
|
}) {
|
|
auto category_button = [&](const char* id, const char* str, GeneralSettingsOptions opt) {
|
|
text_button(gui, id, str, {
|
|
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
|
.isSelected = generalSettingsOptions == opt,
|
|
.wide = true,
|
|
.centered = false,
|
|
.onClick = [&, opt] {
|
|
main.g.load_theme(main.conf.configPath, main.conf.themeCurrentlyLoaded);
|
|
themeData.selectedThemeIndex = std::nullopt;
|
|
generalSettingsOptions = opt;
|
|
main.keybindWaiting = std::nullopt;
|
|
}
|
|
});
|
|
};
|
|
category_button("Generalbutton", "General", GSETTINGS_GENERAL);
|
|
category_button("Tabletbutton", "Tablet", GSETTINGS_TABLET);
|
|
category_button("Themebutton", "Theme", GSETTINGS_THEME);
|
|
category_button("Keybindsbutton", "Keybinds", GSETTINGS_KEYBINDS);
|
|
category_button("Debugbutton", "Debug", GSETTINGS_DEBUG);
|
|
}
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) },
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
}
|
|
}) {
|
|
auto general_scroll_area = [&](const char* id, const std::function<void()>& innerContent) {
|
|
gui.clipping_element<ScrollArea>("general settings scroll area", ScrollArea::Options{
|
|
.scrollVertical = true,
|
|
.clipVertical = true,
|
|
.showScrollbarY = true,
|
|
.innerContent = [&, innerContent](const ScrollArea::InnerContentParameters&) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(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
|
|
}
|
|
}) {
|
|
innerContent();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
switch(generalSettingsOptions) {
|
|
case GSETTINGS_GENERAL: {
|
|
general_scroll_area("general settings", [&] {
|
|
input_text_field(gui, "display name input", "Display name", &main.conf.displayName);
|
|
main.update_display_names();
|
|
color_picker_button_field(gui, "defaultCanvasBackgroundColor", "Default canvas background color", &main.conf.defaultCanvasBackgroundColor, { .hasAlpha = false });
|
|
#ifndef __EMSCRIPTEN__
|
|
checkbox_boolean_field(gui, "native file pick", "Use native file picker", &main.conf.useNativeFilePicker);
|
|
checkbox_boolean_field(gui, "update notifications enable", "Check for updates on startup", &main.conf.checkForUpdates);
|
|
#endif
|
|
slider_scalar_field(gui, "drag zoom slider", "Drag zoom speed", &main.conf.dragZoomSpeed, 0.0, 1.0, {.decimalPrecision = 3});
|
|
slider_scalar_field(gui, "scroll zoom slider", "Scroll zoom speed", &main.conf.scrollZoomSpeed, 0.0, 1.0, {.decimalPrecision = 3});
|
|
checkbox_boolean_field(gui, "flip zoom tool direction", "Flip zoom tool direction", &main.conf.flipZoomToolDirection);
|
|
checkbox_boolean_field(gui, "make all tools share same size", "Make all tools share size", &main.toolConfig.globalConf.useGlobalRelativeWidth);
|
|
#ifndef __EMSCRIPTEN__
|
|
checkbox_boolean_field(gui, "disable graphics driver workarounds", "Disable graphics driver workarounds (enabling or disabling this might fix some graphical glitches, requires restart)", &main.conf.disableGraphicsDriverWorkarounds);
|
|
#endif
|
|
input_scalar_field(gui, "jump transition time", "Jump transition time", &main.conf.jumpTransitionTime, 0.01f, 1000.0f, {.decimalPrecision = 2});
|
|
input_scalar_field(gui, "Max GUI Scale", "Max GUI Scale", &main.conf.guiScale, 0.5f, 5.0f, {
|
|
.decimalPrecision = 1,
|
|
.onEdit = [&] { main.g.window_update(); }
|
|
});
|
|
text_label(gui, "Anti-aliasing:");
|
|
radio_button_selector(gui, "Antialiasing selector", &main.conf.antialiasing, {
|
|
{"None", GlobalConfig::AntiAliasing::NONE},
|
|
{"Skia", GlobalConfig::AntiAliasing::SKIA},
|
|
{"Dynamic MSAA", GlobalConfig::AntiAliasing::DYNAMIC_MSAA}
|
|
}, [&] {
|
|
main.refresh_draw_surfaces();
|
|
});
|
|
text_label(gui, "VSync:");
|
|
radio_button_selector(gui, "VSync selector", &main.conf.vsyncValue, {
|
|
{"On", 1},
|
|
{"Off", 0},
|
|
{"Adaptive", -1}
|
|
}, [&] {
|
|
main.set_vsync_value(main.conf.vsyncValue);
|
|
});
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
checkbox_boolean_field(gui, "apply display scale", "Apply display scale", &main.conf.applyDisplayScale);
|
|
#endif
|
|
});
|
|
break;
|
|
}
|
|
case GSETTINGS_TABLET: {
|
|
general_scroll_area("tablet settings", [&] {
|
|
checkbox_boolean_field(gui, "pen pressure width", "Pen pressure affects brush size", &main.conf.tabletOptions.pressureAffectsBrushWidth);
|
|
slider_scalar_field(gui, "smoothing time", "Smoothing sampling time", &main.conf.tabletOptions.smoothingSamplingTime, 0.001f, 1.0f, {.decimalPrecision = 3});
|
|
input_scalar_field<uint8_t>(gui, "middle click", "Middle click pen button", &main.conf.tabletOptions.middleClickButton, 1, 255);
|
|
input_scalar_field<uint8_t>(gui, "right click", "Right click pen button", &main.conf.tabletOptions.rightClickButton, 1, 255);
|
|
slider_scalar_field(gui, "tablet brush minimum size", "Brush relative minimum size", &main.conf.tabletOptions.brushMinimumSize, 0.0f, 1.0f, {.decimalPrecision = 3});
|
|
checkbox_boolean_field(gui, "tablet zoom with button method", "Zoom when pen touching tablet and pen button assigned to middle click is held", &main.conf.tabletOptions.zoomWhilePenDownAndButtonHeld);
|
|
#ifdef _WIN32
|
|
checkbox_boolean_field(gui, "mouse ignore when pen proximity", "Ignore mouse movement when pen in proximity", &main.conf.tabletOptions.ignoreMouseMovementWhenPenInProximity);
|
|
#endif
|
|
});
|
|
break;
|
|
}
|
|
case GSETTINGS_THEME: {
|
|
general_scroll_area("theme", [&] {
|
|
if(!themeData.selectedThemeIndex)
|
|
reload_theme_list();
|
|
|
|
CLAY_AUTO_ID({.layout = {
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT,
|
|
}
|
|
}) {
|
|
text_label(gui, "Theme: ");
|
|
gui.element<DropDown<size_t>>("dropdownSelectThemes", &themeData.selectedThemeIndex.value(), themeData.themeDirList, DropdownOptions{
|
|
.onClick = [&]() {
|
|
main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()];
|
|
reload_theme_list();
|
|
}
|
|
});
|
|
}
|
|
left_to_right_line_layout(gui, [&]() {
|
|
if(themeData.selectedThemeIndex != 0) {
|
|
text_button_wide("savethemebutton", "Save", [&] {
|
|
main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()];
|
|
main.g.save_theme(main.conf.configPath, main.conf.themeCurrentlyLoaded);
|
|
reload_theme_list();
|
|
});
|
|
}
|
|
text_button_wide("saveasthemebutton", "Save As", [&]() {
|
|
themeData.openedSaveAsMenu = !themeData.openedSaveAsMenu;
|
|
});
|
|
text_button_wide("reloadthemebutton", "Reload", [&] {
|
|
main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()];
|
|
reload_theme_list();
|
|
});
|
|
if(themeData.selectedThemeIndex != 0) {
|
|
text_button_wide("deletethemebutton", "Delete", [&] {
|
|
try { std::filesystem::remove(main.conf.configPath / "themes" / (themeData.themeDirList[themeData.selectedThemeIndex.value()] + ".json")); } catch(...) { }
|
|
main.conf.themeCurrentlyLoaded = "Default";
|
|
reload_theme_list();
|
|
});
|
|
}
|
|
});
|
|
if(themeData.openedSaveAsMenu) {
|
|
input_text_field(gui, "Theme name:", "Theme name: ", &main.conf.themeCurrentlyLoaded);
|
|
left_to_right_line_layout(gui, [&]() {
|
|
text_button_wide("saveasdone", "Done", [&] {
|
|
main.g.save_theme(main.conf.configPath, main.conf.themeCurrentlyLoaded);
|
|
reload_theme_list();
|
|
});
|
|
text_button_wide("saveascancel", "Cancel", [&] {
|
|
themeData.openedSaveAsMenu = false;
|
|
});
|
|
});
|
|
}
|
|
text_label(gui, "Edit theme:");
|
|
text_label_light(gui, "Note: Changes only remain if theme is saved");
|
|
auto theme_color_field = [&](const char* id, const char* name, SkColor4f* c) {
|
|
color_picker_button_field<SkColor4f>(gui, id, name, c, {});
|
|
};
|
|
theme_color_field("fillColor2", "Fill Color 2", &io.theme->fillColor2);
|
|
theme_color_field("backColor1", "Back Color 1", &io.theme->backColor1);
|
|
theme_color_field("backColor2", "Back Color 2", &io.theme->backColor2);
|
|
theme_color_field("frontColor1", "Front Color 1", &io.theme->frontColor1);
|
|
theme_color_field("frontColor2", "Front Color 2", &io.theme->frontColor2);
|
|
theme_color_field("warningColor", "Warning Color", &io.theme->warningColor);
|
|
theme_color_field("errorColor", "Error Color", &io.theme->errorColor);
|
|
//gui.slider_scalar_field("hoverExpandTime", "Hover Expand Time", &io.theme->hoverExpandTime, 0.001f, 1.0f);
|
|
input_scalar_field<uint16_t>(gui, "childGap1", "Gap between child elements", &io.theme->childGap1, 0, 30);
|
|
input_scalar_field<uint16_t>(gui, "padding1", "Window padding", &io.theme->padding1, 0, 30);
|
|
slider_scalar_field<float>(gui, "windowCorners1", "Window corner radius", &io.theme->windowCorners1, 0, 30);
|
|
});
|
|
break;
|
|
}
|
|
case GSETTINGS_KEYBINDS: {
|
|
general_scroll_area("keybind entries", [&] {
|
|
for(unsigned i = 0; i < InputManager::KEY_ASSIGNABLE_COUNT; i++) {
|
|
gui.new_id(i, [&] {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(0),
|
|
.childGap = io.theme->childGap1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
}
|
|
}) {
|
|
text_label(gui, std::string(nlohmann::json(static_cast<InputManager::KeyCodeEnum>(i))));
|
|
auto f = std::find_if(main.input.keyAssignments.begin(), main.input.keyAssignments.end(), [&](auto& p) {
|
|
return p.second == i;
|
|
});
|
|
std::string assignedKeystrokeStr = f != main.input.keyAssignments.end() ? main.input.key_assignment_to_str(f->first) : "";
|
|
text_button(gui, "keybind button", assignedKeystrokeStr, {
|
|
.isSelected = main.keybindWaiting.has_value() && main.keybindWaiting.value() == i,
|
|
.wide = true,
|
|
.onClick = [&, i] {
|
|
main.keybindWaiting = i;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
case GSETTINGS_DEBUG: {
|
|
general_scroll_area("debug settings menu", [&] {
|
|
checkbox_boolean_field(gui, "show performance metrics", "Show metrics", &showPerformance);
|
|
input_scalars_field(gui, "jump transition easing", "Jump easing", &main.conf.jumpTransitionEasing, 4, -10.0f, 10.0f, { .decimalPrecision = 2 });
|
|
input_scalar_field<int>(gui, "image load max threads", "Maximum image loading threads", &ImageResourceDisplay::IMAGE_LOAD_THREAD_COUNT_MAX, 1, 10000);
|
|
text_label_light(gui, "Cache related settings");
|
|
input_scalar_field<size_t>(gui, "cache node resolution", "Cache node resolution", &DrawingProgramCache::CACHE_NODE_RESOLUTION, 256, 8192);
|
|
input_scalar_field<size_t>(gui, "max cache nodes", "Maximum cached nodes", &DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES, 2, 10000);
|
|
size_t cacheVRAMConsumptionInMB = ( DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES // Number of surfaces
|
|
* DrawingProgramCache::CACHE_NODE_RESOLUTION * DrawingProgramCache::CACHE_NODE_RESOLUTION // Number of pixels per cache surface
|
|
* 4) // 4 Channels per pixel (RGBA)
|
|
/ (1024 * 1024); // Bytes -> Megabytes conversion
|
|
text_label_light(gui, "Cache max VRAM consumption (MB): " + std::to_string(cacheVRAMConsumptionInMB));
|
|
input_scalar_field<size_t>(gui, "max components in node", "Maximum components in single node", &DrawingProgramCache::MAXIMUM_COMPONENTS_IN_SINGLE_NODE, 2, 10000);
|
|
input_scalar_field<size_t>(gui, "components to force cache rebuild", "Number of components to force cache rebuild", &DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD, 1, 1000000);
|
|
input_scalar_field<size_t>(gui, "maximum frame time to force cache rebuild", "Maximum frame time to force cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH, 1, 1000000);
|
|
input_scalar_field<size_t>(gui, "minimum time to force cache rebuild", "Minimum time to check cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH, 1, 1000000);
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
text_button_wide("done menu", "Done", [&] {
|
|
main.save_config();
|
|
main.g.load_theme(main.conf.configPath, main.conf.themeCurrentlyLoaded);
|
|
themeData.selectedThemeIndex = std::nullopt;
|
|
main.keybindWaiting = std::nullopt;
|
|
optionsMenuOpen = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void Toolbar::about_menu_inner_gui() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
left_to_right_line_layout(gui, [&]() {
|
|
gui.new_id("Menu Selector", [&] {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(io.theme->padding1),
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
}
|
|
}) {
|
|
gui.clipping_element<ScrollArea>("About Menu Selector Scroll Area", ScrollArea::Options{
|
|
.scrollVertical = true,
|
|
.clipVertical = true,
|
|
.showScrollbarY = true,
|
|
.innerContent = [&](const ScrollArea::InnerContentParameters&) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}
|
|
}
|
|
}) {
|
|
text_button(gui, "infinipaintnoticebutton", "InfiniPaint", {
|
|
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
|
.isSelected = selectedLicense == -1,
|
|
.wide = true,
|
|
.centered = false,
|
|
.onClick = [&] { selectedLicense = -1; }
|
|
});
|
|
}
|
|
text_label_light_centered(gui, "Third Party Components");
|
|
for(int i = 0; i < static_cast<int>(main.conf.thirdPartyLicenses.size()); i++) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) },
|
|
.padding = CLAY_PADDING_ALL(1)
|
|
}
|
|
}) {
|
|
gui.new_id(i, [&] {
|
|
text_button(gui, "noticebutton", main.conf.thirdPartyLicenses[i].first, {
|
|
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
|
.isSelected = selectedLicense == i,
|
|
.wide = true,
|
|
.centered = false,
|
|
.onClick = [&, i] { selectedLicense = i; }
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
gui.clipping_element<ScrollArea>("About Menu Text Scroll Area", ScrollArea::Options{
|
|
.scrollVertical = true,
|
|
.clipVertical = true,
|
|
.showScrollbarY = true,
|
|
.innerContent = [&](const ScrollArea::InnerContentParameters&) {
|
|
text_label_size(gui, (selectedLicense == -1) ? main.conf.ownLicenseText : main.conf.thirdPartyLicenses[selectedLicense].second, 0.8f);
|
|
}
|
|
});
|
|
});
|
|
text_button_wide("done", "Done", [&] {
|
|
optionsMenuOpen = false;
|
|
});
|
|
}
|
|
|
|
void Toolbar::reload_theme_list() {
|
|
themeData.selectedThemeIndex = 0;
|
|
|
|
themeData.themeDirList.clear();
|
|
themeData.themeDirList.emplace_back("Default");
|
|
std::filesystem::path themeDir = main.conf.configPath / "themes";
|
|
if(std::filesystem::exists(themeDir) && std::filesystem::is_directory(themeDir)) {
|
|
for(auto& theme : std::filesystem::recursive_directory_iterator(themeDir)) {
|
|
std::string name = theme.path().stem().string();
|
|
if(name != "Default") {
|
|
themeData.themeDirList.emplace_back(name);
|
|
if(name == main.conf.themeCurrentlyLoaded)
|
|
themeData.selectedThemeIndex = themeData.themeDirList.size() - 1;
|
|
}
|
|
}
|
|
}
|
|
if(!main.g.load_theme(main.conf.configPath, main.conf.themeCurrentlyLoaded))
|
|
themeData.selectedThemeIndex = 0;
|
|
|
|
themeData.openedSaveAsMenu = false;
|
|
}
|
|
|
|
void Toolbar::file_picker_gui_refresh_entries() {
|
|
auto& gui = main.g.gui;
|
|
|
|
filePicker.entries.clear();
|
|
for(;;) {
|
|
try {
|
|
for(const std::filesystem::path& entry : std::filesystem::directory_iterator(main.conf.currentSearchPath))
|
|
filePicker.entries.emplace_back(entry);
|
|
break;
|
|
}
|
|
catch(const std::exception& e) {
|
|
Logger::get().log("INFO", e.what());
|
|
if(main.conf.currentSearchPath == main.homePath) // The home path must exist. If we get errors on the home path, we have a real problem
|
|
throw e;
|
|
main.conf.currentSearchPath = main.homePath;
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> extensionList = split_string_by_token(filePicker.extensionFilters[filePicker.extensionSelected], ";");
|
|
for(std::string& s : extensionList) {
|
|
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
|
|
s.insert(0, ".");
|
|
}
|
|
|
|
std::erase_if(filePicker.entries, [&](const std::filesystem::path& a) {
|
|
if(extensionList.empty())
|
|
return false;
|
|
if(extensionList[0] == ".*")
|
|
return false;
|
|
std::string fExtension;
|
|
if(a.has_extension()) {
|
|
fExtension = a.extension().string();
|
|
std::transform(fExtension.begin(), fExtension.end(), fExtension.begin(), ::tolower);
|
|
}
|
|
return std::filesystem::is_regular_file(a) && (fExtension.empty() || !std::ranges::contains(extensionList, fExtension));
|
|
});
|
|
|
|
std::sort(filePicker.entries.begin(), filePicker.entries.end(), [&](const std::filesystem::path& a, const std::filesystem::path& b) {
|
|
bool aDir = std::filesystem::is_directory(a);
|
|
bool bDir = std::filesystem::is_directory(b);
|
|
const std::string& aStr = a.string();
|
|
const std::string& bStr = b.string();
|
|
if(aDir && !bDir)
|
|
return true;
|
|
if(!aDir && bDir)
|
|
return false;
|
|
return std::lexicographical_compare(aStr.begin(), aStr.end(), bStr.begin(), bStr.end());
|
|
});
|
|
|
|
if(filePicker.entriesScrollArea)
|
|
filePicker.entriesScrollArea->reset_scroll();
|
|
|
|
gui.set_to_layout();
|
|
}
|
|
|
|
void Toolbar::file_picker_gui_done() {
|
|
if(!filePicker.fileName.empty()) {
|
|
std::filesystem::path pathToRet = main.conf.currentSearchPath / filePicker.fileName;
|
|
if(filePicker.isSaving)
|
|
pathToRet = force_extension_on_path(pathToRet, filePicker.extensionFiltersComplete[filePicker.extensionSelected].extensions);
|
|
filePicker.postSelectionFunc(pathToRet, filePicker.extensionFiltersComplete[filePicker.extensionSelected]);
|
|
}
|
|
filePicker.entriesScrollArea = nullptr;
|
|
filePicker.isOpen = false;
|
|
}
|
|
|
|
void Toolbar::file_picker_gui() {
|
|
auto& gui = main.g.gui;
|
|
auto& io = gui.io;
|
|
|
|
center_obstructing_window_gui("file picker gui window", CLAY_SIZING_FIXED(700), CLAY_SIZING_FIXED(500), [&] {
|
|
text_label_centered(gui, filePicker.filePickerWindowName);
|
|
left_to_right_line_layout(gui, [&]() {
|
|
svg_icon_button(gui, "file picker back button", "data/icons/backarrow.svg", {
|
|
.onClick = [&] {
|
|
main.conf.currentSearchPath = main.conf.currentSearchPath.parent_path();
|
|
file_picker_gui_refresh_entries();
|
|
}
|
|
});
|
|
input_path_field(gui, "file picker path", "Path", &main.conf.currentSearchPath, {
|
|
.fileTypeRestriction = std::filesystem::file_type::directory,
|
|
.onEdit = [&] {
|
|
file_picker_gui_refresh_entries();
|
|
}
|
|
});
|
|
});
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
|
|
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = convert_vec4<Clay_Color>(io.theme->backColor2)
|
|
}) {
|
|
constexpr float entryHeight = 25.0f;
|
|
filePicker.entriesScrollArea = scroll_area_many_entries(gui, "file picker entries", {
|
|
.entryHeight = entryHeight,
|
|
.entryCount = filePicker.entries.size(),
|
|
.clipHorizontal = false,
|
|
.elementContent = [&] (size_t i) {
|
|
const std::filesystem::path& entry = filePicker.entries[i];
|
|
bool selectedEntry = filePicker.currentSelectedPath == entry;
|
|
gui.element<LayoutElement>("elem", [&] (LayoutElement*, const Clay_ElementId& lId) {
|
|
CLAY(lId, {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(entryHeight)},
|
|
.childGap = 1,
|
|
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
},
|
|
.backgroundColor = selectedEntry ? convert_vec4<Clay_Color>(io.theme->backColor1) : convert_vec4<Clay_Color>(io.theme->backColor2)
|
|
}) {
|
|
CLAY_AUTO_ID({
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}
|
|
},
|
|
}) {
|
|
if(std::filesystem::is_directory(entry))
|
|
gui.element<SVGIcon>("folder icon", "data/icons/folder.svg", selectedEntry);
|
|
else
|
|
gui.element<SVGIcon>("file icon", "data/icons/file.svg", selectedEntry);
|
|
}
|
|
text_label(gui, entry.filename().string());
|
|
}
|
|
}, LayoutElement::Callbacks {
|
|
.mouseButton = [&, selectedEntry, entry](LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) {
|
|
if(l->mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down) {
|
|
gui.set_post_callback_func([&, button, selectedEntry, entry] {
|
|
if(selectedEntry && button.clicks >= 2) {
|
|
if(std::filesystem::is_directory(entry)) {
|
|
main.conf.currentSearchPath = entry;
|
|
file_picker_gui_refresh_entries();
|
|
}
|
|
else if(std::filesystem::is_regular_file(entry))
|
|
file_picker_gui_done();
|
|
}
|
|
else {
|
|
filePicker.currentSelectedPath = entry;
|
|
if(std::filesystem::is_regular_file(entry))
|
|
filePicker.fileName = entry.filename().string();
|
|
}
|
|
});
|
|
gui.set_to_layout();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
left_to_right_line_layout(gui, [&]() {
|
|
input_text(gui, "filepicker filename", &filePicker.fileName);
|
|
gui.element<DropDown<size_t>>("filepicker select type", &filePicker.extensionSelected, filePicker.extensionFilters, DropdownOptions{
|
|
.onClick = [&] { file_picker_gui_refresh_entries(); }
|
|
});
|
|
});
|
|
left_to_right_line_layout(gui, [&]() {
|
|
text_button_wide("filepicker done", "Done", [&] { file_picker_gui_done(); });
|
|
text_button_wide("filepicker cancel", "Cancel", [&] { filePicker.isOpen = false; });
|
|
});
|
|
});
|
|
}
|