From 6859db54b6c8a75ef54272e5a12c08abf46547ce Mon Sep 17 00:00:00 2001 From: gk646 Date: Sun, 19 May 2024 19:44:54 +0200 Subject: [PATCH] added more constraints check to node creator - cannot use names of any registered nodes - component labels inside a node have to be unique added quick actions - copy, cut, delete fixed more ui things removed unused ui methods added tooltip --- .../cxstructs/include/cxutil/cxstring.h | 26 ++++---- src/raynodes/application/NodeEditor.cpp | 3 +- .../application/context/impl/ContextLogic.cpp | 23 +++++++ .../application/editor/EditorControls.h | 6 +- src/raynodes/application/editor/EditorUI.h | 4 +- .../application/editor/EditorUpdate.h | 2 + src/raynodes/shared/uiutil.h | 50 +++++++++++++++- src/raynodes/ui/elements/ListSearchMenu.cpp | 60 ------------------- src/raynodes/ui/elements/NodeContextMenu.cpp | 39 ++++++++++-- src/raynodes/ui/elements/NodeContextMenu.h | 15 ++++- src/raynodes/ui/elements/SimpleDropDown.cpp | 20 +++---- src/raynodes/ui/elements/ToolTip.cpp | 37 ++++++++++++ .../elements/{ListSearchMenu.h => ToolTip.h} | 18 ++++-- src/raynodes/ui/windows/NodeCreator.cpp | 52 +++++++--------- src/raynodes/ui/windows/NodeCreator.h | 10 ++-- 15 files changed, 226 insertions(+), 139 deletions(-) delete mode 100644 src/raynodes/ui/elements/ListSearchMenu.cpp create mode 100644 src/raynodes/ui/elements/ToolTip.cpp rename src/raynodes/ui/elements/{ListSearchMenu.h => ToolTip.h} (77%) diff --git a/src/external/cxstructs/include/cxutil/cxstring.h b/src/external/cxstructs/include/cxutil/cxstring.h index 11cdafd..261b7d0 100644 --- a/src/external/cxstructs/include/cxutil/cxstring.h +++ b/src/external/cxstructs/include/cxutil/cxstring.h @@ -190,13 +190,17 @@ inline double str_parse_double(const char* str) { return negative ? -result : result; } -// Returns the levenshtein distance for the two given strings - stops when either string ends -// This doesnt penalize long strings +// Returns the Levenshtein distance for the two given strings +// This penalizes differences in length // Failure: returns -1 template -int str_sort_levenshtein_prefix(const char* s1, const char* s2) { - const size_t len1 = str_len(s1), len2 = str_len(s2); - const size_t limit = len1 < len2 ? len1 : len2; +int str_sort_levenshtein(const char* s1, const char* s2) { + if (s1 == nullptr || s2 == nullptr) { + return -1; // Error: string length exceeds maximum allowed length + } + + const size_t len1 = strlen(s1); + const size_t len2 = strlen(s2); if (len1 > MAX_LEN || len2 > MAX_LEN) { return -1; // Error: string length exceeds maximum allowed length @@ -204,23 +208,23 @@ int str_sort_levenshtein_prefix(const char* s1, const char* s2) { unsigned int d[MAX_LEN + 1][MAX_LEN + 1] = {0}; - for (unsigned int i = 1; i <= limit; ++i) + for (unsigned int i = 0; i <= len1; ++i) d[i][0] = i; - for (unsigned int i = 1; i <= len2; ++i) - d[0][i] = i; + for (unsigned int j = 0; j <= len2; ++j) + d[0][j] = j; - for (unsigned int i = 1; i <= limit; ++i) { + for (unsigned int i = 1; i <= len1; ++i) { for (unsigned int j = 1; j <= len2; ++j) { unsigned int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1; unsigned int above = d[i - 1][j] + 1; unsigned int left = d[i][j - 1] + 1; unsigned int diag = d[i - 1][j - 1] + cost; - d[i][j] = above < left ? (above < diag ? above : diag) : (left < diag ? left : diag); + d[i][j] = above < left ? above : left < diag ? left : diag; } } - return d[limit][len2]; + return d[len1][len2]; } // string hash function constexpr auto str_hash_fnv1a_32(char const* s) noexcept -> uint32_t { diff --git a/src/raynodes/application/NodeEditor.cpp b/src/raynodes/application/NodeEditor.cpp index 308b3e7..fc90197 100644 --- a/src/raynodes/application/NodeEditor.cpp +++ b/src/raynodes/application/NodeEditor.cpp @@ -26,6 +26,7 @@ #include "shared/rayutils.h" #include "ui/Window.h" +#include "ui/elements/ToolTip.h" #include "application/elements/Action.h" #include "application/editor/EditorControls.h" @@ -69,6 +70,7 @@ void DrawBackGround(EditorContext& ec) { Editor::DrawSideBar(ec); Editor::DrawWindows(ec); Editor::DrawUnsavedChanges(ec); + ToolTip::Draw(ec); } EndTextureMode(); Editor::UpdateTick(ec); // Updates all nodes @@ -99,7 +101,6 @@ void DrawForeGround(EditorContext& ec) { int NodeEditor::run() { const auto& camera = context.display.camera; - // Double loop to catch the window close event from raylib // Would require native handling and overriding the window function otherwise while (!context.core.closeApplication) { diff --git a/src/raynodes/application/context/impl/ContextLogic.cpp b/src/raynodes/application/context/impl/ContextLogic.cpp index f88fddc..54c8ee5 100644 --- a/src/raynodes/application/context/impl/ContextLogic.cpp +++ b/src/raynodes/application/context/impl/ContextLogic.cpp @@ -117,4 +117,27 @@ void Logic::registerNodeContextActions(EditorContext& ec) { else ec.core.addEditorAction(ec, action); }, 175); + + ec.ui.nodeContextMenu.registerQickAction( + "Cut (Ctrl+X)", + [](EditorContext& ec, Node& node) { + ec.core.selectedNodes.insert({node.uID, &node}); + ec.core.cut(ec); + }, + 17); + ec.ui.nodeContextMenu.registerQickAction( + "Copy (Ctrl+C)", + [](EditorContext& ec, Node& node) { + ec.core.selectedNodes.insert({node.uID, &node}); + ec.core.copy(ec); + }, + 18); + + ec.ui.nodeContextMenu.registerQickAction( + "Delete (Delete)", + [](EditorContext& ec, Node& node) { + ec.core.selectedNodes.insert({node.uID, &node}); + ec.core.erase(ec); + }, + 143); } \ No newline at end of file diff --git a/src/raynodes/application/editor/EditorControls.h b/src/raynodes/application/editor/EditorControls.h index 0f92f76..c55e7a5 100644 --- a/src/raynodes/application/editor/EditorControls.h +++ b/src/raynodes/application/editor/EditorControls.h @@ -61,8 +61,10 @@ inline void PollControls(EditorContext& ec) { if (ec.input.isMouseButtonReleased(MOUSE_BUTTON_RIGHT)) { if (DistEuclidean(contextMenuPos, mouse) <= 5.0F) { if (ec.logic.isAnyPinHovered) ec.logic.showPinContextMenu = true; - else if (ec.logic.isAnyNodeHovered) ec.logic.showNodeContextMenu = true; - else ec.logic.showCanvasContextMenu = true; + else if (ec.logic.isAnyNodeHovered) { + ec.logic.showNodeContextMenu = true; + ec.core.selectedNodes.insert({ec.logic.hoveredNode->uID, ec.logic.hoveredNode}); + } else ec.logic.showCanvasContextMenu = true; ec.logic.isDraggingScreen = false; ec.logic.isSelecting = false; return; diff --git a/src/raynodes/application/editor/EditorUI.h b/src/raynodes/application/editor/EditorUI.h index ab11e31..6a24ac3 100644 --- a/src/raynodes/application/editor/EditorUI.h +++ b/src/raynodes/application/editor/EditorUI.h @@ -230,8 +230,8 @@ inline void DrawUnsavedChanges(EditorContext& ec) { if (UI::DrawButton(ec, windowRect, "#072#Cancel")) { ec.ui.showUnsavedChanges = false; } } inline void DrawSideBar(EditorContext& ec) { - Rectangle button = {5, 250, 50, 25}; - if (GuiButton(ec.display.getFullyScaled(button), "Create custom Node")) { + Rectangle button = {5, 250, 100, 25}; + if (GuiButton(ec.display.getFullyScaled(button), "Custom Nodes")) { ec.ui.getWindow(NODE_CREATOR)->toggleWindow(ec); } } diff --git a/src/raynodes/application/editor/EditorUpdate.h b/src/raynodes/application/editor/EditorUpdate.h index 6f2f3e1..656b4bb 100644 --- a/src/raynodes/application/editor/EditorUpdate.h +++ b/src/raynodes/application/editor/EditorUpdate.h @@ -96,6 +96,8 @@ inline void StartUpdateTick(EditorContext& ec) { const auto scaleY = ec.display.screenSize.y / 1080.0F; const auto fontSize = fmaxf(13.0F, std::round(13.0F * scaleY)); GuiSetStyle(DEFAULT, TEXT_SIZE, static_cast(fontSize)); + + ToolTip::Set(nullptr); } } // namespace Editor diff --git a/src/raynodes/shared/uiutil.h b/src/raynodes/shared/uiutil.h index e7def0d..f28f474 100644 --- a/src/raynodes/shared/uiutil.h +++ b/src/raynodes/shared/uiutil.h @@ -23,10 +23,56 @@ #include "shared/fwd.h" +#include #include +#include // Current limit of 150 -> stacked based so really doesnt matter -using SortVector = cxstructs::StackVector; +using SortVector = cxstructs::StackVector; +// Filters the strings based on prefix and then sorts after levenshtein distance +template +void StringFilterMap(auto& map, const std::string& s, cxstructs::StackVector& vec) { + vec.clear(); -#endif //UIUTIL_H \ No newline at end of file + if constexpr (std::is_same_v) { volatile int s = 5; } + + const char* searchCStr = s.c_str(); + const int searchSize = s.size(); + + for (auto& pair : map) { + const auto* name = pair.first; // Sort after keys + if (searchSize == 0 || strncmp(name, searchCStr, searchSize) == 0) { + if constexpr (std::is_same_v) { + vec.push_back(&pair.second); + } else if constexpr (std::is_same_v) { + vec.push_back(pair.first); + } + } + } + + size_t size = vec.size(); + if (size == 0) return; + + for (uint8_t i = 0; i < size - 1; ++i) { + uint8_t minIndex = i; + for (uint8_t j = i + 1; j < size; ++j) { + const char* str1 = nullptr; + const char* str2 = nullptr; + if constexpr (std::is_same_v) { + str1 = vec[j]->nTemplate.label; + str2 = vec[minIndex]->nTemplate.label; + } else if constexpr (std::is_same_v) { + str1 = vec[j]; + str2 = vec[minIndex]; + } + if (cxstructs::str_sort_levenshtein(str1, searchCStr) + < cxstructs::str_sort_levenshtein(str2, searchCStr)) { + minIndex = j; + } + } + if (minIndex != i) { std::swap(vec[i], vec[minIndex]); } + } +} + +#endif //UIUTIL_H \ No newline at end of file diff --git a/src/raynodes/ui/elements/ListSearchMenu.cpp b/src/raynodes/ui/elements/ListSearchMenu.cpp deleted file mode 100644 index 4762509..0000000 --- a/src/raynodes/ui/elements/ListSearchMenu.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2024 gk646 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#include -#include - -#include -#include -#include "ListSearchMenu.h" - -namespace { -void stringSort(const auto& map, const std::string& searchText, SortVector& sortVec) { - sortVec.clear(); - - for (auto& value : map | std::ranges::views::keys) { - sortVec.push_back(value); - } - - const char* searchCStr = searchText.c_str(); - size_t size = sortVec.size(); - - for (size_t i = 0; i < size - 1; ++i) { - size_t minIndex = i; - for (size_t j = i + 1; j < size; ++j) { - if (cxstructs::str_sort_levenshtein_prefix(sortVec[j], searchCStr) - < cxstructs::str_sort_levenshtein_prefix(sortVec[minIndex], searchCStr)) { - minIndex = j; - } - } - if (minIndex != i) { std::swap(sortVec[i], sortVec[minIndex]); } - } -} -} // namespace - -SortVector ListSearchMenu::GetSortedVector(const auto& mapKeys, const std::string& search) { - SortVector vector; - stringSort(mapKeys, search, vector); - return vector; -} - -template SortVector ListSearchMenu::GetSortedVector( - const std::unordered_map& map, - const std::string&); \ No newline at end of file diff --git a/src/raynodes/ui/elements/NodeContextMenu.cpp b/src/raynodes/ui/elements/NodeContextMenu.cpp index 11f67ff..d7a01b8 100644 --- a/src/raynodes/ui/elements/NodeContextMenu.cpp +++ b/src/raynodes/ui/elements/NodeContextMenu.cpp @@ -22,6 +22,8 @@ #include #include "NodeContextMenu.h" + +#include "ToolTip.h" #include "application/EditorContext.h" #include "shared/rayutils.h" @@ -32,24 +34,49 @@ void NodeContextMenu::draw(EditorContext& ec, const Vector2& pos) { const auto [paddingX, paddingY, w, h] = Rectangle{22, 4, 200, fs + 4}; const auto entryHeight = fs + paddingY; - const auto pressed = ec.input.isMouseButtonPressed(MOUSE_BUTTON_LEFT); + const auto pressed = + ec.input.isMouseButtonDown(MOUSE_BUTTON_LEFT) || ec.input.isMouseButtonPressed(MOUSE_BUTTON_LEFT); const auto released = ec.input.isMouseButtonReleased(MOUSE_BUTTON_LEFT); if (pressed) ec.input.consumeMouse(); const auto background = ColorAlpha(UI::COLORS[UI_DARK], 0.97F); const auto highlight = ColorAlpha(UI::COLORS[UI_MEDIUM], 0.97F); - Rectangle entry = {pos.x, pos.y, w, h * 2}; // Draw First Row - Quick actions - DrawRectangleRec(entry, background); + DrawRectangleRec({pos.x, pos.y, w, 32}, background); + Rectangle entry = {pos.x, pos.y, 32, 32}; + entry.x += 2; + GuiSetIconScale(2); + for (const auto& [action, name, icon] : quickActions) { + const auto hovered = CheckCollisionPointRec(mouse, entry); + const auto back = pressed ? UI::Darken(UI::COLORS[UI_MEDIUM], 25) : UI::COLORS[UI_MEDIUM]; + if (hovered) { + BeginBlendMode(BLEND_ADDITIVE); + DrawRectangleRounded(entry, 0.3F, 30, ColorAlpha(back, 0.4F)); + EndBlendMode(); + ToolTip::Set(name); + } + GuiDrawIcon(icon, entry.x, entry.y, 2, GRAY); + if (hovered && released && ec.logic.hoveredNode) { + action(ec, *ec.logic.hoveredNode); + ec.logic.showNodeContextMenu = false; + } + entry.x += 34; + } + + GuiSetIconScale(1); + + entry.x = pos.x; + entry.y += entry.height; + entry.width = w; entry.height = h; - entry.y += h * 2; + int i = 0; for (const auto& [action, name, icon] : actions) { if (CheckCollisionPointRec(mouse, entry)) { DrawRectangleRounded(entry, 0.1F, 30, highlight); - if (released && ec.logic.hoveredNode) { + if (released && ec.logic.hoveredNode != nullptr) { action(ec, *ec.logic.hoveredNode); ec.logic.showNodeContextMenu = false; } @@ -64,7 +91,7 @@ void NodeContextMenu::draw(EditorContext& ec, const Vector2& pos) { i++; } - const auto totalHeight = static_cast(actions.size()) * entryHeight + h * 2; + const auto totalHeight = static_cast(actions.size()) * entryHeight + h * 1.5F; if (!CheckExtendedRec(mouse, {entry.x, entry.y - totalHeight, entry.width, totalHeight}, UI::CONTEXT_MENU_THRESHOLD)) { ec.logic.showNodeContextMenu = false; diff --git a/src/raynodes/ui/elements/NodeContextMenu.h b/src/raynodes/ui/elements/NodeContextMenu.h index dee7b20..30f8d17 100644 --- a/src/raynodes/ui/elements/NodeContextMenu.h +++ b/src/raynodes/ui/elements/NodeContextMenu.h @@ -31,8 +31,9 @@ struct ContextActionInfo { }; struct NodeContextMenu { - static constexpr int SIZE = 1; + static constexpr int SIZE = 4; cxstructs::StackVector actions; + cxstructs::StackVector quickActions; void draw(EditorContext& ec, const Vector2& pos); void registerAction(const char* name, NodeContextAction action, uint8_t iconID) { @@ -44,6 +45,18 @@ struct NodeContextMenu { } actions.push_back({action, name, iconID}); } + void registerQickAction(const char* name, NodeContextAction action, uint8_t iconID) { + for (const auto& n : quickActions) { + if (strcmp(n.name, name) == 0) { + fprintf(stderr, "QuickAction with this name already exists"); + return; + } + } + quickActions.push_back({action, name, iconID}); + } + + private: + void drawQuickActions(); }; #endif //ACTIONMENU_H \ No newline at end of file diff --git a/src/raynodes/ui/elements/SimpleDropDown.cpp b/src/raynodes/ui/elements/SimpleDropDown.cpp index 5a0e1f0..be68d07 100644 --- a/src/raynodes/ui/elements/SimpleDropDown.cpp +++ b/src/raynodes/ui/elements/SimpleDropDown.cpp @@ -86,7 +86,7 @@ void SimpleDropDown::DrawSearchDropdown(EditorContext& ec, Vector2 pos, TextFiel const float entryHeight = fs + padding; const auto mouse = ec.logic.mouse; - float maxWidth = 0; + float maxWidth = 150; for (const auto item : items) { const float itemWidth = MeasureTextEx(font, item, fs, 0.5F).x + padding * 2; // Additional padding if (itemWidth > maxWidth) maxWidth = itemWidth; @@ -101,28 +101,27 @@ void SimpleDropDown::DrawSearchDropdown(EditorContext& ec, Vector2 pos, TextFiel // Draw the text field bool pressed = ec.input.isMouseButtonPressed(MOUSE_BUTTON_LEFT); - const auto [x, y] = ec.display.getFullyScaled(Vector2{150, 20}); search.bounds = dropdownRect; - search.bounds.width = x; - search.bounds.height = y; + search.bounds.width = maxWidth; + search.bounds.height = 20; search.draw("Type..."); search.update(ec, ec.logic.mouse); - float totalHeight = items.size() * entryHeight; - Rectangle scrollPanelRec = {pos.x, pos.y + entryHeight, maxWidth, std::min(200.0f, totalHeight)}; + const float totalHeight = items.size() * entryHeight; + const Rectangle scrollPanelRec = {pos.x, pos.y + entryHeight, maxWidth, std::min(200.0f, totalHeight)}; if (open) { - Rectangle contentRec = {0, 0, maxWidth - 20, totalHeight}; // -20 for scrollbar width + const Rectangle contentRec = {0, 0, maxWidth - 20, totalHeight}; // -20 for scrollbar width static Vector2 scroll = {0, 0}; Rectangle view; GuiScrollPanel(scrollPanelRec, nullptr, contentRec, &scroll, &view); - BeginScissorMode(view.x, view.y, view.width, view.height); + BeginScissorMode(view.x, view.y, view.width + 7, view.height); for (int i = 0; i < items.size(); ++i) { - Rectangle itemRec = {pos.x, pos.y + entryHeight + i * entryHeight + scroll.y, maxWidth - 20, entryHeight}; + Rectangle itemRec = {pos.x, pos.y + entryHeight + i * entryHeight + scroll.y, maxWidth - 13, entryHeight}; bool isHovered = CheckCollisionPointRec(mouse, itemRec); if (pressed) ec.input.consumeMouse(); if (isHovered && pressed && CheckCollisionPointRec(mouse, scrollPanelRec)) { @@ -131,7 +130,8 @@ void SimpleDropDown::DrawSearchDropdown(EditorContext& ec, Vector2 pos, TextFiel EndScissorMode(); return; } - DrawRectangleRec(itemRec, isHovered ? UI::COLORS[UI_MEDIUM] : UI::COLORS[UI_DARK]); + DrawRectangleRec(itemRec, + isHovered ? UI::Lighten(UI::COLORS[UI_MEDIUM], 15) : UI::Lighten(UI::COLORS[UI_DARK])); DrawTextEx(font, items[i], {itemRec.x + padding, itemRec.y + padding / 2.0F}, fs, 1.0F, UI::COLORS[UI_LIGHT]); } EndScissorMode(); diff --git a/src/raynodes/ui/elements/ToolTip.cpp b/src/raynodes/ui/elements/ToolTip.cpp new file mode 100644 index 0000000..b864b68 --- /dev/null +++ b/src/raynodes/ui/elements/ToolTip.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2024 gk646 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "ToolTip.h" + +#include "application/EditorContext.h" + +void ToolTip::Draw(EditorContext& ec) { + if (toolTip) showCounter++; + else showCounter = 0; + if (!toolTip || showCounter < showDely) return; + const auto mouse = ec.logic.mouse; + const auto& f = ec.display.editorFont; + const auto fs = ec.display.fontSize; + + const auto width = MeasureTextEx(f, toolTip, fs, 0.5F).x; + + DrawRectangleRounded({mouse.x, mouse.y + 20, width, 20}, 0.5F, 25, UI::COLORS[UI_MEDIUM]); + DrawTextEx(f, toolTip, {mouse.x, mouse.y + 21}, fs, 0.5F, UI::COLORS[UI_LIGHT]); +} \ No newline at end of file diff --git a/src/raynodes/ui/elements/ListSearchMenu.h b/src/raynodes/ui/elements/ToolTip.h similarity index 77% rename from src/raynodes/ui/elements/ListSearchMenu.h rename to src/raynodes/ui/elements/ToolTip.h index da8849e..6417c02 100644 --- a/src/raynodes/ui/elements/ListSearchMenu.h +++ b/src/raynodes/ui/elements/ToolTip.h @@ -18,13 +18,19 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#ifndef LISTSEARCHMENU_H -#define LISTSEARCHMENU_H +#ifndef TOOLTIP_H +#define TOOLTIP_H -#include "shared/uiutil.h" +#include "shared/fwd.h" -struct EXPORT ListSearchMenu { - static SortVector GetSortedVector(const auto& mapKeys, const std::string& search); +struct EXPORT ToolTip { + inline static const char* toolTip = nullptr; + inline static int showCounter = 0; + static constexpr int showDely = 60; + static void Draw(EditorContext& ec); + static void Set(const char* txt) { + toolTip = txt; + } }; -#endif //LISTSEARCHMENU_H \ No newline at end of file +#endif //TOOLTIP_H \ No newline at end of file diff --git a/src/raynodes/ui/windows/NodeCreator.cpp b/src/raynodes/ui/windows/NodeCreator.cpp index ad918c2..cbcd7e0 100644 --- a/src/raynodes/ui/windows/NodeCreator.cpp +++ b/src/raynodes/ui/windows/NodeCreator.cpp @@ -25,7 +25,6 @@ #include "raygui.h" #include "application/EditorContext.h" #include "ui/elements/SimpleDropDown.h" -#include "ui/elements/ListSearchMenu.h" #include "ui/elements/PopupMenu.h" NodeCreator::NodeCreator(const Rectangle& r, const char* headerText) : Window(r, NODE_CREATOR, headerText) { @@ -35,6 +34,10 @@ NodeCreator::NodeCreator(const Rectangle& r, const char* headerText) : Window(r, newCompName.growAutomatic = false; } +void NodeCreator::onOpen(EditorContext& ec) { + StringFilterMap(ec.templates.userDefinedNodes, searchField.buffer, filteredNodes); +} + void NodeCreator::drawContent(EditorContext& ec, const Rectangle& body) { constexpr auto listWidth = 200.0F; // Draw search field @@ -49,7 +52,7 @@ void NodeCreator::drawContent(EditorContext& ec, const Rectangle& body) { if (UI::DrawButton(ec, listBounds, "#227#Add")) { showNamePopup = true; } NodeTemplate* activeTemplate = nullptr; - if (searchField.hasUpdate()) { stringSort(ec.templates.userDefinedNodes, searchField.buffer, sortBuffer); } + if (searchField.hasUpdate()) { StringFilterMap(ec.templates.userDefinedNodes, searchField.buffer, filteredNodes); } Rectangle entry = {body.x, body.y + UI::PAD, listWidth, 45}; drawCreatedNodeList(ec, entry, activeTemplate); @@ -76,7 +79,7 @@ void NodeCreator::drawCreatedNodeList(EditorContext& ec, Rectangle& entry, NodeT constexpr float entryHeight = 45.0F; const Vector2 mouse = ec.logic.mouse; - const float totalHeight = static_cast(sortBuffer.size()) * entryHeight; + const float totalHeight = static_cast(filteredNodes.size()) * entryHeight; const auto contentRec = ec.display.getFullyScaled({0, 0, entry.width - 15, totalHeight}); static Vector2 scroll = {0, 0}; Rectangle view; @@ -89,7 +92,7 @@ void NodeCreator::drawCreatedNodeList(EditorContext& ec, Rectangle& entry, NodeT entry.y += scroll.y; entry.width -= 15; int i = 0; - for (const auto nInfo : sortBuffer) { + for (const auto nInfo : filteredNodes) { const bool selected = i == activeEntry; const Rectangle entryBounds = ec.display.getFullyScaled(entry); @@ -130,7 +133,7 @@ void NodeCreator::drawCreatedNodeList(EditorContext& ec, Rectangle& entry, NodeT } ec.templates.userDefinedNodes.erase(nInfo->nTemplate.label); delete eraseInfo.nTemplate.label; - stringSort(ec.templates.userDefinedNodes, searchField.buffer, sortBuffer); + StringFilterMap(ec.templates.userDefinedNodes, searchField.buffer, filteredNodes); } } @@ -165,6 +168,10 @@ void NodeCreator::drawNodeCreateSandbox(EditorContext& ec, Rectangle space, Node const int len = cxstructs::str_len(str); if (len > PLG_MAX_NAME_LEN) return "Name is too long!"; if (len == 0) return "Name is too short"; + const auto window = ec.ui.getWindow(NODE_CREATOR); + for (const auto comp : window->activeNode->components) { + if (strcmp(comp->label, window->newCompName.buffer.c_str()) == 0) return "Name already exists"; + } return nullptr; }; @@ -172,8 +179,9 @@ void NodeCreator::drawNodeCreateSandbox(EditorContext& ec, Rectangle space, Node auto window = ec.ui.getWindow(NODE_CREATOR); auto& search = window->newCompID; const auto pos = Vector2{r.x + r.width / 2.0F, r.y + r.height / 2.0F}; - const auto items = ListSearchMenu::GetSortedVector(ec.templates.componentFactory, search.buffer); - SimpleDropDown::DrawSearchDropdown(ec, pos, search, items); + SortVector vec; + StringFilterMap(ec.templates.componentFactory,search.buffer,vec); + SimpleDropDown::DrawSearchDropdown(ec, pos, search, vec); for (const auto name : ec.templates.componentFactory | std::ranges::views::keys) { if (strcmp(name, search.buffer.c_str()) == 0) return true; } @@ -221,7 +229,10 @@ bool NodeCreator::drawCreatePopup(EditorContext& ec, const Rectangle& body) { const int len = cxstructs::str_len(str); if (len > PLG_MAX_NAME_LEN) return "Name is too long!"; if (len == 0) return "Name is too short"; - for (const auto& name : ec.templates.userDefinedNodes | std::ranges::views::keys) { + for (const auto name : ec.templates.userDefinedNodes | std::ranges::views::keys) { + if (strcmp(name, str) == 0) return "Name already exists!"; + } + for (const auto name : ec.templates.registeredNodes | std::ranges::views::keys) { if (strcmp(name, str) == 0) return "Name already exists!"; } return nullptr; @@ -244,33 +255,10 @@ bool NodeCreator::drawCreatePopup(EditorContext& ec, const Rectangle& body) { newNodeName.buffer.clear(); activeEntry = 0; showNamePopup = false; - stringSort(ec.templates.userDefinedNodes, searchField.buffer, sortBuffer); + StringFilterMap(ec.templates.userDefinedNodes, searchField.buffer, filteredNodes); ec.ui.canvasContextMenu.addNode(UI::USER_CATEGORY, nTemplate.label); setNode(ec, nTemplate); } } return false; -} - -void NodeCreator::stringSort(auto& userCreatedTemplates, const std::string& searchText, auto& sortedNodes) { - sortedNodes.clear(); - - for (auto& value : userCreatedTemplates | std::ranges::views::values) { - sortedNodes.push_back(&value); - } - - const char* searchCStr = searchText.c_str(); - const int size = sortedNodes.size(); - if (size == 0) return; - for (uint8_t i = 0; i < size - 1; ++i) { - uint8_t minIndex = i; - for (uint8_t j = i + 1; j < size; ++j) { - if (cxstructs::str_sort_levenshtein_prefix(sortedNodes[j]->nTemplate.label, searchCStr) - < cxstructs::str_sort_levenshtein_prefix(sortedNodes[minIndex]->nTemplate.label, - searchCStr)) { - minIndex = j; - } - } - if (minIndex != i) { std::swap(sortedNodes[i], sortedNodes[minIndex]); } - } } \ No newline at end of file diff --git a/src/raynodes/ui/windows/NodeCreator.h b/src/raynodes/ui/windows/NodeCreator.h index fc4a950..85eb63c 100644 --- a/src/raynodes/ui/windows/NodeCreator.h +++ b/src/raynodes/ui/windows/NodeCreator.h @@ -29,7 +29,6 @@ #include "ui/elements/TextField.h" struct NodeCreator final : Window { - cxstructs::StackVector sortBuffer; TextField searchField{150, 18, SINGLE_LINE}; TextField newCompID{150, 18, SINGLE_LINE}; TextField newNodeName{150, 18, SINGLE_LINE}; @@ -41,26 +40,25 @@ struct NodeCreator final : Window { bool showComponentSearch = false; bool showRenameField = false; + cxstructs::StackVector filteredNodes; + NodeCreator(const Rectangle& r, const char* headerText); bool drawCreatePopup(EditorContext& ec, const Rectangle& body); void drawContent(EditorContext& ec, const Rectangle& body) override; - void onClose(EditorContext& ec) override { + void onClose(EditorContext& /**/) override { showNamePopup = false; searchField.isFocused = false; newCompID.isFocused = false; newNodeName.isFocused = false; showComponentSearch = false; } - void onOpen(EditorContext& ec) override { - stringSort(ec.templates.userDefinedNodes, searchField.buffer, sortBuffer); - } + void onOpen(EditorContext& ec) override; private: void setNode(EditorContext& ec, const NodeTemplate& nTemplate); void drawSearchField(EditorContext& ec, const Rectangle& body, float width); void drawCreatedNodeList(EditorContext& ec, Rectangle& entry, NodeTemplate*& activeTemplate); void drawNodeCreateSandbox(EditorContext& ec, Rectangle space, NodeTemplate* nTemplate); - static void stringSort(auto& userCreatedTemplates, const std::string& searchText, auto& sortedNodes); }; #endif //NODECREATOR_H \ No newline at end of file