diff --git a/CMakeLists.txt b/CMakeLists.txt index 806f6c6..e0a3a9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,10 +42,11 @@ include(cmake/CPM.cmake) CPMAddPackage("gh:InFlowStructure/flow-core#7253c7d") CPMAddPackage("gh:btzy/nativefiledialog-extended#29e3bcb") +include(FetchContent) FetchContent_Declare( imgui GIT_REPOSITORY https://github.com/pthom/imgui.git - GIT_TAG imgui_bundle + GIT_TAG bundle_20250323 ) FetchContent_Declare( imgui_node_editor @@ -66,7 +67,6 @@ endif() CPMAddPackage("gh:pthom/hello_imgui#c985031") -include(FetchContent) include(cmake/add_libs.cmake) include(${hello_imgui_SOURCE_DIR}/hello_imgui_cmake/msvc/msvc_target_group.cmake) @@ -104,10 +104,12 @@ add_library(flow-ui SHARED src/Core.cpp src/Editor.cpp src/FileExplorer.cpp + src/CommandManager.cpp src/Style.cpp src/Texture.cpp src/ViewFactory.cpp src/Window.cpp + src/WindowManager.cpp # View source files src/views/ConnectionView.cpp @@ -129,7 +131,7 @@ add_library(flow-ui SHARED src/widgets/Text.cpp # Utility files - src/utilities/Builders.cpp + src/utilities/NodeBuilder.cpp src/utilities/Widgets.cpp ${flow-ui_HEADERS} diff --git a/include/flow/ui/CommandManager.hpp b/include/flow/ui/CommandManager.hpp new file mode 100644 index 0000000..be3badd --- /dev/null +++ b/include/flow/ui/CommandManager.hpp @@ -0,0 +1,25 @@ +// Copyright (c) 2024, Cisco Systems, Inc. +// All rights reserved. + +#pragma once + +#include "Core.hpp" + +#include + +#include + +FLOW_UI_NAMESPACE_BEGIN + +class CommandManager +{ + public: + void Handle(); + + void AddCommand(int key_chord, Event<>&& event); + + private: + std::map> _input_events; +}; + +FLOW_UI_NAMESPACE_END \ No newline at end of file diff --git a/include/flow/ui/Config.hpp b/include/flow/ui/Config.hpp index f02d30e..77af382 100644 --- a/include/flow/ui/Config.hpp +++ b/include/flow/ui/Config.hpp @@ -9,7 +9,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN class Font; FLOW_UI_NAMESPACE_END @@ -22,7 +22,7 @@ struct std::default_delete void operator()(flow::ui::Font*) const {} }; -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Flags for choosing dockspace splitting direction. diff --git a/include/flow/ui/Core.hpp b/include/flow/ui/Core.hpp index e97abfb..eab056c 100644 --- a/include/flow/ui/Core.hpp +++ b/include/flow/ui/Core.hpp @@ -8,13 +8,13 @@ #include // clang-format off -#define FLOW_UI_NAMESPACE_START namespace flow::ui { -#define FLOW_UI_SUBNAMESPACE_START(nested) namespace flow::ui { namespace nested { +#define FLOW_UI_NAMESPACE_BEGIN namespace flow::ui { +#define FLOW_UI_SUBNAMESPACE_BEGIN(nested) namespace flow::ui { namespace nested { #define FLOW_UI_NAMESPACE_END } #define FLOW_UI_SUBNAMESPACE_END } } // clang-format on -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN class EditorContext; @@ -26,7 +26,7 @@ struct std::default_delete void operator()(flow::ui::EditorContext*) const {} }; -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Get the current context for the graph editor. diff --git a/include/flow/ui/Editor.hpp b/include/flow/ui/Editor.hpp index c41995f..738a854 100644 --- a/include/flow/ui/Editor.hpp +++ b/include/flow/ui/Editor.hpp @@ -3,14 +3,11 @@ #pragma once +#include "CommandManager.hpp" #include "Config.hpp" -#include "FileExplorer.hpp" -#include "Style.hpp" #include "ViewFactory.hpp" #include "Window.hpp" -#include "views/ConnectionView.hpp" -#include "views/NodeView.hpp" -#include "views/PortView.hpp" +#include "WindowManager.hpp" #include "windows/GraphWindow.hpp" #include @@ -22,7 +19,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Main application class the holds and runs the graphs and other windows. @@ -41,28 +38,6 @@ class Editor */ void Run(); - /** - * @brief Add a new custom window to the editor. - * - * @param new_window The new window to add to the editor. - * @param dockspace The default dockspace to attach the window to. - * @param show Flag to show or hide the window by default. - */ - void AddWindow(std::shared_ptr new_window, const std::string& dockspace, bool show = true); - - /** - * @brief Adds a new dockspace that windows can be docked to. - * - * @param name The name of the dockspace. - * @param initial_dockspace_name The dockspace to split off from. - * @param ratio How much of the original dockspace should be split off for the new dockspace. - * @param direction The direction for the new split. - * - * @note Should be called before adding windows. - */ - void AddDockspace(std::string name, std::string initial_dockspace_name, float ratio, - DockspaceSplitDirection direction); - /** * @brief Get a reference to the shared environment. * @returns THe shared ptr to the Env. @@ -75,22 +50,6 @@ class Editor */ const std::shared_ptr& GetFactory() const noexcept { return _factory; } - /** - * @brief Get a reference to the current editor context. - * @returns The pointer to the current editor context. - */ - void* GetContext() const noexcept; - - /** - * @brief Event that is run to load custom fonts for the editor. - */ - Event LoadFonts = [](auto&) {}; - - /** - * @brief Event that is run to setup a custom appearance of the editor. - */ - Event SetupStyle = [](auto&) {}; - /** * @brief Event dispatcher that is run every time a new graph is marked as the active graph. */ @@ -98,24 +57,28 @@ class Editor protected: void Init(const std::string& initial_file); + void Teardown(); - void HandleInput(); + void SetupParams(const std::string& initial_file); - void DrawMainMenuBar(); + void RegisterInputs(); + + void RegisterNodes(); + + void RegisterInputFieldTypes(); + + const std::shared_ptr& CreateFlow(const std::string& name = ""); - std::shared_ptr& CreateFlow(std::string name); void LoadFlow(const std::filesystem::path& file = ""); + void SaveFlow(); private: std::shared_ptr _factory = std::make_shared(); std::shared_ptr _env = Env::Create(_factory); - - std::vector> _windows; - std::unordered_map> _graph_windows; - EventDispatcher<> OnGraphWindowAdded; - EventDispatcher<> OnGraphWindowRemoved; + std::unique_ptr _window_manager; + std::unique_ptr _input_manager; }; FLOW_UI_NAMESPACE_END diff --git a/include/flow/ui/FileExplorer.hpp b/include/flow/ui/FileExplorer.hpp index efe96da..7c82e28 100644 --- a/include/flow/ui/FileExplorer.hpp +++ b/include/flow/ui/FileExplorer.hpp @@ -8,7 +8,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Native file dialog. diff --git a/include/flow/ui/Style.hpp b/include/flow/ui/Style.hpp index 5aa8f06..c8b8c0f 100644 --- a/include/flow/ui/Style.hpp +++ b/include/flow/ui/Style.hpp @@ -13,7 +13,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Enumeration of different types of Port Icons. @@ -50,98 +50,6 @@ struct Colour */ struct Style { - public: - /** - * @brief Colour flags for windows and widgets. - */ - enum class BaseColour : std::uint8_t - { - Border, - BorderShadow, - Button, - ButtonActive, - ButtonHovered, - CheckMark, - ChildBg, - DockingEmptyBg, - DockingPreview, - DragDropTarget, - FrameBg, - FrameBgActive, - FrameBgHovered, - Header, - HeaderActive, - HeaderHovered, - MenuBarBg, - ModalWindowDimBg, - NavCursor, - NavWindowingDimBg, - NavWindowingHighlight, - PlotHistogram, - PlotHistogramHovered, - PlotLines, - PlotLinesHovered, - PopupBg, - ResizeGrip, - ResizeGripActive, - ResizeGripHovered, - ScrollbarBg, - ScrollbarGrab, - ScrollbarGrabActive, - ScrollbarGrabHovered, - Separator, - SeparatorActive, - SeparatorHovered, - SliderGrab, - SliderGrabActive, - Tab, - TabDimmed, - TabDimmedSelected, - TabDimmedSelectedOverline, - TabHovered, - TableBorderLight, - TableBorderStrong, - TableHeaderBg, - TableRowBg, - TableRowBgAlt, - TabSelected, - TabSelectedOverline, - Text, - TextDisabled, - TextLink, - TextSelectedBg, - TitleBg, - TitleBgActive, - TitleBgCollapsed, - WindowBg, - }; - - /** - * @brief Colour flags for graph editor. - */ - enum class EditorColour : std::uint8_t - { - Bg, - Flow, - FlowMarker, - Grid, - GroupBg, - GroupBorder, - HighlightLinkBorder, - HovLinkBorder, - HovNodeBorder, - LinkSelRect, - LinkSelRectBorder, - NodeBg, - NodeBorder, - NodeSelRect, - NodeSelRectBorder, - PinRect, - PinRectBorder, - SelLinkBorder, - SelNodeBorder, - }; - public: Style(); @@ -154,11 +62,16 @@ struct Style /** * @brief Get the colour of a port type by typename. - * @tparam T The type of the port. + * @param type The name of the type. * @returns The colours registered for the given typename. */ Colour GetTypeColour(std::string_view type) const; + /** + * @brief Get the colour of a port type by typename. + * @tparam T The type of the port. + * @param colour The colour to render the port as. + */ template void SetTypeColour(const Colour& colour) { @@ -187,40 +100,8 @@ struct Style PortIconType Ref; } PortShapes; - /// Registered colours for the editor. - struct - { - /// Registered colours for windows and widgets. - std::map BaseColours; - - /// Registered colours for the graph editor. - std::map EditorColours; - - /// Registered colours for port types. - std::unordered_map TypeColours; - } Colours; - - /// Window border size. - float WindowBorderSize; - - /// Size of inner window frame borders. - float FrameBorderSize; - - /// Rounding for windows tabs. - float TabRounding; - - /// Border size for windows tabs. - float TabBarBorderSize; - - /// Padding for table cells. - struct - { - /// Cell padding width. - float Width; - - /// Cell padding height. - float Height; - } CellPadding; + /// Registered colours for port types. + std::unordered_map TypeColours; }; /** diff --git a/include/flow/ui/Texture.hpp b/include/flow/ui/Texture.hpp index 0af0533..c714f23 100644 --- a/include/flow/ui/Texture.hpp +++ b/include/flow/ui/Texture.hpp @@ -5,11 +5,9 @@ #include "Core.hpp" -#include - #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Texture wrapper type. Access cached images or caches new ones. diff --git a/include/flow/ui/ViewFactory.hpp b/include/flow/ui/ViewFactory.hpp index f617e93..480ebf8 100644 --- a/include/flow/ui/ViewFactory.hpp +++ b/include/flow/ui/ViewFactory.hpp @@ -15,7 +15,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN template concept NodeViewType = std::is_base_of_v; @@ -25,7 +25,9 @@ concept NodeViewType = std::is_base_of_v; */ class ViewFactory : public flow::NodeFactory { - using NodeViewConstructorCallback = std::function; + using NodeViewConstructorCallback_t = std::function; + using InputFieldPtr_t = std::unique_ptr; + using InputFieldConstructor_t = std::function; public: ViewFactory() = default; @@ -70,8 +72,8 @@ class ViewFactory : public flow::NodeFactory template void RegisterInputType(const T& initial_value) { - _input_field_contructors[std::string{flow::TypeName_v}] = - [=](std::string name, const SharedNodeData& data) -> std::shared_ptr { + _input_field_constructors[std::string{flow::TypeName_v}] = + [=](std::string name, const SharedNodeData& data) -> std::unique_ptr { T value = initial_value; if (auto d = CastNodeData(data)) { @@ -82,15 +84,20 @@ class ViewFactory : public flow::NodeFactory value = ref_data->Get(); } - return std::make_shared>(std::move(name), value); + return std::make_unique>(std::move(name), value); }; } - /** - * @brief Get the list of registered input field constructors. - * @returns A reference to the map of registered input field constructors. - */ - const auto& GetRegisteredInputTypes() { return _input_field_contructors; } + std::unique_ptr CreateInputField(const SharedPort& port) + { + const std::string type = std::string{port->GetDataType()}; + if (!_input_field_constructors.contains(type)) + { + return nullptr; + } + + return _input_field_constructors.at(type)(std::string{port->GetVarName()}, port->GetData()); + } private: template @@ -100,12 +107,9 @@ class ViewFactory : public flow::NodeFactory } private: - std::unordered_map _constructors; - - using InputFieldConstructor_t = - std::function(std::string name, const SharedNodeData&)>; + std::unordered_map _constructors; - std::unordered_map _input_field_contructors; + std::unordered_map _input_field_constructors; }; FLOW_UI_NAMESPACE_END diff --git a/include/flow/ui/Widget.hpp b/include/flow/ui/Widget.hpp index e8faa27..52c12ce 100644 --- a/include/flow/ui/Widget.hpp +++ b/include/flow/ui/Widget.hpp @@ -2,7 +2,7 @@ #include "Core.hpp" -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Abstract base widget class. @@ -15,7 +15,7 @@ class Widget /** * @brief Drawing function for widgets. */ - virtual void operator()() noexcept = 0; + virtual void Draw() noexcept = 0; }; FLOW_UI_NAMESPACE_END diff --git a/include/flow/ui/Window.hpp b/include/flow/ui/Window.hpp index c82528e..827e6ba 100644 --- a/include/flow/ui/Window.hpp +++ b/include/flow/ui/Window.hpp @@ -7,7 +7,7 @@ #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /// Default name of the main dockspace. static inline const std::string DefaultDockspace = "MainDockSpace"; diff --git a/include/flow/ui/WindowManager.hpp b/include/flow/ui/WindowManager.hpp new file mode 100644 index 0000000..beaa8c7 --- /dev/null +++ b/include/flow/ui/WindowManager.hpp @@ -0,0 +1,56 @@ +// Copyright (c) 2024, Cisco Systems, Inc. +// All rights reserved. + +#pragma once + +#include "Core.hpp" +#include "Window.hpp" +#include "windows/GraphWindow.hpp" + +#include +#include +#include + +FLOW_UI_NAMESPACE_BEGIN + +class WindowManager +{ + public: + void Teardown(); + + /** + * @brief Add a new custom window to the editor. + * + * @param new_window The new window to add to the editor. + * @param dockspace The default dockspace to attach the window to. + * @param visible_by_default Flag to show or hide the window by default. + */ + void AddWindow(std::shared_ptr new_window, const std::string& dockspace, bool visible_by_default = true); + + const std::shared_ptr& CreateGraphWindow(std::string name, const std::shared_ptr& env); + + const std::vector>& GetWindows() const noexcept { return _windows; } + + const std::unordered_map>& GetGraphWindows() const noexcept + { + return _graph_windows; + } + + std::shared_ptr GetActiveGraphWindow() const noexcept; + + void CleanupDeadWindows(); + + void CloseActiveGraphWindow(); + + void RemoveUnloadedModuleNode(std::string_view class_name); + + public: + /// Event dispatcher that is run every time a new graph is marked as the active graph. + EventDispatcher&> OnActiveGraphChanged; + + private: + std::vector> _windows; + std::unordered_map> _graph_windows; +}; + +FLOW_UI_NAMESPACE_END \ No newline at end of file diff --git a/include/flow/ui/views/ConnectionView.hpp b/include/flow/ui/views/ConnectionView.hpp index bcffee3..b71688a 100644 --- a/include/flow/ui/views/ConnectionView.hpp +++ b/include/flow/ui/views/ConnectionView.hpp @@ -10,7 +10,7 @@ #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Visual representation of a flow::Connection. diff --git a/include/flow/ui/views/NodeView.hpp b/include/flow/ui/views/NodeView.hpp index 3e94716..9a72821 100644 --- a/include/flow/ui/views/NodeView.hpp +++ b/include/flow/ui/views/NodeView.hpp @@ -13,7 +13,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN namespace utility { @@ -85,6 +85,13 @@ class NodeView : public GraphItemView */ void ShowConnectables(const std::shared_ptr& port) override; + protected: + void DrawHeader(); + + void DrawInputs(); + + void DrawOutputs(); + public: /// The ID of the node this view is for. UUID NodeID; @@ -103,7 +110,7 @@ class NodeView : public GraphItemView protected: std::shared_ptr _builder; - bool _received_error = false; + std::optional _received_error; }; /** diff --git a/include/flow/ui/views/PortView.hpp b/include/flow/ui/views/PortView.hpp index 47ada5e..af5ae5e 100644 --- a/include/flow/ui/views/PortView.hpp +++ b/include/flow/ui/views/PortView.hpp @@ -11,7 +11,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN class NodeView; @@ -37,27 +37,21 @@ enum class PortType class PortView { public: - /** - * @brief Input event type for port types that have input fields registered. - */ + /// Input event type for port types that have input fields registered. using InputEvent = Event; /** * @brief Contrusts a port view for a given node. * - * @param node_id The ID of the NodeVIew this port belongs to. + * @param node_id The ID of the NodeView this port belongs to. * @param port_data The data pointer of the Port. - * @param factory View factory for creating ports from registered types. - * @param on_input Event to run on input for types that have input fields regsitered. - * @param show_label Show the port label or not. */ - PortView(const std::uint64_t& node_id, std::shared_ptr port_data, - const std::shared_ptr& factory, InputEvent on_input, bool show_label = true); + PortView(PortType type, const std::uint64_t& node_id, std::shared_ptr port_data); /** * @brief Renders the Port to NodeView. */ - void Draw(); + void Draw(const std::shared_ptr& builder); /** * @brief Get the connected status of the port. @@ -66,8 +60,8 @@ class PortView bool IsConnected() const noexcept { return _port->IsConnected(); } /** - * @brief Checks if the prt can link to the given port. - * @param other THe ports to check against. + * @brief Checks if the port can link to the given port. + * @param other The port to check against. */ bool CanLink(const std::shared_ptr& other) const noexcept; @@ -92,31 +86,25 @@ class PortView * @brief Gets the unique hash key of the port. * @returns The IndexableName of the port. */ - const flow::IndexableName& Key() const noexcept { return _port->GetKey(); } + const flow::IndexableName& GetKey() const noexcept { return _port->GetKey(); } /** * @brief Gets the caption/description of the port. * @returns The port's caption. */ - std::string_view Caption() const noexcept { return _port->GetCaption(); } + std::string_view GetCaption() const noexcept { return _port->GetCaption(); } /** * @brief Get's the typename of the port data. * @returns The port's data typename. */ - std::string_view Type() const noexcept { return _port->GetDataType(); } + std::string_view GetType() const noexcept { return _port->GetDataType(); } /** * @brief Gets the colour of the data type. * @returns The data type's registered colour. */ - Colour GetColour() const noexcept { return GetStyle().GetTypeColour(Type()); } - - /** - * @brief Sets the view builder pointer. - * @param builder The new builder to use. - */ - void SetBuilder(std::shared_ptr builder) noexcept; + Colour GetColour() const noexcept { return GetStyle().GetTypeColour(GetType()); } /** * @brief Sets whether or not the port should be shown as connectable or not. @@ -130,11 +118,20 @@ class PortView */ void SetShowLabel(bool show) { _show_label = show; } - protected: - void DrawInput(); + /** + * @brief Sets the input field widget that will be drawn for input port types. + * @param input_field The input field widget to use. + */ + void SetInputField(std::unique_ptr&& input_field) + { + _input_field = std::move(input_field); + } private: + void DrawInput(); + void DrawLabel(); + void DrawIcon(float alpha); public: @@ -145,7 +142,7 @@ class PortView const std::uint64_t& NodeViewID; /// The type of port. - PortType Kind = PortType::Input; + PortType Type = PortType::Input; /// Event run on setting a new value in the input field. InputEvent OnSetInput; @@ -155,12 +152,10 @@ class PortView private: std::shared_ptr _port; - std::shared_ptr _input_field; + std::unique_ptr _input_field; bool _show_label = true; bool _was_active = false; float _alpha = 1.f; - - std::shared_ptr _builder; }; FLOW_UI_NAMESPACE_END diff --git a/include/flow/ui/widgets/InputField.hpp b/include/flow/ui/widgets/InputField.hpp index 43f30f3..5a1a1a9 100644 --- a/include/flow/ui/widgets/InputField.hpp +++ b/include/flow/ui/widgets/InputField.hpp @@ -8,7 +8,10 @@ #include -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) + +template +static inline bool InputField(std::string_view name, T& value, int flags); /** * @brief Input field interface class. @@ -36,18 +39,13 @@ class Input : public InputInterface virtual ~Input() = default; - virtual void operator()() noexcept override; + virtual void Draw() noexcept override; /** * @brief Gets a node data pointer of a newly entered value. * @returns A new node data pointer containing the newly entered value. */ - virtual flow::SharedNodeData GetData() noexcept override - { - auto d = std::move(_data); - _data = nullptr; - return d; - } + virtual flow::SharedNodeData GetData() noexcept override { return std::move(_data); } /** * @brief Gets the vale that is currently entered into the input field. diff --git a/include/flow/ui/widgets/PropertyTree.hpp b/include/flow/ui/widgets/PropertyTree.hpp index cddbba2..6d19b5c 100644 --- a/include/flow/ui/widgets/PropertyTree.hpp +++ b/include/flow/ui/widgets/PropertyTree.hpp @@ -8,7 +8,7 @@ #include #include -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) /** * @brief Property tree widget for showing user defined properties in a collapsable tree table. @@ -45,7 +45,7 @@ class PropertyTree : public Widget /** * @brief Renders the property tree widget to the windows. */ - virtual void operator()() noexcept override; + virtual void Draw() noexcept override; private: std::map>>> _properties; diff --git a/include/flow/ui/widgets/Table.hpp b/include/flow/ui/widgets/Table.hpp index 27582ee..650f817 100644 --- a/include/flow/ui/widgets/Table.hpp +++ b/include/flow/ui/widgets/Table.hpp @@ -8,7 +8,7 @@ #include #include -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) /** * @brief Table widget for displaying widgets in an organised table view. @@ -28,7 +28,7 @@ class Table : public Widget /** * @brief Renders the table to the window. */ - virtual void operator()() noexcept override; + virtual void Draw() noexcept override; /** * @brief Adds widget entry to the table in the next available column. diff --git a/include/flow/ui/widgets/Text.hpp b/include/flow/ui/widgets/Text.hpp index 01b0dfa..c2c0e53 100644 --- a/include/flow/ui/widgets/Text.hpp +++ b/include/flow/ui/widgets/Text.hpp @@ -1,10 +1,11 @@ #pragma once +#include "flow/ui/Config.hpp" #include "flow/ui/Core.hpp" #include "flow/ui/Style.hpp" #include "flow/ui/Widget.hpp" -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) /** * @brief Text widget to display strings in a window. @@ -22,16 +23,20 @@ class Text : public Widget Right }; + using HAlignment = HorizontalAlignment; + /** * @brief Vertical alignment type. */ enum class VerticalAlignment { Top, - Centre, + Middle, Bottom }; + using VAlignment = VerticalAlignment; + /** * @brief Alignment rules for ttext widget. */ @@ -48,18 +53,15 @@ class Text : public Widget /** * @brief Constructs a text widget. * @param text The text to display. - * @param colour The colour of the displayed text. - * @param align The alignment of the text in the window. */ - Text(const std::string& text, const Colour& colour = Colour(), - const Alignment& align = {HorizontalAlignment::Left, VerticalAlignment::Top}); + Text(const std::string& text); virtual ~Text() = default; /** * @brief Renders the text to the window. */ - virtual void operator()() noexcept override; + virtual void Draw() noexcept override; /** * @brief Sets the text colour. @@ -75,6 +77,14 @@ class Text : public Widget */ Text& SetAlignment(const Alignment& new_align) noexcept; + Text& SetAlignment(HorizontalAlignment new_halign, VerticalAlignment new_valign) noexcept; + + Text& SetHorizontalAlignment(HorizontalAlignment new_halign) noexcept; + + Text& SetVerticalAlignment(VerticalAlignment new_valign) noexcept; + + Text& SetFont(const std::unique_ptr& font); + /** * @brief Sets the font size of the text. * @param new_size The new size of the text. @@ -84,9 +94,10 @@ class Text : public Widget private: std::string _text; - float _font_size = 18.f; Colour _colour; - Alignment _align; + Alignment _align = {HorizontalAlignment::Left, VerticalAlignment::Top}; + void* _font; + float _font_size = 18.f; }; FLOW_UI_SUBNAMESPACE_END \ No newline at end of file diff --git a/include/flow/ui/windows/GraphWindow.hpp b/include/flow/ui/windows/GraphWindow.hpp index dfe53f9..e549d40 100644 --- a/include/flow/ui/windows/GraphWindow.hpp +++ b/include/flow/ui/windows/GraphWindow.hpp @@ -7,6 +7,7 @@ #include "flow/ui/Widget.hpp" #include "flow/ui/Window.hpp" #include "flow/ui/views/NodeView.hpp" +#include "flow/ui/windows/NodeExplorerWindow.hpp" #include #include @@ -18,31 +19,10 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using json = nlohmann::json; -class ContextMenu : public Widget -{ - public: - ContextMenu(std::shared_ptr factory) : _factory(std::move(factory)) {} - - virtual ~ContextMenu() = default; - - virtual void operator()() noexcept override; - - private: - void DrawPopupCategory(const std::string& category, const flow::CategoryMap& registered_nodes); - - public: - Event OnSelection; - - private: - std::shared_ptr _factory; - std::string node_lookup; - bool is_focused = false; -}; - /** * @brief Graph editor window for creating flows. */ @@ -195,6 +175,10 @@ class GraphWindow : public Window void MarkDirty(bool new_value) { _dirty = new_value; } private: + void Configure(); + + void SetStyle(); + void EndDraw(); const std::shared_ptr& GetEnv() const { return _graph->GetEnv(); } @@ -225,7 +209,7 @@ class GraphWindow : public Window std::shared_ptr _new_node_link_pin = nullptr; std::shared_ptr _new_link_pin = nullptr; - ContextMenu _node_creation_context_menu; + NodeExplorerWindow _node_creation_menu; struct { diff --git a/include/flow/ui/windows/ModuleManagerWindow.hpp b/include/flow/ui/windows/ModuleManagerWindow.hpp index c3bf6eb..677de80 100644 --- a/include/flow/ui/windows/ModuleManagerWindow.hpp +++ b/include/flow/ui/windows/ModuleManagerWindow.hpp @@ -11,7 +11,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN /** * @brief Windows for displaying available modules and which of them are loaded/unloaded. diff --git a/include/flow/ui/windows/NewModuleWindow.hpp b/include/flow/ui/windows/NewModuleWindow.hpp index f7626eb..a73b5b4 100644 --- a/include/flow/ui/windows/NewModuleWindow.hpp +++ b/include/flow/ui/windows/NewModuleWindow.hpp @@ -5,7 +5,7 @@ #include "widgets/InputField.hpp" #include "widgets/Table.hpp" -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN class NewModuleWindow : public Window { diff --git a/include/flow/ui/windows/NodeExplorerWindow.hpp b/include/flow/ui/windows/NodeExplorerWindow.hpp index 1a815d7..957998b 100644 --- a/include/flow/ui/windows/NodeExplorerWindow.hpp +++ b/include/flow/ui/windows/NodeExplorerWindow.hpp @@ -8,7 +8,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN class NodeExplorerWindow : public Window { @@ -23,15 +23,13 @@ class NodeExplorerWindow : public Window private: void DrawPopupCategory(const std::string& category, const flow::CategoryMap& registered_nodes); + public: + Event OnSelection; + private: std::shared_ptr _env; std::shared_ptr _active_graph; std::string node_lookup; - struct - { - std::string class_name; - std::string display_name; - } drag_drop_payload; }; FLOW_UI_NAMESPACE_END diff --git a/include/flow/ui/windows/PropertyWindow.hpp b/include/flow/ui/windows/PropertyWindow.hpp index 7594e24..fde0ae8 100644 --- a/include/flow/ui/windows/PropertyWindow.hpp +++ b/include/flow/ui/windows/PropertyWindow.hpp @@ -12,12 +12,12 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN class PropertyWindow : public Window { public: - PropertyWindow(std::shared_ptr env); + PropertyWindow(); virtual ~PropertyWindow() = default; virtual void Draw() override; @@ -27,7 +27,6 @@ class PropertyWindow : public Window static inline const std::string Name = "Properties"; private: - std::weak_ptr _env; std::weak_ptr _graph; }; diff --git a/include/flow/ui/windows/ShortcutsWindow.hpp b/include/flow/ui/windows/ShortcutsWindow.hpp index 7552f0e..8853d60 100644 --- a/include/flow/ui/windows/ShortcutsWindow.hpp +++ b/include/flow/ui/windows/ShortcutsWindow.hpp @@ -8,7 +8,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN class ShortcutsWindow : public Window { diff --git a/programs/editor/src/main.cpp b/programs/editor/src/main.cpp index c007f35..f00a5b8 100644 --- a/programs/editor/src/main.cpp +++ b/programs/editor/src/main.cpp @@ -9,6 +9,8 @@ #include int main(int argc, char** argv) + +try { std::string filename; @@ -51,77 +53,17 @@ int main(int argc, char** argv) #endif flow::ui::Editor app(filename); - - app.LoadFonts = [](flow::ui::Config& config) { - config.DefaultFont = flow::ui::LoadFont("fonts/DroidSans.ttf", 18.f); - config.NodeHeaderFont = flow::ui::LoadFont("fonts/DroidSans.ttf", 20.f); - config.IconFont = flow::ui::LoadFont("fonts/fontawesome-webfont.ttf", 18.f); - }; - - app.SetupStyle = [](flow::ui::Style& style) { - style.WindowBorderSize = 5.f; - style.FrameBorderSize = 2.f; - style.TabRounding = 8.f; - style.TabBarBorderSize = 0.f; - style.CellPadding = {.Width = 7.f, .Height = 7.f}; - - auto& imgui_colours = style.Colours.BaseColours; - using BaseColour = flow::ui::Style::BaseColour; - - imgui_colours[BaseColour::WindowBg] = flow::ui::Colour(21, 21, 21); - imgui_colours[BaseColour::PopupBg] = flow::ui::Colour(15, 15, 15, 175); - imgui_colours[BaseColour::Border] = flow::ui::Colour(15, 15, 15); - imgui_colours[BaseColour::PopupBg] = imgui_colours[BaseColour::WindowBg]; - imgui_colours[BaseColour::FrameBg] = flow::ui::Colour(15, 15, 15); - imgui_colours[BaseColour::MenuBarBg] = flow::ui::Colour(21, 21, 21); - imgui_colours[BaseColour::TitleBg] = flow::ui::Colour(21, 21, 21); - imgui_colours[BaseColour::TitleBgActive] = imgui_colours[BaseColour::TitleBg]; - imgui_colours[BaseColour::Tab] = flow::ui::Colour(21, 21, 21); - imgui_colours[BaseColour::TabDimmed] = flow::ui::Colour(21, 21, 21); - imgui_colours[BaseColour::TabHovered] = flow::ui::Colour(47, 47, 47); - imgui_colours[BaseColour::TabSelected] = flow::ui::Colour(3, 98, 195); - imgui_colours[BaseColour::TabDimmedSelected] = imgui_colours[BaseColour::TabSelected]; - imgui_colours[BaseColour::Button] = flow::ui::Colour(32, 32, 32); - imgui_colours[BaseColour::ButtonHovered] = flow::ui::Colour(3, 98, 195); - imgui_colours[BaseColour::ButtonActive] = flow::ui::Colour(13, 39, 77); - imgui_colours[BaseColour::ScrollbarBg] = flow::ui::Colour(21, 21, 21); - imgui_colours[BaseColour::ScrollbarGrab] = flow::ui::Colour(86, 86, 86); - imgui_colours[BaseColour::TableBorderLight] = flow::ui::Colour(21, 21, 21); - imgui_colours[BaseColour::TableBorderStrong] = flow::ui::Colour(21, 21, 21); - imgui_colours[BaseColour::TableRowBg] = flow::ui::Colour(36, 36, 36); - imgui_colours[BaseColour::TableRowBgAlt] = flow::ui::Colour(36, 36, 36); - imgui_colours[BaseColour::Header] = flow::ui::Colour(47, 47, 47); - imgui_colours[BaseColour::HeaderHovered] = flow::ui::Colour(50, 50, 50); - imgui_colours[BaseColour::CheckMark] = flow::ui::Colour(3, 98, 195); - - auto& colours = style.Colours.EditorColours; - using EditorColour = flow::ui::Style::EditorColour; - - colours[EditorColour::Bg] = flow::ui::Colour(38, 38, 38); - colours[EditorColour::Grid] = flow::ui::Colour(52, 52, 52); - colours[EditorColour::NodeBg] = flow::ui::Colour(15, 17, 15, 240); - colours[EditorColour::NodeBorder] = flow::ui::Colour(0, 0, 0); - colours[EditorColour::SelNodeBorder] = flow::ui::Colour(255, 255, 255); - colours[EditorColour::Flow] = flow::ui::Colour(32, 191, 85); - colours[EditorColour::FlowMarker] = flow::ui::Colour(32, 191, 85); - colours[EditorColour::HighlightLinkBorder] = flow::ui::Colour(0, 188, 235); - colours[EditorColour::SelLinkBorder] = flow::ui::Colour(0, 188, 235); - }; - - try - { - app.Run(); - } - catch (const std::exception& e) - { - SPDLOG_CRITICAL("Exiting with error: {0}", e.what()); - return EXIT_FAILURE; - } - catch (...) - { - SPDLOG_CRITICAL("Exiting with unknown error"); - return EXIT_FAILURE; - } + app.Run(); return EXIT_SUCCESS; } +catch (const std::exception& e) +{ + SPDLOG_CRITICAL("Exiting with error: {0}", e.what()); + return EXIT_FAILURE; +} +catch (...) +{ + SPDLOG_CRITICAL("Exiting with unknown error"); + return EXIT_FAILURE; +} diff --git a/src/CommandManager.cpp b/src/CommandManager.cpp new file mode 100644 index 0000000..3943c9e --- /dev/null +++ b/src/CommandManager.cpp @@ -0,0 +1,20 @@ +#include "CommandManager.hpp" + +#include + +FLOW_UI_NAMESPACE_BEGIN + +void CommandManager::Handle() +{ + for (const auto& [chord, event] : _input_events) + { + if (ImGui::IsKeyChordPressed(chord)) + { + event(); + } + } +} + +void CommandManager::AddCommand(int key_chord, Event<>&& event) { _input_events.emplace(key_chord, std::move(event)); } + +FLOW_UI_NAMESPACE_END diff --git a/src/Config.cpp b/src/Config.cpp index 61bafec..18318d5 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -8,7 +8,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN static Config config{}; diff --git a/src/Core.cpp b/src/Core.cpp index 8261f3e..17b9bb7 100644 --- a/src/Core.cpp +++ b/src/Core.cpp @@ -5,7 +5,7 @@ #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using namespace ax; namespace ed = ax::NodeEditor; diff --git a/src/Editor.cpp b/src/Editor.cpp index 7793d0a..5c723f3 100644 --- a/src/Editor.cpp +++ b/src/Editor.cpp @@ -5,8 +5,10 @@ #include "Config.hpp" #include "EditorNodes.hpp" +#include "FileExplorer.hpp" #include "ViewFactory.hpp" #include "Window.hpp" +#include "WindowManager.hpp" #include "utilities/Conversions.hpp" #include "windows/ModuleManagerWindow.hpp" #include "windows/NodeExplorerWindow.hpp" @@ -31,263 +33,184 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using namespace ax; namespace ed = ax::NodeEditor; +HelloImGui::RunnerParams _params; + +void AddDockspace(std::string name, std::string initial_dockspace_name, float ratio, ImGuiDir direction) +{ + HelloImGui::DockingSplit split; + split.initialDock = std::move(initial_dockspace_name); + split.newDock = std::move(name); + split.direction = direction; + split.ratio = ratio; + + _params.dockingParams.dockingSplits.push_back(std::move(split)); +} const std::filesystem::path default_save_path = FileExplorer::GetDocumentsPath() / "flows"; const std::filesystem::path default_modules_path = FileExplorer::GetExecutablePath() / "modules"; -HelloImGui::RunnerParams _params; - Editor::Editor(const std::string& initial_file) + : _window_manager(std::make_unique()), _input_manager(std::make_unique()) { + SetupParams(initial_file); +} + +void Editor::Init(const std::string& initial_file) +{ + RegisterInputs(); + RegisterNodes(); + RegisterInputFieldTypes(); + + AddDockspace(PropertyDockspace, DefaultDockspace, 0.25f, ImGuiDir_Left); + AddDockspace("PropertySubSpace", PropertyDockspace, 0.5f, ImGuiDir_Down); + AddDockspace("ToolbarSpace", DefaultDockspace, 0.1f, ImGuiDir_Up); + AddDockspace("MiscSpace", DefaultDockspace, 0.25f, ImGuiDir_Down); + + auto node_explorer = std::make_shared(GetEnv()); + _window_manager->OnActiveGraphChanged.Bind(flow::IndexableName{node_explorer->GetName()}, + [window = node_explorer](const auto& g) { window->SetActiveGraph(g); }); + + auto property_window = std::make_shared(); + _window_manager->OnActiveGraphChanged.Bind(flow::IndexableName{property_window->GetName()}, + [=](const auto& g) { property_window->SetCurrentGraph(g); }); + + _window_manager->AddWindow(std::move(property_window), PropertyDockspace); + _window_manager->AddWindow(std::move(node_explorer), "PropertySubSpace"); + _window_manager->AddWindow(std::make_shared(GetEnv(), default_modules_path), + "PropertySubSpace", false); + _window_manager->AddWindow(std::make_shared(), PropertyDockspace, false); + + if (!initial_file.empty()) + { + LoadFlow(initial_file); + } + else + { + CreateFlow(); + } +} + +void Editor::Teardown() { _window_manager->Teardown(); } + +void Editor::SetupParams(const std::string& initial_file) +{ +#pragma region AppWindowParams _params.appWindowParams.windowTitle = "Flow Editor"; _params.appWindowParams.borderless = false; _params.appWindowParams.restorePreviousGeometry = true; - _params.iniFolderType = HelloImGui::IniFolderType::TempFolder; _params.appWindowParams.windowGeometry.size = {1920, 1080}; +#pragma endregion - _params.callbacks.PostInit = [&] { - GetConfig().RenderBackend = utility::to_RendererBackend(_params.rendererBackendType); - - SetupStyle(GetStyle()); - utility::to_ImGuiStyle(GetStyle()); - - Init(initial_file); - }; - _params.callbacks.BeforeExit = [&] { Teardown(); }; - +#pragma region ImGuiWindowParams _params.imGuiWindowParams.defaultImGuiWindowType = HelloImGui::DefaultImGuiWindowType::ProvideFullScreenDockSpace; _params.imGuiWindowParams.enableViewports = true; _params.imGuiWindowParams.showMenuBar = true; _params.imGuiWindowParams.showMenu_App = true; _params.imGuiWindowParams.showMenu_View = false; + _params.imGuiWindowParams.showMenu_View_Themes = false; +#pragma endregion +#pragma region MiscellaneousParams _params.dpiAwareParams.fontRenderingScale = 1.f; _params.dpiAwareParams.dpiWindowSizeFactor = 1.f; + _params.iniFolderType = HelloImGui::IniFolderType::TempFolder; + _params.dockingParams.mainDockSpaceNodeFlags = ImGuiDockNodeFlags_AutoHideTabBar; - _params.callbacks.PreNewFrame = [=, this] { - HandleInput(); + _params.fpsIdling.enableIdling = false; +#pragma endregion - OnGraphWindowAdded.Broadcast(); - OnGraphWindowAdded.UnbindAll(); +#pragma region Callbacks + _params.callbacks.PostInit = [&] { Init(initial_file); }; - OnGraphWindowRemoved.Broadcast(); - OnGraphWindowRemoved.UnbindAll(); - }; + _params.callbacks.BeforeExit = [&] { Teardown(); }; - _params.imGuiWindowParams.showMenu_View_Themes = false; - _params.fpsIdling.enableIdling = false; + _params.callbacks.PreNewFrame = [=, this] { + _input_manager->Handle(); + _window_manager->CleanupDeadWindows(); + }; _params.callbacks.SetupImGuiStyle = [&] { - SetupStyle(GetStyle()); - utility::to_ImGuiStyle(GetStyle()); - - auto& imgui_style = ImGui::GetStyle(); - imgui_style.CircleTessellationMaxError = 0.1f; - imgui_style.CurveTessellationTol = 0.1f; + auto& style = ImGui::GetStyle(); + + style.WindowBorderSize = 5.f; + style.FrameBorderSize = 2.f; + style.TabRounding = 8.f; + style.TabBarBorderSize = 0.f; + style.CellPadding = ImVec2{7.f, 7.f}; + style.CircleTessellationMaxError = 0.1f; + style.CurveTessellationTol = 0.1f; + + auto& imgui_colours = style.Colors; + + imgui_colours[ImGuiCol_WindowBg] = ImColor(21, 21, 21); + imgui_colours[ImGuiCol_PopupBg] = ImColor(15, 15, 15, 175); + imgui_colours[ImGuiCol_Border] = ImColor(15, 15, 15); + imgui_colours[ImGuiCol_PopupBg] = imgui_colours[ImGuiCol_WindowBg]; + imgui_colours[ImGuiCol_FrameBg] = ImColor(15, 15, 15); + imgui_colours[ImGuiCol_MenuBarBg] = ImColor(21, 21, 21); + imgui_colours[ImGuiCol_TitleBg] = ImColor(21, 21, 21); + imgui_colours[ImGuiCol_TitleBgActive] = imgui_colours[ImGuiCol_TitleBg]; + imgui_colours[ImGuiCol_Tab] = ImColor(21, 21, 21); + imgui_colours[ImGuiCol_TabDimmed] = ImColor(21, 21, 21); + imgui_colours[ImGuiCol_TabHovered] = ImColor(47, 47, 47); + imgui_colours[ImGuiCol_TabSelected] = ImColor(3, 98, 195); + imgui_colours[ImGuiCol_TabDimmedSelected] = imgui_colours[ImGuiCol_TabSelected]; + imgui_colours[ImGuiCol_Button] = ImColor(32, 32, 32); + imgui_colours[ImGuiCol_ButtonHovered] = ImColor(3, 98, 195); + imgui_colours[ImGuiCol_ButtonActive] = ImColor(13, 39, 77); + imgui_colours[ImGuiCol_ScrollbarBg] = ImColor(21, 21, 21); + imgui_colours[ImGuiCol_ScrollbarGrab] = ImColor(86, 86, 86); + imgui_colours[ImGuiCol_TableBorderLight] = ImColor(21, 21, 21); + imgui_colours[ImGuiCol_TableBorderStrong] = ImColor(21, 21, 21); + imgui_colours[ImGuiCol_TableRowBg] = ImColor(36, 36, 36); + imgui_colours[ImGuiCol_TableRowBgAlt] = ImColor(36, 36, 36); + imgui_colours[ImGuiCol_Header] = ImColor(47, 47, 47); + imgui_colours[ImGuiCol_HeaderHovered] = ImColor(50, 50, 50); + imgui_colours[ImGuiCol_CheckMark] = ImColor(3, 98, 195); }; + _params.callbacks.LoadAdditionalFonts = [&] { auto& config = GetConfig(); - LoadFonts(config); + + config.DefaultFont = flow::ui::LoadFont("fonts/DroidSans.ttf", 18.f); + config.NodeHeaderFont = flow::ui::LoadFont("fonts/DroidSans.ttf", 20.f); + config.IconFont = flow::ui::LoadFont("fonts/fontawesome-webfont.ttf", 18.f); + ImGui::GetIO().FontDefault = std::bit_cast(config.DefaultFont.get()); }; - _params.callbacks.ShowMenus = [&] { - DrawMainMenuBar(); - HelloImGui::ShowViewMenu(_params); - }; -} -void Editor::Init(const std::string& initial_file) -{ - _factory->OnNodeClassUnregistered.Bind("Unregister", [&](std::string_view class_name) { - for (const auto& [_, gw] : _graph_windows) + _params.callbacks.ShowMenus = [&] { + if (ImGui::BeginMenu("File")) { - const auto& graph = gw->GetGraph(); - - std::set nodes_to_remove; - graph->Visit([&](const auto& node) { - if (node->GetClass() == class_name) - { - nodes_to_remove.insert(node->ID()); - } - }); - - for (const auto& id : nodes_to_remove) + if (ImGui::MenuItem("New Flow")) { - graph->RemoveNodeByID(id); - ed::SetCurrentEditor(std::bit_cast(gw->GetEditorContext().get())); - ed::DeleteNode(std::hash{}(id)); + CreateFlow("untitled##" + std::to_string(_window_manager->GetGraphWindows().size())); } - } - }); - - _factory->RegisterNodeClass("Editor", "Preview"); - _factory->RegisterNodeView(); - - _factory->RegisterInputType(false); - _factory->RegisterInputType(0.f); - _factory->RegisterInputType(0.0); - _factory->RegisterInputType(0); - _factory->RegisterInputType(0); - _factory->RegisterInputType(0); - _factory->RegisterInputType(0); - _factory->RegisterInputType(0); - _factory->RegisterInputType(0); - _factory->RegisterInputType(0); - _factory->RegisterInputType(0); - _factory->RegisterInputType(""); - _factory->RegisterInputType(std::chrono::nanoseconds::zero()); - _factory->RegisterInputType(std::chrono::microseconds::zero()); - _factory->RegisterInputType(std::chrono::milliseconds::zero()); - _factory->RegisterInputType(std::chrono::seconds::zero()); - _factory->RegisterInputType(std::chrono::minutes::zero()); - _factory->RegisterInputType(std::chrono::hours::zero()); - _factory->RegisterInputType(std::chrono::days::zero()); - _factory->RegisterInputType(std::chrono::months::zero()); - _factory->RegisterInputType(std::chrono::years::zero()); - _factory->RegisterInputType(std::filesystem::path("")); - - auto node_explorer = std::make_shared(_env); - OnActiveGraphChanged.Bind("NodeExplorer", [window = node_explorer](const auto& g) { window->SetActiveGraph(g); }); - - AddDockspace(PropertyDockspace, DefaultDockspace, 0.25f, DockspaceSplitDirection::Left); - AddDockspace("PropertySubSpace", PropertyDockspace, 0.5f, DockspaceSplitDirection::Down); - AddDockspace("ToolbarSpace", DefaultDockspace, 0.1f, DockspaceSplitDirection::Up); - AddDockspace("MiscSpace", DefaultDockspace, 0.25f, DockspaceSplitDirection::Down); - - auto property_window = std::make_shared(_env); - OnActiveGraphChanged.Bind(flow::IndexableName{property_window->GetName()}, - [=](const auto& g) { property_window->SetCurrentGraph(g); }); - - AddWindow(std::move(property_window), PropertyDockspace); - AddWindow(std::move(node_explorer), "PropertySubSpace"); - AddWindow(std::make_shared(_env, default_modules_path), "PropertySubSpace", false); - AddWindow(std::make_shared(), PropertyDockspace, false); - - if (!initial_file.empty()) - { - LoadFlow(initial_file); - } - else - { - CreateFlow("untitled##0"); - } -} - -void Editor::Teardown() -{ - for (auto& window : _windows) - { - window->Teardown(); - } -} - -void Editor::Run() { HelloImGui::Run(_params); } - -void Editor::AddWindow(std::shared_ptr new_window, const std::string& dockspace, bool show) -{ - auto& window = _windows.emplace_back(std::move(new_window)); - - HelloImGui::DockableWindow dockable_window; - dockable_window.label = window->GetName(); - dockable_window.dockSpaceName = dockspace; - dockable_window.GuiFunction = [=] { window->Draw(); }; - dockable_window.imGuiWindowFlags = ImGuiWindowFlags_NoCollapse; - dockable_window.isVisible = show; - - window->Init(); - HelloImGui::AddDockableWindow(std::move(dockable_window)); -} - -void Editor::AddDockspace(std::string name, std::string initial_dockspace_name, float ratio, - DockspaceSplitDirection direction) -{ - HelloImGui::DockingSplit split; - split.initialDock = std::move(initial_dockspace_name); - split.newDock = std::move(name); - split.direction = utility::to_ImGuiDir(direction); - split.ratio = ratio; - - _params.dockingParams.dockingSplits.push_back(std::move(split)); -} - -void* Editor::GetContext() const noexcept { return reinterpret_cast(ImGui::GetCurrentContext()); } -void Editor::HandleInput() -{ - if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_N)) - { - CreateFlow("untitled##" + std::to_string(_graph_windows.size())); - } - if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_O)) - { - LoadFlow(); - } - if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_S) || - ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiMod_Alt | ImGuiKey_S)) - { - SaveFlow(); - } - if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_W)) - { - for (auto it = _graph_windows.begin(); it != _graph_windows.end();) - { - if (it->second->IsOpen() && it->second->IsActive()) + if (ImGui::MenuItem("Load Flow")) { - OnGraphWindowRemoved.Bind(IndexableName{it->second->GetName()}, - [name = it->second->GetName()] { HelloImGui::RemoveDockableWindow(name); }); - it = _graph_windows.erase(it); - break; + LoadFlow(); } - ++it; - OnActiveGraphChanged.Broadcast(it->second->GetGraph()); - } - } - - for (auto it = _graph_windows.begin(); it != _graph_windows.end();) - { - if (it->second->IsOpen()) - { - ++it; - } - else - { - OnGraphWindowRemoved.Bind(IndexableName{it->second->GetName()}, - [name = it->second->GetName()] { HelloImGui::RemoveDockableWindow(name); }); - it = _graph_windows.erase(it); - } - } -} -void Editor::DrawMainMenuBar() -{ - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("New Flow")) - { - CreateFlow("untitled##" + std::to_string(_graph_windows.size())); - } + if (ImGui::MenuItem("Save")) + { + SaveFlow(); + } - if (ImGui::MenuItem("Load Flow")) - { - LoadFlow(); + ImGui::EndMenu(); } - if (ImGui::MenuItem("Save")) + if (!ed::GetCurrentEditor()) { - SaveFlow(); + return; } - ImGui::EndMenu(); - } - - if (ed::GetCurrentEditor()) - { if (ImGui::BeginMenu("Graph")) { if (ImGui::MenuItem("Zoom to Content")) @@ -297,37 +220,73 @@ void Editor::DrawMainMenuBar() ImGui::EndMenu(); } - } + + HelloImGui::ShowViewMenu(_params); + }; +#pragma endregion } -std::shared_ptr& Editor::CreateFlow(std::string name) +void Editor::RegisterInputs() { - auto found = std::find_if(_graph_windows.begin(), _graph_windows.end(), - [&](const auto& entry) { return entry.second->GetName() == name; }); - if (found != _graph_windows.end()) return found->second; - - auto graph = std::make_shared(name, _env); - auto [graph_view, _] = _graph_windows.emplace(graph->ID(), std::make_shared(graph)); - OnGraphWindowAdded.Bind(IndexableName{name}, [=, this, graph_view = graph_view->second] { - HelloImGui::DockableWindow graph_window; - graph_window.label = name; - graph_window.dockSpaceName = DefaultDockspace; - graph_window.GuiFunction = [this, gv = std::move(graph_view)]() { - if (gv->IsActive()) - { - OnActiveGraphChanged.Broadcast(gv->GetGraph()); - } - gv->Draw(); - }; - graph_window.includeInViewMenu = false; - graph_window.callBeginEnd = false; - graph_window.focusWindowAtNextFrame = true; - graph_window.imGuiWindowFlags = ImGuiWindowFlags_NoCollapse; + // New Graph (Ctrl + N) + _input_manager->AddCommand(ImGuiMod_Ctrl | ImGuiKey_N, [&] { CreateFlow(); }); + + // Open Flow file (Ctrl + O) + _input_manager->AddCommand(ImGuiMod_Ctrl | ImGuiKey_O, [&] { LoadFlow(); }); + + // Save current flow file (Ctrl + S) + _input_manager->AddCommand(ImGuiMod_Ctrl | ImGuiKey_S, [&] { SaveFlow(); }); - HelloImGui::AddDockableWindow(std::move(graph_window)); - }); + // Save current flow file as (Ctrl + Alt + S) + _input_manager->AddCommand(ImGuiMod_Ctrl | ImGuiMod_Alt | ImGuiKey_S, [&] { SaveFlow(); }); - return graph_view->second; + // Close current active graph window (Ctrl + W) + _input_manager->AddCommand(ImGuiMod_Ctrl | ImGuiKey_W, [&] { _window_manager->CloseActiveGraphWindow(); }); +} + +void Editor::RegisterNodes() +{ + GetFactory()->OnNodeClassUnregistered.Bind( + "Unregister", [&](std::string_view class_name) { _window_manager->RemoveUnloadedModuleNode(class_name); }); + + GetFactory()->RegisterNodeClass("Editor", "Preview"); + GetFactory()->RegisterNodeView(); + + GetFactory()->RegisterFunction("Math", "fmod (double)"); + GetFactory()->RegisterFunction("Math", "fmod (float)"); +} + +void Editor::RegisterInputFieldTypes() +{ + GetFactory()->RegisterInputType(false); + GetFactory()->RegisterInputType(0.f); + GetFactory()->RegisterInputType(0.0); + GetFactory()->RegisterInputType(0); + GetFactory()->RegisterInputType(0); + GetFactory()->RegisterInputType(0); + GetFactory()->RegisterInputType(0); + GetFactory()->RegisterInputType(0); + GetFactory()->RegisterInputType(0); + GetFactory()->RegisterInputType(0); + GetFactory()->RegisterInputType(0); + GetFactory()->RegisterInputType(""); + GetFactory()->RegisterInputType(std::chrono::nanoseconds::zero()); + GetFactory()->RegisterInputType(std::chrono::microseconds::zero()); + GetFactory()->RegisterInputType(std::chrono::milliseconds::zero()); + GetFactory()->RegisterInputType(std::chrono::seconds::zero()); + GetFactory()->RegisterInputType(std::chrono::minutes::zero()); + GetFactory()->RegisterInputType(std::chrono::hours::zero()); + GetFactory()->RegisterInputType(std::chrono::days::zero()); + GetFactory()->RegisterInputType(std::chrono::months::zero()); + GetFactory()->RegisterInputType(std::chrono::years::zero()); + GetFactory()->RegisterInputType(std::filesystem::path("")); +} + +void Editor::Run() { HelloImGui::Run(_params); } + +const std::shared_ptr& Editor::CreateFlow(const std::string& name) +{ + return _window_manager->CreateGraphWindow(name, GetEnv()); } void Editor::LoadFlow(const std::filesystem::path& filename) @@ -362,13 +321,8 @@ void Editor::LoadFlow(const std::filesystem::path& filename) void Editor::SaveFlow() { - auto graph_window_it = std::find_if(_graph_windows.begin(), _graph_windows.end(), [](auto& gw) { - return ed::GetCurrentEditor() == std::bit_cast(gw.second->GetEditorContext().get()); - }); - if (graph_window_it == _graph_windows.end()) return; - - auto& graph = graph_window_it->second->GetGraph(); - auto& graph_view = graph_window_it->second; + auto graph_view = _window_manager->GetActiveGraphWindow(); + auto& graph = graph_view->GetGraph(); std::string name{graph->GetName()}; if (name.find("##") != std::string::npos) diff --git a/src/EditorNodes.hpp b/src/EditorNodes.hpp index ebc5a9c..ba8cce8 100644 --- a/src/EditorNodes.hpp +++ b/src/EditorNodes.hpp @@ -3,8 +3,8 @@ #include "Config.hpp" #include "Core.hpp" #include "Texture.hpp" -#include "utilities/Builders.hpp" #include "utilities/Conversions.hpp" +#include "utilities/NodeBuilder.hpp" #include "views/NodeView.hpp" #include "widgets/Text.hpp" @@ -16,11 +16,17 @@ #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN struct PreviewNodeView : NodeView { - PreviewNodeView(flow::SharedNode node) : NodeView(node), Node(node) {} + PreviewNodeView(flow::SharedNode node) : NodeView(node), Node(node) + { + for (const auto& input : Inputs) + { + input->SetShowLabel(false); + } + } virtual ~PreviewNodeView() = default; @@ -28,72 +34,39 @@ struct PreviewNodeView : NodeView { _builder->Begin(this->ID()); - _builder->Header(utility::to_ImColor(HeaderColour)); - ImGui::Spring(0); + DrawHeader(); - if (GetConfig().NodeHeaderFont) - { - ImGui::PushFont(std::bit_cast(GetConfig().NodeHeaderFont.get())); - ImGui::TextUnformatted(Name.c_str()); - ImGui::PopFont(); - } - else - { - ImGui::TextUnformatted(Name.c_str()); - } - - ImGui::Spring(1); - ImGui::Dummy(ImVec2(0, 28)); - ImGui::Spring(0); + auto input_it = std::find_if(Inputs.begin(), Inputs.end(), [](const auto& in) { return in->Name == "in"; }); + const auto& input = *input_it; - if (GetConfig().IconFont) - { - ImGui::PushFont(std::bit_cast(GetConfig().IconFont.get())); - } + input->Draw(_builder); - const bool should_copy = ImGui::Button(ICON_FA_COPY); + _builder->Middle(); - if (GetConfig().IconFont) + auto data = input->GetData(); + if (!data) { - ImGui::PopFont(); + _builder->End(); + return; } - _builder->EndHeader(); - - auto input_it = std::find_if(Inputs.begin(), Inputs.end(), [](const auto& in) { return in->Name == "in"; }); - const auto& input = *input_it; - input->SetShowLabel(false); - - input->Draw(); + const auto& factory = Node->GetEnv()->GetFactory(); + if (auto texture_data = factory->Convert(data)) + { + Texture& texture = texture_data->Get(); + ImGui::Image(texture.ID, + ImVec2(static_cast(texture.Size.Width), static_cast(texture.Size.Height))); - _builder->Middle(); + _builder->End(); + return; + } - if (auto data = input->GetData()) + std::string data_str = data->ToString(); + if (!data_str.empty()) { - const auto& factory = Node->GetEnv()->GetFactory(); - if (auto texture_data = factory->Convert(data)) - { - Texture& texture = texture_data->Get(); - ImGui::Image(texture.ID, - ImVec2(static_cast(texture.Size.Width), static_cast(texture.Size.Height))); - - if (should_copy) - { - // TODO: Copy image data not implemented - } - _builder->End(); - return; - } - - std::string data_str = data->ToString(); - if (!data_str.empty()) + if (ImGui::Selectable(data_str.c_str(), false, 0, ImGui::CalcTextSize(data_str.c_str()))) { - if (should_copy) - { - ImGui::SetClipboardText(data_str.c_str()); - } - - ImGui::TextUnformatted(data_str.c_str()); + ImGui::SetClipboardText(data_str.c_str()); } } diff --git a/src/FileExplorer.cpp b/src/FileExplorer.cpp index fdf8a62..959d804 100644 --- a/src/FileExplorer.cpp +++ b/src/FileExplorer.cpp @@ -13,7 +13,7 @@ #include #endif -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN std::filesystem::path FileExplorer::Load(const std::filesystem::path& filename, std::string filter_name, std::string filter_types) diff --git a/src/Style.cpp b/src/Style.cpp index 7e213ad..25f900b 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -9,7 +9,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using namespace ax; namespace ed = ax::NodeEditor; @@ -19,7 +19,7 @@ Style::Style() .Default = PortIconType::Circle, .Ref = PortIconType::Diamond, }, - Colours{.TypeColours{ + TypeColours{ {TypeName_v, Colour(120, 120, 127)}, {"flow::Struct", Colour(6, 68, 154)}, @@ -40,39 +40,24 @@ Style::Style() {TypeName_v, Colour(147, 226, 74)}, {TypeName_v, Colour(124, 21, 153)}, - }}, - WindowBorderSize(5.f), - FrameBorderSize(2.f), - TabRounding(8.f), - TabBarBorderSize(0.f), - CellPadding{.Width = 7.f, .Height = 7.f} + } { - auto ed_style = ed::Style{}; - auto& ed_colours = ed_style.Colors; - - int i = 0; - std::for_each(std::begin(ed_colours), std::end(ed_colours), [&](const auto& c) { - Colours.EditorColours[utility::to_EditorColour(ed::StyleColor(i++))] = utility::to_Colour(c); - }); } static Style style{}; Style& GetStyle() { return style; } -void Style::SetTypeColour(std::string_view type, const Colour& colour) -{ - Colours.TypeColours[type] = std::move(colour); -} +void Style::SetTypeColour(std::string_view type, const Colour& colour) { TypeColours[type] = std::move(colour); } Colour Style::GetTypeColour(std::string_view type) const { - if (Colours.TypeColours.contains(type)) + if (TypeColours.contains(type)) { - return Colours.TypeColours.at(type); + return TypeColours.at(type); } - for (const auto& [port_type, colour] : Colours.TypeColours) + for (const auto& [port_type, colour] : TypeColours) { if (type.find(port_type) != std::string_view::npos) { diff --git a/src/Texture.cpp b/src/Texture.cpp index 4eec9a0..c92f638 100644 --- a/src/Texture.cpp +++ b/src/Texture.cpp @@ -14,18 +14,18 @@ namespace { HelloImGui::ImageAbstractPtr CreateImage() { - auto render_backend = flow::ui::GetConfig().RenderBackend; + auto render_backend = HelloImGui::GetRunnerParams()->rendererBackendType; #ifdef HELLOIMGUI_HAS_OPENGL - if (render_backend == flow::ui::RendererBackend::OpenGL3) return std::make_shared(); + if (render_backend == HelloImGui::RendererBackendType::OpenGL3) return std::make_shared(); #endif #if (HELLOIMGUI_HAS_METAL) - if (render_backend == flow::ui::RendererBackend::Metal) return std::make_shared(); + if (render_backend == HelloImGui::RendererBackendType::Metal) return std::make_shared(); #endif #if (HELLOIMGUI_HAS_VULKAN) - if (render_backend == flow::ui::RendererBackend::Vulkan) return std::make_shared(); + if (render_backend == HelloImGui::RendererBackendType::Vulkan) return std::make_shared(); #endif #if (HELLOIMGUI_HAS_DIRECTX11) - if (render_backend == flow::ui::RendererBackend::DirectX11) return std::make_shared(); + if (render_backend == HelloImGui::RendererBackendType::DirectX11) return std::make_shared(); #endif return nullptr; diff --git a/src/ViewFactory.cpp b/src/ViewFactory.cpp index 52d256c..11fe71d 100644 --- a/src/ViewFactory.cpp +++ b/src/ViewFactory.cpp @@ -7,7 +7,7 @@ #undef GetClassName #endif -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN std::shared_ptr ViewFactory::CreateNodeView(flow::SharedNode node) { diff --git a/src/Window.cpp b/src/Window.cpp index e46b518..b7ea12f 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -7,15 +7,16 @@ #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN Window::Window(std::string name) : _name{std::move(name)} {} void Window::Draw() { - widgets::Text("Nothing to show", Colour(175, 175, 175), - widgets::Text::Alignment{widgets::Text::HorizontalAlignment::Centre, - widgets::Text::VerticalAlignment::Centre})(); + widgets::Text("Nothing to show") + .SetColour(Colour(175, 175, 175)) + .SetAlignment(widgets::Text::HorizontalAlignment::Centre, widgets::Text::VerticalAlignment::Middle) + .Draw(); } FLOW_UI_NAMESPACE_END diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp new file mode 100644 index 0000000..c3304e0 --- /dev/null +++ b/src/WindowManager.cpp @@ -0,0 +1,137 @@ +#include "WindowManager.hpp" + +#include +#include +#include + +FLOW_UI_NAMESPACE_BEGIN + +using namespace ax; +namespace ed = ax::NodeEditor; + +void WindowManager::Teardown() +{ + for (auto& window : _windows) + { + window->Teardown(); + } +} + +void WindowManager::AddWindow(std::shared_ptr new_window, const std::string& dockspace, bool visible_by_default) +{ + auto& window = _windows.emplace_back(std::move(new_window)); + + HelloImGui::DockableWindow dockable_window; + dockable_window.label = window->GetName(); + dockable_window.dockSpaceName = dockspace; + dockable_window.GuiFunction = [=] { window->Draw(); }; + dockable_window.imGuiWindowFlags = ImGuiWindowFlags_NoCollapse; + dockable_window.isVisible = visible_by_default; + + window->Init(); + HelloImGui::AddDockableWindow(std::move(dockable_window)); +} + +const std::shared_ptr& WindowManager::CreateGraphWindow(std::string name, const std::shared_ptr& env) +{ + if (name.empty()) + { + name = "untitled##" + std::to_string(_graph_windows.size()); + } + + auto found = std::find_if(_graph_windows.begin(), _graph_windows.end(), + [&](const auto& entry) { return entry.second->GetName() == name; }); + if (found != _graph_windows.end()) return found->second; + + auto graph = std::make_shared(name, env); + auto [graph_view, _] = _graph_windows.emplace(graph->ID(), std::make_shared(graph)); + + HelloImGui::DockableWindow graph_window; + graph_window.label = name; + graph_window.dockSpaceName = DefaultDockspace; + graph_window.GuiFunction = [this, gv = graph_view->second]() { + if (gv->IsActive()) + { + OnActiveGraphChanged.Broadcast(gv->GetGraph()); + } + + gv->Draw(); + }; + graph_window.includeInViewMenu = false; + graph_window.callBeginEnd = false; + graph_window.focusWindowAtNextFrame = true; + graph_window.imGuiWindowFlags = ImGuiWindowFlags_NoCollapse; + + HelloImGui::AddDockableWindow(std::move(graph_window)); + + return graph_view->second; +} + +std::shared_ptr WindowManager::GetActiveGraphWindow() const noexcept +{ + for (const auto& [_, gw] : _graph_windows) + { + if (gw->IsOpen() && gw->IsActive()) + { + return gw; + } + } + + return nullptr; +} + +void WindowManager::CleanupDeadWindows() +{ + for (auto it = _graph_windows.begin(); it != _graph_windows.end();) + { + if (it->second->IsOpen()) + { + ++it; + } + else + { + HelloImGui::RemoveDockableWindow(it->second->GetName()); + it = _graph_windows.erase(it); + } + } +} + +void WindowManager::CloseActiveGraphWindow() +{ + for (auto it = _graph_windows.begin(); it != _graph_windows.end();) + { + if (it->second->IsOpen() && it->second->IsActive()) + { + HelloImGui::RemoveDockableWindow(it->second->GetName()); + it = _graph_windows.erase(it); + break; + } + ++it; + OnActiveGraphChanged.Broadcast(it->second->GetGraph()); + } +} + +void WindowManager::RemoveUnloadedModuleNode(std::string_view class_name) +{ + for (const auto& [_, gw] : _graph_windows) + { + const auto& graph = gw->GetGraph(); + + std::set nodes_to_remove; + graph->Visit([&](const auto& node) { + if (node->GetClass() == class_name) + { + nodes_to_remove.insert(node->ID()); + } + }); + + for (const auto& id : nodes_to_remove) + { + graph->RemoveNodeByID(id); + ed::SetCurrentEditor(reinterpret_cast(gw->GetEditorContext().get())); + ed::DeleteNode(std::hash{}(id)); + } + } +} + +FLOW_UI_NAMESPACE_END diff --git a/src/utilities/Conversions.hpp b/src/utilities/Conversions.hpp index 34a084d..d4d631f 100644 --- a/src/utilities/Conversions.hpp +++ b/src/utilities/Conversions.hpp @@ -8,268 +8,11 @@ #include #include -FLOW_UI_SUBNAMESPACE_START(utility) +FLOW_UI_SUBNAMESPACE_BEGIN(utility) using namespace ax; namespace ed = ax::NodeEditor; -constexpr RendererBackend to_RendererBackend(const HelloImGui::RendererBackendType& backend) -{ - switch (backend) - { - case HelloImGui::RendererBackendType::Null: - return RendererBackend::Null; - case HelloImGui::RendererBackendType::FirstAvailable: - return RendererBackend::FirstAvailable; - case HelloImGui::RendererBackendType::OpenGL3: - return RendererBackend::OpenGL3; - case HelloImGui::RendererBackendType::Metal: - return RendererBackend::Metal; - case HelloImGui::RendererBackendType::Vulkan: - return RendererBackend::Vulkan; - case HelloImGui::RendererBackendType::DirectX11: - return RendererBackend::DirectX11; - case HelloImGui::RendererBackendType::DirectX12: - return RendererBackend::DirectX12; - } - - return RendererBackend::Null; -} - -constexpr ImGuiDir to_ImGuiDir(DockspaceSplitDirection dir) -{ - switch (dir) - { - case DockspaceSplitDirection::Up: - return ImGuiDir_Up; - case DockspaceSplitDirection::Down: - return ImGuiDir_Down; - case DockspaceSplitDirection::Left: - return ImGuiDir_Left; - case DockspaceSplitDirection::Right: - return ImGuiDir_Right; - } - return ImGuiDir_Down; -} - -constexpr ed::StyleColor to_EdStyleColour(const Style::EditorColour& c) -{ - switch (c) - { - case Style::EditorColour::Bg: - return ed::StyleColor_Bg; - case Style::EditorColour::Flow: - return ed::StyleColor_Flow; - case Style::EditorColour::FlowMarker: - return ed::StyleColor_FlowMarker; - case Style::EditorColour::Grid: - return ed::StyleColor_Grid; - case Style::EditorColour::GroupBg: - return ed::StyleColor_GroupBg; - case Style::EditorColour::GroupBorder: - return ed::StyleColor_GroupBorder; - case Style::EditorColour::HighlightLinkBorder: - return ed::StyleColor_HighlightLinkBorder; - case Style::EditorColour::HovLinkBorder: - return ed::StyleColor_HovLinkBorder; - case Style::EditorColour::HovNodeBorder: - return ed::StyleColor_HovNodeBorder; - case Style::EditorColour::LinkSelRect: - return ed::StyleColor_LinkSelRect; - case Style::EditorColour::LinkSelRectBorder: - return ed::StyleColor_LinkSelRectBorder; - case Style::EditorColour::NodeBg: - return ed::StyleColor_NodeBg; - case Style::EditorColour::NodeBorder: - return ed::StyleColor_NodeBorder; - case Style::EditorColour::NodeSelRect: - return ed::StyleColor_NodeSelRect; - case Style::EditorColour::NodeSelRectBorder: - return ed::StyleColor_NodeSelRectBorder; - case Style::EditorColour::PinRect: - return ed::StyleColor_PinRect; - case Style::EditorColour::PinRectBorder: - return ed::StyleColor_PinRectBorder; - case Style::EditorColour::SelLinkBorder: - return ed::StyleColor_SelLinkBorder; - case Style::EditorColour::SelNodeBorder: - return ed::StyleColor_SelNodeBorder; - } - - return ed::StyleColor(0); -} -constexpr Style::EditorColour to_EditorColour(const ed::StyleColor& c) -{ - switch (c) - { - case ed::StyleColor_Bg: - return Style::EditorColour::Bg; - case ed::StyleColor_Flow: - return Style::EditorColour::Flow; - case ed::StyleColor_FlowMarker: - return Style::EditorColour::FlowMarker; - case ed::StyleColor_Grid: - return Style::EditorColour::Grid; - case ed::StyleColor_GroupBg: - return Style::EditorColour::GroupBg; - case ed::StyleColor_GroupBorder: - return Style::EditorColour::GroupBorder; - case ed::StyleColor_HighlightLinkBorder: - return Style::EditorColour::HighlightLinkBorder; - case ed::StyleColor_HovLinkBorder: - return Style::EditorColour::HovLinkBorder; - case ed::StyleColor_HovNodeBorder: - return Style::EditorColour::HovNodeBorder; - case ed::StyleColor_LinkSelRect: - return Style::EditorColour::LinkSelRect; - case ed::StyleColor_LinkSelRectBorder: - return Style::EditorColour::LinkSelRectBorder; - case ed::StyleColor_NodeBg: - return Style::EditorColour::NodeBg; - case ed::StyleColor_NodeBorder: - return Style::EditorColour::NodeBorder; - case ed::StyleColor_NodeSelRect: - return Style::EditorColour::NodeSelRect; - case ed::StyleColor_NodeSelRectBorder: - return Style::EditorColour::NodeSelRectBorder; - case ed::StyleColor_PinRect: - return Style::EditorColour::PinRect; - case ed::StyleColor_PinRectBorder: - return Style::EditorColour::PinRectBorder; - case ed::StyleColor_SelLinkBorder: - return Style::EditorColour::SelLinkBorder; - case ed::StyleColor_SelNodeBorder: - return Style::EditorColour::SelNodeBorder; - default: - return Style::EditorColour(0); - } -} - -constexpr ImGuiCol_ to_ImGuiCol(const Style::BaseColour& c) -{ - switch (c) - { - case Style::BaseColour::Border: - return ImGuiCol_Border; - case Style::BaseColour::BorderShadow: - return ImGuiCol_BorderShadow; - case Style::BaseColour::Button: - return ImGuiCol_Button; - case Style::BaseColour::ButtonActive: - return ImGuiCol_ButtonActive; - case Style::BaseColour::ButtonHovered: - return ImGuiCol_ButtonHovered; - case Style::BaseColour::CheckMark: - return ImGuiCol_CheckMark; - case Style::BaseColour::ChildBg: - return ImGuiCol_ChildBg; - case Style::BaseColour::DockingEmptyBg: - return ImGuiCol_DockingEmptyBg; - case Style::BaseColour::DockingPreview: - return ImGuiCol_DockingPreview; - case Style::BaseColour::DragDropTarget: - return ImGuiCol_DragDropTarget; - case Style::BaseColour::FrameBg: - return ImGuiCol_FrameBg; - case Style::BaseColour::FrameBgActive: - return ImGuiCol_FrameBgActive; - case Style::BaseColour::FrameBgHovered: - return ImGuiCol_FrameBgHovered; - case Style::BaseColour::Header: - return ImGuiCol_Header; - case Style::BaseColour::HeaderActive: - return ImGuiCol_HeaderActive; - case Style::BaseColour::HeaderHovered: - return ImGuiCol_HeaderHovered; - case Style::BaseColour::MenuBarBg: - return ImGuiCol_MenuBarBg; - case Style::BaseColour::ModalWindowDimBg: - return ImGuiCol_ModalWindowDimBg; - case Style::BaseColour::NavCursor: - return ImGuiCol_NavCursor; - case Style::BaseColour::NavWindowingDimBg: - return ImGuiCol_NavWindowingDimBg; - case Style::BaseColour::NavWindowingHighlight: - return ImGuiCol_NavWindowingHighlight; - case Style::BaseColour::PlotHistogram: - return ImGuiCol_PlotHistogram; - case Style::BaseColour::PlotHistogramHovered: - return ImGuiCol_PlotHistogramHovered; - case Style::BaseColour::PlotLines: - return ImGuiCol_PlotLines; - case Style::BaseColour::PlotLinesHovered: - return ImGuiCol_PlotLinesHovered; - case Style::BaseColour::PopupBg: - return ImGuiCol_PopupBg; - case Style::BaseColour::ResizeGrip: - return ImGuiCol_ResizeGrip; - case Style::BaseColour::ResizeGripActive: - return ImGuiCol_ResizeGripActive; - case Style::BaseColour::ResizeGripHovered: - return ImGuiCol_ResizeGripHovered; - case Style::BaseColour::ScrollbarBg: - return ImGuiCol_ScrollbarBg; - case Style::BaseColour::ScrollbarGrab: - return ImGuiCol_ScrollbarGrab; - case Style::BaseColour::ScrollbarGrabActive: - return ImGuiCol_ScrollbarGrabActive; - case Style::BaseColour::ScrollbarGrabHovered: - return ImGuiCol_ScrollbarGrabHovered; - case Style::BaseColour::Separator: - return ImGuiCol_Separator; - case Style::BaseColour::SeparatorActive: - return ImGuiCol_SeparatorActive; - case Style::BaseColour::SeparatorHovered: - return ImGuiCol_SeparatorHovered; - case Style::BaseColour::SliderGrab: - return ImGuiCol_SliderGrab; - case Style::BaseColour::SliderGrabActive: - return ImGuiCol_SliderGrabActive; - case Style::BaseColour::Tab: - return ImGuiCol_Tab; - case Style::BaseColour::TabDimmed: - return ImGuiCol_TabDimmed; - case Style::BaseColour::TabDimmedSelected: - return ImGuiCol_TabDimmedSelected; - case Style::BaseColour::TabDimmedSelectedOverline: - return ImGuiCol_TabDimmedSelectedOverline; - case Style::BaseColour::TabHovered: - return ImGuiCol_TabHovered; - case Style::BaseColour::TableBorderLight: - return ImGuiCol_TableBorderLight; - case Style::BaseColour::TableBorderStrong: - return ImGuiCol_TableBorderStrong; - case Style::BaseColour::TableHeaderBg: - return ImGuiCol_TableHeaderBg; - case Style::BaseColour::TableRowBg: - return ImGuiCol_TableRowBg; - case Style::BaseColour::TableRowBgAlt: - return ImGuiCol_TableRowBgAlt; - case Style::BaseColour::TabSelected: - return ImGuiCol_TabSelected; - case Style::BaseColour::TabSelectedOverline: - return ImGuiCol_TabSelectedOverline; - case Style::BaseColour::Text: - return ImGuiCol_Text; - case Style::BaseColour::TextDisabled: - return ImGuiCol_TextDisabled; - case Style::BaseColour::TextLink: - return ImGuiCol_TextLink; - case Style::BaseColour::TextSelectedBg: - return ImGuiCol_TextSelectedBg; - case Style::BaseColour::TitleBg: - return ImGuiCol_TitleBg; - case Style::BaseColour::TitleBgActive: - return ImGuiCol_TitleBgActive; - case Style::BaseColour::TitleBgCollapsed: - return ImGuiCol_TitleBgCollapsed; - case Style::BaseColour::WindowBg: - return ImGuiCol_WindowBg; - } - - return ImGuiCol_(0); -} - constexpr ImColor to_ImColor(const Colour& c) noexcept { return ImColor(c.R, c.G, c.B, c.A); } constexpr Colour to_Colour(const ImVec4& c) noexcept { @@ -277,22 +20,4 @@ constexpr Colour to_Colour(const ImVec4& c) noexcept static_cast(c.z * 255.f), static_cast(c.w * 255.f)); } -inline ImGuiStyle& to_ImGuiStyle(const Style& style) noexcept -{ - auto& imgui_style = ImGui::GetStyle(); - imgui_style.WindowBorderSize = style.WindowBorderSize; - imgui_style.FrameBorderSize = style.FrameBorderSize; - imgui_style.TabRounding = style.TabRounding; - imgui_style.TabBarBorderSize = style.TabBarBorderSize; - imgui_style.CellPadding = ImVec2(style.CellPadding.Width, style.CellPadding.Height); - - auto& imgui_colours = imgui_style.Colors; - for (auto& [type, colour] : style.Colours.BaseColours) - { - imgui_colours[to_ImGuiCol(type)] = to_ImColor(colour); - } - - return imgui_style; -} - FLOW_UI_SUBNAMESPACE_END diff --git a/src/utilities/Builders.cpp b/src/utilities/NodeBuilder.cpp similarity index 98% rename from src/utilities/Builders.cpp rename to src/utilities/NodeBuilder.cpp index a2c47ff..3d454df 100644 --- a/src/utilities/Builders.cpp +++ b/src/utilities/NodeBuilder.cpp @@ -1,12 +1,12 @@ // Copyright (c) 2024, Cisco Systems, Inc. // All rights reserved. -#include "Builders.hpp" +#include "NodeBuilder.hpp" #include #include -FLOW_UI_SUBNAMESPACE_START(utility) +FLOW_UI_SUBNAMESPACE_BEGIN(utility) void NodeBuilder::Begin(ed::NodeId id) { diff --git a/src/utilities/Builders.hpp b/src/utilities/NodeBuilder.hpp similarity index 97% rename from src/utilities/Builders.hpp rename to src/utilities/NodeBuilder.hpp index 25edb13..33b56aa 100644 --- a/src/utilities/Builders.hpp +++ b/src/utilities/NodeBuilder.hpp @@ -7,7 +7,7 @@ #include -FLOW_UI_SUBNAMESPACE_START(utility) +FLOW_UI_SUBNAMESPACE_BEGIN(utility) namespace ed = ax::NodeEditor; diff --git a/src/utilities/Widgets.cpp b/src/utilities/Widgets.cpp index 9c9ecc6..9885830 100644 --- a/src/utilities/Widgets.cpp +++ b/src/utilities/Widgets.cpp @@ -5,7 +5,7 @@ #include -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) void Icon(const ImVec2& size, PortIconType type, bool filled, const ImVec4& color, const ImVec4& innerColor) { diff --git a/src/utilities/Widgets.hpp b/src/utilities/Widgets.hpp index e674c61..522959d 100644 --- a/src/utilities/Widgets.hpp +++ b/src/utilities/Widgets.hpp @@ -5,6 +5,7 @@ #include "Core.hpp" #include "FileExplorer.hpp" +#include "InputField.hpp" #include "Style.hpp" #include @@ -20,7 +21,7 @@ #include #include -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, PortIconType type, bool filled, ImU32 color, ImU32 innerColor); @@ -53,9 +54,6 @@ static inline bool InputChrono(std::string_view name, D& value, ImGuiInputTextFl return ImGui::IsItemDeactivatedAfterEdit(); } -template -static inline bool InputField(std::string_view name, T& value, ImGuiInputTextFlags flags); - template<> inline bool InputField(std::string_view name, bool& value, ImGuiInputTextFlags) { diff --git a/src/views/ConnectionView.cpp b/src/views/ConnectionView.cpp index bf7e05c..71996c8 100644 --- a/src/views/ConnectionView.cpp +++ b/src/views/ConnectionView.cpp @@ -8,7 +8,7 @@ #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN namespace ed = ax::NodeEditor; diff --git a/src/views/NodeView.cpp b/src/views/NodeView.cpp index 7888454..53734c3 100644 --- a/src/views/NodeView.cpp +++ b/src/views/NodeView.cpp @@ -7,9 +7,10 @@ #include "ConnectionView.hpp" #include "PortView.hpp" #include "ViewFactory.hpp" -#include "utilities/Builders.hpp" #include "utilities/Conversions.hpp" +#include "utilities/NodeBuilder.hpp" #include "utilities/Widgets.hpp" +#include "widgets/Text.hpp" #include #include @@ -22,7 +23,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using namespace ax; namespace ed = ax::NodeEditor; @@ -56,11 +57,15 @@ GraphItemView::~GraphItemView() void GraphItemView::ShowConnectables(const std::shared_ptr&) {} NodeView::NodeView(const flow::SharedNode& node, Colour header_colour) -try : GraphItemView(std::hash{}(node->ID())), NodeID(node->ID()), Name(node->GetName()), - HeaderColour(header_colour), _builder{std::make_shared()} +try + : GraphItemView(std::hash{}(node->ID())), NodeID(node->ID()), Name(node->GetName()), + HeaderColour(header_colour), _builder{std::make_shared()} { - node->OnCompute.Bind("ClearError", [&]() { _received_error = false; }); - node->OnError.Bind("SetError", [&](const std::exception&) { _received_error = true; }); + node->OnCompute.Bind("ClearError", [&]() { _received_error = std::nullopt; }); + node->OnError.Bind("SetError", [&](const std::exception& e) { + _received_error.emplace(e.what()); + SPDLOG_ERROR("{}({}): {}", ID(), Name, _received_error.value()); + }); auto on_input = [this, env = node->GetEnv(), n = node](const auto& key, auto data) { env->AddTask([key, c = std::move(n), d = std::move(data)] { @@ -69,28 +74,25 @@ try : GraphItemView(std::hash{}(node->ID())), NodeID(node->ID()), Na }); }; - std::vector sorted_ports; + std::set sorted_ports; auto ins = node->GetInputPorts(); - std::for_each(ins.begin(), ins.end(), [&](const auto p) { sorted_ports.emplace_back(p.second); }); - std::sort(sorted_ports.begin(), sorted_ports.end(), std::less()); + std::for_each(ins.begin(), ins.end(), [&](const auto p) { sorted_ports.insert(p.second); }); auto view_factory = std::dynamic_pointer_cast(node->GetEnv()->GetFactory()); Inputs.reserve(sorted_ports.size()); for (const auto& input : sorted_ports) { - auto& in = Inputs.emplace_back(std::make_shared(_id, input, view_factory, on_input)); - in->Kind = PortType::Input; - in->SetBuilder(_builder); + auto& in = Inputs.emplace_back(std::make_shared(PortType::Input, _id, input)); + in->SetInputField(view_factory->CreateInputField(input)); + in->OnSetInput = on_input; } Outputs.reserve(node->GetOutputPorts().size()); for (const auto& [_, output] : node->GetOutputPorts()) { - auto& out = Outputs.emplace_back(std::make_shared(_id, output, view_factory, on_input)); - out->Kind = PortType::Output; - out->SetBuilder(_builder); + Outputs.emplace_back(std::make_shared(PortType::Output, _id, output)); } } catch (const std::exception& e) @@ -101,8 +103,6 @@ catch (const std::exception& e) void NodeView::Draw() try { - const auto& name = Name.c_str(); - if (_received_error) { ed::PushStyleColor(ed::StyleColor_NodeBorder, ImColor(227, 36, 27)); @@ -110,38 +110,16 @@ try _builder->Begin(_id); - _builder->Header(utility::to_ImColor(HeaderColour)); - ImGui::Spring(0); - - if (GetConfig().NodeHeaderFont) - { - ImGui::PushFont(std::bit_cast(GetConfig().NodeHeaderFont.get())); - ImGui::TextUnformatted(name); - ImGui::PopFont(); - } - else - { - ImGui::TextUnformatted(name); - } - - ImGui::Spring(1); - ImGui::Dummy(ImVec2(0, 28)); - ImGui::Spring(0); - _builder->EndHeader(); + DrawHeader(); try { ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg) - ImVec4(0.f, 0.f, 0.f, 25.f)); - for (auto& input : Inputs) - { - input->Draw(); - } - for (auto& output : Outputs) - { - output->Draw(); - } + DrawInputs(); + DrawOutputs(); + ImGui::PopStyleColor(); } catch (const std::exception& e) @@ -158,7 +136,7 @@ try } catch (const std::exception& e) { - SPDLOG_ERROR("Encounter and error while trying to draw node: {0}", e.what()); + SPDLOG_ERROR("Encounter and error while trying to draw node'{0}': {1}", std::string(NodeID), e.what()); } void NodeView::ShowConnectables(const std::shared_ptr& new_link_pin) @@ -174,6 +152,35 @@ void NodeView::ShowConnectables(const std::shared_ptr& new_link_pin) } } +void NodeView::DrawHeader() +{ + _builder->Header(utility::to_ImColor(HeaderColour)); + ImGui::Spring(0); + + widgets::Text(Name).SetFont(GetConfig().NodeHeaderFont).Draw(); + + ImGui::Spring(1); + ImGui::Dummy(ImVec2(0, 28)); + ImGui::Spring(0); + _builder->EndHeader(); +} + +void NodeView::DrawInputs() +{ + for (auto& input : Inputs) + { + input->Draw(_builder); + } +} + +void NodeView::DrawOutputs() +{ + for (auto& output : Outputs) + { + output->Draw(_builder); + } +} + SimpleNodeView::SimpleNodeView(const flow::SharedNode& node) : NodeView(node) { for (const auto& input : Inputs) @@ -203,7 +210,7 @@ try { for (auto& input : Inputs) { - input->Draw(); + input->Draw(_builder); } _builder->Middle(); @@ -213,7 +220,7 @@ try for (auto& output : Outputs) { - output->Draw(); + output->Draw(_builder); } } catch (const std::exception& e) diff --git a/src/views/PortView.cpp b/src/views/PortView.cpp index 614b42d..b4e0a35 100644 --- a/src/views/PortView.cpp +++ b/src/views/PortView.cpp @@ -5,7 +5,7 @@ #include "ConnectionView.hpp" #include "ViewFactory.hpp" -#include "utilities/Builders.hpp" +#include "utilities/NodeBuilder.hpp" #include "utilities/Conversions.hpp" #include "utilities/Widgets.hpp" @@ -19,7 +19,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using namespace ax; namespace ed = ax::NodeEditor; @@ -28,8 +28,6 @@ namespace { PortIconType GetIconType(std::string_view type) { - if (type.find("vector") != std::string_view::npos) return PortIconType::Grid; - if (type.find("*") != std::string_view::npos || type.find("unique_ptr") != std::string_view::npos || type.find("&") != std::string_view::npos) { @@ -43,28 +41,22 @@ void DrawPinIcon(const PortView& pin, bool connected, int alpha) { auto colour = pin.GetColour(); colour.A = static_cast(alpha); - widgets::Icon(ImVec2(24.f, 24.f), GetIconType(pin.Type()), connected, utility::to_ImColor(colour), + widgets::Icon(ImVec2(24.f, 24.f), GetIconType(pin.GetType()), connected, utility::to_ImColor(colour), ImColor(32, 32, 32, alpha)); } } // namespace -PortView::PortView(const std::uint64_t& node_id, std::shared_ptr port_data, - const std::shared_ptr& factory, InputEvent input_function, bool show_label) +PortView::PortView(PortType type, const std::uint64_t& node_id, SharedPort port_data) : ID(std::hash{}({})), NodeViewID(node_id), Name(port_data->GetVarName()), _port{std::move(port_data)}, - _show_label{_port->GetKey() != flow::IndexableName::None && show_label}, OnSetInput{input_function} + _show_label{_port->GetKey() != flow::IndexableName::None}, Type(type) { - const auto& input_ctors = factory->GetRegisteredInputTypes(); - if (input_ctors.contains(std::string{Type()})) - { - _input_field = input_ctors.at(std::string{Type()})(Name, _port->GetData()); - } } -void PortView::Draw() +void PortView::Draw(const std::shared_ptr& builder) { - if (Kind == PortType::Input) + if (Type == PortType::Input) { - _builder->Input(ID); + builder->Input(ID); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, _alpha); DrawIcon(_alpha); @@ -78,7 +70,7 @@ void PortView::Draw() ImGui::PopStyleVar(); } - _builder->EndInput(); + builder->EndInput(); if (ImGui::IsItemActive() && !_was_active) { @@ -91,15 +83,15 @@ void PortView::Draw() _was_active = false; } } - else if (Kind == PortType::Output) + else if (Type == PortType::Output) { - _builder->Output(ID); + builder->Output(ID); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, _alpha); ImGui::Spring(1); DrawLabel(); DrawIcon(_alpha); ImGui::PopStyleVar(); - _builder->EndOutput(); + builder->EndOutput(); } } @@ -107,25 +99,28 @@ constexpr std::string_view AnyType = flow::TypeName_v; bool PortView::CanLink(const std::shared_ptr& other) const noexcept { - if (IsConnected() && Kind == PortType::Input || !other || ID == other->ID || Kind == other->Kind || + if (IsConnected() && Type == PortType::Input || !other || ID == other->ID || Type == other->Type || NodeViewID == other->NodeViewID) { return false; } - return Type() == other->Type() || Type() == AnyType || other->Type() == AnyType; + return GetType() == other->GetType() || GetType() == AnyType || other->GetType() == AnyType; } void PortView::DrawInput() try { - if (!_input_field) return; + if (!_input_field) + { + return; + } - (*_input_field)(); + _input_field->Draw(); if (auto new_data = _input_field->GetData()) { - OnSetInput(Key(), std::move(new_data)); + OnSetInput(GetKey(), std::move(new_data)); } } catch (const std::exception& e) @@ -143,16 +138,12 @@ void PortView::DrawLabel() void PortView::DrawIcon(float alpha) { ::flow::ui::DrawPinIcon(*this, IsConnected(), static_cast(alpha * 255)); } -void PortView::SetBuilder(std::shared_ptr builder) noexcept { _builder = std::move(builder); } - void PortView::ShowConnectable(const std::shared_ptr& new_link_pin) { _alpha = ImGui::GetStyle().Alpha; - if (new_link_pin && !CanLink(new_link_pin)) + if (new_link_pin && new_link_pin.get() != this && !CanLink(new_link_pin)) { - if (new_link_pin.get() == this) return; - - _alpha *= (48.0f / 255.0f); + _alpha *= 0.2f; } } diff --git a/src/widgets/InputField.cpp b/src/widgets/InputField.cpp index ba8c103..247e3cd 100644 --- a/src/widgets/InputField.cpp +++ b/src/widgets/InputField.cpp @@ -5,7 +5,7 @@ #include -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) template Input::Input(std::string name, const T& initial_value) @@ -14,7 +14,7 @@ Input::Input(std::string name, const T& initial_value) } template -void Input::operator()() noexcept +void Input::Draw() noexcept { ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(36, 36, 36, 255)); if (InputField>(_name, _value, ImGuiInputTextFlags_AutoSelectAll)) diff --git a/src/widgets/PropertyTree.cpp b/src/widgets/PropertyTree.cpp index 6079cc1..d440735 100644 --- a/src/widgets/PropertyTree.cpp +++ b/src/widgets/PropertyTree.cpp @@ -3,7 +3,7 @@ #include -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) PropertyTree::PropertyTree(const std::string& name, std::size_t columns) : _name(name), _columns(columns) {} @@ -19,7 +19,7 @@ void PropertyTree::AddProperty(const std::string& name, const std::vector -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) Table::Table(const std::string& name, std::size_t columns, std::size_t outer_width, std::size_t outer_height) : _name(name), _columns(columns), _outer_width(outer_width), _outer_height(outer_height) { } -void Table::operator()() noexcept +void Table::Draw() noexcept { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(15.f, 5.f)); ImGui::BeginTable(_name.c_str(), static_cast(_columns), ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, @@ -21,7 +21,7 @@ void Table::operator()() noexcept ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); - (*widget)(); + widget->Draw(); } ImGui::EndTable(); diff --git a/src/widgets/Text.cpp b/src/widgets/Text.cpp index c75bb6b..90903b6 100644 --- a/src/widgets/Text.cpp +++ b/src/widgets/Text.cpp @@ -1,17 +1,18 @@ #include "Text.hpp" #include "utilities/Conversions.hpp" -FLOW_UI_SUBNAMESPACE_START(widgets) +FLOW_UI_SUBNAMESPACE_BEGIN(widgets) -Text::Text(const std::string& text, const Colour& colour, const Alignment& align) - : _text(text), _colour(colour), _align(align) +Text::Text(const std::string& text) + : _text(text), _font(reinterpret_cast(ImGui::GetFont())), _font_size(ImGui::GetFontSize()) { } -void Text::operator()() noexcept +void Text::Draw() noexcept { - const auto window_size = ImGui::GetWindowSize(); - const auto text_size = ImGui::CalcTextSize(_text.c_str()); + const auto original_cursor_pos = ImGui::GetCursorPos(); + const auto region_size = original_cursor_pos + ImGui::GetContentRegionAvail(); + const auto text_size = ImGui::CalcTextSize(_text.c_str()); ImVec2 pos = ImGui::GetCursorPos(); switch (_align.Horizontal) @@ -19,10 +20,10 @@ void Text::operator()() noexcept case HorizontalAlignment::Left: break; case HorizontalAlignment::Centre: - pos.x = (window_size.x - text_size.x) * 0.5f; + pos.x = (region_size.x - text_size.x) * 0.5f; break; case HorizontalAlignment::Right: - pos.x = window_size.x - text_size.x; + pos.x = region_size.x - text_size.x; break; } @@ -30,27 +31,37 @@ void Text::operator()() noexcept { case VerticalAlignment::Top: break; - case VerticalAlignment::Centre: - pos.y = (window_size.y - text_size.y) * 0.5f; + case VerticalAlignment::Middle: + pos.y = (region_size.y - text_size.y) * 0.5f; break; case VerticalAlignment::Bottom: - pos.y = window_size.y - (text_size.y * 2.f); + pos.y = region_size.y - text_size.y; break; } + ImGui::SetCursorPos(pos); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(_colour.R, _colour.G, _colour.B, _colour.A)); - float old_font_size = ImGui::GetFont()->Scale; - ImGui::GetFont()->Scale = _font_size / ImGui::GetFont()->FontSize; - ImGui::PushFont(ImGui::GetFont()); + if (!_font) + { + _font = reinterpret_cast(ImGui::GetFont()); + } + + auto* font = reinterpret_cast(_font); + + float old_font_size = font->Scale; + font->Scale = _font_size / font->FontSize; + ImGui::PushFont(font); ImGui::TextUnformatted(_text.c_str()); - ImGui::GetFont()->Scale = old_font_size; + font->Scale = old_font_size; ImGui::PopFont(); ImGui::PopStyleColor(); + + ImGui::SetCursorPos(original_cursor_pos); } Text& Text::SetColour(const Colour& new_colour) noexcept @@ -65,6 +76,34 @@ Text& Text::SetAlignment(const Alignment& new_align) noexcept return *this; } +Text& Text::SetAlignment(HorizontalAlignment new_halign, VerticalAlignment new_valign) noexcept +{ + _align = {new_halign, new_valign}; + return *this; +} + +Text& Text::SetHorizontalAlignment(HorizontalAlignment new_halign) noexcept +{ + _align.Horizontal = new_halign; + return *this; +} + +Text& Text::SetVerticalAlignment(VerticalAlignment new_valign) noexcept +{ + _align.Vertical = new_valign; + return *this; +} + +Text& Text::SetFont(const std::unique_ptr& font) +{ + if (font) + { + _font = reinterpret_cast(font.get()); + } + + return *this; +} + Text& Text::SetFontSize(float new_size) noexcept { _font_size = new_size; diff --git a/src/windows/GraphWindow.cpp b/src/windows/GraphWindow.cpp index 33280cb..d530a8e 100644 --- a/src/windows/GraphWindow.cpp +++ b/src/windows/GraphWindow.cpp @@ -25,114 +25,11 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ImVec2, x, y); -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using namespace ax; namespace ed = ax::NodeEditor; -void ContextMenu::operator()() noexcept -{ - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5, 5)); - ImGui::PushStyleColor(ImGuiCol_PopupBg, IM_COL32(15, 15, 15, 240)); - - ImGui::SetNextWindowSizeConstraints(ImVec2(300, 300), ImVec2(315, 400)); - if (!ImGui::BeginPopup("Create New Node", ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking)) - { - is_focused = false; - node_lookup = ""; - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - return; - } - - if (node_lookup.empty() && !is_focused) - { - ImGui::SetKeyboardFocusHere(0); - is_focused = true; - } - - ImGui::BeginHorizontal("search"); - - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 20.f); - ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(36, 36, 36, 255)); - - ImGui::SetNextItemAllowOverlap(); - ImGui::InputText("##Search", &node_lookup, 0); - - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - - ImGui::SetCursorPosX((ImGui::GetItemRectMax() - ImGui::GetItemRectMin()).x - 18); - - ImGui::PushFont(std::bit_cast(GetConfig().IconFont.get())); - ImGui::TextUnformatted(ICON_FA_MAGNIFYING_GLASS); - ImGui::PopFont(); - - ImGui::EndHorizontal(); - - auto registered_nodes = _factory->GetCategories(); - - if (!node_lookup.empty()) - { - auto partial_match_func = [&](const auto& entry) -> bool { - auto [_, class_name] = entry; - std::string display_name = _factory->GetFriendlyName(class_name); - std::string filter = node_lookup; - - const auto& to_lower = [](unsigned char c) { return static_cast(std::tolower(c)); }; - std::transform(filter.begin(), filter.end(), filter.begin(), to_lower); - std::transform(class_name.begin(), class_name.end(), class_name.begin(), to_lower); - std::transform(display_name.begin(), display_name.end(), display_name.begin(), to_lower); - - return std::string_view(display_name).find(filter) == std::string_view::npos && - std::string_view(class_name).find(filter) == std::string_view::npos; - }; - - std::erase_if(registered_nodes, partial_match_func); - } - - if (ImGui::BeginChild("Categories")) - { - std::set categories; - for (const auto& [category, _] : registered_nodes) - { - categories.insert(category); - } - - for (const auto& category : categories) - { - DrawPopupCategory(category, registered_nodes); - } - - ImGui::EndChild(); - } - - ImGui::EndPopup(); - - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); -} - -void ContextMenu::DrawPopupCategory(const std::string& category, const flow::CategoryMap& registered_nodes) -{ - if (!ImGui::TreeNodeEx(category.c_str(), node_lookup.empty() ? 0 : ImGuiTreeNodeFlags_DefaultOpen)) return; - - auto [begin_it, end_it] = registered_nodes.equal_range(category); - for (auto it = begin_it; it != end_it; ++it) - { - auto class_name = it->second; - auto display_name = _factory->GetFriendlyName(class_name); - - ImGui::Bullet(); - if (ImGui::MenuItem(display_name.c_str())) - { - OnSelection(class_name, display_name); - break; - } - } - ImGui::TreePop(); -} - namespace { inline void DrawLabel(const char* label, ImColor color) @@ -206,91 +103,53 @@ constexpr GraphWindow::ActionType operator&(const GraphWindow::ActionType& a, co } GraphWindow::GraphWindow(std::shared_ptr graph) - : Window(graph->GetName()), _graph{std::move(graph)}, _node_creation_context_menu{GetEnv()->GetFactory()} + : Window(graph->GetName()), _graph{std::move(graph)}, _node_creation_menu{GetEnv()} { - ed::Config config; - config.UserPointer = this; - config.EnableSmoothZoom = true; - config.SettingsFile = ""; - config.CanvasSizeMode = ed::CanvasSizeMode::CenterOnly; - - config.SaveNodeSettings = []([[maybe_unused]] ed::NodeId nodeId, [[maybe_unused]] const char* data, - [[maybe_unused]] std::size_t size, ed::SaveReasonFlags reason, - void* userPointer) -> bool { - if (reason == ed::SaveReasonFlags::None) - { - SPDLOG_TRACE("Nothing happened during node save."); - return true; - } - - GraphWindow* self = std::bit_cast(userPointer); + Configure(); + SetStyle(); - if ((reason & ed::SaveReasonFlags::Position) == ed::SaveReasonFlags::Position || - (reason & ed::SaveReasonFlags::AddNode) == ed::SaveReasonFlags::AddNode || - (reason & ed::SaveReasonFlags::RemoveNode) == ed::SaveReasonFlags::RemoveNode || - (reason & ed::SaveReasonFlags::Size) == ed::SaveReasonFlags::Size || - (reason & ed::SaveReasonFlags::User) == ed::SaveReasonFlags::User) - { - self->MarkDirty(true); - } - - return true; + _node_creation_menu.SetActiveGraph(_graph); + _node_creation_menu.OnSelection = [this](const auto& class_name, const auto& display_name) { + CreateNode(class_name, display_name); + ImGui::CloseCurrentPopup(); }; - _editor_ctx = std::unique_ptr(std::bit_cast(ed::CreateEditor(&config))); - - // Set initial view state - ed::SetCurrentEditor(std::bit_cast(_editor_ctx.get())); - ed::NavigateToContent(0.0f); // Navigate without animation - - auto& ed_style = GetEditorDetailContext(GetEditorContext())->GetStyle(); - ed_style.NodeBorderWidth = 0.5f; - ed_style.FlowDuration = 1.f; - - auto& ed_colours = ed_style.Colors; - auto& colours = GetStyle().Colours.EditorColours; - - std::for_each(std::begin(colours), std::end(colours), [&](const auto& p) { - const auto& [i, c] = p; - ed_colours[utility::to_EdStyleColour(i)] = utility::to_ImColor(c); - }); - - _node_creation_context_menu.OnSelection = - [this, factory = std::dynamic_pointer_cast(GetEnv()->GetFactory())](const auto& class_name, - const auto& display_name) { - CreateNode(class_name, display_name); - ImGui::CloseCurrentPopup(); - }; - _graph->OnNodeAdded.Bind("CreateNodeView", [this](const auto& n) { const auto factory = std::dynamic_pointer_cast(GetEnv()->GetFactory()); auto node_view = factory->CreateNodeView(n); _item_views.emplace(node_view->ID(), node_view); + ed::SetNodePosition(node_view->ID(), {_open_popup_position.x, _open_popup_position.y}); - if (auto start_pin = _new_node_link_pin) + if (!_new_node_link_pin) { - auto& pins = start_pin->Kind == PortType::Input ? node_view->Outputs : node_view->Inputs; - for (auto& pin : pins) + return; + } + + auto start_pin = _new_node_link_pin; + auto& pins = start_pin->Type == PortType::Input ? node_view->Outputs : node_view->Inputs; + for (auto& pin : pins) + { + if (!start_pin->CanLink(pin) && !(factory->IsConvertible(start_pin->GetType(), pin->GetType()) || + factory->IsConvertible(pin->GetType(), start_pin->GetType()))) { - if (!start_pin->CanLink(pin) && !(factory->IsConvertible(start_pin->Type(), pin->Type()) || - factory->IsConvertible(pin->Type(), start_pin->Type()))) - { - continue; - } + continue; + } - auto end_pin = pin; - if (start_pin->Kind == PortType::Input) std::swap(start_pin, end_pin); + auto end_pin = pin; + if (start_pin->Type == PortType::Input) + { + std::swap(start_pin, end_pin); + } - const auto& start_node = FindNode(start_pin->NodeViewID); - const auto& end_node = FindNode(end_pin->NodeViewID); - const auto& conn = _graph->ConnectNodes(start_node->NodeID, IndexableName{start_pin->Name}, - end_node->NodeID, IndexableName{end_pin->Name}); + const auto& start_node = FindNode(start_pin->NodeViewID); + const auto& end_node = FindNode(end_pin->NodeViewID); + const auto& conn = _graph->ConnectNodes(start_node->NodeID, IndexableName{start_pin->Name}, + end_node->NodeID, IndexableName{end_pin->Name}); - _links.emplace(std::hash{}(conn->ID()), - ConnectionView{conn->ID(), start_pin->ID, end_pin->ID, start_pin->GetColour()}); - break; - } + _links.emplace(std::hash{}(conn->ID()), + ConnectionView{conn->ID(), start_pin->ID, end_pin->ID, start_pin->GetColour()}); + break; } }); @@ -304,20 +163,22 @@ GraphWindow::~GraphWindow() _links.clear(); - if (ed::GetCurrentEditor() == std::bit_cast(_editor_ctx.get())) + auto* ctx = std::bit_cast(_editor_ctx.get()); + if (ed::GetCurrentEditor() == ctx) { ed::SetCurrentEditor(nullptr); } - ed::DestroyEditor(std::bit_cast(_editor_ctx.get())); + ed::DestroyEditor(ctx); _editor_ctx.reset(); } void GraphWindow::SetCurrentGraph() { - if (std::bit_cast(_editor_ctx.get()) != ed::GetCurrentEditor()) + auto* ctx = std::bit_cast(_editor_ctx.get()); + if (ctx != ed::GetCurrentEditor()) { - ed::SetCurrentEditor(std::bit_cast(_editor_ctx.get())); + ed::SetCurrentEditor(ctx); } } @@ -342,7 +203,7 @@ try SetCurrentGraph(); ed::Begin(_graph->GetName().c_str()); - auto cursorTopLeft = ImGui::GetCursorScreenPos(); + const auto cursor_top_left = ImGui::GetCursorScreenPos(); CreateItems(); @@ -353,10 +214,8 @@ try std::string class_name = reinterpret_cast(payload->Data); std::string display_name = GetEnv()->GetFactory()->GetFriendlyName(class_name); - _graph->OnNodeAdded.Bind( - "SetPos", [](auto&& n) { ed::SetNodePosition(std::hash{}(n->ID()), ImGui::GetMousePos()); }); - CreateNode(class_name, display_name); - _graph->OnNodeAdded.Unbind("SetPos"); + const auto& node = CreateNode(class_name, display_name); + ed::SetNodePosition(std::hash{}(node->ID()), ImGui::GetMousePos()); } ImGui::EndDragDropTarget(); @@ -378,7 +237,7 @@ try } } - ImGui::SetCursorScreenPos(cursorTopLeft); + ImGui::SetCursorScreenPos(cursor_top_left); ed::Suspend(); @@ -478,7 +337,13 @@ try ImGui::EndPopup(); } - _node_creation_context_menu(); + if (ImGui::BeginPopup("Create New Node", ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking)) + { + ImGui::SetNextWindowSizeConstraints(ImVec2(300, 300), ImVec2(315, 400)); + + _node_creation_menu.Draw(); + ImGui::EndPopup(); + } ImGui::PopStyleColor(); ImGui::PopStyleVar(); @@ -545,6 +410,58 @@ catch (...) EndDraw(); } +void GraphWindow::Configure() +{ + ed::Config config; + config.UserPointer = this; + config.EnableSmoothZoom = true; + config.SettingsFile = ""; + config.CanvasSizeMode = ed::CanvasSizeMode::CenterOnly; + + config.SaveNodeSettings = []([[maybe_unused]] ed::NodeId nodeId, [[maybe_unused]] const char* data, + [[maybe_unused]] std::size_t size, ed::SaveReasonFlags reason, + void* userPointer) -> bool { + if (reason == ed::SaveReasonFlags::None) + { + SPDLOG_TRACE("Nothing happened during node save."); + return true; + } + + GraphWindow* self = std::bit_cast(userPointer); + + if ((reason & ed::SaveReasonFlags::Position) == ed::SaveReasonFlags::Position || + (reason & ed::SaveReasonFlags::AddNode) == ed::SaveReasonFlags::AddNode || + (reason & ed::SaveReasonFlags::RemoveNode) == ed::SaveReasonFlags::RemoveNode || + (reason & ed::SaveReasonFlags::Size) == ed::SaveReasonFlags::Size || + (reason & ed::SaveReasonFlags::User) == ed::SaveReasonFlags::User) + { + self->MarkDirty(true); + } + + return true; + }; + + _editor_ctx = std::unique_ptr(std::bit_cast(ed::CreateEditor(&config))); +} + +void GraphWindow::SetStyle() +{ + auto& style = GetEditorDetailContext(GetEditorContext())->GetStyle(); + style.NodeBorderWidth = 0.5f; + style.FlowDuration = 1.f; + + auto& colours = style.Colors; + colours[ed::StyleColor_Bg] = ImColor(38, 38, 38); + colours[ed::StyleColor_Grid] = ImColor(52, 52, 52); + colours[ed::StyleColor_NodeBg] = ImColor(15, 17, 15, 240); + colours[ed::StyleColor_NodeBorder] = ImColor(0, 0, 0); + colours[ed::StyleColor_SelNodeBorder] = ImColor(255, 255, 255); + colours[ed::StyleColor_Flow] = ImColor(32, 191, 85); + colours[ed::StyleColor_FlowMarker] = ImColor(32, 191, 85); + colours[ed::StyleColor_HighlightLinkBorder] = ImColor(0, 188, 235); + colours[ed::StyleColor_SelLinkBorder] = ImColor(0, 188, 235); +} + void GraphWindow::EndDraw() { if (_active) @@ -558,11 +475,6 @@ void GraphWindow::EndDraw() std::shared_ptr GraphWindow::FindNode(std::uint64_t id) const { - if (!id) - { - throw std::invalid_argument("Node ID cannot be null"); - } - if (_item_views.contains(id)) { return std::dynamic_pointer_cast(_item_views.at(id)); @@ -571,23 +483,10 @@ std::shared_ptr GraphWindow::FindNode(std::uint64_t id) const return nullptr; } -ConnectionView& GraphWindow::FindConnection(std::uint64_t id) -{ - if (!id) - { - throw std::invalid_argument("Link ID cannot be null"); - } - - return _links.at(id); -} +ConnectionView& GraphWindow::FindConnection(std::uint64_t id) { return _links.at(id); } std::shared_ptr GraphWindow::FindPort(std::uint64_t id) const { - if (!id) - { - throw std::invalid_argument("Pin ID cannot be null"); - } - for (auto& [_, item] : _item_views) { auto node = std::dynamic_pointer_cast(item); @@ -618,11 +517,6 @@ std::shared_ptr GraphWindow::FindPort(std::uint64_t id) const std::shared_ptr GraphWindow::FindComment(std::uint64_t id) const { - if (!id) - { - throw std::invalid_argument("Comment ID cannot be null"); - } - if (_item_views.contains(id)) { return std::dynamic_pointer_cast(_item_views.at(id)); @@ -633,35 +527,40 @@ std::shared_ptr GraphWindow::FindComment(std::uint64_t id) const void GraphWindow::DeleteNode(std::uint64_t id) { - if (!_item_views.contains(id)) return; + if (!_item_views.contains(id)) + { + return; + } const auto& node = std::dynamic_pointer_cast(_item_views.at(id)); - if (node) + if (!node) { - std::vector links_to_delete; - for (const auto& [link_id, link] : _links) - { - if (std::any_of(node->Inputs.begin(), node->Inputs.end(), - [&, end_pin_id = link.EndPortID](const auto& in) { return in->ID == end_pin_id; })) - { - links_to_delete.push_back(link_id); - } + return; + } - if (std::any_of(node->Outputs.begin(), node->Outputs.end(), - [&, start_pin_id = link.StartPortID](const auto& out) { return out->ID == start_pin_id; })) - { - links_to_delete.push_back(link_id); - } + std::vector links_to_delete; + for (const auto& [link_id, link] : _links) + { + if (std::any_of(node->Inputs.begin(), node->Inputs.end(), + [&, end_pin_id = link.EndPortID](const auto& in) { return in->ID == end_pin_id; })) + { + links_to_delete.push_back(link_id); } - for (const auto& link_id : links_to_delete) + if (std::any_of(node->Outputs.begin(), node->Outputs.end(), + [&, start_pin_id = link.StartPortID](const auto& out) { return out->ID == start_pin_id; })) { - DeleteLink(link_id); + links_to_delete.push_back(link_id); } + } - _graph->RemoveNodeByID(node->NodeID); + for (const auto& link_id : links_to_delete) + { + DeleteLink(link_id); } + _graph->RemoveNodeByID(node->NodeID); + _item_views.erase(id); } @@ -680,7 +579,7 @@ bool GraphWindow::DeleteLink(std::uint64_t id) auto start_node = FindNode(start_pin->NodeViewID); auto end_node = FindNode(end_pin->NodeViewID); - _graph->DisconnectNodes(start_node->NodeID, start_pin->Key(), end_node->NodeID, end_pin->Key()); + _graph->DisconnectNodes(start_node->NodeID, start_pin->GetKey(), end_node->NodeID, end_pin->GetKey()); return _links.erase(id) != 0; } @@ -712,7 +611,7 @@ void GraphWindow::CreateItems() { _new_link_pin = start_pin; - if (start_pin->Kind == PortType::Input) + if (start_pin->Type == PortType::Input) { std::swap(start_pin, end_pin); std::swap(start_pin_id, end_pin_id); @@ -722,12 +621,12 @@ void GraphWindow::CreateItems() { ed::RejectNewItem(ImColor(255, 0, 0), 2.0f); } - else if (end_pin->Kind == start_pin->Kind) + else if (end_pin->Type == start_pin->Type) { - DrawLabel("x Incompatible Pin Kind", ImColor(45, 32, 32, 180)); + DrawLabel("x Incompatible Pin Type", ImColor(45, 32, 32, 180)); ed::RejectNewItem(ImColor(255, 0, 0), 2.0f); } - else if (!GetEnv()->GetFactory()->IsConvertible(start_pin->Type(), end_pin->Type())) + else if (!GetEnv()->GetFactory()->IsConvertible(start_pin->GetType(), end_pin->GetType())) { DrawLabel("x Incompatible Pin Type", ImColor(45, 32, 32, 180)); ed::RejectNewItem(ImColor(255, 128, 128), 1.0f); @@ -735,8 +634,8 @@ void GraphWindow::CreateItems() else { std::string label = "+ Create Link"; - if (start_pin->Type() != end_pin->Type() && - GetEnv()->GetFactory()->IsConvertible(start_pin->Type(), end_pin->Type())) + if (start_pin->GetType() != end_pin->GetType() && + GetEnv()->GetFactory()->IsConvertible(start_pin->GetType(), end_pin->GetType())) { label = "+ Create Converting Link"; } diff --git a/src/windows/ModuleInfo.hpp b/src/windows/ModuleInfo.hpp index bcc4ade..5ae451c 100644 --- a/src/windows/ModuleInfo.hpp +++ b/src/windows/ModuleInfo.hpp @@ -6,7 +6,7 @@ #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN struct ModuleInfo { diff --git a/src/windows/ModuleManagerWindow.cpp b/src/windows/ModuleManagerWindow.cpp index 1905cdf..8c54e6e 100644 --- a/src/windows/ModuleManagerWindow.cpp +++ b/src/windows/ModuleManagerWindow.cpp @@ -14,7 +14,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN const std::string module_file_extension = "flowmod"; #ifdef FLOW_WINDOWS @@ -29,12 +29,12 @@ class ModuleView : public Widget { public: ModuleView(const std::filesystem::path& name, std::shared_ptr env) - : _binary_path(name), _enabled(name.filename().replace_extension("").string(), true) + : _binary_path(name), _loaded_checkbox(name.filename().replace_extension("").string(), true) { _module = std::make_shared(_binary_path, env->GetFactory()); } - virtual void operator()() noexcept + virtual void Draw() noexcept { const std::string& name = _module->GetName(); const std::string& version = "Version: " + _module->GetVersion(); @@ -42,12 +42,12 @@ class ModuleView : public Widget ImGui::TableNextColumn(); - _enabled(); + _loaded_checkbox.Draw(); ImGui::TableNextColumn(); ImGui::BeginHorizontal(("module_" + name).c_str()); - widgets::Text{name}.SetFontSize(20.f)(); + widgets::Text{name}.SetFontSize(20.f).Draw(); auto pos_x = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(author.length() > version.length() ? author.c_str() : version.c_str()).x + @@ -60,14 +60,14 @@ class ModuleView : public Widget ImGui::BeginVertical("version/author"); constexpr Colour version_author_colour{150, 150, 150}; - widgets::Text{version}.SetFontSize(20.f).SetColour(version_author_colour)(); - widgets::Text{author}.SetFontSize(18.f).SetColour(version_author_colour)(); + widgets::Text{version}.SetFontSize(20.f).SetColour(version_author_colour).Draw(); + widgets::Text{author}.SetFontSize(18.f).SetColour(version_author_colour).Draw(); ImGui::EndVertical(); ImGui::EndHorizontal(); - if (auto data = _enabled.GetData()) + if (auto data = _loaded_checkbox.GetData()) { - if (_enabled.GetValue()) + if (_loaded_checkbox.GetValue()) { _module->Load(_binary_path); } @@ -81,7 +81,7 @@ class ModuleView : public Widget private: std::filesystem::path _binary_path; std::shared_ptr _module; - widgets::Input _enabled; + widgets::Input _loaded_checkbox; }; ModuleManagerWindow::ModuleManagerWindow(std::shared_ptr env, const std::filesystem::path& modules_path) @@ -150,7 +150,7 @@ void ModuleManagerWindow::Draw() for (const auto& [_, w] : _widgets) { - (*w)(); + w->Draw(); } ImGui::EndTable(); diff --git a/src/windows/NewModuleWindow.cpp b/src/windows/NewModuleWindow.cpp index 7414833..386924d 100644 --- a/src/windows/NewModuleWindow.cpp +++ b/src/windows/NewModuleWindow.cpp @@ -12,7 +12,7 @@ using namespace std::string_literals; -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN std::string replace_all(std::string str, const std::string& from, const std::string& to) { @@ -180,7 +180,7 @@ try new_module_form.AddEntry(std::make_shared("Dependencies")); new_module_form.AddEntry(dependencies); - new_module_form(); + new_module_form.Draw(); bool created = false; ImGui::BeginHorizontal("buttons"); diff --git a/src/windows/NodeExplorerWindow.cpp b/src/windows/NodeExplorerWindow.cpp index f4c95d6..0907780 100644 --- a/src/windows/NodeExplorerWindow.cpp +++ b/src/windows/NodeExplorerWindow.cpp @@ -8,7 +8,7 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using namespace ax; namespace ed = ax::NodeEditor; @@ -91,14 +91,22 @@ void NodeExplorerWindow::DrawPopupCategory(const std::string& category, const fl auto [begin_it, end_it] = registered_nodes.equal_range(category); for (auto it = begin_it; it != end_it; ++it) { + const auto class_name = it->second; const auto display_name = _env->GetFactory()->GetFriendlyName(it->second); ImGui::Bullet(); - ImGui::Selectable(display_name.c_str()); + if (ImGui::Selectable(display_name.c_str())) + { + if (OnSelection != nullptr) + { + OnSelection(class_name, display_name); + ImGui::TreePop(); + return; + } + } if (ImGui::BeginDragDropSource()) { - const auto class_name = it->second; ImGui::Text("+ Create Node"); ImGui::SetDragDropPayload("NewNode", class_name.c_str(), class_name.size() + 1, ImGuiCond_Once); ImGui::EndDragDropSource(); diff --git a/src/windows/PropertyWindow.cpp b/src/windows/PropertyWindow.cpp index 5c54b29..f0ca1ae 100644 --- a/src/windows/PropertyWindow.cpp +++ b/src/windows/PropertyWindow.cpp @@ -14,41 +14,19 @@ #include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN using namespace ax; namespace ed = ax::NodeEditor; -constexpr Colour empty_property_text_colour = Colour(175, 175, 175); - -struct CentredText : public widgets::Text -{ - CentredText(const std::string& text, const Colour& c) - : Text(text, c, {widgets::Text::HorizontalAlignment::Centre, widgets::Text::VerticalAlignment::Centre}) - { - } - - virtual ~CentredText() = default; -}; - -PropertyWindow::PropertyWindow(std::shared_ptr env) : Window(PropertyWindow::Name), _env{env} {} - -struct NodeProperty -{ - std::string Name; - flow::SharedNode Node; -}; +PropertyWindow::PropertyWindow() : Window(PropertyWindow::Name) {} void PropertyWindow::Draw() { - auto env = _env.lock(); - if (!env) return; - auto graph = _graph.lock(); if (!GetEditorContext() || !graph) { - CentredText("Nothing to show", empty_property_text_colour)(); - return; + return Window::Draw(); } ed::SetCurrentEditor(std::bit_cast(GetEditorContext().get())); @@ -58,25 +36,20 @@ void PropertyWindow::Draw() if (result == 0) { - CentredText("Select one or more nodes", empty_property_text_colour)(); + widgets::Text("Select one or more nodes") + .SetColour(Colour(175, 175, 175)) + .SetAlignment(widgets::Text::HAlignment::Centre, widgets::Text::VAlignment::Middle) + .Draw(); return; } std::set ids(selected_ids.begin(), std::next(selected_ids.begin(), result)); - std::vector nodes; - nodes.reserve(result); - graph->Visit([&](auto& node) { if (!ids.contains(std::hash{}(node->ID()))) return; - nodes.emplace_back(node); - }); - for (auto& node : nodes) - { - - std::string c_name = node->GetName() + "##" + std::to_string(std::hash{}(node->ID())); - widgets::PropertyTree properties(c_name, 2); + const std::string node_tree_name = node->GetName() + "##" + std::string(node->ID()); + widgets::PropertyTree properties(node_tree_name, 2); const auto make_port_data_property = [&](const auto& port) -> std::vector> { return { @@ -99,8 +72,8 @@ void PropertyWindow::Draw() properties.AddProperty(key_name, make_port_data_property(output), "Outputs"); } - properties(); - } + properties.Draw(); + }); } FLOW_UI_NAMESPACE_END diff --git a/src/windows/ShortcutsWindow.cpp b/src/windows/ShortcutsWindow.cpp index b66f1f3..6207a8f 100644 --- a/src/windows/ShortcutsWindow.cpp +++ b/src/windows/ShortcutsWindow.cpp @@ -4,61 +4,54 @@ #include "widgets/Text.hpp" #include +#include #include -FLOW_UI_NAMESPACE_START +FLOW_UI_NAMESPACE_BEGIN -std::shared_ptr ShorcutTextBuilder(std::initializer_list keys) +std::shared_ptr ShorcutTextBuilder(ImGuiKeyChord chord) { - std::string text = ""; - for (auto it = keys.begin(); it != std::prev(keys.end()); ++it) - { - text += std::string(ImGui::GetKeyName(*it)) + " + "; - } - text += ImGui::GetKeyName(*std::prev(keys.end())); - - return std::make_shared(text); + return std::make_shared(ImGui::GetKeyChordName(chord)); } -void AddShortcutText(widgets::Table& table, const std::string& name, std::initializer_list keys, +void AddShortcutText(widgets::Table& table, const std::string& name, ImGuiKeyChord keys, const Colour& colour = Colour()) { - table.AddEntry(std::make_shared(name, colour)); + table.AddEntry(std::make_shared(widgets::Text(name).SetColour(colour))); table.AddEntry(ShorcutTextBuilder(keys)); } -auto window_shortcuts = widgets::Table("Window Shortcuts", 2); -auto graph_shortcuts = widgets::Table("Graph Shortcuts", 2); - -ShortcutsWindow::ShortcutsWindow() : Window("Shortcuts") -{ - AddShortcutText(window_shortcuts, "New Flow", {ImGuiKey_LeftCtrl, ImGuiKey_N}); - AddShortcutText(window_shortcuts, "Open Flow", {ImGuiKey_LeftCtrl, ImGuiKey_O}); - AddShortcutText(window_shortcuts, "Save Flow", {ImGuiKey_LeftCtrl, ImGuiKey_S}); - AddShortcutText(window_shortcuts, "Save Flow As", {ImGuiKey_LeftCtrl, ImGuiKey_LeftAlt, ImGuiKey_S}); - AddShortcutText(window_shortcuts, "Close Flow", {ImGuiKey_LeftCtrl, ImGuiKey_W}); - - AddShortcutText(graph_shortcuts, "Break Link", {ImGuiKey_LeftAlt, ImGuiKey_MouseLeft}); - AddShortcutText(graph_shortcuts, "Copy", {ImGuiKey_LeftCtrl, ImGuiKey_C}); - AddShortcutText(graph_shortcuts, "Cut (Experimental)", {ImGuiKey_LeftCtrl, ImGuiKey_X}, Colour(244, 129, 36)); - AddShortcutText(graph_shortcuts, "Duplicate", {ImGuiKey_LeftCtrl, ImGuiKey_D}); - AddShortcutText(graph_shortcuts, "Paste", {ImGuiKey_LeftCtrl, ImGuiKey_V}); - AddShortcutText(graph_shortcuts, "Delete Selection", {ImGuiKey_Delete}); - AddShortcutText(graph_shortcuts, "Focus", {ImGuiKey_F}); - AddShortcutText(graph_shortcuts, "Undo (Experimental)", {ImGuiKey_LeftCtrl, ImGuiKey_Z}, Colour(244, 129, 36)); - AddShortcutText(graph_shortcuts, "Redo (Experimental)", {ImGuiKey_LeftCtrl, ImGuiKey_Y}, Colour(244, 129, 36)); -} +ShortcutsWindow::ShortcutsWindow() : Window("Shortcuts") {} void ShortcutsWindow::Draw() { - widgets::Text("Window Shortcuts")(); + auto window_shortcuts = widgets::Table("Window Shortcuts", 2); + auto graph_shortcuts = widgets::Table("Graph Shortcuts", 2); + + AddShortcutText(window_shortcuts, "New Flow", ImGuiMod_Ctrl | ImGuiKey_N); + AddShortcutText(window_shortcuts, "Open Flow", ImGuiMod_Ctrl | ImGuiKey_O); + AddShortcutText(window_shortcuts, "Save Flow", ImGuiMod_Ctrl | ImGuiKey_S); + AddShortcutText(window_shortcuts, "Save Flow As", ImGuiMod_Ctrl | ImGuiMod_Alt | ImGuiKey_S); + AddShortcutText(window_shortcuts, "Close Flow", ImGuiMod_Ctrl | ImGuiKey_W); + + AddShortcutText(graph_shortcuts, "Break Link", ImGuiMod_Alt | ImGuiKey_MouseLeft); + AddShortcutText(graph_shortcuts, "Copy", ImGuiMod_Ctrl | ImGuiKey_C); + AddShortcutText(graph_shortcuts, "Cut (Experimental)", ImGuiMod_Ctrl | ImGuiKey_X, Colour(244, 129, 36)); + AddShortcutText(graph_shortcuts, "Duplicate", ImGuiMod_Ctrl | ImGuiKey_D); + AddShortcutText(graph_shortcuts, "Paste", ImGuiMod_Ctrl | ImGuiKey_V); + AddShortcutText(graph_shortcuts, "Delete Selection", ImGuiKey_Delete); + AddShortcutText(graph_shortcuts, "Focus", ImGuiKey_F); + AddShortcutText(graph_shortcuts, "Undo (Experimental)", ImGuiMod_Ctrl | ImGuiKey_Z, Colour(244, 129, 36)); + AddShortcutText(graph_shortcuts, "Redo (Experimental)", ImGuiMod_Ctrl | ImGuiKey_Y, Colour(244, 129, 36)); + + widgets::Text("Window Shortcuts").Draw(); ImGui::Separator(); - window_shortcuts(); + window_shortcuts.Draw(); - widgets::Text("Graph Shortcuts")(); + widgets::Text("Graph Shortcuts").Draw(); ImGui::Separator(); - graph_shortcuts(); + graph_shortcuts.Draw(); } FLOW_UI_NAMESPACE_END