From 798c88562d1a6b1859c8c231f378cfca63a01b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Fri, 16 Feb 2024 01:28:54 -0500 Subject: [PATCH 01/27] feat(GraphView): node search 1/N --- src/nodable/gui/GraphView.cpp | 33 +++++++++++++++++++++++++++++---- src/nodable/gui/GraphView.h | 9 +++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 43f8ab768..72c70a58a 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -74,14 +74,15 @@ bool GraphView::draw() */ auto draw_invokable_menu = [&]( const SlotView* dragged_slot_view, - const std::string& _key) -> void + const std::string& _key, + const std::multimap& _items) -> void { char menuLabel[255]; snprintf( menuLabel, 255, ICON_FA_CALCULATOR" %s", _key.c_str()); if (ImGui::BeginMenu(menuLabel)) { - auto range = m_contextual_menus.equal_range(_key); + auto range = _items.equal_range(_key); for (auto it = range.first; it != range.second; it++) { FunctionMenuItem menu_item = it->second; @@ -438,8 +439,32 @@ bool GraphView::draw() if ( !dragged_slot ) { - draw_invokable_menu( dragged_slot, k_operator_menu_label ); - draw_invokable_menu( dragged_slot, k_function_menu_label ); + char search_input[255] = "\0"; + static std::multimap filtered_items = m_contextual_menus; + ImGui::SetKeyboardFocusHere(); + if ( ImGui::InputText("Search", search_input, 255) ) + { + // Update contextual menu items from search_input + filtered_items.clear(); + size_t count = 0; + for ( auto& each : m_contextual_menus) + { + if( each.second.label.compare(0, strlen( search_input ), search_input ) == 0 ) + { + filtered_items.insert(each); + if (count < 5) + { + if ( ImGui::Button(each.second.label.c_str())) + { + new_node_id = each.second.create_node_fct()->poolid(); + } + } + count++; + } + } + } + draw_invokable_menu( dragged_slot, k_operator_menu_label, filtered_items ); + draw_invokable_menu( dragged_slot, k_function_menu_label, filtered_items ); ImGui::Separator(); } diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index f16c14297..1cb8dea7e 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -19,11 +19,12 @@ namespace ndbl class Nodable; class Graph; - typedef struct { - std::string label; + class FunctionMenuItem { + public: + std::string label; std::function(void)> create_node_fct; - const fw::func_type* function_signature; - } FunctionMenuItem; + const fw::func_type* function_signature = nullptr; + }; class GraphView: public fw::View { From b97527e4b4fcb65a167289d3e410d5074b29a15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Fri, 23 Feb 2024 01:21:54 -0500 Subject: [PATCH 02/27] feat(GraphView): node search 2/N --- src/nodable/gui/GraphView.cpp | 451 +++++++++++++++++++--------------- src/nodable/gui/GraphView.h | 17 +- 2 files changed, 266 insertions(+), 202 deletions(-) diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 72c70a58a..5dfb69083 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -37,6 +37,7 @@ GraphView::GraphView(Graph* graph) : fw::View() , m_graph(graph) , m_new_node_desired_position(-1, -1) + , m_focus_search_input(false) { const Nodlang& language = Nodlang::get_instance(); for (auto& each_fct : language.get_api()) @@ -56,128 +57,103 @@ GraphView::GraphView(Graph* graph) } } -bool GraphView::draw() -{ - bool changed = false; - bool pixel_perfect = true; - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - Nodable & app = Nodable::get_instance(); - const bool enable_edition = app.virtual_machine.is_program_stopped(); - PoolID new_node_id; - ImVec2 origin = ImGui::GetCursorScreenPos(); - auto node_registry = Pool::get_pool()->get( m_graph->get_node_registry() ); - const SlotView* dragged_slot = SlotView::get_dragged(); - const SlotView* hovered_slot = SlotView::get_hovered(); - - /* +/* * Function to draw an invocable menu (operators or functions) */ - auto draw_invokable_menu = [&]( - const SlotView* dragged_slot_view, - const std::string& _key, - const std::multimap& _items) -> void +PoolID GraphView::draw_menu(const std::string& _key) +{ + char menu_label[255]; + snprintf( menu_label, 255, ICON_FA_CALCULATOR" %s", _key.c_str()); + + if (ImGui::BeginMenu( menu_label )) { - char menuLabel[255]; - snprintf( menuLabel, 255, ICON_FA_CALCULATOR" %s", _key.c_str()); + SlotView* dragged_slot = SlotView::get_dragged(); - if (ImGui::BeginMenu(menuLabel)) - { - auto range = _items.equal_range(_key); - for (auto it = range.first; it != range.second; it++) - { - FunctionMenuItem menu_item = it->second; + auto range = m_contextual_menus.equal_range(_key); + for (auto it = range.first; it != range.second; it++) + { + FunctionMenuItem menu_item = it->second; - /* - * First we determine if the current menu_item points to a function with compatible signature. - */ - bool has_compatible_signature; + /* + * First we determine if the current menu_item points to a function with compatible signature. + */ + bool has_compatible_signature; + + if ( !dragged_slot ) + { + has_compatible_signature = true; + } + else if ( !dragged_slot->is_this() ) + { + const fw::type* dragged_property_type = dragged_slot->get_property_type(); - if ( !dragged_slot ) + if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) { - has_compatible_signature = true; + has_compatible_signature = menu_item.function_signature->has_an_arg_of_type(dragged_property_type); } - else if ( !dragged_slot->is_this() ) + else { - const fw::type* dragged_property_type = dragged_slot->get_property_type(); - - if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) - { - has_compatible_signature = menu_item.function_signature->has_an_arg_of_type(dragged_property_type); - } - else - { - has_compatible_signature = menu_item.function_signature->get_return_type()->equals(dragged_property_type); - } + has_compatible_signature = menu_item.function_signature->get_return_type()->equals(dragged_property_type); } + } - /* + /* * Then, since we know signature compatibility, we add or not a new MenuItem. */ - if ( has_compatible_signature && ImGui::MenuItem( menu_item.label.c_str() )) + if ( has_compatible_signature && ImGui::MenuItem( menu_item.label.c_str() )) + { + if ( menu_item.create_node_fct ) { - if ( menu_item.create_node_fct ) - { - new_node_id = menu_item.create_node_fct()->poolid(); - } - else - { - LOG_WARNING("GraphView", "The function associated to the key %s is nullptr", - menu_item.label.c_str()) - } + m_new_node_id = menu_item.create_node_fct(); + } + else + { + LOG_WARNING("GraphView", "The function associated to the key %s is nullptr", + menu_item.label.c_str()) } } + } - ImGui::EndMenu(); - } - }; + ImGui::EndMenu(); + } - auto create_variable = [&](const fw::type* _type, const char* _name, PoolID _scope) -> PoolID - { - if( !_scope) - { - _scope = m_graph->get_root()->get_component(); - } + return m_new_node_id; +}; - PoolID var_node = m_graph->create_variable(_type, _name, _scope ); - var_node->set_declared(true); +PoolID GraphView::create_variable(const fw::type* _type, const char* _name, PoolID _scope) +{ + if( !_scope) + { + _scope = m_graph->get_root()->get_component(); + } - Token token(Token_t::keyword_operator, " = "); - token.m_word_start_pos = 1; - token.m_word_size = 1; + PoolID var_node = m_graph->create_variable(_type, _name, _scope ); + var_node->set_declared(true); - var_node->assignment_operator_token = token; - return var_node; - }; + Token token(Token_t::keyword_operator, " = "); + token.m_word_start_pos = 1; + token.m_word_size = 1; - /* - Grid - Draw X vertical and Y horizontal lines every grid_size pixels - */ - const int grid_size = app.config.ui_graph_grid_size; - const int grid_subdiv_size = app.config.ui_graph_grid_size / app.config.ui_graph_grid_subdivs; - const int vertical_line_count = int(m_screen_space_content_region.GetSize().x) / grid_subdiv_size; - const int horizontal_line_count = int(m_screen_space_content_region.GetSize().y) / grid_subdiv_size; - ImColor grid_color = app.config.ui_graph_grid_color_major; - ImColor grid_color_light = app.config.ui_graph_grid_color_minor; + var_node->assignment_operator_token = token; + return var_node; +} - for(int coord = 0; coord <= vertical_line_count; ++coord) - { - float pos = m_screen_space_content_region.GetTL().x + float(coord) * grid_subdiv_size; - const ImVec2 line_start{pos, m_screen_space_content_region.GetTL().y}; - const ImVec2 line_end{pos, m_screen_space_content_region.GetBL().y}; - bool is_major = coord % app.config.ui_graph_grid_subdivs == 0; - draw_list->AddLine(line_start, line_end, is_major ? grid_color : grid_color_light); - } +bool GraphView::draw() +{ + m_new_node_id.reset(); - for(int coord = 0; coord <= horizontal_line_count; ++coord) - { - float pos = m_screen_space_content_region.GetTL().y + float(coord) * grid_subdiv_size; - const ImVec2 line_start{m_screen_space_content_region.GetTL().x, pos}; - const ImVec2 line_end{m_screen_space_content_region.GetBR().x, pos}; - bool is_major = coord % app.config.ui_graph_grid_subdivs == 0; - draw_list->AddLine(line_start, line_end, is_major ? grid_color : grid_color_light); - } + bool changed = false; + bool pixel_perfect = true; + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + Nodable & app = Nodable::get_instance(); + const bool enable_edition = app.virtual_machine.is_program_stopped(); + ImVec2 origin = ImGui::GetCursorScreenPos(); + auto node_registry = Pool::get_pool()->get( m_graph->get_node_registry() ); + const SlotView* dragged_slot = SlotView::get_dragged(); + const SlotView* hovered_slot = SlotView::get_hovered(); + // Draw grid in the background + draw_grid( draw_list, app.config ); /* Draw Code Flow. @@ -203,7 +179,7 @@ bool GraphView::draw() continue; } - for( auto adjacent_slot : slot->adjacent() ) + for( const auto& adjacent_slot : slot->adjacent() ) { Node* each_successor_node = adjacent_slot->get_node(); NodeView* each_successor_view = NodeView::substitute_with_parent_if_not_visible( each_successor_node->get_component().get() ); @@ -211,7 +187,7 @@ bool GraphView::draw() if ( each_successor_view && each_view->is_visible() && each_successor_view->is_visible() ) { ImRect start = each_view->get_slot_rect( *slot, app.config, slot_index ); - ImRect end = each_successor_view->get_slot_rect( *adjacent_slot.get(), app.config, 0 );// there is only 1 previous slot + ImRect end = each_successor_view->get_slot_rect( *adjacent_slot, app.config, 0 );// there is only 1 previous slot fw::ImGuiEx::DrawVerticalWire( ImGui::GetWindowDrawList(), @@ -264,11 +240,7 @@ bool GraphView::draw() // Need a need node ? if (require_new_node) { - if (!ImGui::IsPopupOpen(k_context_menu_popup) ) - { - ImGui::OpenPopup(k_context_menu_popup); - m_new_node_desired_position = ImGui::GetMousePos() - origin; - } + open_popup_context_menu(); } } @@ -431,123 +403,103 @@ bool GraphView::draw() Mouse right-click popup menu */ - if ( enable_edition && !isAnyNodeHovered && ImGui::BeginPopupContextWindow(k_context_menu_popup) ) + if ( enable_edition && !isAnyNodeHovered && ImGui::IsMouseClicked(1)) { + open_popup_context_menu(); + } + + if ( ImGui::BeginPopup(k_context_menu_popup) ) + { + m_new_node_desired_position = ImGui::GetMousePos() - origin; + // Title : fw::ImGuiEx::ColoredShadowedText( ImVec2( 1, 1 ), ImColor( 0.00f, 0.00f, 0.00f, 1.00f ), ImColor( 1.00f, 1.00f, 1.00f, 0.50f ), "Create new node :" ); ImGui::Separator(); + if ( draw_search_input( 10 ) ) + { + ImGui::CloseCurrentPopup(); + } + if ( !dragged_slot ) { - char search_input[255] = "\0"; - static std::multimap filtered_items = m_contextual_menus; - ImGui::SetKeyboardFocusHere(); - if ( ImGui::InputText("Search", search_input, 255) ) - { - // Update contextual menu items from search_input - filtered_items.clear(); - size_t count = 0; - for ( auto& each : m_contextual_menus) - { - if( each.second.label.compare(0, strlen( search_input ), search_input ) == 0 ) - { - filtered_items.insert(each); - if (count < 5) - { - if ( ImGui::Button(each.second.label.c_str())) - { - new_node_id = each.second.create_node_fct()->poolid(); - } - } - count++; - } - } - } - draw_invokable_menu( dragged_slot, k_operator_menu_label, filtered_items ); - draw_invokable_menu( dragged_slot, k_function_menu_label, filtered_items ); + draw_menu( k_operator_menu_label ); + draw_menu( k_function_menu_label ); ImGui::Separator(); } - - if ( dragged_slot ) + else { SlotFlags slot_type = dragged_slot->slot().type(); - switch ( slot_type ) + if ( slot_type == SlotFlag_TYPE_CODEFLOW) { - case SlotFlag_TYPE_CODEFLOW: - { - if ( ImGui::MenuItem( ICON_FA_CODE " Condition" ) ) - new_node_id = m_graph->create_cond_struct(); - if ( ImGui::MenuItem( ICON_FA_CODE " For Loop" ) ) - new_node_id = m_graph->create_for_loop(); - if ( ImGui::MenuItem( ICON_FA_CODE " While Loop" ) ) - new_node_id = m_graph->create_while_loop(); + if ( ImGui::MenuItem( ICON_FA_CODE " Condition" ) ) + m_new_node_id = m_graph->create_cond_struct(); + if ( ImGui::MenuItem( ICON_FA_CODE " For Loop" ) ) + m_new_node_id = m_graph->create_for_loop(); + if ( ImGui::MenuItem( ICON_FA_CODE " While Loop" ) ) + m_new_node_id = m_graph->create_while_loop(); - ImGui::Separator(); + ImGui::Separator(); - if ( ImGui::MenuItem( ICON_FA_CODE " Scope" ) ) - new_node_id = m_graph->create_scope(); + if ( ImGui::MenuItem( ICON_FA_CODE " Scope" ) ) + m_new_node_id = m_graph->create_scope(); - ImGui::Separator(); + ImGui::Separator(); - if ( ImGui::MenuItem( ICON_FA_CODE " Program" ) ) - { - m_graph->clear(); - new_node_id = m_graph->create_root(); - } - break; + if ( ImGui::MenuItem( ICON_FA_CODE " Program" ) ) + { + m_graph->clear(); + m_new_node_id = m_graph->create_root(); } - default: + } + else if ( !dragged_slot->is_this() ) + { + if ( ImGui::MenuItem( ICON_FA_DATABASE " Variable" ) ) { - if ( !dragged_slot->is_this() ) - { - if ( ImGui::MenuItem( ICON_FA_DATABASE " Variable" ) ) - { - new_node_id = create_variable( dragged_slot->get_property_type(), "var", {} ); - } - - // we allow literal only if connected to variables. - // why? behavior when connecting a literal to a non var node is to digest it. - if ( dragged_slot->get_node()->get_type()->is() && ImGui::MenuItem( ICON_FA_FILE "Literal" ) ) - { - new_node_id = m_graph->create_literal( dragged_slot->get_property_type() ); - } - } - else - { - if ( ImGui::BeginMenu( "Variable" ) ) - { - if ( ImGui::MenuItem( ICON_FA_DATABASE " Boolean" ) ) - new_node_id = create_variable( fw::type::get(), "var", {} ); + m_new_node_id = create_variable( dragged_slot->get_property_type(), "var", {} ); + } + + // we allow literal only if connected to variables. + // why? behavior when connecting a literal to a non var node is to digest it. + if ( dragged_slot->get_node()->get_type()->is() && ImGui::MenuItem( ICON_FA_FILE "Literal" ) ) + { + m_new_node_id = m_graph->create_literal( dragged_slot->get_property_type() ); + } + } + else + { + if ( ImGui::BeginMenu( "Variable" ) ) + { + if ( ImGui::MenuItem( ICON_FA_DATABASE " Boolean" ) ) + m_new_node_id = create_variable( fw::type::get(), "var", {} ); - if ( ImGui::MenuItem( ICON_FA_DATABASE " Double" ) ) - new_node_id = create_variable( fw::type::get(), "var", {} ); + if ( ImGui::MenuItem( ICON_FA_DATABASE " Double" ) ) + m_new_node_id = create_variable( fw::type::get(), "var", {} ); - if ( ImGui::MenuItem( ICON_FA_DATABASE " Int (16bits)" ) ) - new_node_id = create_variable( fw::type::get(), "var", {} ); + if ( ImGui::MenuItem( ICON_FA_DATABASE " Int (16bits)" ) ) + m_new_node_id = create_variable( fw::type::get(), "var", {} ); - if ( ImGui::MenuItem( ICON_FA_DATABASE " String" ) ) - new_node_id = create_variable( fw::type::get(), "var", {} ); + if ( ImGui::MenuItem( ICON_FA_DATABASE " String" ) ) + m_new_node_id = create_variable( fw::type::get(), "var", {} ); - ImGui::EndMenu(); - } + ImGui::EndMenu(); + } - if ( ImGui::BeginMenu( "Literal" ) ) - { - if ( ImGui::MenuItem( ICON_FA_FILE " Boolean" ) ) - new_node_id = m_graph->create_literal( fw::type::get() ); + if ( ImGui::BeginMenu( "Literal" ) ) + { + if ( ImGui::MenuItem( ICON_FA_FILE " Boolean" ) ) + m_new_node_id = m_graph->create_literal( fw::type::get() ); - if ( ImGui::MenuItem( ICON_FA_FILE " Double" ) ) - new_node_id = m_graph->create_literal( fw::type::get() ); + if ( ImGui::MenuItem( ICON_FA_FILE " Double" ) ) + m_new_node_id = m_graph->create_literal( fw::type::get() ); - if ( ImGui::MenuItem( ICON_FA_FILE " Int (16bits)" ) ) - new_node_id = m_graph->create_literal( fw::type::get() ); + if ( ImGui::MenuItem( ICON_FA_FILE " Int (16bits)" ) ) + m_new_node_id = m_graph->create_literal( fw::type::get() ); - if ( ImGui::MenuItem( ICON_FA_FILE " String" ) ) - new_node_id = m_graph->create_literal( fw::type::get() ); + if ( ImGui::MenuItem( ICON_FA_FILE " String" ) ) + m_new_node_id = m_graph->create_literal( fw::type::get() ); - ImGui::EndMenu(); - } - } + ImGui::EndMenu(); } } } @@ -556,14 +508,14 @@ bool GraphView::draw() * In case user has created a new node we need to connect it to the m_graph depending * on if a slot is being dragged and what is its nature. */ - if ( new_node_id ) + if ( m_new_node_id ) { // dragging node slot ? if ( dragged_slot ) { SlotFlags complementary_flags = flip_order( dragged_slot->slot().static_flags() ); - Slot* complementary_slot = new_node_id->find_slot_by_property_type( complementary_flags, dragged_slot->get_property()->get_type() ); + Slot* complementary_slot = m_new_node_id->find_slot_by_property_type( complementary_flags, dragged_slot->get_property()->get_type() ); ConnectFlags connect_flags = ConnectFlag_ALLOW_SIDE_EFFECTS; Slot* out = &dragged_slot->slot(); @@ -575,16 +527,17 @@ bool GraphView::draw() SlotView::reset_dragged(); } - else if (new_node_id != m_graph->get_root() && app.config.experimental_graph_autocompletion ) + else if (m_new_node_id != m_graph->get_root() && app.config.experimental_graph_autocompletion ) { m_graph->ensure_has_root(); // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); } - // set new_node's view position - if( PoolID view = new_node_id->get_component() ) + // set new_node's view position, select it + if( PoolID view = m_new_node_id->get_component() ) { view->set_position(m_new_node_desired_position, fw::Space_Local); + NodeView::set_selected(view); } } @@ -604,6 +557,34 @@ bool GraphView::draw() return changed; } +void GraphView::draw_grid( ImDrawList* draw_list, const Config& config ) const +{ + const int grid_size = config.ui_graph_grid_size; + const int grid_subdiv_size = config.ui_graph_grid_size / config.ui_graph_grid_subdivs; + const int vertical_line_count = int( m_screen_space_content_region.GetSize().x) / grid_subdiv_size; + const int horizontal_line_count = int( m_screen_space_content_region.GetSize().y) / grid_subdiv_size; + ImColor grid_color = config.ui_graph_grid_color_major; + ImColor grid_color_light = config.ui_graph_grid_color_minor; + + for(int coord = 0; coord <= vertical_line_count; ++coord) + { + float pos = m_screen_space_content_region.GetTL().x + float(coord) * float(grid_subdiv_size); + const ImVec2 line_start{pos, m_screen_space_content_region.GetTL().y}; + const ImVec2 line_end{pos, m_screen_space_content_region.GetBL().y}; + bool is_major = coord % config.ui_graph_grid_subdivs == 0; + draw_list->AddLine(line_start, line_end, is_major ? grid_color : grid_color_light); + } + + for(int coord = 0; coord <= horizontal_line_count; ++coord) + { + float pos = m_screen_space_content_region.GetTL().y + float(coord) * float(grid_subdiv_size); + const ImVec2 line_start{ m_screen_space_content_region.GetTL().x, pos}; + const ImVec2 line_end{ m_screen_space_content_region.GetBR().x, pos}; + bool is_major = coord % config.ui_graph_grid_subdivs == 0; + draw_list->AddLine(line_start, line_end, is_major ? grid_color : grid_color_light); + } +} + void GraphView::add_contextual_menu_item( const std::string &_category, const std::string &_label, @@ -734,3 +715,75 @@ void GraphView::translate_view(ImVec2 delta) // TODO: implement a better solution, storing an offset. And then substract it in draw(); // m_view_origin += delta; } + +PoolID GraphView::draw_search_input( size_t _result_max_count ) +{ + if (m_focus_search_input) + { + ImGui::SetKeyboardFocusHere(); + m_focus_search_input = false; + } + + // Filter contextual menu items depending on m_search_input (search by label) + if ( ImGui::InputText("Search", m_search_input, 255, ImGuiInputTextFlags_EscapeClearsAll)) + { + m_filtered_contextual_menus.clear(); + if ( m_search_input[0] != '\0' ) + { + for ( auto& [_, menu_item] : m_contextual_menus ) + { + if( menu_item.label.find( m_search_input ) != std::string::npos ) + { + m_filtered_contextual_menus.push_back(menu_item); + if ( m_filtered_contextual_menus.size() == _result_max_count ) + { + break; + } + } + } + } + } + + if ( !m_filtered_contextual_menus.empty() ) + { + + // When a single item is filtered, pressing enter will press the item's button. + if ( m_filtered_contextual_menus.size() == 1) + { + if ( ImGui::SmallButton( m_filtered_contextual_menus[0].label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) + { + m_new_node_id = m_filtered_contextual_menus[0].create_node_fct(); + } + } + else + { + // Otherwise, user has to move with arrow keys and press enter to trigger the highlighted button. + for ( auto& menu_item : m_filtered_contextual_menus ) + { + if ( ImGui::SmallButton( menu_item.label.c_str()) || // User can click on the button... + (ImGui::IsKeyDown( ImGuiKey_Enter ) && ImGui::IsItemFocused() ) // ...or press enter if this item is the first + ) + { + m_new_node_id = menu_item.create_node_fct(); + } + } + } + } + else if ( m_search_input[0] != '\0' ) + { + ImGui::Text("No matches..."); + } + + return m_new_node_id; +} + +void GraphView::open_popup_context_menu() +{ + if (!ImGui::IsPopupOpen(k_context_menu_popup) ) + { + ImGui::OpenPopup(k_context_menu_popup); + m_focus_search_input = true; + m_search_input[0] = '\0'; + m_filtered_contextual_menus.clear(); + } +} diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index 1cb8dea7e..dc98ad445 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -10,8 +10,10 @@ #include "core/Component.h" // base class #include "core/IScope.h" -#include "types.h" +#include "Config.h" #include "NodeViewConstraint.h" +#include "core/Scope.h" +#include "types.h" namespace ndbl { @@ -47,17 +49,26 @@ namespace ndbl void unfold(); // unfold the graph until it is stabilized private: - void frame_views(const std::vector &_views, bool _align_top_left_corner); - void translate_view(ImVec2 vec2); + PoolID draw_menu(const std::string& _menu_key); + void draw_grid( ImDrawList*, const Config& ) const; + PoolID draw_search_input( size_t _result_max_count ); // Search input filtering nodes as short clickable list + void frame_views(const std::vector &_views, bool _align_top_left_corner); + void translate_view(ImVec2 vec2); + PoolID create_variable(const fw::type* _type, const char* _name, PoolID _scope); Graph* m_graph; ImVec2 m_view_origin; + bool m_focus_search_input; ImVec2 m_new_node_desired_position; + char m_search_input[255] = "\0"; + PoolID m_new_node_id; // contains the last id created, cleared each frame std::multimap m_contextual_menus; + std::vector m_filtered_contextual_menus; static constexpr const char* k_context_menu_popup = "GraphView.ContextMenu"; static constexpr const char* k_operator_menu_label = "Operators"; static constexpr const char* k_function_menu_label = "Functions"; REFLECT_DERIVED_CLASS() + void open_popup_context_menu(); }; } \ No newline at end of file From 09e703286842a47e3c434ce6880ed3f9535da9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sat, 24 Feb 2024 19:38:12 -0500 Subject: [PATCH 03/27] feat(GraphView): node search 3/N --- src/nodable/gui/GraphView.cpp | 265 ++++++++++++---------------------- src/nodable/gui/GraphView.h | 15 +- 2 files changed, 98 insertions(+), 182 deletions(-) diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 5dfb69083..db0da65c3 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -2,6 +2,7 @@ #include #include // std::shared_ptr +#include #include #include "core/types.h" #include "fw/core/log.h" @@ -37,89 +38,46 @@ GraphView::GraphView(Graph* graph) : fw::View() , m_graph(graph) , m_new_node_desired_position(-1, -1) - , m_focus_search_input(false) -{ + , m_search_input_should_init(false) +{ + + // Prepare context menu items + + // 1) Blocks + add_contextual_menu_item( "1", ICON_FA_CODE " Condition", "condition", [&]() { return m_graph->create_cond_struct(); } ); + add_contextual_menu_item( "1", ICON_FA_CODE " For Loop", "for loop", [&]() { return m_graph->create_for_loop(); } ); + add_contextual_menu_item( "1", ICON_FA_CODE " While Loop", "while loop", [&]() { return m_graph->create_while_loop(); } ); + add_contextual_menu_item( "1", ICON_FA_CODE " Scope", "scope", [&]() { return m_graph->create_scope(); } ); + add_contextual_menu_item( "1", ICON_FA_CODE " Program", "program scope", [&]() { m_graph->clear(); return m_graph->create_root(); } ); + // 2) Variables + add_contextual_menu_item( "2", ICON_FA_DATABASE " Boolean Variable", "boolean variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + add_contextual_menu_item( "2", ICON_FA_DATABASE " Double Variable", "double variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + add_contextual_menu_item( "2", ICON_FA_DATABASE " Integer Variable", "integer variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + add_contextual_menu_item( "2", ICON_FA_DATABASE " String Variable", "string variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + // 3) Literals + add_contextual_menu_item( "3", ICON_FA_FILE " Boolean Literal", "boolean literal", [&]() { return m_graph->create_literal( fw::type::get() ); } ); + add_contextual_menu_item( "3", ICON_FA_FILE " Double Literal", "double float literal", [&]() { return m_graph->create_literal( fw::type::get() ); } ); + add_contextual_menu_item( "3", ICON_FA_FILE " Integer Literal", "integer literal", [&]() { return m_graph->create_literal( fw::type::get()); } ); + add_contextual_menu_item( "3", ICON_FA_FILE " String Literal", "string literal", [&]() { return m_graph->create_literal( fw::type::get()); } ); + // 4) Functions/Operators from the API const Nodlang& language = Nodlang::get_instance(); for (auto& each_fct : language.get_api()) { - const fw::func_type* type = each_fct->get_type(); - bool is_operator = language.find_operator_fct(type) != nullptr; - - auto create_node = [this, each_fct, is_operator]() -> PoolID + const fw::func_type* func_type = each_fct->get_type(); + bool is_operator = Nodlang::get_instance().find_operator_fct( func_type ) != nullptr; + auto create_node = [&]() -> PoolID { return m_graph->create_function(each_fct.get(), is_operator); }; std::string label; - language.serialize_func_sig(label, type); - std::string category = is_operator ? k_operator_menu_label : k_function_menu_label; - add_contextual_menu_item(category, label, create_node, type); + language.serialize_func_sig(label, func_type ); + std::string search_target_string = func_type->get_identifier(); + search_target_string.append(is_operator ? " operator" : " function"); + add_contextual_menu_item("4", label, search_target_string, create_node, func_type ); } } -/* - * Function to draw an invocable menu (operators or functions) - */ -PoolID GraphView::draw_menu(const std::string& _key) -{ - char menu_label[255]; - snprintf( menu_label, 255, ICON_FA_CALCULATOR" %s", _key.c_str()); - - if (ImGui::BeginMenu( menu_label )) - { - SlotView* dragged_slot = SlotView::get_dragged(); - - auto range = m_contextual_menus.equal_range(_key); - for (auto it = range.first; it != range.second; it++) - { - FunctionMenuItem menu_item = it->second; - - /* - * First we determine if the current menu_item points to a function with compatible signature. - */ - bool has_compatible_signature; - - if ( !dragged_slot ) - { - has_compatible_signature = true; - } - else if ( !dragged_slot->is_this() ) - { - const fw::type* dragged_property_type = dragged_slot->get_property_type(); - - if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) - { - has_compatible_signature = menu_item.function_signature->has_an_arg_of_type(dragged_property_type); - } - else - { - has_compatible_signature = menu_item.function_signature->get_return_type()->equals(dragged_property_type); - } - } - - /* - * Then, since we know signature compatibility, we add or not a new MenuItem. - */ - if ( has_compatible_signature && ImGui::MenuItem( menu_item.label.c_str() )) - { - if ( menu_item.create_node_fct ) - { - m_new_node_id = menu_item.create_node_fct(); - } - else - { - LOG_WARNING("GraphView", "The function associated to the key %s is nullptr", - menu_item.label.c_str()) - } - } - } - - ImGui::EndMenu(); - } - - return m_new_node_id; -}; - PoolID GraphView::create_variable(const fw::type* _type, const char* _name, PoolID _scope) { if( !_scope) @@ -421,89 +379,6 @@ bool GraphView::draw() ImGui::CloseCurrentPopup(); } - if ( !dragged_slot ) - { - draw_menu( k_operator_menu_label ); - draw_menu( k_function_menu_label ); - ImGui::Separator(); - } - else - { - SlotFlags slot_type = dragged_slot->slot().type(); - if ( slot_type == SlotFlag_TYPE_CODEFLOW) - { - if ( ImGui::MenuItem( ICON_FA_CODE " Condition" ) ) - m_new_node_id = m_graph->create_cond_struct(); - if ( ImGui::MenuItem( ICON_FA_CODE " For Loop" ) ) - m_new_node_id = m_graph->create_for_loop(); - if ( ImGui::MenuItem( ICON_FA_CODE " While Loop" ) ) - m_new_node_id = m_graph->create_while_loop(); - - ImGui::Separator(); - - if ( ImGui::MenuItem( ICON_FA_CODE " Scope" ) ) - m_new_node_id = m_graph->create_scope(); - - ImGui::Separator(); - - if ( ImGui::MenuItem( ICON_FA_CODE " Program" ) ) - { - m_graph->clear(); - m_new_node_id = m_graph->create_root(); - } - } - else if ( !dragged_slot->is_this() ) - { - if ( ImGui::MenuItem( ICON_FA_DATABASE " Variable" ) ) - { - m_new_node_id = create_variable( dragged_slot->get_property_type(), "var", {} ); - } - - // we allow literal only if connected to variables. - // why? behavior when connecting a literal to a non var node is to digest it. - if ( dragged_slot->get_node()->get_type()->is() && ImGui::MenuItem( ICON_FA_FILE "Literal" ) ) - { - m_new_node_id = m_graph->create_literal( dragged_slot->get_property_type() ); - } - } - else - { - if ( ImGui::BeginMenu( "Variable" ) ) - { - if ( ImGui::MenuItem( ICON_FA_DATABASE " Boolean" ) ) - m_new_node_id = create_variable( fw::type::get(), "var", {} ); - - if ( ImGui::MenuItem( ICON_FA_DATABASE " Double" ) ) - m_new_node_id = create_variable( fw::type::get(), "var", {} ); - - if ( ImGui::MenuItem( ICON_FA_DATABASE " Int (16bits)" ) ) - m_new_node_id = create_variable( fw::type::get(), "var", {} ); - - if ( ImGui::MenuItem( ICON_FA_DATABASE " String" ) ) - m_new_node_id = create_variable( fw::type::get(), "var", {} ); - - ImGui::EndMenu(); - } - - if ( ImGui::BeginMenu( "Literal" ) ) - { - if ( ImGui::MenuItem( ICON_FA_FILE " Boolean" ) ) - m_new_node_id = m_graph->create_literal( fw::type::get() ); - - if ( ImGui::MenuItem( ICON_FA_FILE " Double" ) ) - m_new_node_id = m_graph->create_literal( fw::type::get() ); - - if ( ImGui::MenuItem( ICON_FA_FILE " Int (16bits)" ) ) - m_new_node_id = m_graph->create_literal( fw::type::get() ); - - if ( ImGui::MenuItem( ICON_FA_FILE " String" ) ) - m_new_node_id = m_graph->create_literal( fw::type::get() ); - - ImGui::EndMenu(); - } - } - } - /* * In case user has created a new node we need to connect it to the m_graph depending * on if a slot is being dragged and what is its nature. @@ -586,12 +461,16 @@ void GraphView::draw_grid( ImDrawList* draw_list, const Config& config ) const } void GraphView::add_contextual_menu_item( - const std::string &_category, - const std::string &_label, + const std::string& _category, + const std::string& _label, + std::string _search_target_string, std::function(void)> _function, - const fw::func_type *_signature) + const fw::func_type * _signature) { - m_contextual_menus.insert( {_category, {_label, _function, _signature }} ); + // Prepare a lower case string for search purposes + std::transform(_search_target_string.begin(), _search_target_string.end(), _search_target_string.begin(), [](unsigned char c){ return std::tolower(c); }); + + m_contextual_menus.insert( {_category, {_label, _search_target_string, std::move(_function), _signature }} ); } bool GraphView::update(float delta_time, i16_t subsample_count) @@ -718,24 +597,55 @@ void GraphView::translate_view(ImVec2 delta) PoolID GraphView::draw_search_input( size_t _result_max_count ) { - if (m_focus_search_input) + if ( m_search_input_should_init ) { + m_search_input_should_init = false; ImGui::SetKeyboardFocusHere(); - m_focus_search_input = false; + + // On init, we filter the functions/operators matching with the currently dragged slot + m_context_menu_with_compatible_signature.clear(); + SlotView* dragged_slot = SlotView::get_dragged(); + if ( !dragged_slot ) + { + for (auto& [_, menu_item] : m_contextual_menus) + { + m_context_menu_with_compatible_signature.push_back( menu_item ); + } + } + else + { + for (auto& [_, menu_item] : m_contextual_menus) + { + if ( !dragged_slot->is_this() ) + { + const fw::type* dragged_property_type = dragged_slot->get_property_type(); + + bool has_compatible_signature = + ! menu_item.function_signature || + dragged_slot->allows( SlotFlag_ORDER_FIRST ) && menu_item.function_signature->has_an_arg_of_type(dragged_property_type) + || menu_item.function_signature->get_return_type()->equals(dragged_property_type); + + if ( has_compatible_signature ) + { + m_context_menu_with_compatible_signature.push_back( menu_item ); + } + } + } + } } - // Filter contextual menu items depending on m_search_input (search by label) - if ( ImGui::InputText("Search", m_search_input, 255, ImGuiInputTextFlags_EscapeClearsAll)) + // Filter by label + if ( ImGui::InputText("Search", m_search_input, 255, ImGuiInputTextFlags_EscapeClearsAll )) { - m_filtered_contextual_menus.clear(); + m_context_menu_with_label_matching_search.clear(); if ( m_search_input[0] != '\0' ) { - for ( auto& [_, menu_item] : m_contextual_menus ) + for ( auto& menu_item : m_context_menu_with_compatible_signature ) { - if( menu_item.label.find( m_search_input ) != std::string::npos ) + if( menu_item.search_target_string.find( m_search_input ) != std::string::npos ) { - m_filtered_contextual_menus.push_back(menu_item); - if ( m_filtered_contextual_menus.size() == _result_max_count ) + m_context_menu_with_label_matching_search.push_back(menu_item); + if ( m_context_menu_with_label_matching_search.size() == _result_max_count ) { break; } @@ -744,21 +654,20 @@ PoolID GraphView::draw_search_input( size_t _result_max_count ) } } - if ( !m_filtered_contextual_menus.empty() ) + if ( !m_context_menu_with_label_matching_search.empty() ) { - // When a single item is filtered, pressing enter will press the item's button. - if ( m_filtered_contextual_menus.size() == 1) + if ( m_context_menu_with_label_matching_search.size() == 1) { - if ( ImGui::SmallButton( m_filtered_contextual_menus[0].label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) + if ( ImGui::SmallButton( m_context_menu_with_label_matching_search[0].label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) { - m_new_node_id = m_filtered_contextual_menus[0].create_node_fct(); + m_new_node_id = m_context_menu_with_label_matching_search[0].create_node_fct(); } } else { // Otherwise, user has to move with arrow keys and press enter to trigger the highlighted button. - for ( auto& menu_item : m_filtered_contextual_menus ) + for ( auto& menu_item : m_context_menu_with_label_matching_search ) { if ( ImGui::SmallButton( menu_item.label.c_str()) || // User can click on the button... (ImGui::IsKeyDown( ImGuiKey_Enter ) && ImGui::IsItemFocused() ) // ...or press enter if this item is the first @@ -773,6 +682,10 @@ PoolID GraphView::draw_search_input( size_t _result_max_count ) { ImGui::Text("No matches..."); } + else + { + ImGui::Text("Search for a function by typing its name"); + } return m_new_node_id; } @@ -782,8 +695,8 @@ void GraphView::open_popup_context_menu() if (!ImGui::IsPopupOpen(k_context_menu_popup) ) { ImGui::OpenPopup(k_context_menu_popup); - m_focus_search_input = true; + m_search_input_should_init = true; m_search_input[0] = '\0'; - m_filtered_contextual_menus.clear(); + m_context_menu_with_label_matching_search.clear(); } } diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index dc98ad445..10baab05d 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -21,9 +21,11 @@ namespace ndbl class Nodable; class Graph; - class FunctionMenuItem { + class ContextMenuItem + { public: std::string label; + std::string search_target_string; std::function(void)> create_node_fct; const fw::func_type* function_signature = nullptr; }; @@ -41,15 +43,15 @@ namespace ndbl void add_contextual_menu_item( const std::string &_category, const std::string &_label, + std::string _search_target_string, std::function(void)> _function, - const fw::func_type *_signature); + const fw::func_type *_signature = nullptr); void frame_all_node_views(); void frame_selected_node_views(); void translate_all(ImVec2 /* delta */, const std::vector&); void unfold(); // unfold the graph until it is stabilized private: - PoolID draw_menu(const std::string& _menu_key); void draw_grid( ImDrawList*, const Config& ) const; PoolID draw_search_input( size_t _result_max_count ); // Search input filtering nodes as short clickable list void frame_views(const std::vector &_views, bool _align_top_left_corner); @@ -58,12 +60,13 @@ namespace ndbl Graph* m_graph; ImVec2 m_view_origin; - bool m_focus_search_input; + bool m_search_input_should_init; ImVec2 m_new_node_desired_position; char m_search_input[255] = "\0"; PoolID m_new_node_id; // contains the last id created, cleared each frame - std::multimap m_contextual_menus; - std::vector m_filtered_contextual_menus; + std::multimap m_contextual_menus; + std::vector m_context_menu_with_compatible_signature; + std::vector m_context_menu_with_label_matching_search; static constexpr const char* k_context_menu_popup = "GraphView.ContextMenu"; static constexpr const char* k_operator_menu_label = "Operators"; static constexpr const char* k_function_menu_label = "Functions"; From 04ff8695b24d5bfa77954cd7d912af5592cb0b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sun, 25 Feb 2024 11:33:30 -0500 Subject: [PATCH 04/27] fix(GraphView): create node by dragging a slot --- src/nodable/core/Node.cpp | 2 +- src/nodable/core/SlotFlag.h | 2 +- src/nodable/gui/GraphView.cpp | 44 ++++++++++++++++++++++------------- src/nodable/gui/Nodable.cpp | 10 ++++---- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/nodable/core/Node.cpp b/src/nodable/core/Node.cpp index b78310f7d..e4490b830 100644 --- a/src/nodable/core/Node.cpp +++ b/src/nodable/core/Node.cpp @@ -213,7 +213,7 @@ Slot* Node::find_slot_by_property_type(SlotFlags flags, const fw::type* _type) { for(Slot* slot : filter_slots( flags ) ) { - if( slot->get_property()->get_type()->equals( _type ) ) + if( fw::type::is_implicitly_convertible( slot->get_property()->get_type(), _type ) ) { return slot; } diff --git a/src/nodable/core/SlotFlag.h b/src/nodable/core/SlotFlag.h index b1266f664..195c65c96 100644 --- a/src/nodable/core/SlotFlag.h +++ b/src/nodable/core/SlotFlag.h @@ -30,7 +30,7 @@ namespace ndbl SlotFlag_TYPE_MASK = SlotFlag_TYPE_CODEFLOW | SlotFlag_TYPE_HIERARCHICAL | SlotFlag_TYPE_VALUE, }; - static SlotFlags flip_order(SlotFlags flags) + static SlotFlags get_complementary_flags(SlotFlags flags) { return (i8_t)(flags ^ SlotFlag_ORDER_MASK); } diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index db0da65c3..0f75a124b 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -385,27 +385,39 @@ bool GraphView::draw() */ if ( m_new_node_id ) { - - // dragging node slot ? - if ( dragged_slot ) + if ( !dragged_slot ) { - SlotFlags complementary_flags = flip_order( dragged_slot->slot().static_flags() ); - Slot* complementary_slot = m_new_node_id->find_slot_by_property_type( complementary_flags, dragged_slot->get_property()->get_type() ); - ConnectFlags connect_flags = ConnectFlag_ALLOW_SIDE_EFFECTS; + // Experimental: we try to connect a parent-less child + if (m_new_node_id != m_graph->get_root() && app.config.experimental_graph_autocompletion ) + { + m_graph->ensure_has_root(); + // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); + } + } + else + { + Slot* complementary_slot = m_new_node_id->find_slot_by_property_type( + get_complementary_flags( dragged_slot->slot().static_flags() ), + dragged_slot->get_property()->get_type() + ); + + if ( !complementary_slot ) + { + // TODO: this case should not happens, instead we should check ahead of time whether or not this not can be attached + LOG_ERROR("GraphView", "unable to connect this node") + } + else + { + Slot* out = &dragged_slot->slot(); + Slot* in = complementary_slot; - Slot* out = &dragged_slot->slot(); - Slot* in = complementary_slot; + if( out->has_flags(SlotFlag_ORDER_SECOND) ) std::swap(out, in); - if( out->has_flags(SlotFlag_ORDER_SECOND) ) std::swap(out, in); + m_graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); - m_graph->connect( *out, *in, connect_flags ); + SlotView::reset_dragged(); + } - SlotView::reset_dragged(); - } - else if (m_new_node_id != m_graph->get_root() && app.config.experimental_graph_autocompletion ) - { - m_graph->ensure_has_root(); - // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); } // set new_node's view position, select it diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index b4f4fca26..1683d8b83 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -395,11 +395,13 @@ void Nodable::on_update() case EventType_select_successor_node_action_triggered: { if (!selected_view) break; - PoolID possible_successor = selected_view->get_owner()->successors().front(); - if (!possible_successor) break; - if (PoolID successor_view = possible_successor->get_component() ) + std::vector> successors = selected_view->get_owner()->successors(); + if (!successors.empty()) { - NodeView::set_selected(successor_view); + if (PoolID successor_view = successors.front()->get_component() ) + { + NodeView::set_selected(successor_view); + } } break; } From 455e89f1a2c4b918f4d32f4d48fc2fbcce45ea7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sun, 25 Feb 2024 14:45:06 -0500 Subject: [PATCH 05/27] fix(GraphView): contextual menu 1/N --- src/nodable/gui/GraphView.cpp | 215 ++++++++++++++++------------------ src/nodable/gui/GraphView.h | 42 ++++--- 2 files changed, 129 insertions(+), 128 deletions(-) diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 0f75a124b..8597db129 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -37,28 +37,27 @@ REGISTER GraphView::GraphView(Graph* graph) : fw::View() , m_graph(graph) - , m_new_node_desired_position(-1, -1) - , m_search_input_should_init(false) + , m_context_menu() { // Prepare context menu items // 1) Blocks - add_contextual_menu_item( "1", ICON_FA_CODE " Condition", "condition", [&]() { return m_graph->create_cond_struct(); } ); - add_contextual_menu_item( "1", ICON_FA_CODE " For Loop", "for loop", [&]() { return m_graph->create_for_loop(); } ); - add_contextual_menu_item( "1", ICON_FA_CODE " While Loop", "while loop", [&]() { return m_graph->create_while_loop(); } ); - add_contextual_menu_item( "1", ICON_FA_CODE " Scope", "scope", [&]() { return m_graph->create_scope(); } ); - add_contextual_menu_item( "1", ICON_FA_CODE " Program", "program scope", [&]() { m_graph->clear(); return m_graph->create_root(); } ); + m_context_menu.add_item( "1", ICON_FA_CODE " Condition", "condition", [&]() { return m_graph->create_cond_struct(); } ); + m_context_menu.add_item( "1", ICON_FA_CODE " For Loop", "for loop", [&]() { return m_graph->create_for_loop(); } ); + m_context_menu.add_item( "1", ICON_FA_CODE " While Loop", "while loop", [&]() { return m_graph->create_while_loop(); } ); + m_context_menu.add_item( "1", ICON_FA_CODE " Scope", "scope", [&]() { return m_graph->create_scope(); } ); + m_context_menu.add_item( "1", ICON_FA_CODE " Program", "program scope", [&]() { m_graph->clear(); return m_graph->create_root(); } ); // 2) Variables - add_contextual_menu_item( "2", ICON_FA_DATABASE " Boolean Variable", "boolean variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); - add_contextual_menu_item( "2", ICON_FA_DATABASE " Double Variable", "double variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); - add_contextual_menu_item( "2", ICON_FA_DATABASE " Integer Variable", "integer variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); - add_contextual_menu_item( "2", ICON_FA_DATABASE " String Variable", "string variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + m_context_menu.add_item( "2", ICON_FA_DATABASE " Boolean Variable", "boolean variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + m_context_menu.add_item( "2", ICON_FA_DATABASE " Double Variable", "double variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + m_context_menu.add_item( "2", ICON_FA_DATABASE " Integer Variable", "integer variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + m_context_menu.add_item( "2", ICON_FA_DATABASE " String Variable", "string variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); // 3) Literals - add_contextual_menu_item( "3", ICON_FA_FILE " Boolean Literal", "boolean literal", [&]() { return m_graph->create_literal( fw::type::get() ); } ); - add_contextual_menu_item( "3", ICON_FA_FILE " Double Literal", "double float literal", [&]() { return m_graph->create_literal( fw::type::get() ); } ); - add_contextual_menu_item( "3", ICON_FA_FILE " Integer Literal", "integer literal", [&]() { return m_graph->create_literal( fw::type::get()); } ); - add_contextual_menu_item( "3", ICON_FA_FILE " String Literal", "string literal", [&]() { return m_graph->create_literal( fw::type::get()); } ); + m_context_menu.add_item( "3", ICON_FA_FILE " Boolean Literal", "boolean literal", [&]() { return m_graph->create_literal( fw::type::get() ); } ); + m_context_menu.add_item( "3", ICON_FA_FILE " Double Literal", "double float literal", [&]() { return m_graph->create_literal( fw::type::get() ); } ); + m_context_menu.add_item( "3", ICON_FA_FILE " Integer Literal", "integer literal", [&]() { return m_graph->create_literal( fw::type::get()); } ); + m_context_menu.add_item( "3", ICON_FA_FILE " String Literal", "string literal", [&]() { return m_graph->create_literal( fw::type::get()); } ); // 4) Functions/Operators from the API const Nodlang& language = Nodlang::get_instance(); for (auto& each_fct : language.get_api()) @@ -74,7 +73,7 @@ GraphView::GraphView(Graph* graph) language.serialize_func_sig(label, func_type ); std::string search_target_string = func_type->get_identifier(); search_target_string.append(is_operator ? " operator" : " function"); - add_contextual_menu_item("4", label, search_target_string, create_node, func_type ); + m_context_menu.add_item("4", label, search_target_string, create_node, func_type ); } } @@ -105,10 +104,12 @@ bool GraphView::draw() ImDrawList* draw_list = ImGui::GetWindowDrawList(); Nodable & app = Nodable::get_instance(); const bool enable_edition = app.virtual_machine.is_program_stopped(); - ImVec2 origin = ImGui::GetCursorScreenPos(); auto node_registry = Pool::get_pool()->get( m_graph->get_node_registry() ); const SlotView* dragged_slot = SlotView::get_dragged(); const SlotView* hovered_slot = SlotView::get_hovered(); + bool drop_behavior_requires_a_new_node = false; + bool is_any_node_dragged = false; + bool is_any_node_hovered = false; // Draw grid in the background draw_grid( draw_list, app.config ); @@ -163,16 +164,22 @@ bool GraphView::draw() // slot Drag'n Drop if ( ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows) ) { + // Get the current dragged slot, or the slot that was dragged when context menu opened + const SlotView* _dragged_slot = dragged_slot ? dragged_slot : m_context_menu.dragged_slot; + // Draw temporary edge - if (dragged_slot) + if ( _dragged_slot ) { - if ( dragged_slot->slot().type() == SlotFlag_TYPE_CODEFLOW ) + // When dragging, edge follows mouse cursor. Otherwise, it sticks the contextual menu. + ImVec2 edge_end = m_context_menu.dragged_slot ? m_context_menu.opened_at_screen_pos : ImGui::GetMousePos(); + + if ( _dragged_slot->slot().type() == SlotFlag_TYPE_CODEFLOW ) { // Thick line fw::ImGuiEx::DrawVerticalWire( ImGui::GetWindowDrawList(), - dragged_slot->rect(app.config).GetCenter(), - hovered_slot ? hovered_slot->rect(app.config).GetCenter(): ImGui::GetMousePos(), + _dragged_slot->rect(app.config).GetCenter(), + hovered_slot ? hovered_slot->rect(app.config).GetCenter(): edge_end, app.config.ui_codeflow_color, app.config.ui_codeflow_shadowColor, app.config.ui_node_slot_size.x * app.config.ui_codeflow_thickness_ratio, @@ -183,27 +190,18 @@ bool GraphView::draw() { // Simple line ImGui::GetWindowDrawList()->AddLine( - dragged_slot->position(), - hovered_slot ? hovered_slot->position() : ImGui::GetMousePos(), + _dragged_slot->position(), + hovered_slot ? hovered_slot->position() : edge_end, ImGui::ColorConvertFloat4ToU32(app.config.ui_node_borderHighlightedColor), app.config.ui_wire_bezier_thickness ); } } - // Drops ? - bool require_new_node = false; - SlotView::drop_behavior(require_new_node, enable_edition); - - // Need a need node ? - if (require_new_node) - { - open_popup_context_menu(); - } + // Determine whether the current dragged SlotView should be dropped or not, and if a new node is required + SlotView::drop_behavior( drop_behavior_requires_a_new_node, enable_edition); } - bool isAnyNodeDragged = false; - bool isAnyNodeHovered = false; { /* Wires @@ -308,13 +306,13 @@ bool GraphView::draw() each_node_view->pinned( true ); } - isAnyNodeDragged |= NodeView::get_dragged() == each_node_view->poolid(); - isAnyNodeHovered |= each_node_view->is_hovered(); + is_any_node_dragged |= NodeView::get_dragged() == each_node_view->poolid(); + is_any_node_hovered |= each_node_view->is_hovered(); } } } - isAnyNodeDragged |= SlotView::is_dragging(); + is_any_node_dragged |= SlotView::is_dragging(); // Virtual Machine cursor if ( app.virtual_machine.is_program_running() ) @@ -343,7 +341,7 @@ bool GraphView::draw() /* Deselection (by double click) */ - if ( NodeView::is_any_selected() && !isAnyNodeHovered && ImGui::IsMouseDoubleClicked(0) && ImGui::IsWindowFocused()) + if ( NodeView::is_any_selected() && !is_any_node_hovered && ImGui::IsMouseDoubleClicked(0) && ImGui::IsWindowFocused()) { NodeView::set_selected({}); } @@ -351,41 +349,39 @@ bool GraphView::draw() /* Mouse PAN (global) */ - if (ImGui::IsMouseDragging(0) && ImGui::IsWindowFocused() && !isAnyNodeDragged ) + if (ImGui::IsMouseDragging(0) && ImGui::IsWindowFocused() && !is_any_node_dragged ) { translate_view(ImGui::GetMouseDragDelta()); ImGui::ResetMouseDragDelta(); } - /* - Mouse right-click popup menu - */ - - if ( enable_edition && !isAnyNodeHovered && ImGui::IsMouseClicked(1)) + // Decides whether contextual menu should be opened. + if ( drop_behavior_requires_a_new_node || (enable_edition && !is_any_node_hovered && ImGui::IsMouseClicked(1) ) ) { - open_popup_context_menu(); + if ( !ImGui::IsPopupOpen( k_context_menu_popup ) ) + { + ImGui::OpenPopup( k_context_menu_popup ); + m_context_menu.reset_state( SlotView::get_dragged() ); + SlotView::reset_dragged(); + } } + // Defines contextual menu popup (not rendered if popup is closed) if ( ImGui::BeginPopup(k_context_menu_popup) ) { - m_new_node_desired_position = ImGui::GetMousePos() - origin; - // Title : fw::ImGuiEx::ColoredShadowedText( ImVec2( 1, 1 ), ImColor( 0.00f, 0.00f, 0.00f, 1.00f ), ImColor( 1.00f, 1.00f, 1.00f, 0.50f ), "Create new node :" ); ImGui::Separator(); - if ( draw_search_input( 10 ) ) - { - ImGui::CloseCurrentPopup(); - } + bool search_input_created_a_node = draw_search_input( 10 ); /* * In case user has created a new node we need to connect it to the m_graph depending * on if a slot is being dragged and what is its nature. */ - if ( m_new_node_id ) + if ( search_input_created_a_node ) { - if ( !dragged_slot ) + if ( !m_context_menu.dragged_slot ) { // Experimental: we try to connect a parent-less child if (m_new_node_id != m_graph->get_root() && app.config.experimental_graph_autocompletion ) @@ -397,8 +393,8 @@ bool GraphView::draw() else { Slot* complementary_slot = m_new_node_id->find_slot_by_property_type( - get_complementary_flags( dragged_slot->slot().static_flags() ), - dragged_slot->get_property()->get_type() + get_complementary_flags( m_context_menu.dragged_slot->slot().static_flags() ), + m_context_menu.dragged_slot->get_property()->get_type() ); if ( !complementary_slot ) @@ -408,34 +404,29 @@ bool GraphView::draw() } else { - Slot* out = &dragged_slot->slot(); + Slot* out = &m_context_menu.dragged_slot->slot(); Slot* in = complementary_slot; if( out->has_flags(SlotFlag_ORDER_SECOND) ) std::swap(out, in); m_graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); - - SlotView::reset_dragged(); } } // set new_node's view position, select it - if( PoolID view = m_new_node_id->get_component() ) + if( auto view = m_new_node_id->get_component() ) { - view->set_position(m_new_node_desired_position, fw::Space_Local); + view->set_position(m_context_menu.opened_at_pos, fw::Space_Local); NodeView::set_selected(view); } - } + ImGui::CloseCurrentPopup(); + SlotView::reset_dragged(); + } ImGui::EndPopup(); - } - - // reset dragged if right click - if ( ImGui::IsMouseClicked(1) ) - { - ImGui::CloseCurrentPopup(); - SlotView::reset_dragged(); + } else { + m_context_menu.reset_state(); } // add some empty space @@ -472,19 +463,6 @@ void GraphView::draw_grid( ImDrawList* draw_list, const Config& config ) const } } -void GraphView::add_contextual_menu_item( - const std::string& _category, - const std::string& _label, - std::string _search_target_string, - std::function(void)> _function, - const fw::func_type * _signature) -{ - // Prepare a lower case string for search purposes - std::transform(_search_target_string.begin(), _search_target_string.end(), _search_target_string.begin(), [](unsigned char c){ return std::tolower(c); }); - - m_contextual_menus.insert( {_category, {_label, _search_target_string, std::move(_function), _signature }} ); -} - bool GraphView::update(float delta_time, i16_t subsample_count) { const float subsample_delta_time = delta_time / float(subsample_count); @@ -607,26 +585,26 @@ void GraphView::translate_view(ImVec2 delta) // m_view_origin += delta; } -PoolID GraphView::draw_search_input( size_t _result_max_count ) +bool GraphView::draw_search_input( size_t _result_max_count ) { - if ( m_search_input_should_init ) + if ( m_context_menu.must_be_reset_flag ) { - m_search_input_should_init = false; + m_context_menu.must_be_reset_flag = false; ImGui::SetKeyboardFocusHere(); // On init, we filter the functions/operators matching with the currently dragged slot - m_context_menu_with_compatible_signature.clear(); + m_context_menu.items_with_compatible_signature.clear(); SlotView* dragged_slot = SlotView::get_dragged(); if ( !dragged_slot ) { - for (auto& [_, menu_item] : m_contextual_menus) + for (auto& [_, menu_item] : m_context_menu.items_by_category ) { - m_context_menu_with_compatible_signature.push_back( menu_item ); + m_context_menu.items_with_compatible_signature.push_back( menu_item ); } } else { - for (auto& [_, menu_item] : m_contextual_menus) + for (auto& [_, menu_item] : m_context_menu.items_by_category ) { if ( !dragged_slot->is_this() ) { @@ -639,7 +617,7 @@ PoolID GraphView::draw_search_input( size_t _result_max_count ) if ( has_compatible_signature ) { - m_context_menu_with_compatible_signature.push_back( menu_item ); + m_context_menu.items_with_compatible_signature.push_back( menu_item ); } } } @@ -647,17 +625,17 @@ PoolID GraphView::draw_search_input( size_t _result_max_count ) } // Filter by label - if ( ImGui::InputText("Search", m_search_input, 255, ImGuiInputTextFlags_EscapeClearsAll )) + if ( ImGui::InputText("Search", m_context_menu.search_input, 255, ImGuiInputTextFlags_EscapeClearsAll )) { - m_context_menu_with_label_matching_search.clear(); - if ( m_search_input[0] != '\0' ) + m_context_menu.items_matching_search.clear(); + if ( m_context_menu.search_input[0] != '\0' ) { - for ( auto& menu_item : m_context_menu_with_compatible_signature ) + for ( auto& menu_item : m_context_menu.items_with_compatible_signature ) { - if( menu_item.search_target_string.find( m_search_input ) != std::string::npos ) + if( menu_item.search_target_string.find( m_context_menu.search_input ) != std::string::npos ) { - m_context_menu_with_label_matching_search.push_back(menu_item); - if ( m_context_menu_with_label_matching_search.size() == _result_max_count ) + m_context_menu.items_matching_search.push_back(menu_item); + if ( m_context_menu.items_matching_search.size() == _result_max_count ) { break; } @@ -666,31 +644,32 @@ PoolID GraphView::draw_search_input( size_t _result_max_count ) } } - if ( !m_context_menu_with_label_matching_search.empty() ) + if ( !m_context_menu.items_matching_search.empty() ) { // When a single item is filtered, pressing enter will press the item's button. - if ( m_context_menu_with_label_matching_search.size() == 1) + if ( m_context_menu.items_matching_search.size() == 1) { - if ( ImGui::SmallButton( m_context_menu_with_label_matching_search[0].label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) + if ( ImGui::SmallButton( m_context_menu.items_matching_search[0].label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) { - m_new_node_id = m_context_menu_with_label_matching_search[0].create_node_fct(); + m_new_node_id = m_context_menu.items_matching_search[0].create_node_fct(); } } else { // Otherwise, user has to move with arrow keys and press enter to trigger the highlighted button. - for ( auto& menu_item : m_context_menu_with_label_matching_search ) + for ( auto& menu_item : m_context_menu.items_matching_search ) { if ( ImGui::SmallButton( menu_item.label.c_str()) || // User can click on the button... (ImGui::IsKeyDown( ImGuiKey_Enter ) && ImGui::IsItemFocused() ) // ...or press enter if this item is the first ) { m_new_node_id = menu_item.create_node_fct(); + return true; } } } } - else if ( m_search_input[0] != '\0' ) + else if ( m_context_menu.search_input[0] != '\0' ) { ImGui::Text("No matches..."); } @@ -699,16 +678,30 @@ PoolID GraphView::draw_search_input( size_t _result_max_count ) ImGui::Text("Search for a function by typing its name"); } - return m_new_node_id; + return false; } -void GraphView::open_popup_context_menu() +void ContextMenuState::reset_state( SlotView* _dragged_slot ) { - if (!ImGui::IsPopupOpen(k_context_menu_popup) ) - { - ImGui::OpenPopup(k_context_menu_popup); - m_search_input_should_init = true; - m_search_input[0] = '\0'; - m_context_menu_with_label_matching_search.clear(); - } + must_be_reset_flag = true; + search_input[0] = '\0'; + opened_at_pos = ImGui::GetMousePos() - ImGui::GetCursorScreenPos(); + opened_at_screen_pos = ImGui::GetMousePos(); + dragged_slot = _dragged_slot; + + items_matching_search.clear(); + items_with_compatible_signature.clear(); +} + +void ContextMenuState::add_item( + const std::string& _category_key, + const std::string& _label, + std::string _search_target_string, + std::function(void)> _node_factory_fct, + const fw::func_type * _signature) +{ + // Prepare a lower case string for search purposes + std::transform(_search_target_string.begin(), _search_target_string.end(), _search_target_string.begin(), [](unsigned char c){ return std::tolower(c); }); + + items_by_category.insert( { _category_key, {_label, _search_target_string, std::move( _node_factory_fct ), _signature }} ); } diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index 10baab05d..59ecf5340 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -12,6 +12,7 @@ #include "Config.h" #include "NodeViewConstraint.h" +#include "SlotView.h" #include "core/Scope.h" #include "types.h" @@ -30,6 +31,24 @@ namespace ndbl const fw::func_type* function_signature = nullptr; }; + struct ContextMenuState { + bool must_be_reset_flag = false; + ImVec2 opened_at_pos = ImVec2(-1,-1); // relative + ImVec2 opened_at_screen_pos = ImVec2(-1,-1); // absolute (screen space) + SlotView* dragged_slot = nullptr; // The slot being dragged when the context menu opened. + char search_input[255] = "\0"; // The search input entered by the user. + std::multimap items_by_category; // All the available items sorted by category + std::vector items_with_compatible_signature; // Only the items having a compatible signature (with the slot dragged) + std::vector items_matching_search; // Only the items having a compatible signature AND matching the search_input. + void reset_state(SlotView* _dragged_slot = nullptr); + void add_item( + const std::string& _category_key, // The category key to put the item into. + const std::string& _label, // The displayed label for this item. + std::string _search_target_string, // The string used to search (will be converted to lower case). + std::function(void)> _node_factory_fct, // The lambda function to invoke to create the node linked to this menu item. + const fw::func_type *_signature = nullptr); + }; + class GraphView: public fw::View { public: @@ -40,12 +59,6 @@ namespace ndbl bool update(); bool update(float /* delta_time */); bool update(float /* delta_time */, i16_t /* subsample_count */); - void add_contextual_menu_item( - const std::string &_category, - const std::string &_label, - std::string _search_target_string, - std::function(void)> _function, - const fw::func_type *_signature = nullptr); void frame_all_node_views(); void frame_selected_node_views(); void translate_all(ImVec2 /* delta */, const std::vector&); @@ -53,25 +66,20 @@ namespace ndbl private: void draw_grid( ImDrawList*, const Config& ) const; - PoolID draw_search_input( size_t _result_max_count ); // Search input filtering nodes as short clickable list + bool draw_search_input( size_t _result_max_count ); // Search input field, return true if user created a node. void frame_views(const std::vector &_views, bool _align_top_left_corner); void translate_view(ImVec2 vec2); + void close_popup_context_menu(); PoolID create_variable(const fw::type* _type, const char* _name, PoolID _scope); - Graph* m_graph; - ImVec2 m_view_origin; - bool m_search_input_should_init; - ImVec2 m_new_node_desired_position; - char m_search_input[255] = "\0"; - PoolID m_new_node_id; // contains the last id created, cleared each frame - std::multimap m_contextual_menus; - std::vector m_context_menu_with_compatible_signature; - std::vector m_context_menu_with_label_matching_search; + Graph* m_graph; + ImVec2 m_view_origin; + ContextMenuState m_context_menu; + PoolID m_new_node_id; // contains the last id created, cleared each frame static constexpr const char* k_context_menu_popup = "GraphView.ContextMenu"; static constexpr const char* k_operator_menu_label = "Operators"; static constexpr const char* k_function_menu_label = "Functions"; REFLECT_DERIVED_CLASS() - void open_popup_context_menu(); }; } \ No newline at end of file From 646df1bfa5b88920e285925f193151877318a5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sun, 25 Feb 2024 19:06:14 -0500 Subject: [PATCH 06/27] fix(GraphView): contextual menu 2/N --- src/nodable/gui/GraphView.cpp | 232 ++++++++++++++++++++-------------- src/nodable/gui/GraphView.h | 50 ++++---- 2 files changed, 163 insertions(+), 119 deletions(-) diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 8597db129..2d6b62e77 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -29,6 +29,16 @@ using namespace ndbl; using namespace ndbl::assembly; using namespace fw; +const char* k_context_menu_popup = "GraphView.ContextMenu"; + +template +static fw::func_type* create_variable_node_signature() +{ return fw::func_type_builder::with_id("variable"); } + +template +static fw::func_type* create_literal_node_signature() +{ return fw::func_type_builder::with_id("literal"); } + REGISTER { fw::registration::push_class("GraphView").extends(); @@ -43,28 +53,28 @@ GraphView::GraphView(Graph* graph) // Prepare context menu items // 1) Blocks - m_context_menu.add_item( "1", ICON_FA_CODE " Condition", "condition", [&]() { return m_graph->create_cond_struct(); } ); - m_context_menu.add_item( "1", ICON_FA_CODE " For Loop", "for loop", [&]() { return m_graph->create_for_loop(); } ); - m_context_menu.add_item( "1", ICON_FA_CODE " While Loop", "while loop", [&]() { return m_graph->create_while_loop(); } ); - m_context_menu.add_item( "1", ICON_FA_CODE " Scope", "scope", [&]() { return m_graph->create_scope(); } ); - m_context_menu.add_item( "1", ICON_FA_CODE " Program", "program scope", [&]() { m_graph->clear(); return m_graph->create_root(); } ); + m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " Condition", "condition", [&]() { return m_graph->create_cond_struct(); } } ); + m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " For Loop", "for loop", [&]() { return m_graph->create_for_loop(); } } ); + m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " While Loop", "while loop", [&]() { return m_graph->create_while_loop(); } } ); + m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " Scope", "scope", [&]() { return m_graph->create_scope(); } } ); + m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " Program", "program scope", [&]() { m_graph->clear(); return m_graph->create_root(); } } ); // 2) Variables - m_context_menu.add_item( "2", ICON_FA_DATABASE " Boolean Variable", "boolean variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); - m_context_menu.add_item( "2", ICON_FA_DATABASE " Double Variable", "double variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); - m_context_menu.add_item( "2", ICON_FA_DATABASE " Integer Variable", "integer variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); - m_context_menu.add_item( "2", ICON_FA_DATABASE " String Variable", "string variable", [&]() { return create_variable( fw::type::get(), "var", {} ); } ); + m_context_menu.add_item( VARIABLES, { ICON_FA_DATABASE " Boolean Variable", "boolean variable", [&]() { return create_variable(); }, create_variable_node_signature() } ); + m_context_menu.add_item( VARIABLES, { ICON_FA_DATABASE " Double Variable", "double variable", [&]() { return create_variable(); }, create_variable_node_signature() } ); + m_context_menu.add_item( VARIABLES, { ICON_FA_DATABASE " Integer Variable", "integer variable", [&]() { return create_variable(); }, create_variable_node_signature() } ); + m_context_menu.add_item( VARIABLES, { ICON_FA_DATABASE " String Variable", "string variable", [&]() { return create_variable(); }, create_variable_node_signature() } ); // 3) Literals - m_context_menu.add_item( "3", ICON_FA_FILE " Boolean Literal", "boolean literal", [&]() { return m_graph->create_literal( fw::type::get() ); } ); - m_context_menu.add_item( "3", ICON_FA_FILE " Double Literal", "double float literal", [&]() { return m_graph->create_literal( fw::type::get() ); } ); - m_context_menu.add_item( "3", ICON_FA_FILE " Integer Literal", "integer literal", [&]() { return m_graph->create_literal( fw::type::get()); } ); - m_context_menu.add_item( "3", ICON_FA_FILE " String Literal", "string literal", [&]() { return m_graph->create_literal( fw::type::get()); } ); + m_context_menu.add_item( LITERALS, { ICON_FA_FILE " Boolean Literal", "boolean literal", [&]() { return m_graph->create_literal(); }, create_literal_node_signature() } ); + m_context_menu.add_item( LITERALS, { ICON_FA_FILE " Double Literal", "double float literal", [&]() { return m_graph->create_literal(); }, create_literal_node_signature() } ); + m_context_menu.add_item( LITERALS, { ICON_FA_FILE " Integer Literal", "integer literal", [&]() { return m_graph->create_literal(); }, create_literal_node_signature() } ); + m_context_menu.add_item( LITERALS, { ICON_FA_FILE " String Literal", "string literal", [&]() { return m_graph->create_literal(); }, create_literal_node_signature() } ); // 4) Functions/Operators from the API const Nodlang& language = Nodlang::get_instance(); for (auto& each_fct : language.get_api()) { const fw::func_type* func_type = each_fct->get_type(); bool is_operator = Nodlang::get_instance().find_operator_fct( func_type ) != nullptr; - auto create_node = [&]() -> PoolID + auto node_factory_fct = [&]() -> PoolID { return m_graph->create_function(each_fct.get(), is_operator); }; @@ -73,7 +83,7 @@ GraphView::GraphView(Graph* graph) language.serialize_func_sig(label, func_type ); std::string search_target_string = func_type->get_identifier(); search_target_string.append(is_operator ? " operator" : " function"); - m_context_menu.add_item("4", label, search_target_string, create_node, func_type ); + m_context_menu.add_item( FUNCTIONS, {label, search_target_string, node_factory_fct, func_type} ); } } @@ -97,8 +107,6 @@ PoolID GraphView::create_variable(const fw::type* _type, const cha bool GraphView::draw() { - m_new_node_id.reset(); - bool changed = false; bool pixel_perfect = true; ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -372,19 +380,16 @@ bool GraphView::draw() // Title : fw::ImGuiEx::ColoredShadowedText( ImVec2( 1, 1 ), ImColor( 0.00f, 0.00f, 0.00f, 1.00f ), ImColor( 1.00f, 1.00f, 1.00f, 0.50f ), "Create new node :" ); ImGui::Separator(); - - bool search_input_created_a_node = draw_search_input( 10 ); - /* * In case user has created a new node we need to connect it to the m_graph depending * on if a slot is being dragged and what is its nature. */ - if ( search_input_created_a_node ) + if ( PoolID new_node_id = m_context_menu.draw_search_input( 10 ) ) { if ( !m_context_menu.dragged_slot ) { // Experimental: we try to connect a parent-less child - if (m_new_node_id != m_graph->get_root() && app.config.experimental_graph_autocompletion ) + if (new_node_id != m_graph->get_root() && app.config.experimental_graph_autocompletion ) { m_graph->ensure_has_root(); // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); @@ -392,7 +397,7 @@ bool GraphView::draw() } else { - Slot* complementary_slot = m_new_node_id->find_slot_by_property_type( + Slot* complementary_slot = new_node_id->find_slot_by_property_type( get_complementary_flags( m_context_menu.dragged_slot->slot().static_flags() ), m_context_menu.dragged_slot->get_property()->get_type() ); @@ -415,7 +420,7 @@ bool GraphView::draw() } // set new_node's view position, select it - if( auto view = m_new_node_id->get_component() ) + if( auto view = new_node_id->get_component() ) { view->set_position(m_context_menu.opened_at_pos, fw::Space_Local); NodeView::set_selected(view); @@ -585,103 +590,145 @@ void GraphView::translate_view(ImVec2 delta) // m_view_origin += delta; } -bool GraphView::draw_search_input( size_t _result_max_count ) +PoolID ContextMenu::draw_search_input( size_t _result_max_count ) { - if ( m_context_menu.must_be_reset_flag ) + PoolID new_node_id; + + if ( must_be_reset_flag ) { - m_context_menu.must_be_reset_flag = false; ImGui::SetKeyboardFocusHere(); - // On init, we filter the functions/operators matching with the currently dragged slot - m_context_menu.items_with_compatible_signature.clear(); - SlotView* dragged_slot = SlotView::get_dragged(); - if ( !dragged_slot ) - { - for (auto& [_, menu_item] : m_context_menu.items_by_category ) - { - m_context_menu.items_with_compatible_signature.push_back( menu_item ); - } - } - else - { - for (auto& [_, menu_item] : m_context_menu.items_by_category ) - { - if ( !dragged_slot->is_this() ) - { - const fw::type* dragged_property_type = dragged_slot->get_property_type(); + // + do_filter_based_on_signature(); - bool has_compatible_signature = - ! menu_item.function_signature || - dragged_slot->allows( SlotFlag_ORDER_FIRST ) && menu_item.function_signature->has_an_arg_of_type(dragged_property_type) - || menu_item.function_signature->get_return_type()->equals(dragged_property_type); + // Initial search + do_search( 100 ); - if ( has_compatible_signature ) - { - m_context_menu.items_with_compatible_signature.push_back( menu_item ); - } - } - } - } + // Ensure we reset once + must_be_reset_flag = false; } - // Filter by label - if ( ImGui::InputText("Search", m_context_menu.search_input, 255, ImGuiInputTextFlags_EscapeClearsAll )) + // Draw search input and do_search on input change + if ( ImGui::InputText("Search", search_input, 255, ImGuiInputTextFlags_EscapeClearsAll )) { - m_context_menu.items_matching_search.clear(); - if ( m_context_menu.search_input[0] != '\0' ) - { - for ( auto& menu_item : m_context_menu.items_with_compatible_signature ) - { - if( menu_item.search_target_string.find( m_context_menu.search_input ) != std::string::npos ) - { - m_context_menu.items_matching_search.push_back(menu_item); - if ( m_context_menu.items_matching_search.size() == _result_max_count ) - { - break; - } - } - } - } + do_search( 100 ); } - if ( !m_context_menu.items_matching_search.empty() ) + if ( !items_matching_search.empty() ) { // When a single item is filtered, pressing enter will press the item's button. - if ( m_context_menu.items_matching_search.size() == 1) + if ( items_matching_search.size() == 1) { - if ( ImGui::SmallButton( m_context_menu.items_matching_search[0].label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) + if ( ImGui::SmallButton( items_matching_search[0].label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) { - m_new_node_id = m_context_menu.items_matching_search[0].create_node_fct(); + new_node_id = items_matching_search[0].node_factory_fct(); } } else { + size_t more = items_matching_search.size() > _result_max_count ? items_matching_search.size() : 0; + if ( more ) + { + ImGui::Text("Found %zu result(s)", items_matching_search.size() ); + } // Otherwise, user has to move with arrow keys and press enter to trigger the highlighted button. - for ( auto& menu_item : m_context_menu.items_matching_search ) + auto it = items_matching_search.begin(); + while( it != items_matching_search.end() && std::distance(items_matching_search.begin(), it) != _result_max_count) { - if ( ImGui::SmallButton( menu_item.label.c_str()) || // User can click on the button... + if ( ImGui::Button( (*it).label.c_str()) || // User can click on the button... (ImGui::IsKeyDown( ImGuiKey_Enter ) && ImGui::IsItemFocused() ) // ...or press enter if this item is the first ) { - m_new_node_id = menu_item.create_node_fct(); - return true; + new_node_id = (*it).node_factory_fct(); } + it++; + } + if ( more ) + { + ImGui::Text(".. %zu more ..", more ); } } } - else if ( m_context_menu.search_input[0] != '\0' ) + else { ImGui::Text("No matches..."); } - else + + return new_node_id; +} +void ContextMenu::do_filter_based_on_signature() +{ + items_with_compatible_signature.clear(); + + if ( !dragged_slot ) { - ImGui::Text("Search for a function by typing its name"); + // When no slot is dragged, user can create any node + for (auto& [_, menu_item] : items_by_category ) + { + items_with_compatible_signature.push_back( menu_item ); + } } + else + { + for (auto& [category_key, menu_item] : items_by_category ) + { + switch ( category_key ) + { + case BLOCKS: + if ( dragged_slot->is_this() ) + { + items_with_compatible_signature.push_back( menu_item ); + } + break; + + case LITERALS: + case VARIABLES: + // I defined fake node signatures, they are handled like any other function - return false; + case FUNCTIONS: + if ( !dragged_slot->is_this() ) + { + const type* dragged_property_type = dragged_slot->get_property_type(); + + if ( menu_item.node_signature ) + { + if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) + { + if ( !menu_item.node_signature->has_an_arg_of_type(dragged_property_type) ) + { + continue; + } + } + else if ( !menu_item.node_signature->get_return_type()->equals(dragged_property_type) ) + { + continue; + } + } else { + // by default, we accept any item not having a signature + } + items_with_compatible_signature.push_back( menu_item ); + } + } + } + } +} +void ContextMenu::do_search( size_t _limit ) +{ + items_matching_search.clear(); + for ( auto& menu_item : items_with_compatible_signature ) + { + if( menu_item.search_target_string.find( search_input ) != std::string::npos ) + { + items_matching_search.push_back(menu_item); + if ( items_matching_search.size() == _limit ) + { + break; + } + } + } } -void ContextMenuState::reset_state( SlotView* _dragged_slot ) +void ContextMenu::reset_state( SlotView* _dragged_slot ) { must_be_reset_flag = true; search_input[0] = '\0'; @@ -693,15 +740,10 @@ void ContextMenuState::reset_state( SlotView* _dragged_slot ) items_with_compatible_signature.clear(); } -void ContextMenuState::add_item( - const std::string& _category_key, - const std::string& _label, - std::string _search_target_string, - std::function(void)> _node_factory_fct, - const fw::func_type * _signature) +void ContextMenu::add_item( + ContextMenuGroups _category, + ContextMenuItem _item + ) { - // Prepare a lower case string for search purposes - std::transform(_search_target_string.begin(), _search_target_string.end(), _search_target_string.begin(), [](unsigned char c){ return std::tolower(c); }); - - items_by_category.insert( { _category_key, {_label, _search_target_string, std::move( _node_factory_fct ), _signature }} ); + items_by_category.insert( { _category, std::move(_item)}); } diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index 59ecf5340..79fde3042 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -22,31 +22,36 @@ namespace ndbl class Nodable; class Graph; - class ContextMenuItem + struct ContextMenuItem { - public: - std::string label; - std::string search_target_string; - std::function(void)> create_node_fct; - const fw::func_type* function_signature = nullptr; + std::string label; // The displayed label for this item. + std::string search_target_string; // The lower case string used to search. + std::function(void)> node_factory_fct; // The lambda function to invoke to create the node linked to this menu item. + const fw::func_type* node_signature = nullptr; // The signature of the node this->node_factory_fct would create. }; - struct ContextMenuState { + enum ContextMenuGroups // To group and order context menu items + { + BLOCKS = 0, // first to be displayed ... + VARIABLES, + LITERALS, + FUNCTIONS // ... last + }; + + struct ContextMenu { bool must_be_reset_flag = false; ImVec2 opened_at_pos = ImVec2(-1,-1); // relative ImVec2 opened_at_screen_pos = ImVec2(-1,-1); // absolute (screen space) SlotView* dragged_slot = nullptr; // The slot being dragged when the context menu opened. char search_input[255] = "\0"; // The search input entered by the user. - std::multimap items_by_category; // All the available items sorted by category + std::multimap items_by_category; // All the available items sorted by category std::vector items_with_compatible_signature; // Only the items having a compatible signature (with the slot dragged) std::vector items_matching_search; // Only the items having a compatible signature AND matching the search_input. - void reset_state(SlotView* _dragged_slot = nullptr); - void add_item( - const std::string& _category_key, // The category key to put the item into. - const std::string& _label, // The displayed label for this item. - std::string _search_target_string, // The string used to search (will be converted to lower case). - std::function(void)> _node_factory_fct, // The lambda function to invoke to create the node linked to this menu item. - const fw::func_type *_signature = nullptr); + void reset_state(SlotView* _dragged_slot = nullptr); + void add_item( ContextMenuGroups _category, ContextMenuItem _item); + PoolID draw_search_input( size_t _result_max_count ); + void do_search( size_t _limit ); + void do_filter_based_on_signature(); }; class GraphView: public fw::View @@ -66,19 +71,16 @@ namespace ndbl private: void draw_grid( ImDrawList*, const Config& ) const; - bool draw_search_input( size_t _result_max_count ); // Search input field, return true if user created a node. void frame_views(const std::vector &_views, bool _align_top_left_corner); void translate_view(ImVec2 vec2); - void close_popup_context_menu(); PoolID create_variable(const fw::type* _type, const char* _name, PoolID _scope); + template + PoolID create_variable(const char* _name = "var", PoolID _scope = {}) + { return create_variable( fw::type::get(), _name, _scope); } - Graph* m_graph; - ImVec2 m_view_origin; - ContextMenuState m_context_menu; - PoolID m_new_node_id; // contains the last id created, cleared each frame - static constexpr const char* k_context_menu_popup = "GraphView.ContextMenu"; - static constexpr const char* k_operator_menu_label = "Operators"; - static constexpr const char* k_function_menu_label = "Functions"; + Graph* m_graph; + ImVec2 m_view_origin; + ContextMenu m_context_menu; REFLECT_DERIVED_CLASS() }; From 9a93a96c4fad0f04d26be23a486d5e9d70167681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Wed, 28 Feb 2024 17:42:52 -0500 Subject: [PATCH 07/27] fix(GraphView): contextual menu 3/N (large refactor) --- src/fw/core/hash.h | 2 + src/fw/gui/App.cpp | 22 +-- src/fw/gui/EventManager.cpp | 68 ++++--- src/fw/gui/EventManager.h | 49 +++-- src/fw/gui/ImGuiEx.cpp | 4 +- src/nodable/core/Graph.cpp | 65 ++++++ src/nodable/core/Graph.h | 26 ++- src/nodable/core/language/Nodlang.cpp | 21 ++ src/nodable/core/language/Nodlang.h | 7 +- src/nodable/gui/Condition.h | 7 +- src/nodable/gui/Event.h | 11 ++ src/nodable/gui/GraphView.cpp | 194 +++++------------- src/nodable/gui/GraphView.h | 51 ++--- src/nodable/gui/HybridFileView.cpp | 2 +- src/nodable/gui/Nodable.cpp | 272 +++++++++++++++++--------- src/nodable/gui/NodableView.cpp | 8 +- 16 files changed, 465 insertions(+), 344 deletions(-) diff --git a/src/fw/core/hash.h b/src/fw/core/hash.h index eeced19bb..f476f5321 100644 --- a/src/fw/core/hash.h +++ b/src/fw/core/hash.h @@ -7,6 +7,8 @@ namespace fw { namespace hash { + using type = size_t; + inline static size_t hash(char* buffer, size_t buf_size, size_t seed = 0) { return XXHash32::hash(buffer, buf_size, seed); } diff --git a/src/fw/gui/App.cpp b/src/fw/gui/App.cpp index 313a7ef29..7b34b052b 100644 --- a/src/fw/gui/App.cpp +++ b/src/fw/gui/App.cpp @@ -240,31 +240,31 @@ void App::handle_events() // With mode key only if( event.key.keysym.mod & (KMOD_CTRL | KMOD_ALT) ) { - for(const auto& _binded_event: event_manager.get_binded_events() ) + for(const auto& _action: event_manager.get_actions() ) { // first, priority to shortcuts with mod - if ( _binded_event.shortcut.mod != KMOD_NONE - && _binded_event.event_t - && (_binded_event.shortcut.mod & event.key.keysym.mod) - && _binded_event.shortcut.key == event.key.keysym.sym + if ( _action.shortcut.mod != KMOD_NONE + && _action.event_t + && ( _action.shortcut.mod & event.key.keysym.mod) + && _action.shortcut.key == event.key.keysym.sym ) { - event_manager.push(_binded_event.event_t); + event_manager.push_event( _action.event_t ); break; } } } else // without any mod key { - for(const auto& _binded_event: event_manager.get_binded_events() ) + for(const auto& _action: event_manager.get_actions() ) { // first, priority to shortcuts with mod - if ( _binded_event.shortcut.mod == KMOD_NONE - && _binded_event.event_t - && _binded_event.shortcut.key == event.key.keysym.sym + if ( _action.shortcut.mod == KMOD_NONE + && _action.event_t + && _action.shortcut.key == event.key.keysym.sym ) { - event_manager.push(_binded_event.event_t); + event_manager.push_event( _action.event_t ); break; } } diff --git a/src/fw/gui/EventManager.cpp b/src/fw/gui/EventManager.cpp index 4c21c600a..d9ce384ba 100644 --- a/src/fw/gui/EventManager.cpp +++ b/src/fw/gui/EventManager.cpp @@ -1,15 +1,36 @@ #include "EventManager.h" +#include "core/assertions.h" +#include "core/async.h" +#include "core/log.h" +#include "core/reflection/type.h" #include #include #include -#include "core/log.h" -#include "core/assertions.h" -#include "core/async.h" using namespace fw; EventManager* EventManager::s_instance = nullptr; +EventManager::~EventManager() +{ + LOG_VERBOSE("fw::EventManager", "Destructor ...\n"); + s_instance = nullptr; + LOG_VERBOSE("fw::EventManager", "Destructor " OK "\n"); +} +EventManager::EventManager() +{ + LOG_VERBOSE("fw::EventManager", "Constructor ...\n"); + FW_EXPECT(!s_instance, "cannot have two instances at a time"); + s_instance = this; + LOG_VERBOSE("fw::EventManager", "Constructor " OK "\n"); +} + +EventManager& EventManager::get_instance() +{ + FW_EXPECT(s_instance, "No instance found."); + return *s_instance; +} + void EventManager::push_event(Event &_event) { m_events.push(_event); @@ -28,56 +49,38 @@ size_t EventManager::poll_event(Event &_event) return count; } -void EventManager::push(EventType _type) +void EventManager::push_event(EventType _type) { Event simple_event = { _type }; push_event(simple_event); } -void EventManager::bind(const BindedEvent &binded_cmd) +void EventManager::add_action(Action _action ) { - m_binded_events.push_back(binded_cmd); - m_binded_events_by_type.insert({binded_cmd.event_t, binded_cmd}); + m_actions.push_back( _action ); + m_actions_by_event_type.insert({ _action.event_t, _action }); } -const BindedEvent &EventManager::get_binded(uint16_t type) -{ - return m_binded_events_by_type.at(type); -} -const std::vector &EventManager::get_binded_events() const -{ - return m_binded_events; -} -EventManager& EventManager::get_instance() +const Action& EventManager::get_action_by_type( u16_t type ) { - FW_EXPECT(s_instance, "No instance found."); - return *s_instance; + return m_actions_by_event_type.at(type); } -EventManager::~EventManager() -{ - LOG_VERBOSE("fw::EventManager", "Destructor ...\n"); - s_instance = nullptr; - LOG_VERBOSE("fw::EventManager", "Destructor " OK "\n"); -} -EventManager::EventManager() + +const std::vector& EventManager::get_actions() const { - LOG_VERBOSE("fw::EventManager", "Constructor ...\n"); - FW_EXPECT(!s_instance, "cannot have two instances at a time"); - s_instance = this; - LOG_VERBOSE("fw::EventManager", "Constructor " OK "\n"); + return m_actions; } -void EventManager::push_async(EventType type, u64_t delay) +void EventManager::push_event_delayed(EventType type, u64_t delay) { fw::async::get_instance().add_task( std::async(std::launch::async, [this, type, delay]() -> void { std::this_thread::sleep_for(std::chrono::milliseconds{delay}); - push(type); + push_event( type ); }) ); } - std::string Shortcut::to_string() const { std::string result; @@ -89,3 +92,4 @@ std::string Shortcut::to_string() const return result; } + diff --git a/src/fw/gui/EventManager.h b/src/fw/gui/EventManager.h index 3c3b47af8..ae575610a 100644 --- a/src/fw/gui/EventManager.h +++ b/src/fw/gui/EventManager.h @@ -4,9 +4,11 @@ #include #include #include +#include #include #include "core/types.h" +#include "core/reflection/func_type.h" namespace fw { @@ -51,12 +53,27 @@ namespace fw SimpleEvent common; }; - struct BindedEvent - { - std::string label; - u16_t event_t; - Shortcut shortcut; - u16_t condition; + // An action can trigger an event under certain circumstances + class Action { + public: + std::string label; + u16_t event_t; + Shortcut shortcut; + u16_t condition; + const fw::func_type* signature; // If action creates a node, this will point to the signature of if. + + Action( + const char* label, + u16_t event_t, + Shortcut shortcut = {}, + u16_t condition = {}, + const fw::func_type* signature = nullptr) + : label(label) + , event_t(event_t) + , shortcut(std::move(shortcut)) + , condition(condition) + , signature(signature) + {} }; class EventManager @@ -66,20 +83,20 @@ namespace fw EventManager(const EventManager&) = delete; ~EventManager(); + void push_event(EventType _type); + void push_event_delayed(EventType, u64_t); // Push an event with a delay in millisecond void push_event(Event& _event); size_t poll_event(Event& _event); - void push(EventType _type); - void push_async(EventType, u64_t); // Push an event with a delay in millisecond - const std::vector& get_binded_events() const; - void bind(const BindedEvent& binded_cmd); - const BindedEvent& get_binded(u16_t type); + const std::vector& get_actions() const; + void add_action(Action); + const Action& get_action_by_type(u16_t type); - static EventManager& get_instance(); + static EventManager& get_instance(); private: - static EventManager* s_instance; - std::queue m_events; - std::vector m_binded_events; - std::map m_binded_events_by_type; + static EventManager* s_instance; + std::queue m_events; + std::vector m_actions; + std::map m_actions_by_event_type; }; } \ No newline at end of file diff --git a/src/fw/gui/ImGuiEx.cpp b/src/fw/gui/ImGuiEx.cpp index 2d827346b..cbec72b69 100644 --- a/src/fw/gui/ImGuiEx.cpp +++ b/src/fw/gui/ImGuiEx.cpp @@ -229,10 +229,10 @@ void ImGuiEx::BeginFrame() void ImGuiEx::MenuItemBindedToEvent(uint16_t type, bool selected, bool enable) { - auto binded_evt = EventManager::get_instance().get_binded(type); + auto binded_evt = EventManager::get_instance().get_action_by_type( type ); if (ImGui::MenuItem( binded_evt.label.c_str(), binded_evt.shortcut.to_string().c_str(), selected, enable)) { - EventManager::get_instance().push(binded_evt.event_t); + EventManager::get_instance().push_event( binded_evt.event_t ); } } diff --git a/src/nodable/core/Graph.cpp b/src/nodable/core/Graph.cpp index cd31b873e..dc5839007 100644 --- a/src/nodable/core/Graph.cpp +++ b/src/nodable/core/Graph.cpp @@ -510,3 +510,68 @@ PoolID Graph::create_literal(const fw::type *_type) add(node); return node; } + +PoolID Graph::create_node( NodeType _type, const char* _signature_hint ) +{ + switch ( _type ) + { + /* + * TODO: That's not great we have special cases for blocks, variables and literals... + * Can't we simply give a generic signature for both blocks, variables, literals, functions, and operators? + * What if we pass: + * - "bool|int|string|double" for a variable + * - "false|true|0|42|0.1|\"string\"" for a literal + * - "if|for|while|{}" for a block (not sure if we need a BLOCK_PROGRAM) + * This can be made via a PoolID Nodlang::parse_single_node(const std::string&) method. + * + */ + case NodeType_BLOCK_CONDITION: return create_cond_struct(); + case NodeType_BLOCK_FOR_LOOP: return create_for_loop(); + case NodeType_BLOCK_WHILE_LOOP: return create_while_loop(); + case NodeType_BLOCK_SCOPE: return create_scope(); + case NodeType_BLOCK_PROGRAM: clear(); return create_root(); + + case NodeType_VARIABLE_BOOLEAN: return create_variable_decl(); + case NodeType_VARIABLE_DOUBLE: return create_variable_decl(); + case NodeType_VARIABLE_INTEGER: return create_variable_decl(); + case NodeType_VARIABLE_STRING: return create_variable_decl(); + + case NodeType_LITERAL_BOOLEAN: return create_literal(); + case NodeType_LITERAL_DOUBLE: return create_literal(); + case NodeType_LITERAL_INTEGER: return create_literal(); + case NodeType_LITERAL_STRING: return create_literal(); + + case NodeType_OPERATOR: + case NodeType_FUNCTION: + { + FW_EXPECT(_signature_hint != nullptr, "_signature_hint is expected when dealing with functions or operators") + auto& language = Nodlang::get_instance(); + // Currently, we handle operators and functions the exact same way + const auto invokable = language.find_function(_signature_hint); + bool is_operator = language.find_operator_fct( invokable->get_type() ) != nullptr; + return create_function(invokable.get(), is_operator); + } + default: + FW_EXPECT( false, "Unhandled NodeType."); + } +} + +PoolID Graph::create_variable_decl(const fw::type* _type, const char* _name, PoolID _scope) +{ + if( !_scope) + { + _scope = get_root()->get_component(); + } + + // Create variable + PoolID var_node = create_variable(_type, _name, _scope ); + var_node->set_declared(true); + Token token(Token_t::keyword_operator, " = "); + token.m_word_start_pos = 1; + token.m_word_size = 1; + var_node->assignment_operator_token = token; + + // TODO: attach a default Literal? + + return var_node; +} diff --git a/src/nodable/core/Graph.h b/src/nodable/core/Graph.h index 825a49ea0..e12c60611 100644 --- a/src/nodable/core/Graph.h +++ b/src/nodable/core/Graph.h @@ -27,6 +27,24 @@ namespace ndbl ConnectFlag_ALLOW_SIDE_EFFECTS = 1 << 0, }; + enum NodeType : u16_t { + NodeType_BLOCK_CONDITION, + NodeType_BLOCK_FOR_LOOP, + NodeType_BLOCK_WHILE_LOOP, + NodeType_BLOCK_SCOPE, + NodeType_BLOCK_PROGRAM, + NodeType_VARIABLE_BOOLEAN, + NodeType_VARIABLE_DOUBLE, + NodeType_VARIABLE_INTEGER, + NodeType_VARIABLE_STRING, + NodeType_LITERAL_BOOLEAN, + NodeType_LITERAL_DOUBLE, + NodeType_LITERAL_INTEGER, + NodeType_LITERAL_STRING, + NodeType_FUNCTION, + NodeType_OPERATOR, + }; + /** * @brief To manage a graph (nodes and edges) */ @@ -40,8 +58,15 @@ namespace ndbl // node related + PoolID create_node(); // Create a basic node. + PoolID create_node(NodeType, const char* _signature_hint = nullptr); // Create a given node type in a simple way. PoolID create_root(); PoolID create_variable(const fw::type *_type, const std::string &_name, PoolID _scope); + PoolID create_variable_decl(const fw::type* _type, const char* _name, PoolID _scope); + template + PoolID create_variable_decl(const char* _name = "var", PoolID _scope = {}) + { return create_variable_decl( fw::type::get(), _name, _scope); } + PoolID create_literal(const fw::type *_type); template PoolID create_literal() { return create_literal(fw::type::get()); } @@ -53,7 +78,6 @@ namespace ndbl PoolID create_cond_struct(); PoolID create_for_loop(); PoolID create_while_loop(); - PoolID create_node(); void destroy(PoolID _node); void ensure_has_root(); PoolID get_root()const { return m_root; } diff --git a/src/nodable/core/language/Nodlang.cpp b/src/nodable/core/language/Nodlang.cpp index 05bd13a94..572632e8a 100644 --- a/src/nodable/core/language/Nodlang.cpp +++ b/src/nodable/core/language/Nodlang.cpp @@ -1873,6 +1873,27 @@ std::string &Nodlang::serialize_cond_struct(std::string &_out, const IfNode*_con // Language definition ------------------------------------------------------------------------------------------------------------ +std::shared_ptr Nodlang::find_function(const char* _signature_hint) const +{ + if (_signature_hint == nullptr) + { + return nullptr; + } + + fw::hash::type hash = fw::hash::hash(_signature_hint); + return find_function( hash ); +} + +std::shared_ptr Nodlang::find_function(fw::hash::type _hash) const +{ + auto found = m_functions_by_signature.find(_hash); + if ( found != m_functions_by_signature.end()) + { + return found->second; + } + return nullptr; +} + std::shared_ptr Nodlang::find_function(const func_type* _type) const { if (!_type) diff --git a/src/nodable/core/language/Nodlang.h b/src/nodable/core/language/Nodlang.h index 8353fcc2b..e42e7f9d6 100644 --- a/src/nodable/core/language/Nodlang.h +++ b/src/nodable/core/language/Nodlang.h @@ -121,7 +121,9 @@ namespace ndbl{ std::string& serialize_property(std::string &_out, const Property*) const; // Language definition ------------------------------------------------------------------------- + public: + invokable_ptr find_function( const char* _signature ) const; // Find a function by signature as string (ex: "int multiply(int,int)" ) invokable_ptr find_function(const fw::func_type*) const; // Find a function by signature (strict first, then cast allowed) invokable_ptr find_function_exact(const fw::func_type*) const; // Find a function by signature (no cast allowed). invokable_ptr find_function_fallback(const fw::func_type*) const; // Find a function by signature (casts allowed). @@ -139,7 +141,8 @@ namespace ndbl{ int get_precedence(const fw::iinvokable*)const; // Get the precedence of a given function (precedence may vary because function could be an operator implementation). template void load_library(); // Instantiate a library from its type (uses reflection to get all its static methods). - + private: + invokable_ptr find_function( unsigned long _hash ) const; private: struct { std::vector> keywords; @@ -151,6 +154,7 @@ namespace ndbl{ operators_vec m_operators; // the allowed operators (!= implementations). Invokable_vec m_operators_impl; // operators' implementations. Invokable_vec m_functions; // all the functions (including operator's). + std::unordered_map> m_functions_by_signature; // Functions indexed by signature hash std::unordered_map m_single_char_by_keyword; std::unordered_map m_keyword_by_token_t; // token_t to string (ex: Token_t::keyword_double => "double"). std::unordered_map m_keyword_by_type_id; @@ -158,6 +162,7 @@ namespace ndbl{ std::unordered_map m_token_t_by_keyword; // keyword reserved by the language (ex: int, string, operator, if, for, etc.) std::unordered_map m_token_t_by_type_id; std::unordered_map m_type_by_token_t; // token_t to type. Works only if token_t refers to a type keyword. + }; template diff --git a/src/nodable/gui/Condition.h b/src/nodable/gui/Condition.h index cd19ec126..c29a6e1fd 100644 --- a/src/nodable/gui/Condition.h +++ b/src/nodable/gui/Condition.h @@ -9,11 +9,14 @@ namespace ndbl Condition_ENABLE_IF_HAS_SELECTION = 1 << 0, Condition_ENABLE_IF_HAS_NO_SELECTION = 1 << 1, Condition_ENABLE_IF_HAS_GRAPH = 1 << 3, + Condition_DISABLE_IF_DRAGGING_THIS_SLOT = 1 << 4, + Condition_DISABLE_IF_DRAGGING_NON_THIS_SLOT= 1 << 5, + Condition_ONLY_FROM_GRAPH_EDITOR_CONTEXTUAL= 1 << 6, Condition_ENABLE = Condition_ENABLE_IF_HAS_SELECTION | Condition_ENABLE_IF_HAS_NO_SELECTION | Condition_ENABLE_IF_HAS_GRAPH, - Condition_HIGHLIGHTED_IN_GRAPH_EDITOR = 1 << 4, - Condition_HIGHLIGHTED_IN_TEXT_EDITOR = 1 << 5, + Condition_HIGHLIGHTED_IN_GRAPH_EDITOR = 1 << 10, + Condition_HIGHLIGHTED_IN_TEXT_EDITOR = 1 << 11, Condition_HIGHLIGHTED = Condition_HIGHLIGHTED_IN_GRAPH_EDITOR | Condition_HIGHLIGHTED_IN_TEXT_EDITOR, }; diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index d60236f2d..7b62c980a 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -19,6 +19,8 @@ namespace ndbl EventType_node_view_selected, EventType_node_view_deselected, EventType_frame_all_node_views, + EventType_create_node, + EventType_create_block, EventType_frame_selected_node_views, EventType_slot_dropped, EventType_slot_disconnected, @@ -44,6 +46,14 @@ namespace ndbl SlotRef second; }; + struct CreateNodeEvent + { + fw::EventType type; + SlotRef dragged; // The slot being dragged. + ImVec2 desired_pos; + Graph* graph = nullptr; + }; + union Event { fw::EventType type; @@ -52,6 +62,7 @@ namespace ndbl NodeViewEvent node; SlotEvent slot; ToggleFoldingEvent toggle_folding; + CreateNodeEvent create_node; }; }// namespace ndbl diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 2d6b62e77..8518e1c1b 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -8,7 +8,9 @@ #include "fw/core/log.h" #include "fw/core/system.h" +#include "Condition.h" #include "Config.h" +#include "Event.h" #include "Nodable.h" #include "NodeView.h" #include "Physics.h" @@ -31,14 +33,6 @@ using namespace fw; const char* k_context_menu_popup = "GraphView.ContextMenu"; -template -static fw::func_type* create_variable_node_signature() -{ return fw::func_type_builder::with_id("variable"); } - -template -static fw::func_type* create_literal_node_signature() -{ return fw::func_type_builder::with_id("literal"); } - REGISTER { fw::registration::push_class("GraphView").extends(); @@ -49,60 +43,14 @@ GraphView::GraphView(Graph* graph) , m_graph(graph) , m_context_menu() { - - // Prepare context menu items - - // 1) Blocks - m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " Condition", "condition", [&]() { return m_graph->create_cond_struct(); } } ); - m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " For Loop", "for loop", [&]() { return m_graph->create_for_loop(); } } ); - m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " While Loop", "while loop", [&]() { return m_graph->create_while_loop(); } } ); - m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " Scope", "scope", [&]() { return m_graph->create_scope(); } } ); - m_context_menu.add_item( BLOCKS, { ICON_FA_CODE " Program", "program scope", [&]() { m_graph->clear(); return m_graph->create_root(); } } ); - // 2) Variables - m_context_menu.add_item( VARIABLES, { ICON_FA_DATABASE " Boolean Variable", "boolean variable", [&]() { return create_variable(); }, create_variable_node_signature() } ); - m_context_menu.add_item( VARIABLES, { ICON_FA_DATABASE " Double Variable", "double variable", [&]() { return create_variable(); }, create_variable_node_signature() } ); - m_context_menu.add_item( VARIABLES, { ICON_FA_DATABASE " Integer Variable", "integer variable", [&]() { return create_variable(); }, create_variable_node_signature() } ); - m_context_menu.add_item( VARIABLES, { ICON_FA_DATABASE " String Variable", "string variable", [&]() { return create_variable(); }, create_variable_node_signature() } ); - // 3) Literals - m_context_menu.add_item( LITERALS, { ICON_FA_FILE " Boolean Literal", "boolean literal", [&]() { return m_graph->create_literal(); }, create_literal_node_signature() } ); - m_context_menu.add_item( LITERALS, { ICON_FA_FILE " Double Literal", "double float literal", [&]() { return m_graph->create_literal(); }, create_literal_node_signature() } ); - m_context_menu.add_item( LITERALS, { ICON_FA_FILE " Integer Literal", "integer literal", [&]() { return m_graph->create_literal(); }, create_literal_node_signature() } ); - m_context_menu.add_item( LITERALS, { ICON_FA_FILE " String Literal", "string literal", [&]() { return m_graph->create_literal(); }, create_literal_node_signature() } ); - // 4) Functions/Operators from the API - const Nodlang& language = Nodlang::get_instance(); - for (auto& each_fct : language.get_api()) + // Fill the contextual menu + for( auto& each : EventManager::get_instance().get_actions() ) { - const fw::func_type* func_type = each_fct->get_type(); - bool is_operator = Nodlang::get_instance().find_operator_fct( func_type ) != nullptr; - auto node_factory_fct = [&]() -> PoolID + if( each.event_t == EventType_create_node || each.event_t == EventType_create_block ) { - return m_graph->create_function(each_fct.get(), is_operator); - }; - - std::string label; - language.serialize_func_sig(label, func_type ); - std::string search_target_string = func_type->get_identifier(); - search_target_string.append(is_operator ? " operator" : " function"); - m_context_menu.add_item( FUNCTIONS, {label, search_target_string, node_factory_fct, func_type} ); - } -} - -PoolID GraphView::create_variable(const fw::type* _type, const char* _name, PoolID _scope) -{ - if( !_scope) - { - _scope = m_graph->get_root()->get_component(); + m_context_menu.items.push_back( each ); + } } - - PoolID var_node = m_graph->create_variable(_type, _name, _scope ); - var_node->set_declared(true); - - Token token(Token_t::keyword_operator, " = "); - token.m_word_start_pos = 1; - token.m_word_size = 1; - - var_node->assignment_operator_token = token; - return var_node; } bool GraphView::draw() @@ -384,50 +332,16 @@ bool GraphView::draw() * In case user has created a new node we need to connect it to the m_graph depending * on if a slot is being dragged and what is its nature. */ - if ( PoolID new_node_id = m_context_menu.draw_search_input( 10 ) ) + if ( fw::Action* action = m_context_menu.draw_search_input( 10 ) ) { - if ( !m_context_menu.dragged_slot ) - { - // Experimental: we try to connect a parent-less child - if (new_node_id != m_graph->get_root() && app.config.experimental_graph_autocompletion ) - { - m_graph->ensure_has_root(); - // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); - } - } - else - { - Slot* complementary_slot = new_node_id->find_slot_by_property_type( - get_complementary_flags( m_context_menu.dragged_slot->slot().static_flags() ), - m_context_menu.dragged_slot->get_property()->get_type() - ); - - if ( !complementary_slot ) - { - // TODO: this case should not happens, instead we should check ahead of time whether or not this not can be attached - LOG_ERROR("GraphView", "unable to connect this node") - } - else - { - Slot* out = &m_context_menu.dragged_slot->slot(); - Slot* in = complementary_slot; - - if( out->has_flags(SlotFlag_ORDER_SECOND) ) std::swap(out, in); - - m_graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); - } - - } - - // set new_node's view position, select it - if( auto view = new_node_id->get_component() ) - { - view->set_position(m_context_menu.opened_at_pos, fw::Space_Local); - NodeView::set_selected(view); - } - + ndbl::CreateNodeEvent event{ + action->event_t, + m_context_menu.dragged_slot->slot(), + m_context_menu.opened_at_pos, + m_graph + }; + EventManager::get_instance().push_event((fw::Event&)event); ImGui::CloseCurrentPopup(); - SlotView::reset_dragged(); } ImGui::EndPopup(); } else { @@ -590,28 +504,28 @@ void GraphView::translate_view(ImVec2 delta) // m_view_origin += delta; } -PoolID ContextMenu::draw_search_input( size_t _result_max_count ) +fw::Action* ContextMenu::draw_search_input( size_t _result_max_count ) { - PoolID new_node_id; + bool validated; if ( must_be_reset_flag ) { ImGui::SetKeyboardFocusHere(); // - do_filter_based_on_signature(); + update_cache_based_on_signature(); // Initial search - do_search( 100 ); + update_cache_based_on_user_input( 100 ); // Ensure we reset once must_be_reset_flag = false; } - // Draw search input and do_search on input change + // Draw search input and update_cache_based_on_user_input on input change if ( ImGui::InputText("Search", search_input, 255, ImGuiInputTextFlags_EscapeClearsAll )) { - do_search( 100 ); + update_cache_based_on_user_input( 100 ); } if ( !items_matching_search.empty() ) @@ -619,9 +533,10 @@ PoolID ContextMenu::draw_search_input( size_t _result_max_count ) // When a single item is filtered, pressing enter will press the item's button. if ( items_matching_search.size() == 1) { - if ( ImGui::SmallButton( items_matching_search[0].label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) + Action& action = items_matching_search[0]; + if ( ImGui::SmallButton( action.label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) { - new_node_id = items_matching_search[0].node_factory_fct(); + return &action; } } else @@ -635,11 +550,12 @@ PoolID ContextMenu::draw_search_input( size_t _result_max_count ) auto it = items_matching_search.begin(); while( it != items_matching_search.end() && std::distance(items_matching_search.begin(), it) != _result_max_count) { - if ( ImGui::Button( (*it).label.c_str()) || // User can click on the button... + auto& action = *it; + if ( ImGui::Button( action.label.c_str()) || // User can click on the button... (ImGui::IsKeyDown( ImGuiKey_Enter ) && ImGui::IsItemFocused() ) // ...or press enter if this item is the first ) { - new_node_id = (*it).node_factory_fct(); + return &action; } it++; } @@ -654,52 +570,44 @@ PoolID ContextMenu::draw_search_input( size_t _result_max_count ) ImGui::Text("No matches..."); } - return new_node_id; + return nullptr; } -void ContextMenu::do_filter_based_on_signature() +void ContextMenu::update_cache_based_on_signature() { items_with_compatible_signature.clear(); if ( !dragged_slot ) { // When no slot is dragged, user can create any node - for (auto& [_, menu_item] : items_by_category ) - { - items_with_compatible_signature.push_back( menu_item ); - } + items_with_compatible_signature = items; } else { - for (auto& [category_key, menu_item] : items_by_category ) + for (auto& menu_item : items ) { - switch ( category_key ) + if ( menu_item.event_t == EventType_create_block ) { - case BLOCKS: - if ( dragged_slot->is_this() ) - { - items_with_compatible_signature.push_back( menu_item ); - } - break; - - case LITERALS: - case VARIABLES: - // I defined fake node signatures, they are handled like any other function - - case FUNCTIONS: - if ( !dragged_slot->is_this() ) - { - const type* dragged_property_type = dragged_slot->get_property_type(); + if ( dragged_slot->is_this() ) + { + items_with_compatible_signature.push_back( menu_item ); + } + } + else + { + if ( !dragged_slot->is_this() ) + { + const type* dragged_property_type = dragged_slot->get_property_type(); - if ( menu_item.node_signature ) + if ( menu_item.signature ) { if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) { - if ( !menu_item.node_signature->has_an_arg_of_type(dragged_property_type) ) + if ( !menu_item.signature->has_an_arg_of_type(dragged_property_type) ) { continue; } } - else if ( !menu_item.node_signature->get_return_type()->equals(dragged_property_type) ) + else if ( !menu_item.signature->get_return_type()->equals(dragged_property_type) ) { continue; } @@ -712,12 +620,12 @@ void ContextMenu::do_filter_based_on_signature() } } } -void ContextMenu::do_search( size_t _limit ) +void ContextMenu::update_cache_based_on_user_input( size_t _limit ) { items_matching_search.clear(); for ( auto& menu_item : items_with_compatible_signature ) { - if( menu_item.search_target_string.find( search_input ) != std::string::npos ) + if( menu_item.label.find( search_input ) != std::string::npos ) { items_matching_search.push_back(menu_item); if ( items_matching_search.size() == _limit ) @@ -739,11 +647,3 @@ void ContextMenu::reset_state( SlotView* _dragged_slot ) items_matching_search.clear(); items_with_compatible_signature.clear(); } - -void ContextMenu::add_item( - ContextMenuGroups _category, - ContextMenuItem _item - ) -{ - items_by_category.insert( { _category, std::move(_item)}); -} diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index 79fde3042..268315045 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -22,36 +22,21 @@ namespace ndbl class Nodable; class Graph; - struct ContextMenuItem + struct ContextMenu { - std::string label; // The displayed label for this item. - std::string search_target_string; // The lower case string used to search. - std::function(void)> node_factory_fct; // The lambda function to invoke to create the node linked to this menu item. - const fw::func_type* node_signature = nullptr; // The signature of the node this->node_factory_fct would create. - }; + bool must_be_reset_flag = false; + ImVec2 opened_at_pos = ImVec2(-1,-1); // relative + ImVec2 opened_at_screen_pos = ImVec2(-1,-1); // absolute (screen space) + SlotView* dragged_slot = nullptr; // The slot being dragged when the context menu opened. + char search_input[255] = "\0"; // The search input entered by the user. + std::vector items; // All the available items + std::vector items_with_compatible_signature; // Only the items having a compatible signature (with the slot dragged) + std::vector items_matching_search; // Only the items having a compatible signature AND matching the search_input. - enum ContextMenuGroups // To group and order context menu items - { - BLOCKS = 0, // first to be displayed ... - VARIABLES, - LITERALS, - FUNCTIONS // ... last - }; - - struct ContextMenu { - bool must_be_reset_flag = false; - ImVec2 opened_at_pos = ImVec2(-1,-1); // relative - ImVec2 opened_at_screen_pos = ImVec2(-1,-1); // absolute (screen space) - SlotView* dragged_slot = nullptr; // The slot being dragged when the context menu opened. - char search_input[255] = "\0"; // The search input entered by the user. - std::multimap items_by_category; // All the available items sorted by category - std::vector items_with_compatible_signature; // Only the items having a compatible signature (with the slot dragged) - std::vector items_matching_search; // Only the items having a compatible signature AND matching the search_input. - void reset_state(SlotView* _dragged_slot = nullptr); - void add_item( ContextMenuGroups _category, ContextMenuItem _item); - PoolID draw_search_input( size_t _result_max_count ); - void do_search( size_t _limit ); - void do_filter_based_on_signature(); + fw::Action* draw_search_input( size_t _result_max_count ); // Return the triggered action, user has to deal with the Action. + void reset_state(SlotView* _dragged_slot = nullptr); + void update_cache_based_on_signature(); + void update_cache_based_on_user_input( size_t _limit ); }; class GraphView: public fw::View @@ -70,13 +55,9 @@ namespace ndbl void unfold(); // unfold the graph until it is stabilized private: - void draw_grid( ImDrawList*, const Config& ) const; - void frame_views(const std::vector &_views, bool _align_top_left_corner); - void translate_view(ImVec2 vec2); - PoolID create_variable(const fw::type* _type, const char* _name, PoolID _scope); - template - PoolID create_variable(const char* _name = "var", PoolID _scope = {}) - { return create_variable( fw::type::get(), _name, _scope); } + void draw_grid( ImDrawList*, const Config& ) const; + void frame_views(const std::vector &_views, bool _align_top_left_corner); + void translate_view(ImVec2 vec2); Graph* m_graph; ImVec2 m_view_origin; diff --git a/src/nodable/gui/HybridFileView.cpp b/src/nodable/gui/HybridFileView.cpp index 6326c60ca..7a20316e3 100644 --- a/src/nodable/gui/HybridFileView.cpp +++ b/src/nodable/gui/HybridFileView.cpp @@ -51,7 +51,7 @@ HybridFileView::HybridFileView(HybridFile& _file) graph_view->translate_all( ImVec2(-1000.f, -1000.0f) , views); // frame all (33ms delayed) - fw::EventManager::get_instance().push_async(EventType_frame_all_node_views, 33); + fw::EventManager::get_instance().push_event_delayed( EventType_frame_all_node_views, 33 ); } } }); diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 1683d8b83..30c70ff2b 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -29,6 +29,14 @@ using fw::View; Nodable *Nodable::s_instance = nullptr; +template +static fw::func_type* create_variable_node_signature() +{ return fw::func_type_builder::with_id("variable"); } + +template +static fw::func_type* create_literal_node_signature() +{ return fw::func_type_builder::with_id("literal"); } + Nodable::Nodable() : App(config.common, new NodableView(this) ) , current_file(nullptr) @@ -102,97 +110,126 @@ bool Nodable::on_init() // Bind commands to shortcuts using fw::EventType; - event_manager.bind( - {"Delete", - EventType_delete_node_action_triggered, - {SDLK_DELETE, KMOD_NONE}, - Condition_ENABLE}); - event_manager.bind( - {"Arrange", - EventType_arrange_node_action_triggered, - {SDLK_a, KMOD_NONE}, - Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR}); - event_manager.bind( - {"Fold", - EventType_toggle_folding_selected_node_action_triggered, - {SDLK_x, KMOD_NONE}, - Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR}); - event_manager.bind( - {"Next", - EventType_select_successor_node_action_triggered, - {SDLK_n, KMOD_NONE}, - Condition_ENABLE}); - event_manager.bind( - {ICON_FA_SAVE " Save", - fw::EventType_save_file_triggered, - {SDLK_s, KMOD_CTRL}, - Condition_ENABLE}); - event_manager.bind( - {ICON_FA_SAVE " Save as", - fw::EventType_save_file_as_triggered, - {SDLK_s, KMOD_CTRL}, - Condition_ENABLE}); - event_manager.bind( - {ICON_FA_TIMES " Close", - fw::EventType_close_file_triggered, - {SDLK_w, KMOD_CTRL}, - Condition_ENABLE}); - event_manager.bind( - {ICON_FA_FOLDER_OPEN " Open", - fw::EventType_browse_file_triggered, - {SDLK_o, KMOD_CTRL}, - Condition_ENABLE}); - event_manager.bind( - {ICON_FA_FILE " New", - fw::EventType_new_file_triggered, - {SDLK_n, KMOD_CTRL}, - Condition_ENABLE}); - event_manager.bind( - {"Splashscreen", - fw::EventType_show_splashscreen_triggered, - {SDLK_F1}, - Condition_ENABLE}); - event_manager.bind( - {ICON_FA_SIGN_OUT_ALT " Exit", - fw::EventType_exit_triggered, - {SDLK_F4, KMOD_ALT}, - Condition_ENABLE}); - event_manager.bind( - {"Undo", - fw::EventType_undo_triggered, - {SDLK_z, KMOD_CTRL}, - Condition_ENABLE}); - event_manager.bind( - {"Redo", - fw::EventType_redo_triggered, - {SDLK_y, KMOD_CTRL}, - Condition_ENABLE}); - event_manager.bind( - {"Isolate", - EventType_toggle_isolate_selection, - {SDLK_i, KMOD_CTRL}, - Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR}); - event_manager.bind( - {"Deselect", - fw::EventType_none, - {0, KMOD_NONE, "Double click on bg"}, - Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR}); - event_manager.bind( - {"Move Graph", - fw::EventType_none, - {0, KMOD_NONE, "Drag background"}, - Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR}); - event_manager.bind( - {"Frame Selection", - EventType_frame_selected_node_views, - {SDLK_f, KMOD_NONE}, - Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR}); - event_manager.bind( - {"Frame All", - EventType_frame_all_node_views, - {SDLK_f, KMOD_LCTRL}, - Condition_ENABLE}); + event_manager.add_action( + { "Delete", + EventType_delete_node_action_triggered, + { SDLK_DELETE, KMOD_NONE }, + Condition_ENABLE } ); + event_manager.add_action( + { "Arrange", + EventType_arrange_node_action_triggered, + { SDLK_a, KMOD_NONE }, + Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); + event_manager.add_action( + { "Fold", + EventType_toggle_folding_selected_node_action_triggered, + { SDLK_x, KMOD_NONE }, + Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); + event_manager.add_action( + { "Next", + EventType_select_successor_node_action_triggered, + { SDLK_n, KMOD_NONE }, + Condition_ENABLE } ); + event_manager.add_action( + { ICON_FA_SAVE " Save", + fw::EventType_save_file_triggered, + { SDLK_s, KMOD_CTRL }, + Condition_ENABLE } ); + event_manager.add_action( + { ICON_FA_SAVE " Save as", + fw::EventType_save_file_as_triggered, + { SDLK_s, KMOD_CTRL }, + Condition_ENABLE } ); + event_manager.add_action( + { ICON_FA_TIMES " Close", + fw::EventType_close_file_triggered, + { SDLK_w, KMOD_CTRL }, + Condition_ENABLE } ); + event_manager.add_action( + { ICON_FA_FOLDER_OPEN " Open", + fw::EventType_browse_file_triggered, + { SDLK_o, KMOD_CTRL }, + Condition_ENABLE } ); + event_manager.add_action( + { ICON_FA_FILE " New", + fw::EventType_new_file_triggered, + { SDLK_n, KMOD_CTRL }, + Condition_ENABLE } ); + event_manager.add_action( + { "Splashscreen", + fw::EventType_show_splashscreen_triggered, + { SDLK_F1 }, + Condition_ENABLE } ); + event_manager.add_action( + { ICON_FA_SIGN_OUT_ALT " Exit", + fw::EventType_exit_triggered, + { SDLK_F4, KMOD_ALT }, + Condition_ENABLE } ); + event_manager.add_action( + { "Undo", + fw::EventType_undo_triggered, + { SDLK_z, KMOD_CTRL }, + Condition_ENABLE } ); + event_manager.add_action( + { "Redo", + fw::EventType_redo_triggered, + { SDLK_y, KMOD_CTRL }, + Condition_ENABLE } ); + event_manager.add_action( + { "Isolate", + EventType_toggle_isolate_selection, + { SDLK_i, KMOD_CTRL }, + Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR } ); + event_manager.add_action( + { "Deselect", + fw::EventType_none, + { 0, KMOD_NONE, "Double click on bg" }, + Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); + event_manager.add_action( + { "Move Graph", + fw::EventType_none, + { 0, KMOD_NONE, "Drag background" }, + Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); + event_manager.add_action( + { "Frame Selection", + EventType_frame_selected_node_views, + { SDLK_f, KMOD_NONE }, + Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); + event_manager.add_action( + { "Frame All", + EventType_frame_all_node_views, + { SDLK_f, KMOD_LCTRL }, + Condition_ENABLE } ); + + // Prepare context menu items + { + // 1) Blocks + event_manager.add_action( { ICON_FA_CODE " Condition", EventType_create_block, {}, Condition_ENABLE } ); + event_manager.add_action( { ICON_FA_CODE " For Loop", EventType_create_block, {}, Condition_ENABLE } ); + event_manager.add_action( { ICON_FA_CODE " While Loop", EventType_create_block, {}, Condition_ENABLE } ); + event_manager.add_action( { ICON_FA_CODE " Scope", EventType_create_block, {}, Condition_ENABLE } ); + event_manager.add_action( { ICON_FA_CODE " Program", EventType_create_block, {}, Condition_ENABLE } ); + // 2) Variables + event_manager.add_action( { ICON_FA_DATABASE " Boolean Variable", EventType_create_node, {}, Condition_ENABLE, create_variable_node_signature()} ); + event_manager.add_action( { ICON_FA_DATABASE " Double Variable", EventType_create_node, {}, Condition_ENABLE, create_variable_node_signature() } ); + event_manager.add_action( { ICON_FA_DATABASE " Integer Variable", EventType_create_node, {}, Condition_ENABLE, create_variable_node_signature() } ); + event_manager.add_action( { ICON_FA_DATABASE " String Variable", EventType_create_node, {}, Condition_ENABLE, create_variable_node_signature() } ); + // 3) Literals + event_manager.add_action( { ICON_FA_FILE " Boolean Literal", EventType_create_node, {}, Condition_ENABLE, create_literal_node_signature() } ); + event_manager.add_action( { ICON_FA_FILE " Double Literal", EventType_create_node, {}, Condition_ENABLE, create_literal_node_signature() } ); + event_manager.add_action( { ICON_FA_FILE " Integer Literal", EventType_create_node, {}, Condition_ENABLE, create_literal_node_signature() } ); + event_manager.add_action( { ICON_FA_FILE " String Literal", EventType_create_node, {}, Condition_ENABLE, create_literal_node_signature() } ); + // 4) Functions/Operators from the API + const Nodlang& language = Nodlang::get_instance(); + for ( auto& each_fct: language.get_api() ) + { + const fw::func_type* func_type = each_fct->get_type(); + std::string label; + language.serialize_func_sig( label, func_type ); + event_manager.add_action( { label.c_str(), EventType_create_node, {}, Condition_ENABLE, func_type } ); + } + } return true; } @@ -219,7 +256,7 @@ void Nodable::on_update() // shorthand to push all shortcuts to a file view overlay depending on conditions auto push_overlay_shortcuts = [&](ndbl::HybridFileView& _view, Condition _condition) -> void { - for (const auto& _binded_event: event_manager.get_binded_events()) + for (const auto& _binded_event: event_manager.get_actions()) { if( (_binded_event.condition & _condition) == _condition) { @@ -442,6 +479,57 @@ void Nodable::on_update() break; } + case EventType_create_node: + { + const fw::func_type* signature = event.create_node.signature; + SlotRef dragged_slot = event.create_node.dragged; + ImVec2 desired_pos = event.create_node.desired_pos; + Graph* graph = event.create_node.graph; + + // 1) create the node + PoolID new_node_id = current_file->get_graph()->create_node( signature ); // TODO: store the type + + // 2) handle connections + if ( !dragged_slot ) + { + // Experimental: we try to connect a parent-less child + if ( new_node_id != graph->get_root() && config.experimental_graph_autocompletion ) + { + graph->ensure_has_root(); + // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); + } + } + else + { + Slot* complementary_slot = new_node_id->find_slot_by_property_type( + get_complementary_flags( dragged_slot->static_flags() ), + dragged_slot->get_property()->get_type() ); + + if ( !complementary_slot ) + { + // TODO: this case should not happens, instead we should check ahead of time whether or not this not can be attached + LOG_ERROR( "GraphView", "unable to connect this node" ) + } + else + { + Slot* out = dragged_slot.get(); + Slot* in = complementary_slot; + + if ( out->has_flags( SlotFlag_ORDER_SECOND ) ) std::swap( out, in ); + + graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); + } + } + + // set new_node's view position, select it + if ( auto view = new_node_id->get_component() ) + { + view->set_position( desired_pos, fw::Space_Local ); + NodeView::set_selected( view ); + } + break; + } + default: { LOG_VERBOSE("App", "Ignoring and event, this case is not handled\n") @@ -486,7 +574,7 @@ HybridFile *Nodable::add_file(HybridFile* _file) FW_EXPECT(_file, "File is nullptr"); m_loaded_files.push_back( _file ); current_file = _file; - event_manager.push(fw::EventType_file_opened); + event_manager.push_event( fw::EventType_file_opened ); return _file; } diff --git a/src/nodable/gui/NodableView.cpp b/src/nodable/gui/NodableView.cpp index 0af461b6a..ba753708a 100644 --- a/src/nodable/gui/NodableView.cpp +++ b/src/nodable/gui/NodableView.cpp @@ -95,7 +95,7 @@ void NodableView::on_draw() auto has_selection = NodeView::is_any_selected(); if (ImGui::MenuItem("Delete", "Del.", false, has_selection && vm_is_stopped)) { - event_manager.push(EventType_delete_node_action_triggered); + event_manager.push_event( EventType_delete_node_action_triggered ); } fw::ImGuiEx::MenuItemBindedToEvent(EventType_arrange_node_action_triggered, false, has_selection); @@ -503,10 +503,10 @@ void NodableView::draw_startup_window(ImGuiID dockspace_id) { ImVec2 btn_size(center_area.x * 0.44f, 40.0f); if (ImGui::Button(ICON_FA_FILE" New File", btn_size)) - event_manager.push(fw::EventType_new_file_triggered); + event_manager.push_event( fw::EventType_new_file_triggered ); ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN" Open ...", btn_size)) - event_manager.push(fw::EventType_browse_file_triggered); + event_manager.push_event( fw::EventType_browse_file_triggered ); ImGui::NewLine(); ImGui::Separator(); @@ -841,7 +841,7 @@ void NodableView::draw_toolbar_window() { if (ImGui::Button( config.isolate_selection ? ICON_FA_CROP " isolation mode: ON " : ICON_FA_CROP " isolation mode: OFF", button_size)) { - m_app->event_manager.push(EventType_toggle_isolate_selection); + m_app->event_manager.push_event( EventType_toggle_isolate_selection ); } ImGui::SameLine(); ImGui::EndGroup(); From 65cba44f0f72af5acbc5d7107542c56ccb0b7820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Thu, 29 Feb 2024 23:21:33 -0500 Subject: [PATCH 08/27] fix(GraphView): contextual menu 4/N (large refactor) --- src/fw/gui/Action.h | 56 +++++++++++ src/fw/gui/App.cpp | 16 ++- src/fw/gui/Event.h | 54 ++++++++++ src/fw/gui/EventManager.cpp | 45 ++++----- src/fw/gui/EventManager.h | 108 ++++++-------------- src/fw/gui/ImGuiEx.cpp | 8 +- src/fw/gui/ImGuiEx.h | 2 +- src/nodable/core/Graph.cpp | 16 +-- src/nodable/core/Graph.h | 4 +- src/nodable/gui/Action.h | 14 +++ src/nodable/gui/Event.h | 70 ++++++------- src/nodable/gui/GraphView.cpp | 77 ++++++++------- src/nodable/gui/GraphView.h | 31 +++--- src/nodable/gui/HybridFile.cpp | 8 ++ src/nodable/gui/HybridFileView.cpp | 2 +- src/nodable/gui/Nodable.cpp | 152 +++++++++++++++-------------- src/nodable/gui/NodableView.cpp | 39 ++++---- src/nodable/gui/NodeView.cpp | 10 +- src/nodable/gui/SlotView.cpp | 9 +- 19 files changed, 392 insertions(+), 329 deletions(-) create mode 100644 src/fw/gui/Action.h create mode 100644 src/fw/gui/Event.h create mode 100644 src/nodable/gui/Action.h diff --git a/src/fw/gui/Action.h b/src/fw/gui/Action.h new file mode 100644 index 000000000..bde8e0702 --- /dev/null +++ b/src/fw/gui/Action.h @@ -0,0 +1,56 @@ +#pragma once +#include "Event.h" +#include "core/types.h" +#include + +namespace fw +{ + /** Data describing a shortcut (ex: "Reset": Ctrl + Alt + R) */ + struct Shortcut + { + SDL_Keycode key = SDLK_UNKNOWN; // a key to be pressed + SDL_Keymod mod = KMOD_NONE; // modifiers (alt, ctrl, etc.) + std::string description; + std::string to_string() const; + }; + + /** Basic action defining which event has to be triggered when a given shortcut is detected */ + class Action + { + public: + Action( + const char* label, + EventID event_t, + const Shortcut& shortcut = {}) + : label(label) + , event_id(event_t) + , shortcut(shortcut) + {} + std::string label; + EventID event_id; + Shortcut shortcut; + virtual void* data() const { return nullptr; } // Pointer to custom data + }; + + /** Generic action with a custom payload */ + template + class TAction : public Action { + public: + static_assert( !std::is_same_v ); + static_assert( !std::is_same_v ); + + using payload_t = PayloadT; + + TAction( + const char* label, + EventID event_t, + Shortcut shortcut = {}, + PayloadT payload = {} + ) + : Action(label, event_t, shortcut) + , payload(payload) + {} + [[nodiscard]] void* data() const override { return (void*)const_cast( &payload ); } + PayloadT payload; // Custom data to attach + }; +} \ No newline at end of file diff --git a/src/fw/gui/App.cpp b/src/fw/gui/App.cpp index 7b34b052b..dc62a3fc0 100644 --- a/src/fw/gui/App.cpp +++ b/src/fw/gui/App.cpp @@ -243,13 +243,12 @@ void App::handle_events() for(const auto& _action: event_manager.get_actions() ) { // first, priority to shortcuts with mod - if ( _action.shortcut.mod != KMOD_NONE - && _action.event_t - && ( _action.shortcut.mod & event.key.keysym.mod) - && _action.shortcut.key == event.key.keysym.sym + if ( _action->shortcut.mod != KMOD_NONE + && _action->event_id && ( _action->shortcut.mod & event.key.keysym.mod) + && _action->shortcut.key == event.key.keysym.sym ) { - event_manager.push_event( _action.event_t ); + event_manager.dispatch( _action->event_id ); break; } } @@ -259,12 +258,11 @@ void App::handle_events() for(const auto& _action: event_manager.get_actions() ) { // first, priority to shortcuts with mod - if ( _action.shortcut.mod == KMOD_NONE - && _action.event_t - && _action.shortcut.key == event.key.keysym.sym + if ( _action->shortcut.mod == KMOD_NONE + && _action->event_id && _action->shortcut.key == event.key.keysym.sym ) { - event_manager.push_event( _action.event_t ); + event_manager.dispatch( _action->event_id ); break; } } diff --git a/src/fw/gui/Event.h b/src/fw/gui/Event.h new file mode 100644 index 000000000..66fc5a4a7 --- /dev/null +++ b/src/fw/gui/Event.h @@ -0,0 +1,54 @@ +#pragma once +#include "core/types.h" + +namespace fw +{ + typedef u16_t EventID; + /** + * Enumerate event types + * Can be extended starting at EventID_USER_DEFINED + */ + enum EventID_ : u16_t + { + // Declare common event types + + EventID_NONE = 0, + + EventID_REQUEST_FILE_SAVE, + EventID_REQUEST_FILE_SAVE_AS, + EventID_REQUEST_FILE_NEW, + EventID_REQUEST_FILE_CLOSE, + EventID_REQUEST_FILE_BROWSE, + EventID_REQUEST_UNDO, + EventID_REQUEST_REDO, + EventID_REQUEST_EXIT, + EventID_REQUEST_SHOW_SLASHSCREEN, + + EventID_FILE_OPENED, + + EventID_USER_DEFINED = 0xff, + }; + + + /** Basic event, can be extended via TEvent */ + class Event + { + public: + const EventID id; + constexpr explicit Event(EventID id): id(id) {} + [[nodiscard]] virtual void* data() const { return nullptr; } + }; + + /** Template to extend Event with a specific payload */ + template + class TEvent : public fw::Event + { + public: + constexpr static EventID id = event_id; + using payload_t = PayloadT; + PayloadT m_payload; + template + explicit TEvent(Args... args): Event(event_id), m_payload(args...){} + [[nodiscard]] void* data() const override { return const_cast( static_cast( &m_payload ) ); } + }; +} \ No newline at end of file diff --git a/src/fw/gui/EventManager.cpp b/src/fw/gui/EventManager.cpp index d9ce384ba..1055ce15b 100644 --- a/src/fw/gui/EventManager.cpp +++ b/src/fw/gui/EventManager.cpp @@ -15,6 +15,10 @@ EventManager::~EventManager() { LOG_VERBOSE("fw::EventManager", "Destructor ...\n"); s_instance = nullptr; + for( auto action : m_actions ) + { + delete action; + } LOG_VERBOSE("fw::EventManager", "Destructor " OK "\n"); } EventManager::EventManager() @@ -31,56 +35,49 @@ EventManager& EventManager::get_instance() return *s_instance; } -void EventManager::push_event(Event &_event) +void EventManager::dispatch(Event* _event) { m_events.push(_event); } -size_t EventManager::poll_event(Event &_event) -{ - size_t count = m_events.size(); - - if( count ) - { - _event = m_events.front(); - m_events.pop(); - } - - return count; -} - -void EventManager::push_event(EventType _type) +Event* EventManager::poll_event() { - Event simple_event = { _type }; - push_event(simple_event); + return m_events.empty() ? nullptr : m_events.front(); } -void EventManager::add_action(Action _action ) +Event* EventManager::dispatch( EventID _event_id ) { - m_actions.push_back( _action ); - m_actions_by_event_type.insert({ _action.event_t, _action }); + auto new_event = new Event{ _event_id }; + dispatch(new_event ); + return new_event; } -const Action& EventManager::get_action_by_type( u16_t type ) +const Action* EventManager::get_action_by_type( u16_t type ) { return m_actions_by_event_type.at(type); } -const std::vector& EventManager::get_actions() const +const std::vector& EventManager::get_actions() const { return m_actions; } -void EventManager::push_event_delayed(EventType type, u64_t delay) +void EventManager::dispatch_delayed( EventID type, u64_t delay) { fw::async::get_instance().add_task( std::async(std::launch::async, [this, type, delay]() -> void { std::this_thread::sleep_for(std::chrono::milliseconds{delay}); - push_event( type ); + dispatch( type ); }) ); } +void EventManager::add_action(Action* _action )// Add a new action (can be triggered via shortcut) +{ + m_actions.push_back( _action ); + m_actions_by_event_type.insert({ _action->event_id, _action }); +} + std::string Shortcut::to_string() const { std::string result; diff --git a/src/fw/gui/EventManager.h b/src/fw/gui/EventManager.h index ae575610a..e7f047783 100644 --- a/src/fw/gui/EventManager.h +++ b/src/fw/gui/EventManager.h @@ -5,77 +5,14 @@ #include #include #include -#include -#include "core/types.h" +#include "Action.h" +#include "Event.h" #include "core/reflection/func_type.h" +#include "core/types.h" namespace fw { - struct Shortcut - { - SDL_Keycode key = SDLK_UNKNOWN; // a key to be pressed - SDL_Keymod mod = KMOD_NONE; // modifiers (alt, ctrl, etc.) - std::string description; - std::string to_string() const; - }; - - - // Declare some event types. - // EventType can be extended starting at EventType_USER_DEFINED - typedef u16_t EventType; - enum EventType_ : u16_t - { - // Declare common event types - - EventType_none = 0, - EventType_save_file_triggered, // operation on files - EventType_save_file_as_triggered, - EventType_new_file_triggered, - EventType_close_file_triggered, - EventType_browse_file_triggered, - EventType_file_opened, - EventType_undo_triggered, // history - EventType_redo_triggered, - EventType_exit_triggered, // general - EventType_show_splashscreen_triggered, - - EventType_USER_DEFINED = 0xff, - }; - - struct SimpleEvent { - EventType type; - }; - - union Event - { - EventType type; - SimpleEvent common; - }; - - // An action can trigger an event under certain circumstances - class Action { - public: - std::string label; - u16_t event_t; - Shortcut shortcut; - u16_t condition; - const fw::func_type* signature; // If action creates a node, this will point to the signature of if. - - Action( - const char* label, - u16_t event_t, - Shortcut shortcut = {}, - u16_t condition = {}, - const fw::func_type* signature = nullptr) - : label(label) - , event_t(event_t) - , shortcut(std::move(shortcut)) - , condition(condition) - , signature(signature) - {} - }; - class EventManager { public: @@ -83,20 +20,37 @@ namespace fw EventManager(const EventManager&) = delete; ~EventManager(); - void push_event(EventType _type); - void push_event_delayed(EventType, u64_t); // Push an event with a delay in millisecond - void push_event(Event& _event); - size_t poll_event(Event& _event); - const std::vector& get_actions() const; - void add_action(Action); - const Action& get_action_by_type(u16_t type); + Event* dispatch( EventID ); // Create and push a basic event to the queue + void dispatch_delayed( EventID, u64_t); // Does the same as dispatch(EventID) with a delay in millisecond. A delay of 0ms will be processed after a regular dispatch though. + void dispatch(Event* _event); // Push an existing event to the queue. + Event* poll_event(); // Pop the first event in the queue + const std::vector& get_actions() const; // Get all the actions bound to any event + template + Action* emplace_action(Args... args) + { + static_assert( std::is_base_of_v ); + Action* action = new ActionT(args...); + add_action(action); + return action; + } + template + Event* dispatch(const typename EventT::payload_t& payload) + { + static_assert( std::is_base_of_v ); + auto new_event = new EventT(payload); + dispatch(new_event); + return new_event; + } + + const Action* get_action_by_type(u16_t type); // Get the action bound to a given event type static EventManager& get_instance(); - private: + void add_action(Action* _action); + static EventManager* s_instance; - std::queue m_events; - std::vector m_actions; - std::map m_actions_by_event_type; + std::queue m_events; + std::vector m_actions; + std::map m_actions_by_event_type; }; } \ No newline at end of file diff --git a/src/fw/gui/ImGuiEx.cpp b/src/fw/gui/ImGuiEx.cpp index cbec72b69..65495b118 100644 --- a/src/fw/gui/ImGuiEx.cpp +++ b/src/fw/gui/ImGuiEx.cpp @@ -227,12 +227,12 @@ void ImGuiEx::BeginFrame() s_is_any_tooltip_open = false; } -void ImGuiEx::MenuItemBindedToEvent(uint16_t type, bool selected, bool enable) +void ImGuiEx::MenuItem(uint16_t type, bool selected, bool enable) { - auto binded_evt = EventManager::get_instance().get_action_by_type( type ); - if (ImGui::MenuItem( binded_evt.label.c_str(), binded_evt.shortcut.to_string().c_str(), selected, enable)) + const Action* action = EventManager::get_instance().get_action_by_type( type ); + if (ImGui::MenuItem( action->label.c_str(), action->shortcut.to_string().c_str(), selected, enable)) { - EventManager::get_instance().push_event( binded_evt.event_t ); + EventManager::get_instance().dispatch( action->event_id ); } } diff --git a/src/fw/gui/ImGuiEx.h b/src/fw/gui/ImGuiEx.h index 7619a2d2c..2a265c83b 100644 --- a/src/fw/gui/ImGuiEx.h +++ b/src/fw/gui/ImGuiEx.h @@ -106,7 +106,7 @@ namespace fw static void EndTooltip(); static ImRect& EnlargeToInclude(ImRect& _rect, ImRect _other); - static void MenuItemBindedToEvent(uint16_t type, bool selected = false, bool enable = true); + static void MenuItem(uint16_t type, bool selected = false, bool enable = true); static void BulletTextWrapped(const char*); static ImRect GetContentRegion(Space); diff --git a/src/nodable/core/Graph.cpp b/src/nodable/core/Graph.cpp index dc5839007..ae37c5649 100644 --- a/src/nodable/core/Graph.cpp +++ b/src/nodable/core/Graph.cpp @@ -511,19 +511,13 @@ PoolID Graph::create_literal(const fw::type *_type) return node; } -PoolID Graph::create_node( NodeType _type, const char* _signature_hint ) +PoolID Graph::create_node( NodeType _type, const fw::func_type* _signature ) { switch ( _type ) { /* - * TODO: That's not great we have special cases for blocks, variables and literals... - * Can't we simply give a generic signature for both blocks, variables, literals, functions, and operators? - * What if we pass: - * - "bool|int|string|double" for a variable - * - "false|true|0|42|0.1|\"string\"" for a literal - * - "if|for|while|{}" for a block (not sure if we need a BLOCK_PROGRAM) - * This can be made via a PoolID Nodlang::parse_single_node(const std::string&) method. - * + * TODO: We could consider narowwing the enum to few cases (BLOCK, VARIABLE, LITERAL, OPERATOR, FUNCTION) + * and rely more on _signature (ex: a bool variable could be simply "bool" or "bool bool(bool)") */ case NodeType_BLOCK_CONDITION: return create_cond_struct(); case NodeType_BLOCK_FOR_LOOP: return create_for_loop(); @@ -544,10 +538,10 @@ PoolID Graph::create_node( NodeType _type, const char* _signature_hint ) case NodeType_OPERATOR: case NodeType_FUNCTION: { - FW_EXPECT(_signature_hint != nullptr, "_signature_hint is expected when dealing with functions or operators") + FW_EXPECT(_signature != nullptr, "_signature is expected when dealing with functions or operators") auto& language = Nodlang::get_instance(); // Currently, we handle operators and functions the exact same way - const auto invokable = language.find_function(_signature_hint); + const auto invokable = language.find_function(_signature); bool is_operator = language.find_operator_fct( invokable->get_type() ) != nullptr; return create_function(invokable.get(), is_operator); } diff --git a/src/nodable/core/Graph.h b/src/nodable/core/Graph.h index e12c60611..e39040205 100644 --- a/src/nodable/core/Graph.h +++ b/src/nodable/core/Graph.h @@ -58,8 +58,8 @@ namespace ndbl // node related - PoolID create_node(); // Create a basic node. - PoolID create_node(NodeType, const char* _signature_hint = nullptr); // Create a given node type in a simple way. + PoolID create_node(); // Create a raw node. + PoolID create_node(NodeType, const fw::func_type* _signature = nullptr); // Create a given node type in a simple way. PoolID create_root(); PoolID create_variable(const fw::type *_type, const std::string &_name, PoolID _scope); PoolID create_variable_decl(const fw::type* _type, const char* _name, PoolID _scope); diff --git a/src/nodable/gui/Action.h b/src/nodable/gui/Action.h new file mode 100644 index 000000000..6aa257d03 --- /dev/null +++ b/src/nodable/gui/Action.h @@ -0,0 +1,14 @@ +#pragma once +#include "core/Graph.h" +#include "fw/core/reflection/func_type.h" +#include "fw/gui/EventManager.h" + +namespace ndbl +{ + struct CreateNodeActionPayload + { + const fw::func_type* node_signature; + NodeType node_type; + }; + using CreateNodeAction = fw::TAction ; +} \ No newline at end of file diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index 7b62c980a..b72b2649f 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -1,5 +1,6 @@ #pragma once #include "SlotView.h" +#include "core/Graph.h" #include "fw/core/Pool.h" #include "fw/gui/EventManager.h" #include "nodable/core/SlotRef.h" @@ -10,59 +11,50 @@ namespace ndbl class NodeView; using fw::PoolID; - enum EventType_: fw::EventType + enum EventID_ : fw::EventID { - EventType_delete_node_action_triggered = fw::EventType_USER_DEFINED, // operation on nodes - EventType_arrange_node_action_triggered, - EventType_select_successor_node_action_triggered, - EventType_toggle_folding_selected_node_action_triggered, - EventType_node_view_selected, - EventType_node_view_deselected, - EventType_frame_all_node_views, - EventType_create_node, - EventType_create_block, - EventType_frame_selected_node_views, - EventType_slot_dropped, - EventType_slot_disconnected, - EventType_toggle_isolate_selection + EventID_REQUEST_DELETE_NODE = fw::EventID_USER_DEFINED, // operation on nodes + EventID_REQUEST_ARRANGE_HIERARCHY, + EventID_REQUEST_SELECT_SUCCESSOR, + EventID_REQUEST_TOGGLE_FOLDING, + EventID_REQUEST_FRAME_ALL, + EventID_REQUEST_CREATE_NODE, + EventID_REQUEST_CREATE_BLOCK, + EventID_REQUEST_FRAME_SELECTION, + EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, + EventID_SLOT_DROPPED, + EventID_SLOT_DISCONNECTED, + EventID_NODE_VIEW_SELECTED, }; - struct NodeViewEvent - { - fw::EventType type; + struct NodeViewEventPayload { PoolID view; }; + using NodeViewSelectedEvent = fw::TEvent; + using NodeViewEvent = fw::TEvent; - struct ToggleFoldingEvent + struct ToggleFoldingEventPayload { - fw::EventType type; - bool recursive; + bool recursive = false; }; + using ToggleFoldingEvent = fw::TEvent; - struct SlotEvent + struct SlotEventPayload { - fw::EventType type; SlotRef first; SlotRef second; }; + using SlotDisconnectedEvent = fw::TEvent; + using SlotDroppedEvent = fw::TEvent; - struct CreateNodeEvent + struct CreateNodeEventPayload { - fw::EventType type; - SlotRef dragged; // The slot being dragged. - ImVec2 desired_pos; - Graph* graph = nullptr; + NodeType node_type; // The note type to create + const fw::func_type* node_signature; // The signature of the node that must be created + SlotView* dragged_slot; // The slot view being dragged. + Graph* graph = nullptr; // The graph to create the node into + ImVec2 node_view_local_pos; // The desired position for the new node view }; - - union Event - { - fw::EventType type; - fw::Event event; - fw::SimpleEvent common; - NodeViewEvent node; - SlotEvent slot; - ToggleFoldingEvent toggle_folding; - CreateNodeEvent create_node; - }; - + using CreateNodeEvent = fw::TEvent; + using CreateBlockEvent = fw::TEvent; }// namespace ndbl diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 8518e1c1b..98874b727 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -8,6 +8,7 @@ #include "fw/core/log.h" #include "fw/core/system.h" +#include "Action.h" #include "Condition.h" #include "Config.h" #include "Event.h" @@ -31,7 +32,7 @@ using namespace ndbl; using namespace ndbl::assembly; using namespace fw; -const char* k_context_menu_popup = "GraphView.ContextMenu"; +const char* k_context_menu_popup = "GraphView.CreateNodeContextMenu"; REGISTER { @@ -41,16 +42,8 @@ REGISTER GraphView::GraphView(Graph* graph) : fw::View() , m_graph(graph) - , m_context_menu() + , m_create_node_context_menu() { - // Fill the contextual menu - for( auto& each : EventManager::get_instance().get_actions() ) - { - if( each.event_t == EventType_create_node || each.event_t == EventType_create_block ) - { - m_context_menu.items.push_back( each ); - } - } } bool GraphView::draw() @@ -121,13 +114,13 @@ bool GraphView::draw() if ( ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows) ) { // Get the current dragged slot, or the slot that was dragged when context menu opened - const SlotView* _dragged_slot = dragged_slot ? dragged_slot : m_context_menu.dragged_slot; + const SlotView* _dragged_slot = dragged_slot ? dragged_slot : m_create_node_context_menu.dragged_slot; // Draw temporary edge if ( _dragged_slot ) { // When dragging, edge follows mouse cursor. Otherwise, it sticks the contextual menu. - ImVec2 edge_end = m_context_menu.dragged_slot ? m_context_menu.opened_at_screen_pos : ImGui::GetMousePos(); + ImVec2 edge_end = m_create_node_context_menu.dragged_slot ? m_create_node_context_menu.opened_at_screen_pos : ImGui::GetMousePos(); if ( _dragged_slot->slot().type() == SlotFlag_TYPE_CODEFLOW ) { @@ -317,7 +310,7 @@ bool GraphView::draw() if ( !ImGui::IsPopupOpen( k_context_menu_popup ) ) { ImGui::OpenPopup( k_context_menu_popup ); - m_context_menu.reset_state( SlotView::get_dragged() ); + m_create_node_context_menu.reset_state( SlotView::get_dragged() ); SlotView::reset_dragged(); } } @@ -332,20 +325,21 @@ bool GraphView::draw() * In case user has created a new node we need to connect it to the m_graph depending * on if a slot is being dragged and what is its nature. */ - if ( fw::Action* action = m_context_menu.draw_search_input( 10 ) ) + if ( CreateNodeAction* action = m_create_node_context_menu.draw_search_input( 10 ) ) { - ndbl::CreateNodeEvent event{ - action->event_t, - m_context_menu.dragged_slot->slot(), - m_context_menu.opened_at_pos, - m_graph - }; - EventManager::get_instance().push_event((fw::Event&)event); + EventManager::get_instance().dispatch({ + action->payload.node_type, + action->payload.node_signature, + m_create_node_context_menu.dragged_slot, + m_graph, + m_create_node_context_menu.opened_at_pos + }); + ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } else { - m_context_menu.reset_state(); + m_create_node_context_menu.reset_state(); } // add some empty space @@ -504,7 +498,12 @@ void GraphView::translate_view(ImVec2 delta) // m_view_origin += delta; } -fw::Action* ContextMenu::draw_search_input( size_t _result_max_count ) +void GraphView::add_action_to_context_menu( CreateNodeAction* _action ) +{ + m_create_node_context_menu.items.push_back(_action); +} + +CreateNodeAction* CreateNodeContextMenu::draw_search_input( size_t _result_max_count ) { bool validated; @@ -533,10 +532,10 @@ fw::Action* ContextMenu::draw_search_input( size_t _result_max_count ) // When a single item is filtered, pressing enter will press the item's button. if ( items_matching_search.size() == 1) { - Action& action = items_matching_search[0]; - if ( ImGui::SmallButton( action.label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) + auto action = items_matching_search.front(); + if ( ImGui::SmallButton( action->label.c_str()) || ImGui::IsKeyDown( ImGuiKey_Enter ) ) { - return &action; + return action; } } else @@ -551,11 +550,11 @@ fw::Action* ContextMenu::draw_search_input( size_t _result_max_count ) while( it != items_matching_search.end() && std::distance(items_matching_search.begin(), it) != _result_max_count) { auto& action = *it; - if ( ImGui::Button( action.label.c_str()) || // User can click on the button... + if ( ImGui::Button( action->label.c_str()) || // User can click on the button... (ImGui::IsKeyDown( ImGuiKey_Enter ) && ImGui::IsItemFocused() ) // ...or press enter if this item is the first ) { - return &action; + return action; } it++; } @@ -572,7 +571,7 @@ fw::Action* ContextMenu::draw_search_input( size_t _result_max_count ) return nullptr; } -void ContextMenu::update_cache_based_on_signature() +void CreateNodeContextMenu::update_cache_based_on_signature() { items_with_compatible_signature.clear(); @@ -583,13 +582,13 @@ void ContextMenu::update_cache_based_on_signature() } else { - for (auto& menu_item : items ) + for (auto& action: items ) { - if ( menu_item.event_t == EventType_create_block ) + if ( action->event_id == EventID_REQUEST_CREATE_BLOCK ) { if ( dragged_slot->is_this() ) { - items_with_compatible_signature.push_back( menu_item ); + items_with_compatible_signature.push_back( action ); } } else @@ -598,34 +597,34 @@ void ContextMenu::update_cache_based_on_signature() { const type* dragged_property_type = dragged_slot->get_property_type(); - if ( menu_item.signature ) + if ( action->payload.node_signature ) { if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) { - if ( !menu_item.signature->has_an_arg_of_type(dragged_property_type) ) + if ( !action->payload.node_signature->has_an_arg_of_type(dragged_property_type) ) { continue; } } - else if ( !menu_item.signature->get_return_type()->equals(dragged_property_type) ) + else if ( !action->payload.node_signature->get_return_type()->equals(dragged_property_type) ) { continue; } } else { // by default, we accept any item not having a signature } - items_with_compatible_signature.push_back( menu_item ); + items_with_compatible_signature.push_back( action ); } } } } } -void ContextMenu::update_cache_based_on_user_input( size_t _limit ) +void CreateNodeContextMenu::update_cache_based_on_user_input( size_t _limit ) { items_matching_search.clear(); for ( auto& menu_item : items_with_compatible_signature ) { - if( menu_item.label.find( search_input ) != std::string::npos ) + if( menu_item->label.find( search_input ) != std::string::npos ) { items_matching_search.push_back(menu_item); if ( items_matching_search.size() == _limit ) @@ -636,7 +635,7 @@ void ContextMenu::update_cache_based_on_user_input( size_t _limit ) } } -void ContextMenu::reset_state( SlotView* _dragged_slot ) +void CreateNodeContextMenu::reset_state( SlotView* _dragged_slot ) { must_be_reset_flag = true; search_input[0] = '\0'; diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index 268315045..ccaf735ec 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -10,6 +10,7 @@ #include "core/Component.h" // base class #include "core/IScope.h" +#include "Action.h" #include "Config.h" #include "NodeViewConstraint.h" #include "SlotView.h" @@ -22,21 +23,21 @@ namespace ndbl class Nodable; class Graph; - struct ContextMenu + struct CreateNodeContextMenu { - bool must_be_reset_flag = false; - ImVec2 opened_at_pos = ImVec2(-1,-1); // relative - ImVec2 opened_at_screen_pos = ImVec2(-1,-1); // absolute (screen space) - SlotView* dragged_slot = nullptr; // The slot being dragged when the context menu opened. - char search_input[255] = "\0"; // The search input entered by the user. - std::vector items; // All the available items - std::vector items_with_compatible_signature; // Only the items having a compatible signature (with the slot dragged) - std::vector items_matching_search; // Only the items having a compatible signature AND matching the search_input. + bool must_be_reset_flag = false; + ImVec2 opened_at_pos = ImVec2(-1,-1); // relative + ImVec2 opened_at_screen_pos = ImVec2(-1,-1); // absolute (screen space) + SlotView* dragged_slot = nullptr; // The slot being dragged when the context menu opened. + char search_input[255] = "\0"; // The search input entered by the user. + std::vector items; // All the available items + std::vector items_with_compatible_signature; // Only the items having a compatible signature (with the slot dragged) + std::vector items_matching_search; // Only the items having a compatible signature AND matching the search_input. - fw::Action* draw_search_input( size_t _result_max_count ); // Return the triggered action, user has to deal with the Action. - void reset_state(SlotView* _dragged_slot = nullptr); - void update_cache_based_on_signature(); - void update_cache_based_on_user_input( size_t _limit ); + CreateNodeAction* draw_search_input( size_t _result_max_count ); // Return the triggered action, user has to deal with the Action. + void reset_state(SlotView* _dragged_slot = nullptr); + void update_cache_based_on_signature(); + void update_cache_based_on_user_input( size_t _limit ); }; class GraphView: public fw::View @@ -53,7 +54,7 @@ namespace ndbl void frame_selected_node_views(); void translate_all(ImVec2 /* delta */, const std::vector&); void unfold(); // unfold the graph until it is stabilized - + void add_action_to_context_menu(CreateNodeAction* _action); private: void draw_grid( ImDrawList*, const Config& ) const; void frame_views(const std::vector &_views, bool _align_top_left_corner); @@ -61,7 +62,7 @@ namespace ndbl Graph* m_graph; ImVec2 m_view_origin; - ContextMenu m_context_menu; + CreateNodeContextMenu m_create_node_context_menu; REFLECT_DERIVED_CLASS() }; diff --git a/src/nodable/gui/HybridFile.cpp b/src/nodable/gui/HybridFile.cpp index b243701d0..e1ac88d99 100644 --- a/src/nodable/gui/HybridFile.cpp +++ b/src/nodable/gui/HybridFile.cpp @@ -39,6 +39,14 @@ HybridFile::HybridFile(std::string _name) m_graph = new Graph(&Nodable::get_instance().node_factory); m_graph_view = new GraphView(m_graph); + for( Action* action : EventManager::get_instance().get_actions() ) // Fill the "create node" context menu + { + if ( auto create_node_action = dynamic_cast(action)) + { + m_graph_view->add_action_to_context_menu( create_node_action ); + } + } + LOG_VERBOSE( "File", "Constructor being called.\n") } diff --git a/src/nodable/gui/HybridFileView.cpp b/src/nodable/gui/HybridFileView.cpp index 7a20316e3..329e92195 100644 --- a/src/nodable/gui/HybridFileView.cpp +++ b/src/nodable/gui/HybridFileView.cpp @@ -51,7 +51,7 @@ HybridFileView::HybridFileView(HybridFile& _file) graph_view->translate_all( ImVec2(-1000.f, -1000.0f) , views); // frame all (33ms delayed) - fw::EventManager::get_instance().push_event_delayed( EventType_frame_all_node_views, 33 ); + fw::EventManager::get_instance().dispatch_delayed( EventID_REQUEST_FRAME_ALL, 33 ); } } }); diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 30c70ff2b..432013340 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -1,5 +1,6 @@ #include "Nodable.h" +#include "Action.h" #include "Condition.h" #include "Event.h" #include "GraphView.h" @@ -12,11 +13,11 @@ #include "commands/Cmd_ConnectEdge.h" #include "commands/Cmd_DisconnectEdge.h" #include "commands/Cmd_Group.h" -#include "core/Slot.h" #include "core/DataAccess.h" #include "core/InvokableComponent.h" #include "core/LiteralNode.h" #include "core/NodeUtils.h" +#include "core/Slot.h" #include "core/VariableNode.h" #include "fw/core/assertions.h" #include "fw/core/system.h" @@ -109,117 +110,121 @@ bool Nodable::on_init() fw::Pool::init(); // Bind commands to shortcuts - using fw::EventType; - event_manager.add_action( - { "Delete", - EventType_delete_node_action_triggered, - { SDLK_DELETE, KMOD_NONE }, - Condition_ENABLE } ); + using fw::EventID; + event_manager.emplace_action( + "Delete", + EventID_REQUEST_DELETE_NODE, + Shortcut{ SDLK_DELETE, KMOD_NONE } + ); + event_manager.add_action( { "Arrange", - EventType_arrange_node_action_triggered, + EventID_REQUEST_ARRANGE_HIERARCHY, { SDLK_a, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); event_manager.add_action( { "Fold", - EventType_toggle_folding_selected_node_action_triggered, + EventID_REQUEST_TOGGLE_FOLDING, { SDLK_x, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); event_manager.add_action( { "Next", - EventType_select_successor_node_action_triggered, + EventID_REQUEST_SELECT_SUCCESSOR, { SDLK_n, KMOD_NONE }, Condition_ENABLE } ); event_manager.add_action( { ICON_FA_SAVE " Save", - fw::EventType_save_file_triggered, + fw::EventID_REQUEST_FILE_SAVE, { SDLK_s, KMOD_CTRL }, Condition_ENABLE } ); event_manager.add_action( { ICON_FA_SAVE " Save as", - fw::EventType_save_file_as_triggered, + fw::EventID_REQUEST_FILE_SAVE_AS, { SDLK_s, KMOD_CTRL }, Condition_ENABLE } ); event_manager.add_action( { ICON_FA_TIMES " Close", - fw::EventType_close_file_triggered, + fw::EventID_REQUEST_FILE_CLOSE, { SDLK_w, KMOD_CTRL }, Condition_ENABLE } ); event_manager.add_action( { ICON_FA_FOLDER_OPEN " Open", - fw::EventType_browse_file_triggered, + fw::EventID_REQUEST_FILE_BROWSE, { SDLK_o, KMOD_CTRL }, Condition_ENABLE } ); event_manager.add_action( { ICON_FA_FILE " New", - fw::EventType_new_file_triggered, + fw::EventID_REQUEST_FILE_NEW, { SDLK_n, KMOD_CTRL }, Condition_ENABLE } ); event_manager.add_action( { "Splashscreen", - fw::EventType_show_splashscreen_triggered, + fw::EventID_REQUEST_SHOW_SLASHSCREEN, { SDLK_F1 }, Condition_ENABLE } ); event_manager.add_action( { ICON_FA_SIGN_OUT_ALT " Exit", - fw::EventType_exit_triggered, + fw::EventID_REQUEST_EXIT, { SDLK_F4, KMOD_ALT }, Condition_ENABLE } ); event_manager.add_action( { "Undo", - fw::EventType_undo_triggered, + fw::EventID_REQUEST_UNDO, { SDLK_z, KMOD_CTRL }, Condition_ENABLE } ); event_manager.add_action( { "Redo", - fw::EventType_redo_triggered, + fw::EventID_REQUEST_REDO, { SDLK_y, KMOD_CTRL }, Condition_ENABLE } ); event_manager.add_action( { "Isolate", - EventType_toggle_isolate_selection, + EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, { SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR } ); event_manager.add_action( { "Deselect", - fw::EventType_none, + fw::EventID_NONE, { 0, KMOD_NONE, "Double click on bg" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); event_manager.add_action( { "Move Graph", - fw::EventType_none, + fw::EventID_NONE, { 0, KMOD_NONE, "Drag background" }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); event_manager.add_action( { "Frame Selection", - EventType_frame_selected_node_views, + EventID_REQUEST_FRAME_SELECTION, { SDLK_f, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); event_manager.add_action( { "Frame All", - EventType_frame_all_node_views, + EventID_REQUEST_FRAME_ALL, { SDLK_f, KMOD_LCTRL }, Condition_ENABLE } ); // Prepare context menu items { - + static std::vector actions; + { + { ICON_FA_CODE " Condition", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE, { nullptr, NodeType_BLOCK_CONDITION }} + }; // 1) Blocks - event_manager.add_action( { ICON_FA_CODE " Condition", EventType_create_block, {}, Condition_ENABLE } ); - event_manager.add_action( { ICON_FA_CODE " For Loop", EventType_create_block, {}, Condition_ENABLE } ); - event_manager.add_action( { ICON_FA_CODE " While Loop", EventType_create_block, {}, Condition_ENABLE } ); - event_manager.add_action( { ICON_FA_CODE " Scope", EventType_create_block, {}, Condition_ENABLE } ); - event_manager.add_action( { ICON_FA_CODE " Program", EventType_create_block, {}, Condition_ENABLE } ); + event_manager.add_action( ); + event_manager.add_action( { ICON_FA_CODE " For Loop", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE } ); + event_manager.add_action( { ICON_FA_CODE " While Loop", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE } ); + event_manager.add_action( { ICON_FA_CODE " Scope", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE } ); + event_manager.add_action( { ICON_FA_CODE " Program", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE } ); // 2) Variables - event_manager.add_action( { ICON_FA_DATABASE " Boolean Variable", EventType_create_node, {}, Condition_ENABLE, create_variable_node_signature()} ); - event_manager.add_action( { ICON_FA_DATABASE " Double Variable", EventType_create_node, {}, Condition_ENABLE, create_variable_node_signature() } ); - event_manager.add_action( { ICON_FA_DATABASE " Integer Variable", EventType_create_node, {}, Condition_ENABLE, create_variable_node_signature() } ); - event_manager.add_action( { ICON_FA_DATABASE " String Variable", EventType_create_node, {}, Condition_ENABLE, create_variable_node_signature() } ); + event_manager.add_action( { ICON_FA_DATABASE " Boolean Variable", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_variable_node_signature()} ); + event_manager.add_action( { ICON_FA_DATABASE " Double Variable", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_variable_node_signature() } ); + event_manager.add_action( { ICON_FA_DATABASE " Integer Variable", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_variable_node_signature() } ); + event_manager.add_action( { ICON_FA_DATABASE " String Variable", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_variable_node_signature() } ); // 3) Literals - event_manager.add_action( { ICON_FA_FILE " Boolean Literal", EventType_create_node, {}, Condition_ENABLE, create_literal_node_signature() } ); - event_manager.add_action( { ICON_FA_FILE " Double Literal", EventType_create_node, {}, Condition_ENABLE, create_literal_node_signature() } ); - event_manager.add_action( { ICON_FA_FILE " Integer Literal", EventType_create_node, {}, Condition_ENABLE, create_literal_node_signature() } ); - event_manager.add_action( { ICON_FA_FILE " String Literal", EventType_create_node, {}, Condition_ENABLE, create_literal_node_signature() } ); + event_manager.add_action( { ICON_FA_FILE " Boolean Literal", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_literal_node_signature() } ); + event_manager.add_action( { ICON_FA_FILE " Double Literal", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_literal_node_signature() } ); + event_manager.add_action( { ICON_FA_FILE " Integer Literal", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_literal_node_signature() } ); + event_manager.add_action( { ICON_FA_FILE " String Literal", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_literal_node_signature() } ); // 4) Functions/Operators from the API const Nodlang& language = Nodlang::get_instance(); for ( auto& each_fct: language.get_api() ) @@ -227,7 +232,7 @@ bool Nodable::on_init() const fw::func_type* func_type = each_fct->get_type(); std::string label; language.serialize_func_sig( label, func_type ); - event_manager.add_action( { label.c_str(), EventType_create_node, {}, Condition_ENABLE, func_type } ); + event_manager.add_action( { label.c_str(), EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, func_type } ); } } return true; @@ -287,11 +292,11 @@ void Nodable::on_update() GraphView* graph_view = current_file ? current_file->get_graph_view() : nullptr; History* curr_file_history = current_file ? current_file->get_history() : nullptr; - while(event_manager.poll_event((fw::Event&)event) ) + while( event_manager.poll_event((fw::Event&)event) ) { switch ( event.type ) { - case EventType_toggle_isolate_selection: + case EventID_REQUEST_TOGGLE_ISOLATE_SELECTION: { config.isolate_selection = !config.isolate_selection; if(current_file) @@ -301,30 +306,30 @@ void Nodable::on_update() break; } - case fw::EventType_exit_triggered: + case fw::EventID_REQUEST_EXIT: { should_stop = true; break; } - case fw::EventType_close_file_triggered: + case fw::EventID_REQUEST_FILE_CLOSE: { if(current_file) close_file(current_file); break; } - case fw::EventType_undo_triggered: + case fw::EventID_REQUEST_UNDO: { if(curr_file_history) curr_file_history->undo(); break; } - case fw::EventType_redo_triggered: + case fw::EventID_REQUEST_REDO: { if(curr_file_history) curr_file_history->redo(); break; } - case fw::EventType_browse_file_triggered: + case fw::EventID_REQUEST_FILE_BROWSE: { std::string path; if( m_view->pick_file_path(path, fw::AppView::DIALOG_Browse)) @@ -337,13 +342,13 @@ void Nodable::on_update() } - case fw::EventType_new_file_triggered: + case fw::EventID_REQUEST_FILE_NEW: { new_file(); break; } - case fw::EventType_save_file_as_triggered: + case fw::EventID_REQUEST_FILE_SAVE_AS: { if (current_file) { @@ -357,7 +362,7 @@ void Nodable::on_update() break; } - case fw::EventType_save_file_triggered: + case fw::EventID_REQUEST_FILE_SAVE: { if (current_file) { @@ -377,30 +382,30 @@ void Nodable::on_update() break; } - case fw::EventType_show_splashscreen_triggered: + case fw::EventID_REQUEST_SHOW_SLASHSCREEN: { config.common.splashscreen = true; break; } - case EventType_frame_selected_node_views: + case EventID_REQUEST_FRAME_SELECTION: { if (graph_view) graph_view->frame_selected_node_views(); break; } - case EventType_frame_all_node_views: + case EventID_REQUEST_FRAME_ALL: { if (graph_view) graph_view->frame_all_node_views(); break; } - case EventType_node_view_selected: + case EventID_NODE_VIEW_SELECTED: { current_file->view.clear_overlay(); push_overlay_shortcuts(current_file->view, Condition_ENABLE_IF_HAS_SELECTION); LOG_MESSAGE( "App", "NodeView selected\n") break; } - case fw::EventType_file_opened: + case fw::EventID_FILE_OPENED: { if (!current_file) break; current_file->view.clear_overlay(); @@ -413,7 +418,7 @@ void Nodable::on_update() push_overlay_shortcuts(current_file->view, Condition_ENABLE_IF_HAS_NO_SELECTION ); break; } - case EventType_delete_node_action_triggered: + case EventID_REQUEST_DELETE_NODE: { if ( selected_view && !ImGui::IsAnyItemFocused() ) { @@ -423,13 +428,13 @@ void Nodable::on_update() break; } - case EventType_arrange_node_action_triggered: + case EventID_REQUEST_ARRANGE_HIERARCHY: { if ( selected_view ) selected_view->arrange_recursively(); break; } - case EventType_select_successor_node_action_triggered: + case EventID_REQUEST_SELECT_SUCCESSOR: { if (!selected_view) break; std::vector> successors = selected_view->get_owner()->successors(); @@ -442,7 +447,7 @@ void Nodable::on_update() } break; } - case EventType_toggle_folding_selected_node_action_triggered: + case EventID_REQUEST_TOGGLE_FOLDING: { if ( !selected_view ) break; event.toggle_folding.recursive ? selected_view->expand_toggle_rec() @@ -450,7 +455,7 @@ void Nodable::on_update() break; } - case EventType_slot_dropped: + case EventID_SLOT_DROPPED: { SlotRef tail = event.slot.first; SlotRef head = event.slot.second; @@ -463,7 +468,7 @@ void Nodable::on_update() break; } - case EventType_slot_disconnected: + case EventID_SLOT_DISCONNECTED: { SlotRef slot = event.slot.first; @@ -479,31 +484,28 @@ void Nodable::on_update() break; } - case EventType_create_node: + case EventID_REQUEST_CREATE_NODE: { - const fw::func_type* signature = event.create_node.signature; - SlotRef dragged_slot = event.create_node.dragged; - ImVec2 desired_pos = event.create_node.desired_pos; - Graph* graph = event.create_node.graph; + auto& evt = event.create_node; // 1) create the node - PoolID new_node_id = current_file->get_graph()->create_node( signature ); // TODO: store the type + PoolID new_node_id = current_file->get_graph()->create_node( evt.node_type, evt.node_signature ); // 2) handle connections - if ( !dragged_slot ) + if ( !evt.dragged_slot ) { // Experimental: we try to connect a parent-less child - if ( new_node_id != graph->get_root() && config.experimental_graph_autocompletion ) + if ( new_node_id != evt.graph->get_root() && config.experimental_graph_autocompletion ) { - graph->ensure_has_root(); + evt.graph->ensure_has_root(); // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); } } else { Slot* complementary_slot = new_node_id->find_slot_by_property_type( - get_complementary_flags( dragged_slot->static_flags() ), - dragged_slot->get_property()->get_type() ); + get_complementary_flags( evt.dragged_slot->slot().static_flags() ), + evt.dragged_slot->get_property()->get_type() ); if ( !complementary_slot ) { @@ -512,19 +514,19 @@ void Nodable::on_update() } else { - Slot* out = dragged_slot.get(); + Slot* out = &evt.dragged_slot->slot(); Slot* in = complementary_slot; if ( out->has_flags( SlotFlag_ORDER_SECOND ) ) std::swap( out, in ); - graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); + evt.graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); } } // set new_node's view position, select it if ( auto view = new_node_id->get_component() ) { - view->set_position( desired_pos, fw::Space_Local ); + view->set_position( evt.node_view_local_pos, fw::Space_Local ); NodeView::set_selected( view ); } break; @@ -574,7 +576,7 @@ HybridFile *Nodable::add_file(HybridFile* _file) FW_EXPECT(_file, "File is nullptr"); m_loaded_files.push_back( _file ); current_file = _file; - event_manager.push_event( fw::EventType_file_opened ); + event_manager.dispatch( fw::EventID_FILE_OPENED ); return _file; } diff --git a/src/nodable/gui/NodableView.cpp b/src/nodable/gui/NodableView.cpp index ba753708a..df9311f56 100644 --- a/src/nodable/gui/NodableView.cpp +++ b/src/nodable/gui/NodableView.cpp @@ -65,13 +65,13 @@ void NodableView::on_draw() if (ImGui::BeginMenu("File")) { bool has_file = current_file; bool changed = current_file != nullptr && current_file->changed; - fw::ImGuiEx::MenuItemBindedToEvent(fw::EventType_new_file_triggered); - fw::ImGuiEx::MenuItemBindedToEvent(fw::EventType_browse_file_triggered); + fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_NEW ); + fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_BROWSE ); ImGui::Separator(); - fw::ImGuiEx::MenuItemBindedToEvent(fw::EventType_save_file_as_triggered, false, has_file); - fw::ImGuiEx::MenuItemBindedToEvent(fw::EventType_save_file_triggered, false, has_file && changed); + fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_SAVE_AS, false, has_file ); + fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_SAVE, false, has_file && changed ); ImGui::Separator(); - fw::ImGuiEx::MenuItemBindedToEvent(fw::EventType_close_file_triggered, false, has_file); + fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_CLOSE, false, has_file ); auto auto_paste = has_file && current_file->view.experimental_clipboard_auto_paste(); @@ -79,7 +79,7 @@ void NodableView::on_draw() current_file->view.experimental_clipboard_auto_paste(!auto_paste); } - fw::ImGuiEx::MenuItemBindedToEvent(fw::EventType_exit_triggered); + fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_EXIT ); ImGui::EndMenu(); } @@ -87,27 +87,26 @@ void NodableView::on_draw() bool vm_is_stopped = virtual_machine.is_program_stopped(); if (ImGui::BeginMenu("Edit")) { if (current_file_history) { - fw::ImGuiEx::MenuItemBindedToEvent(fw::EventType_undo_triggered); - fw::ImGuiEx::MenuItemBindedToEvent(fw::EventType_redo_triggered); + fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_UNDO ); + fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_REDO ); ImGui::Separator(); } auto has_selection = NodeView::is_any_selected(); if (ImGui::MenuItem("Delete", "Del.", false, has_selection && vm_is_stopped)) { - event_manager.push_event( EventType_delete_node_action_triggered ); + event_manager.dispatch( EventID_REQUEST_DELETE_NODE ); } - fw::ImGuiEx::MenuItemBindedToEvent(EventType_arrange_node_action_triggered, false, has_selection); - fw::ImGuiEx::MenuItemBindedToEvent(EventType_toggle_folding_selected_node_action_triggered, false, - has_selection); + fw::ImGuiEx::MenuItem( EventID_REQUEST_ARRANGE_HIERARCHY, false, has_selection ); + fw::ImGuiEx::MenuItem( EventID_REQUEST_TOGGLE_FOLDING, false, + has_selection ); if (ImGui::MenuItem("Expand/Collapse recursive", nullptr, false, has_selection)) { - Event event{}; - event.toggle_folding.type = EventType_toggle_folding_selected_node_action_triggered; - event.toggle_folding.recursive = true; - event_manager.push_event((fw::Event &) event); + ToggleFoldingEventPayload payload; + payload.recursive = true; + event_manager.dispatch(payload); } ImGui::EndMenu(); } @@ -147,7 +146,7 @@ void NodableView::on_draw() ImGui::Separator(); - fw::ImGuiEx::MenuItemBindedToEvent(EventType_toggle_isolate_selection, config.isolate_selection); + fw::ImGuiEx::MenuItem( EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, config.isolate_selection ); ImGui::EndMenu(); } @@ -503,10 +502,10 @@ void NodableView::draw_startup_window(ImGuiID dockspace_id) { ImVec2 btn_size(center_area.x * 0.44f, 40.0f); if (ImGui::Button(ICON_FA_FILE" New File", btn_size)) - event_manager.push_event( fw::EventType_new_file_triggered ); + event_manager.dispatch( fw::EventID_REQUEST_FILE_NEW ); ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN" Open ...", btn_size)) - event_manager.push_event( fw::EventType_browse_file_triggered ); + event_manager.dispatch( fw::EventID_REQUEST_FILE_BROWSE ); ImGui::NewLine(); ImGui::Separator(); @@ -841,7 +840,7 @@ void NodableView::draw_toolbar_window() { if (ImGui::Button( config.isolate_selection ? ICON_FA_CROP " isolation mode: ON " : ICON_FA_CROP " isolation mode: OFF", button_size)) { - m_app->event_manager.push_event( EventType_toggle_isolate_selection ); + m_app->event_manager.dispatch( EventID_REQUEST_TOGGLE_ISOLATE_SELECTION ); } ImGui::SameLine(); ImGui::EndGroup(); diff --git a/src/nodable/gui/NodeView.cpp b/src/nodable/gui/NodeView.cpp index 6bd2ba73c..54dd35cf6 100644 --- a/src/nodable/gui/NodeView.cpp +++ b/src/nodable/gui/NodeView.cpp @@ -161,23 +161,21 @@ void NodeView::update_labels_from_name(const Node* _node) void NodeView::set_selected(PoolID new_selection) { + fw::EventManager& event_manager = fw::EventManager::get_instance(); + if( s_selected == new_selection ) return; // Handle de-selection if( s_selected ) { - Event event{ EventType_node_view_deselected }; - event.node.view = s_selected; - fw::EventManager::get_instance().push_event((fw::Event&)event); + event_manager.dispatch( { s_selected } ); s_selected.reset(); } // Handle selection if( new_selection ) { - Event event{ EventType_node_view_selected }; - event.node.view = new_selection; - fw::EventManager::get_instance().push_event((fw::Event&)event); + event_manager.dispatch( { new_selection }); s_selected = new_selection; } } diff --git a/src/nodable/gui/SlotView.cpp b/src/nodable/gui/SlotView.cpp index 9c5e82460..b7bc947db 100644 --- a/src/nodable/gui/SlotView.cpp +++ b/src/nodable/gui/SlotView.cpp @@ -85,10 +85,7 @@ void SlotView::behavior(SlotView& _view, bool _readonly) { if ( ImGui::MenuItem(ICON_FA_TRASH " Disconnect")) { - Event event{}; - event.type = EventType_slot_disconnected; - event.slot.first = _view.m_slot; - fw::EventManager::get_instance().push_event((fw::Event&)event); + fw::EventManager::get_instance().dispatch( EventID_SLOT_DISCONNECTED, { _view.m_slot } ); } ImGui::EndPopup(); @@ -149,10 +146,10 @@ void SlotView::drop_behavior(bool &require_new_node, bool _enable_edition) if ( s_hovered ) { SlotEvent evt{}; - evt.type = EventType_slot_dropped; + evt.type = EventID_SLOT_DROPPED; evt.first = s_dragged->m_slot; evt.second = s_hovered->m_slot; - fw::EventManager::get_instance().push_event((fw::Event&)evt); + fw::EventManager::get_instance().dispatch( (fw::Event&) evt ); reset_hovered(); reset_dragged(); From 4483c100e5ee1b1d1a0e2bdf25572efc1b4f0867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Fri, 1 Mar 2024 00:52:52 -0500 Subject: [PATCH 09/27] fix(GraphView): contextual menu 5/N (large refactor) --- src/fw/gui/Action.h | 3 +- src/fw/gui/Event.h | 6 +- src/fw/gui/EventManager.h | 14 +++- src/nodable/core/Graph.cpp | 3 +- src/nodable/core/Graph.h | 3 +- src/nodable/gui/Action.h | 20 ++++- src/nodable/gui/Event.h | 11 +-- src/nodable/gui/Nodable.cpp | 156 ++++++++++++++++------------------- src/nodable/gui/NodeView.cpp | 4 +- src/nodable/gui/SlotView.cpp | 11 ++- 10 files changed, 116 insertions(+), 115 deletions(-) diff --git a/src/fw/gui/Action.h b/src/fw/gui/Action.h index bde8e0702..4d529991a 100644 --- a/src/fw/gui/Action.h +++ b/src/fw/gui/Action.h @@ -33,13 +33,14 @@ namespace fw }; /** Generic action with a custom payload */ - template + template class TAction : public Action { public: static_assert( !std::is_same_v ); static_assert( !std::is_same_v ); using payload_t = PayloadT; + constexpr static EventID event_id = id; TAction( const char* label, diff --git a/src/fw/gui/Event.h b/src/fw/gui/Event.h index 66fc5a4a7..117d7a3eb 100644 --- a/src/fw/gui/Event.h +++ b/src/fw/gui/Event.h @@ -46,9 +46,9 @@ namespace fw public: constexpr static EventID id = event_id; using payload_t = PayloadT; - PayloadT m_payload; + PayloadT payload; template - explicit TEvent(Args... args): Event(event_id), m_payload(args...){} - [[nodiscard]] void* data() const override { return const_cast( static_cast( &m_payload ) ); } + explicit TEvent(Args... args): Event(event_id), payload(args...){} + [[nodiscard]] void* data() const override { return const_cast( static_cast( &payload ) ); } }; } \ No newline at end of file diff --git a/src/fw/gui/EventManager.h b/src/fw/gui/EventManager.h index e7f047783..afeed591f 100644 --- a/src/fw/gui/EventManager.h +++ b/src/fw/gui/EventManager.h @@ -25,13 +25,19 @@ namespace fw void dispatch(Event* _event); // Push an existing event to the queue. Event* poll_event(); // Pop the first event in the queue const std::vector& get_actions() const; // Get all the actions bound to any event - template - Action* emplace_action(Args... args) + template + EventManager& register_action( const char* label, Shortcut shortcut = {}, typename ActionT::payload_t&& payload = {} ) { static_assert( std::is_base_of_v ); - Action* action = new ActionT(args...); + Action* action = new ActionT(label, ActionT::event_id, shortcut, payload); add_action(action); - return action; + return *this; // To be able to chain + } + EventManager& add_action( const char* label, EventID event_id, const Shortcut& shortcut = {} ) + { + auto* action = new Action(label, event_id, shortcut); + add_action(action); + return *this; // To be able to chain } template Event* dispatch(const typename EventT::payload_t& payload) diff --git a/src/nodable/core/Graph.cpp b/src/nodable/core/Graph.cpp index ae37c5649..4054ebd6f 100644 --- a/src/nodable/core/Graph.cpp +++ b/src/nodable/core/Graph.cpp @@ -535,8 +535,7 @@ PoolID Graph::create_node( NodeType _type, const fw::func_type* _signature case NodeType_LITERAL_INTEGER: return create_literal(); case NodeType_LITERAL_STRING: return create_literal(); - case NodeType_OPERATOR: - case NodeType_FUNCTION: + case NodeType_INVOKABLE: { FW_EXPECT(_signature != nullptr, "_signature is expected when dealing with functions or operators") auto& language = Nodlang::get_instance(); diff --git a/src/nodable/core/Graph.h b/src/nodable/core/Graph.h index e39040205..98d1157c6 100644 --- a/src/nodable/core/Graph.h +++ b/src/nodable/core/Graph.h @@ -41,8 +41,7 @@ namespace ndbl NodeType_LITERAL_DOUBLE, NodeType_LITERAL_INTEGER, NodeType_LITERAL_STRING, - NodeType_FUNCTION, - NodeType_OPERATOR, + NodeType_INVOKABLE, }; /** diff --git a/src/nodable/gui/Action.h b/src/nodable/gui/Action.h index 6aa257d03..9b7655813 100644 --- a/src/nodable/gui/Action.h +++ b/src/nodable/gui/Action.h @@ -1,14 +1,28 @@ #pragma once +#include "Event.h" #include "core/Graph.h" #include "fw/core/reflection/func_type.h" -#include "fw/gui/EventManager.h" +#include "fw/gui/Action.h" namespace ndbl { + + enum class NodeActionType + { + DELETE, + ARRANGE + }; + using NodeAction = fw::TAction; + struct CreateNodeActionPayload { - const fw::func_type* node_signature; NodeType node_type; + const fw::func_type* node_signature{}; + SlotView* dragged_slot{}; + Graph* graph{}; + ImVec2 node_view_local_pos; }; - using CreateNodeAction = fw::TAction ; + using CreateNodeAction = fw::TAction ; + + using CreateBlockAction = fw::TAction; } \ No newline at end of file diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index b72b2649f..24db8a529 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -24,14 +24,15 @@ namespace ndbl EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, EventID_SLOT_DROPPED, EventID_SLOT_DISCONNECTED, - EventID_NODE_VIEW_SELECTED, + EventID_NODE_SELECTION_CHANGE, }; - struct NodeViewEventPayload { - PoolID view; + struct NodeViewSelectionChangePayload + { + PoolID new_selection; + PoolID old_selection; }; - using NodeViewSelectedEvent = fw::TEvent; - using NodeViewEvent = fw::TEvent; + using NodeViewSelectionChangeEvent = fw::TEvent; struct ToggleFoldingEventPayload { diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 432013340..bff26404b 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -111,93 +111,80 @@ bool Nodable::on_init() // Bind commands to shortcuts using fw::EventID; - event_manager.emplace_action( - "Delete", - EventID_REQUEST_DELETE_NODE, - Shortcut{ SDLK_DELETE, KMOD_NONE } - ); - - event_manager.add_action( - { "Arrange", - EventID_REQUEST_ARRANGE_HIERARCHY, - { SDLK_a, KMOD_NONE }, - Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); - event_manager.add_action( - { "Fold", - EventID_REQUEST_TOGGLE_FOLDING, - { SDLK_x, KMOD_NONE }, - Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); - event_manager.add_action( + event_manager.register_action("Delete", { SDLK_DELETE, KMOD_NONE }, { NodeActionType::DELETE } ); + event_manager.register_action("Arrange", { SDLK_a, KMOD_NONE }, { NodeActionType::ARRANGE } /* Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR */ ); + event_manager.register_action("Fold", { SDLK_x, KMOD_NONE }, { NodeActionType::FOLD } /* Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR }*/ ); + event_manager.register_action( { "Next", EventID_REQUEST_SELECT_SUCCESSOR, { SDLK_n, KMOD_NONE }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { ICON_FA_SAVE " Save", fw::EventID_REQUEST_FILE_SAVE, { SDLK_s, KMOD_CTRL }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { ICON_FA_SAVE " Save as", fw::EventID_REQUEST_FILE_SAVE_AS, { SDLK_s, KMOD_CTRL }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { ICON_FA_TIMES " Close", fw::EventID_REQUEST_FILE_CLOSE, { SDLK_w, KMOD_CTRL }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { ICON_FA_FOLDER_OPEN " Open", fw::EventID_REQUEST_FILE_BROWSE, { SDLK_o, KMOD_CTRL }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { ICON_FA_FILE " New", fw::EventID_REQUEST_FILE_NEW, { SDLK_n, KMOD_CTRL }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { "Splashscreen", fw::EventID_REQUEST_SHOW_SLASHSCREEN, { SDLK_F1 }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { ICON_FA_SIGN_OUT_ALT " Exit", fw::EventID_REQUEST_EXIT, { SDLK_F4, KMOD_ALT }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { "Undo", fw::EventID_REQUEST_UNDO, { SDLK_z, KMOD_CTRL }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { "Redo", fw::EventID_REQUEST_REDO, { SDLK_y, KMOD_CTRL }, Condition_ENABLE } ); - event_manager.add_action( + event_manager.register_action( { "Isolate", EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, { SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR } ); - event_manager.add_action( + event_manager.register_action( { "Deselect", fw::EventID_NONE, { 0, KMOD_NONE, "Double click on bg" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); - event_manager.add_action( + event_manager.register_action( { "Move Graph", fw::EventID_NONE, { 0, KMOD_NONE, "Drag background" }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); - event_manager.add_action( + event_manager.register_action( { "Frame Selection", EventID_REQUEST_FRAME_SELECTION, { SDLK_f, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); - event_manager.add_action( + event_manager.register_action( { "Frame All", EventID_REQUEST_FRAME_ALL, { SDLK_f, KMOD_LCTRL }, @@ -205,26 +192,25 @@ bool Nodable::on_init() // Prepare context menu items { - static std::vector actions; - { - { ICON_FA_CODE " Condition", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE, { nullptr, NodeType_BLOCK_CONDITION }} - }; // 1) Blocks - event_manager.add_action( ); - event_manager.add_action( { ICON_FA_CODE " For Loop", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE } ); - event_manager.add_action( { ICON_FA_CODE " While Loop", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE } ); - event_manager.add_action( { ICON_FA_CODE " Scope", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE } ); - event_manager.add_action( { ICON_FA_CODE " Program", EventID_REQUEST_CREATE_BLOCK, {}, Condition_ENABLE } ); + event_manager.register_action( ICON_FA_CODE " Condition", {}, NodeType_BLOCK_CONDITION ); + event_manager.register_action( ICON_FA_CODE " For Loop", {}, NodeType_BLOCK_FOR_LOOP ); + event_manager.register_action( ICON_FA_CODE " While Loop", {}, NodeType_BLOCK_WHILE_LOOP ); + event_manager.register_action( ICON_FA_CODE " Scope", {}, NodeType_BLOCK_SCOPE ); + event_manager.register_action( ICON_FA_CODE " Program", {}, NodeType_BLOCK_PROGRAM ); + // 2) Variables - event_manager.add_action( { ICON_FA_DATABASE " Boolean Variable", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_variable_node_signature()} ); - event_manager.add_action( { ICON_FA_DATABASE " Double Variable", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_variable_node_signature() } ); - event_manager.add_action( { ICON_FA_DATABASE " Integer Variable", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_variable_node_signature() } ); - event_manager.add_action( { ICON_FA_DATABASE " String Variable", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_variable_node_signature() } ); + event_manager.register_action( ICON_FA_DATABASE " Boolean Variable", {}, { NodeType_VARIABLE_BOOLEAN, create_variable_node_signature() } ); + event_manager.register_action( ICON_FA_DATABASE " Double Variable", {}, { NodeType_VARIABLE_DOUBLE, create_variable_node_signature() } ); + event_manager.register_action( ICON_FA_DATABASE " Integer Variable", {}, { NodeType_VARIABLE_INTEGER, create_variable_node_signature() } ); + event_manager.register_action( ICON_FA_DATABASE " String Variable", {}, { NodeType_VARIABLE_STRING, create_variable_node_signature() } ); + // 3) Literals - event_manager.add_action( { ICON_FA_FILE " Boolean Literal", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_literal_node_signature() } ); - event_manager.add_action( { ICON_FA_FILE " Double Literal", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_literal_node_signature() } ); - event_manager.add_action( { ICON_FA_FILE " Integer Literal", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_literal_node_signature() } ); - event_manager.add_action( { ICON_FA_FILE " String Literal", EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, create_literal_node_signature() } ); + event_manager.register_action( ICON_FA_FILE " Boolean Literal", {}, { NodeType_LITERAL_BOOLEAN, create_variable_node_signature() } ); + event_manager.register_action( ICON_FA_FILE " Double Literal", {}, { NodeType_LITERAL_DOUBLE, create_variable_node_signature() } ); + event_manager.register_action( ICON_FA_FILE " Integer Literal", {}, { NodeType_LITERAL_INTEGER, create_variable_node_signature() } ); + event_manager.register_action( ICON_FA_FILE " String Literal", {}, { NodeType_LITERAL_STRING, create_variable_node_signature() } ); + // 4) Functions/Operators from the API const Nodlang& language = Nodlang::get_instance(); for ( auto& each_fct: language.get_api() ) @@ -232,7 +218,7 @@ bool Nodable::on_init() const fw::func_type* func_type = each_fct->get_type(); std::string label; language.serialize_func_sig( label, func_type ); - event_manager.add_action( { label.c_str(), EventID_REQUEST_CREATE_NODE, {}, Condition_ENABLE, func_type } ); + event_manager.register_action( label.c_str(), {}, { NodeType_INVOKABLE, func_type } ); } } return true; @@ -261,24 +247,22 @@ void Nodable::on_update() // shorthand to push all shortcuts to a file view overlay depending on conditions auto push_overlay_shortcuts = [&](ndbl::HybridFileView& _view, Condition _condition) -> void { - for (const auto& _binded_event: event_manager.get_actions()) + for (const auto& _action: event_manager.get_actions()) { - if( (_binded_event.condition & _condition) == _condition) + if( ( _action.condition & _condition) == _condition) { - if (_binded_event.condition & Condition_HIGHLIGHTED_IN_GRAPH_EDITOR) + if ( _action.condition & Condition_HIGHLIGHTED_IN_GRAPH_EDITOR) { _view.push_overlay( - { - _binded_event.label.substr(0, 12), - _binded_event.shortcut.to_string() + { _action.label.substr(0, 12), + _action.shortcut.to_string() }, OverlayType_GRAPH); } - if ( _binded_event.condition & Condition_HIGHLIGHTED_IN_TEXT_EDITOR) + if ( _action.condition & Condition_HIGHLIGHTED_IN_TEXT_EDITOR) { _view.push_overlay( - { - _binded_event.label.substr(0,12), - _binded_event.shortcut.to_string() + { _action.label.substr(0,12), + _action.shortcut.to_string() }, OverlayType_TEXT); } } @@ -286,15 +270,15 @@ void Nodable::on_update() } }; - // Nodable events ( SDL_ API inspired, but with custom events) - ndbl::Event event{}; + // Nodable events auto selected_view = NodeView::get_selected(); GraphView* graph_view = current_file ? current_file->get_graph_view() : nullptr; History* curr_file_history = current_file ? current_file->get_history() : nullptr; - while( event_manager.poll_event((fw::Event&)event) ) + Event* event = nullptr; + while( (event = event_manager.poll_event()) && event != nullptr ) { - switch ( event.type ) + switch ( event->id ) { case EventID_REQUEST_TOGGLE_ISOLATE_SELECTION: { @@ -398,11 +382,13 @@ void Nodable::on_update() if (graph_view) graph_view->frame_all_node_views(); break; } - case EventID_NODE_VIEW_SELECTED: + case EventID_NODE_SELECTION_CHANGE: { + auto _event = dynamic_cast( event ); current_file->view.clear_overlay(); - push_overlay_shortcuts(current_file->view, Condition_ENABLE_IF_HAS_SELECTION); - LOG_MESSAGE( "App", "NodeView selected\n") + Condition_ condition = _event->payload.new_selection ? Condition_ENABLE_IF_HAS_SELECTION + : Condition_ENABLE_IF_HAS_NO_SELECTION; + push_overlay_shortcuts(current_file->view, condition ); break; } case fw::EventID_FILE_OPENED: @@ -412,12 +398,6 @@ void Nodable::on_update() push_overlay_shortcuts(current_file->view, Condition_ENABLE_IF_HAS_NO_SELECTION); break; } - case EventType_node_view_deselected: - { - current_file->view.clear_overlay(); - push_overlay_shortcuts(current_file->view, Condition_ENABLE_IF_HAS_NO_SELECTION ); - break; - } case EventID_REQUEST_DELETE_NODE: { if ( selected_view && !ImGui::IsAnyItemFocused() ) @@ -450,16 +430,17 @@ void Nodable::on_update() case EventID_REQUEST_TOGGLE_FOLDING: { if ( !selected_view ) break; - event.toggle_folding.recursive ? selected_view->expand_toggle_rec() + auto _event = dynamic_cast(event); + _event->payload.recursive ? selected_view->expand_toggle_rec() : selected_view->expand_toggle(); break; } case EventID_SLOT_DROPPED: { - SlotRef tail = event.slot.first; - SlotRef head = event.slot.second; - + auto _event = dynamic_cast(event); + SlotRef tail = _event->payload.first; + SlotRef head = _event->payload.second; if (head.flags & SlotFlag_ORDER_SECOND ) std::swap(tail, head); // guarantee src to be the output DirectedEdge edge(tail, head); auto cmd = std::make_shared(edge); @@ -470,7 +451,8 @@ void Nodable::on_update() case EventID_SLOT_DISCONNECTED: { - SlotRef slot = event.slot.first; + auto _event = dynamic_cast(event); + SlotRef slot = _event->payload.first; auto cmd_grp = std::make_shared("Disconnect All Edges"); for( const auto& adjacent_slot: slot->adjacent() ) @@ -486,26 +468,26 @@ void Nodable::on_update() case EventID_REQUEST_CREATE_NODE: { - auto& evt = event.create_node; + auto _event = dynamic_cast(event); // 1) create the node - PoolID new_node_id = current_file->get_graph()->create_node( evt.node_type, evt.node_signature ); + PoolID new_node_id = current_file->get_graph()->create_node( _event->payload.node_type, _event->payload.node_signature ); // 2) handle connections - if ( !evt.dragged_slot ) + if ( !_event->payload.dragged_slot ) { // Experimental: we try to connect a parent-less child - if ( new_node_id != evt.graph->get_root() && config.experimental_graph_autocompletion ) + if ( new_node_id != _event->payload.graph->get_root() && config.experimental_graph_autocompletion ) { - evt.graph->ensure_has_root(); + _event->payload.graph->ensure_has_root(); // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); } } else { Slot* complementary_slot = new_node_id->find_slot_by_property_type( - get_complementary_flags( evt.dragged_slot->slot().static_flags() ), - evt.dragged_slot->get_property()->get_type() ); + get_complementary_flags( _event->payload.dragged_slot->slot().static_flags() ), + _event->payload.dragged_slot->get_property()->get_type() ); if ( !complementary_slot ) { @@ -514,19 +496,19 @@ void Nodable::on_update() } else { - Slot* out = &evt.dragged_slot->slot(); + Slot* out = &_event->payload.dragged_slot->slot(); Slot* in = complementary_slot; if ( out->has_flags( SlotFlag_ORDER_SECOND ) ) std::swap( out, in ); - evt.graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); + _event->payload.graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); } } // set new_node's view position, select it if ( auto view = new_node_id->get_component() ) { - view->set_position( evt.node_view_local_pos, fw::Space_Local ); + view->set_position( _event->payload.node_view_local_pos, fw::Space_Local ); NodeView::set_selected( view ); } break; diff --git a/src/nodable/gui/NodeView.cpp b/src/nodable/gui/NodeView.cpp index 54dd35cf6..915364c2e 100644 --- a/src/nodable/gui/NodeView.cpp +++ b/src/nodable/gui/NodeView.cpp @@ -168,14 +168,14 @@ void NodeView::set_selected(PoolID new_selection) // Handle de-selection if( s_selected ) { - event_manager.dispatch( { s_selected } ); + event_manager.dispatch( { s_selected } ); s_selected.reset(); } // Handle selection if( new_selection ) { - event_manager.dispatch( { new_selection }); + event_manager.dispatch( { new_selection }); s_selected = new_selection; } } diff --git a/src/nodable/gui/SlotView.cpp b/src/nodable/gui/SlotView.cpp index b7bc947db..c18752f15 100644 --- a/src/nodable/gui/SlotView.cpp +++ b/src/nodable/gui/SlotView.cpp @@ -85,7 +85,7 @@ void SlotView::behavior(SlotView& _view, bool _readonly) { if ( ImGui::MenuItem(ICON_FA_TRASH " Disconnect")) { - fw::EventManager::get_instance().dispatch( EventID_SLOT_DISCONNECTED, { _view.m_slot } ); + fw::EventManager::get_instance().dispatch( { _view.m_slot } ); } ImGui::EndPopup(); @@ -145,11 +145,10 @@ void SlotView::drop_behavior(bool &require_new_node, bool _enable_edition) { if ( s_hovered ) { - SlotEvent evt{}; - evt.type = EventID_SLOT_DROPPED; - evt.first = s_dragged->m_slot; - evt.second = s_hovered->m_slot; - fw::EventManager::get_instance().dispatch( (fw::Event&) evt ); + SlotEventPayload payload; + payload.first = s_dragged->m_slot; + payload.second = s_hovered->m_slot; + fw::EventManager::get_instance().dispatch(payload); reset_hovered(); reset_dragged(); From 38530151e36aa7b3f3986214c6a577b9fa8defaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Fri, 1 Mar 2024 13:56:17 -0500 Subject: [PATCH 10/27] fix(GraphView): contextual menu 7/N (large refactor) --- src/fw/gui/Action.h | 61 ++++++---- src/fw/gui/Event.h | 49 +++++--- src/fw/gui/EventManager.cpp | 14 +-- src/fw/gui/EventManager.h | 51 ++++---- src/fw/gui/ImGuiEx.cpp | 2 +- src/nodable/gui/Action.h | 30 ++--- src/nodable/gui/Event.h | 62 +++++++--- src/nodable/gui/GraphView.cpp | 26 ++-- src/nodable/gui/GraphView.h | 10 +- src/nodable/gui/HybridFile.cpp | 4 +- src/nodable/gui/Nodable.cpp | 205 ++++++++++++-------------------- src/nodable/gui/NodableView.cpp | 4 +- src/nodable/gui/SlotView.cpp | 9 +- 13 files changed, 270 insertions(+), 257 deletions(-) diff --git a/src/fw/gui/Action.h b/src/fw/gui/Action.h index 4d529991a..2eb2eb3c1 100644 --- a/src/fw/gui/Action.h +++ b/src/fw/gui/Action.h @@ -15,43 +15,60 @@ namespace fw }; /** Basic action defining which event has to be triggered when a given shortcut is detected */ - class Action + class BaseAction { public: - Action( + BaseAction( + EventID event_id, const char* label, - EventID event_t, const Shortcut& shortcut = {}) : label(label) - , event_id(event_t) + , event_id(event_id) , shortcut(shortcut) {} std::string label; EventID event_id; Shortcut shortcut; - virtual void* data() const { return nullptr; } // Pointer to custom data + virtual BaseEvent* make_event() const { return new BaseEvent(event_id); } }; - /** Generic action with a custom payload */ - template - class TAction : public Action { + /** Generic Action to trigger a given EventT */ + template + class SimpleAction : public BaseAction + { public: - static_assert( !std::is_same_v ); - static_assert( !std::is_same_v ); - - using payload_t = PayloadT; - constexpr static EventID event_id = id; + explicit SimpleAction( const char* label, Shortcut shortcut = {} ) + : BaseAction(event_id, label, shortcut) + {} + }; - TAction( - const char* label, - EventID event_t, - Shortcut shortcut = {}, - PayloadT payload = {} + /** Generic Action able to make a given EventT from an ActionConfigT */ + template + class CustomAction : public BaseAction + { + public: + static_assert( !std::is_base_of_v ); + using event_t = EventT; + using event_data_t = typename EventT::data_t; + CustomAction( + const char* label, + Shortcut shortcut = {}, + event_data_t event_initial_state = {} ) - : Action(label, event_t, shortcut) - , payload(payload) + : BaseAction(EventT::id, label, shortcut) + , event_initial_state( event_initial_state ) {} - [[nodiscard]] void* data() const override { return (void*)const_cast( &payload ); } - PayloadT payload; // Custom data to attach + EventT* make_event() const override { return new EventT( event_initial_state ); } + event_data_t event_initial_state; // Custom data to attach }; + + using Action_FileSave = SimpleAction; + using Action_FileSaveAs = SimpleAction; + using Action_FileClose = SimpleAction; + using Action_FileBrowse = SimpleAction; + using Action_FileNew = SimpleAction; + using Action_Exit = SimpleAction; + using Action_Undo = SimpleAction; + using Action_Redo = SimpleAction; + using Action_ShowWindow = CustomAction; } \ No newline at end of file diff --git a/src/fw/gui/Event.h b/src/fw/gui/Event.h index 117d7a3eb..14d13ff89 100644 --- a/src/fw/gui/Event.h +++ b/src/fw/gui/Event.h @@ -22,33 +22,54 @@ namespace fw EventID_REQUEST_UNDO, EventID_REQUEST_REDO, EventID_REQUEST_EXIT, - EventID_REQUEST_SHOW_SLASHSCREEN, + EventID_REQUEST_SHOW_WINDOW, EventID_FILE_OPENED, EventID_USER_DEFINED = 0xff, }; - - /** Basic event, can be extended via TEvent */ - class Event + /** Basic event, can be extended via CustomEvent */ + class BaseEvent { public: const EventID id; - constexpr explicit Event(EventID id): id(id) {} - [[nodiscard]] virtual void* data() const { return nullptr; } + constexpr explicit BaseEvent(EventID id): id(id) {} + virtual ~BaseEvent() = default; + }; + + template + class SimpleEvent : public fw::BaseEvent + { + public: + using data_t = void; + constexpr static EventID id = static_cast(event_id); + + SimpleEvent() + : BaseEvent(event_id) + {} }; - /** Template to extend Event with a specific payload */ - template - class TEvent : public fw::Event + /** Template to extend BaseEvent with a specific payload */ + template + class CustomEvent : public fw::BaseEvent { public: constexpr static EventID id = event_id; - using payload_t = PayloadT; - PayloadT payload; - template - explicit TEvent(Args... args): Event(event_id), payload(args...){} - [[nodiscard]] void* data() const override { return const_cast( static_cast( &payload ) ); } + using data_t = DataT; + + DataT data; + + CustomEvent( DataT _data = {}) + : BaseEvent(event_id) + , data( _data ) + {} + }; + + struct EventPayload_ShowWindow + { + std::string window_id; // String identifying a given window (user defined) + bool visible; // Desired state }; + using Event_ShowWindow = CustomEvent; } \ No newline at end of file diff --git a/src/fw/gui/EventManager.cpp b/src/fw/gui/EventManager.cpp index 1055ce15b..67c08ab0b 100644 --- a/src/fw/gui/EventManager.cpp +++ b/src/fw/gui/EventManager.cpp @@ -35,29 +35,29 @@ EventManager& EventManager::get_instance() return *s_instance; } -void EventManager::dispatch(Event* _event) +void EventManager::dispatch( BaseEvent* _event) { m_events.push(_event); } -Event* EventManager::poll_event() +BaseEvent* EventManager::poll_event() { return m_events.empty() ? nullptr : m_events.front(); } -Event* EventManager::dispatch( EventID _event_id ) +BaseEvent* EventManager::dispatch( EventID _event_id ) { - auto new_event = new Event{ _event_id }; + auto new_event = new BaseEvent{ _event_id }; dispatch(new_event ); return new_event; } -const Action* EventManager::get_action_by_type( u16_t type ) +const BaseAction* EventManager::get_action_by_type( u16_t type ) { return m_actions_by_event_type.at(type); } -const std::vector& EventManager::get_actions() const +const std::vector& EventManager::get_actions() const { return m_actions; } @@ -72,7 +72,7 @@ void EventManager::dispatch_delayed( EventID type, u64_t delay) ); } -void EventManager::add_action(Action* _action )// Add a new action (can be triggered via shortcut) +void EventManager::add_action( BaseAction* _action )// Add a new action (can be triggered via shortcut) { m_actions.push_back( _action ); m_actions_by_event_type.insert({ _action->event_id, _action }); diff --git a/src/fw/gui/EventManager.h b/src/fw/gui/EventManager.h index afeed591f..1b563b561 100644 --- a/src/fw/gui/EventManager.h +++ b/src/fw/gui/EventManager.h @@ -20,43 +20,46 @@ namespace fw EventManager(const EventManager&) = delete; ~EventManager(); - Event* dispatch( EventID ); // Create and push a basic event to the queue - void dispatch_delayed( EventID, u64_t); // Does the same as dispatch(EventID) with a delay in millisecond. A delay of 0ms will be processed after a regular dispatch though. - void dispatch(Event* _event); // Push an existing event to the queue. - Event* poll_event(); // Pop the first event in the queue - const std::vector& get_actions() const; // Get all the actions bound to any event + BaseEvent* dispatch( EventID ); // Create and push a basic event to the queue + void dispatch_delayed( EventID, u64_t); // Does the same as dispatch(EventID) with a delay in millisecond. A delay of 0ms will be processed after a regular dispatch though. + void dispatch( BaseEvent* _event); // Push an existing event to the queue. + BaseEvent* poll_event(); // Pop the first event in the queue + const std::vector& get_actions() const; // Get all the actions bound to any event template - EventManager& register_action( const char* label, Shortcut shortcut = {}, typename ActionT::payload_t&& payload = {} ) + void bind( const char* label, Shortcut shortcut = {}, typename ActionT::event_data_t&& event_state = {} ) { - static_assert( std::is_base_of_v ); - Action* action = new ActionT(label, ActionT::event_id, shortcut, payload); + static_assert( std::is_base_of_v ); + BaseAction* action = new ActionT(label, shortcut, event_state ); add_action(action); - return *this; // To be able to chain } - EventManager& add_action( const char* label, EventID event_id, const Shortcut& shortcut = {} ) + template + void bind( const char* label, Shortcut shortcut = {} ) + { + static_assert( std::is_base_of_v ); + BaseAction* action = new ActionT(label, shortcut); + add_action(action); + } + void bind( const char* label, EventID event_id, const Shortcut& shortcut = {} ) { - auto* action = new Action(label, event_id, shortcut); + auto* action = new BaseAction(event_id, label, shortcut); add_action(action); - return *this; // To be able to chain } template - Event* dispatch(const typename EventT::payload_t& payload) + BaseEvent* dispatch(typename EventT::data_t&& state = {} ) { - static_assert( std::is_base_of_v ); - auto new_event = new EventT(payload); + static_assert( std::is_base_of_v ); + auto new_event = new EventT(state); dispatch(new_event); return new_event; } - const Action* get_action_by_type(u16_t type); // Get the action bound to a given event type - - static EventManager& get_instance(); + const BaseAction* get_action_by_type(u16_t type); // Get the action bound to a given event type + static EventManager& get_instance(); private: - void add_action(Action* _action); - - static EventManager* s_instance; - std::queue m_events; - std::vector m_actions; - std::map m_actions_by_event_type; + void add_action( BaseAction* _action); + static EventManager* s_instance; + std::queue m_events; + std::vector m_actions; + std::map m_actions_by_event_type; }; } \ No newline at end of file diff --git a/src/fw/gui/ImGuiEx.cpp b/src/fw/gui/ImGuiEx.cpp index 65495b118..a3cab7b18 100644 --- a/src/fw/gui/ImGuiEx.cpp +++ b/src/fw/gui/ImGuiEx.cpp @@ -229,7 +229,7 @@ void ImGuiEx::BeginFrame() void ImGuiEx::MenuItem(uint16_t type, bool selected, bool enable) { - const Action* action = EventManager::get_instance().get_action_by_type( type ); + const BaseAction* action = EventManager::get_instance().get_action_by_type( type ); if (ImGui::MenuItem( action->label.c_str(), action->shortcut.to_string().c_str(), selected, enable)) { EventManager::get_instance().dispatch( action->event_id ); diff --git a/src/nodable/gui/Action.h b/src/nodable/gui/Action.h index 9b7655813..c788ecce2 100644 --- a/src/nodable/gui/Action.h +++ b/src/nodable/gui/Action.h @@ -6,23 +6,19 @@ namespace ndbl { + using fw::SimpleAction; + using fw::CustomAction; - enum class NodeActionType - { - DELETE, - ARRANGE - }; - using NodeAction = fw::TAction; + // Actions specific to Nodable, more actions defined in framework's Action.h - struct CreateNodeActionPayload - { - NodeType node_type; - const fw::func_type* node_signature{}; - SlotView* dragged_slot{}; - Graph* graph{}; - ImVec2 node_view_local_pos; - }; - using CreateNodeAction = fw::TAction ; - - using CreateBlockAction = fw::TAction; + using Action_DeleteNode = SimpleAction; + using Action_ArrangeNode = SimpleAction; + using Action_ToggleFolding = SimpleAction; + using Action_SelectNext = SimpleAction; + using Action_Isolate = SimpleAction; + using Action_SelectionChange = SimpleAction; + using Action_MoveGraph = SimpleAction; + using Action_FrameGraph = SimpleAction; + using Action_CreateBlock = CustomAction; + using Action_CreateNode = CustomAction ; } \ No newline at end of file diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index 24db8a529..cca320280 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -1,4 +1,6 @@ #pragma once +#include + #include "SlotView.h" #include "core/Graph.h" #include "fw/core/Pool.h" @@ -21,41 +23,67 @@ namespace ndbl EventID_REQUEST_CREATE_NODE, EventID_REQUEST_CREATE_BLOCK, EventID_REQUEST_FRAME_SELECTION, + EventID_REQUEST_MOVE_SELECTION, EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, EventID_SLOT_DROPPED, EventID_SLOT_DISCONNECTED, EventID_NODE_SELECTION_CHANGE, }; - struct NodeViewSelectionChangePayload + using Event_FrameSelection = fw::SimpleEvent; + using Event_FrameAll = fw::SimpleEvent; + + struct EventPayload_SlotPair { + SlotRef first; + SlotRef second; + EventPayload_SlotPair(SlotRef&& first = {}, SlotRef&& second = {}) + : first(first) + , second(second) + {} + }; + using Event_SlotDisconnected = fw::CustomEvent; + using Event_SlotDropped = fw::CustomEvent; + + struct EventPayload_Node { - PoolID new_selection; - PoolID old_selection; + PoolID node; }; - using NodeViewSelectionChangeEvent = fw::TEvent; + using Event_DeleteNode = fw::CustomEvent; + using Event_ArrangeNode = fw::CustomEvent; + using Event_SelectNext = fw::CustomEvent; - struct ToggleFoldingEventPayload + enum ToggleFoldingMode { - bool recursive = false; + NON_RECURSIVELY = 0, + RECURSIVELY = 1, }; - using ToggleFoldingEvent = fw::TEvent; + struct EventPayload_ToggleFoldingEvent + { + ToggleFoldingMode mode; + }; + using Event_ToggleFolding = fw::CustomEvent; - struct SlotEventPayload + struct EventPayload_NodeViewSelectionChange { - SlotRef first; - SlotRef second; + PoolID new_selection; + PoolID old_selection; }; - using SlotDisconnectedEvent = fw::TEvent; - using SlotDroppedEvent = fw::TEvent; + using NodeViewSelectionChangeEvent = fw::CustomEvent; - struct CreateNodeEventPayload + struct EventPayload_CreateNode { NodeType node_type; // The note type to create - const fw::func_type* node_signature; // The signature of the node that must be created - SlotView* dragged_slot; // The slot view being dragged. + const fw::func_type* node_signature{}; // The signature of the node that must be created + SlotView* dragged_slot{}; // The slot view being dragged. Graph* graph = nullptr; // The graph to create the node into ImVec2 node_view_local_pos; // The desired position for the new node view + + EventPayload_CreateNode(NodeType node_type, const fw::func_type* signature = nullptr ) + : node_type(node_type) + , node_signature(signature) + {} }; - using CreateNodeEvent = fw::TEvent; - using CreateBlockEvent = fw::TEvent; + using Event_CreateNode = fw::CustomEvent; + using Event_CreateBlock = fw::CustomEvent; + }// namespace ndbl diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 98874b727..b5aa08fac 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -325,15 +325,15 @@ bool GraphView::draw() * In case user has created a new node we need to connect it to the m_graph depending * on if a slot is being dragged and what is its nature. */ - if ( CreateNodeAction* action = m_create_node_context_menu.draw_search_input( 10 ) ) + if ( Action_CreateNode* action = m_create_node_context_menu.draw_search_input( 10 ) ) { - EventManager::get_instance().dispatch({ - action->payload.node_type, - action->payload.node_signature, - m_create_node_context_menu.dragged_slot, - m_graph, - m_create_node_context_menu.opened_at_pos - }); + // Generate an event from this action, add some info to the state and dispatch it. + auto& event_manager = EventManager::get_instance(); + auto* event = action->make_event(); + event->data.graph = m_graph; + event->data.dragged_slot = m_create_node_context_menu.dragged_slot; + event->data.node_view_local_pos = m_create_node_context_menu.opened_at_pos; + event_manager.dispatch(event); ImGui::CloseCurrentPopup(); } @@ -498,12 +498,12 @@ void GraphView::translate_view(ImVec2 delta) // m_view_origin += delta; } -void GraphView::add_action_to_context_menu( CreateNodeAction* _action ) +void GraphView::add_action_to_context_menu( Action_CreateNode* _action ) { m_create_node_context_menu.items.push_back(_action); } -CreateNodeAction* CreateNodeContextMenu::draw_search_input( size_t _result_max_count ) +Action_CreateNode* CreateNodeContextMenu::draw_search_input( size_t _result_max_count ) { bool validated; @@ -597,16 +597,16 @@ void CreateNodeContextMenu::update_cache_based_on_signature() { const type* dragged_property_type = dragged_slot->get_property_type(); - if ( action->payload.node_signature ) + if ( action->event_initial_state.node_signature ) { if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) { - if ( !action->payload.node_signature->has_an_arg_of_type(dragged_property_type) ) + if ( !action->event_initial_state.node_signature->has_an_arg_of_type(dragged_property_type) ) { continue; } } - else if ( !action->payload.node_signature->get_return_type()->equals(dragged_property_type) ) + else if ( !action->event_initial_state.node_signature->get_return_type()->equals(dragged_property_type) ) { continue; } diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index ccaf735ec..e10e051a4 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -30,11 +30,11 @@ namespace ndbl ImVec2 opened_at_screen_pos = ImVec2(-1,-1); // absolute (screen space) SlotView* dragged_slot = nullptr; // The slot being dragged when the context menu opened. char search_input[255] = "\0"; // The search input entered by the user. - std::vector items; // All the available items - std::vector items_with_compatible_signature; // Only the items having a compatible signature (with the slot dragged) - std::vector items_matching_search; // Only the items having a compatible signature AND matching the search_input. + std::vector items; // All the available items + std::vector items_with_compatible_signature; // Only the items having a compatible signature (with the slot dragged) + std::vector items_matching_search; // Only the items having a compatible signature AND matching the search_input. - CreateNodeAction* draw_search_input( size_t _result_max_count ); // Return the triggered action, user has to deal with the Action. + Action_CreateNode* draw_search_input( size_t _result_max_count ); // Return the triggered action, user has to deal with the Action. void reset_state(SlotView* _dragged_slot = nullptr); void update_cache_based_on_signature(); void update_cache_based_on_user_input( size_t _limit ); @@ -54,7 +54,7 @@ namespace ndbl void frame_selected_node_views(); void translate_all(ImVec2 /* delta */, const std::vector&); void unfold(); // unfold the graph until it is stabilized - void add_action_to_context_menu(CreateNodeAction* _action); + void add_action_to_context_menu( Action_CreateNode* _action); private: void draw_grid( ImDrawList*, const Config& ) const; void frame_views(const std::vector &_views, bool _align_top_left_corner); diff --git a/src/nodable/gui/HybridFile.cpp b/src/nodable/gui/HybridFile.cpp index e1ac88d99..ec7c54920 100644 --- a/src/nodable/gui/HybridFile.cpp +++ b/src/nodable/gui/HybridFile.cpp @@ -39,9 +39,9 @@ HybridFile::HybridFile(std::string _name) m_graph = new Graph(&Nodable::get_instance().node_factory); m_graph_view = new GraphView(m_graph); - for( Action* action : EventManager::get_instance().get_actions() ) // Fill the "create node" context menu + for( BaseAction* action : EventManager::get_instance().get_actions() ) // Fill the "create node" context menu { - if ( auto create_node_action = dynamic_cast(action)) + if ( auto create_node_action = dynamic_cast(action)) { m_graph_view->add_action_to_context_menu( create_node_action ); } diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index bff26404b..0eb739895 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -111,105 +111,45 @@ bool Nodable::on_init() // Bind commands to shortcuts using fw::EventID; - event_manager.register_action("Delete", { SDLK_DELETE, KMOD_NONE }, { NodeActionType::DELETE } ); - event_manager.register_action("Arrange", { SDLK_a, KMOD_NONE }, { NodeActionType::ARRANGE } /* Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR */ ); - event_manager.register_action("Fold", { SDLK_x, KMOD_NONE }, { NodeActionType::FOLD } /* Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR }*/ ); - event_manager.register_action( - { "Next", - EventID_REQUEST_SELECT_SUCCESSOR, - { SDLK_n, KMOD_NONE }, - Condition_ENABLE } ); - event_manager.register_action( - { ICON_FA_SAVE " Save", - fw::EventID_REQUEST_FILE_SAVE, - { SDLK_s, KMOD_CTRL }, - Condition_ENABLE } ); - event_manager.register_action( - { ICON_FA_SAVE " Save as", - fw::EventID_REQUEST_FILE_SAVE_AS, - { SDLK_s, KMOD_CTRL }, - Condition_ENABLE } ); - event_manager.register_action( - { ICON_FA_TIMES " Close", - fw::EventID_REQUEST_FILE_CLOSE, - { SDLK_w, KMOD_CTRL }, - Condition_ENABLE } ); - event_manager.register_action( - { ICON_FA_FOLDER_OPEN " Open", - fw::EventID_REQUEST_FILE_BROWSE, - { SDLK_o, KMOD_CTRL }, - Condition_ENABLE } ); - event_manager.register_action( - { ICON_FA_FILE " New", - fw::EventID_REQUEST_FILE_NEW, - { SDLK_n, KMOD_CTRL }, - Condition_ENABLE } ); - event_manager.register_action( - { "Splashscreen", - fw::EventID_REQUEST_SHOW_SLASHSCREEN, - { SDLK_F1 }, - Condition_ENABLE } ); - event_manager.register_action( - { ICON_FA_SIGN_OUT_ALT " Exit", - fw::EventID_REQUEST_EXIT, - { SDLK_F4, KMOD_ALT }, - Condition_ENABLE } ); - event_manager.register_action( - { "Undo", - fw::EventID_REQUEST_UNDO, - { SDLK_z, KMOD_CTRL }, - Condition_ENABLE } ); - event_manager.register_action( - { "Redo", - fw::EventID_REQUEST_REDO, - { SDLK_y, KMOD_CTRL }, - Condition_ENABLE } ); - event_manager.register_action( - { "Isolate", - EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, - { SDLK_i, KMOD_CTRL }, - Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR } ); - event_manager.register_action( - { "Deselect", - fw::EventID_NONE, - { 0, KMOD_NONE, "Double click on bg" }, - Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); - event_manager.register_action( - { "Move Graph", - fw::EventID_NONE, - { 0, KMOD_NONE, "Drag background" }, - Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); - event_manager.register_action( - { "Frame Selection", - EventID_REQUEST_FRAME_SELECTION, - { SDLK_f, KMOD_NONE }, - Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR } ); - event_manager.register_action( - { "Frame All", - EventID_REQUEST_FRAME_ALL, - { SDLK_f, KMOD_LCTRL }, - Condition_ENABLE } ); + event_manager.bind( "Delete", { SDLK_DELETE, KMOD_NONE } ); + event_manager.bind( "Arrange", { SDLK_a, KMOD_NONE } /*, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR */ ); + event_manager.bind( "Fold", { SDLK_x, KMOD_NONE } /*, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR */ ); + event_manager.bind( "Next", { SDLK_n, KMOD_NONE } ); + event_manager.bind( ICON_FA_SAVE " Save", { SDLK_s, KMOD_CTRL } ); + event_manager.bind( ICON_FA_SAVE " Save as", { SDLK_s, KMOD_CTRL } ); + event_manager.bind( ICON_FA_TIMES " Close", { SDLK_w, KMOD_CTRL } ); + event_manager.bind( ICON_FA_FOLDER_OPEN " Open", { SDLK_o, KMOD_CTRL } ); + event_manager.bind( ICON_FA_FILE " New", { SDLK_n, KMOD_CTRL } ); + event_manager.bind( "Splashscreen", { SDLK_F1 }, { "splashscreen" } ); + event_manager.bind( ICON_FA_SIGN_OUT_ALT " Exit", { SDLK_F4, KMOD_ALT } ); + event_manager.bind( "Undo", { SDLK_z, KMOD_CTRL } ); + event_manager.bind( "Redo", { SDLK_y, KMOD_CTRL } ); + event_manager.bind( "Isolate", { SDLK_i, KMOD_CTRL } /*, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR */ ); + event_manager.bind("Deselect", { 0, KMOD_NONE, "Double click on bg" } /*, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR }*/ ); + event_manager.bind("Move Graph", { 0, KMOD_NONE, "Drag background" } /*, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR }*/ ); + event_manager.bind("Frame Selection", { SDLK_f, KMOD_NONE } /*, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR }*/ ); + event_manager.bind("Frame All", { SDLK_f, KMOD_LCTRL } ); // Prepare context menu items { // 1) Blocks - event_manager.register_action( ICON_FA_CODE " Condition", {}, NodeType_BLOCK_CONDITION ); - event_manager.register_action( ICON_FA_CODE " For Loop", {}, NodeType_BLOCK_FOR_LOOP ); - event_manager.register_action( ICON_FA_CODE " While Loop", {}, NodeType_BLOCK_WHILE_LOOP ); - event_manager.register_action( ICON_FA_CODE " Scope", {}, NodeType_BLOCK_SCOPE ); - event_manager.register_action( ICON_FA_CODE " Program", {}, NodeType_BLOCK_PROGRAM ); + event_manager.bind( ICON_FA_CODE " Condition", {}, { NodeType_BLOCK_CONDITION } ); + event_manager.bind( ICON_FA_CODE " For Loop", {}, NodeType_BLOCK_FOR_LOOP ); + event_manager.bind( ICON_FA_CODE " While Loop", {}, NodeType_BLOCK_WHILE_LOOP ); + event_manager.bind( ICON_FA_CODE " Scope", {}, NodeType_BLOCK_SCOPE ); + event_manager.bind( ICON_FA_CODE " Program", {}, NodeType_BLOCK_PROGRAM ); // 2) Variables - event_manager.register_action( ICON_FA_DATABASE " Boolean Variable", {}, { NodeType_VARIABLE_BOOLEAN, create_variable_node_signature() } ); - event_manager.register_action( ICON_FA_DATABASE " Double Variable", {}, { NodeType_VARIABLE_DOUBLE, create_variable_node_signature() } ); - event_manager.register_action( ICON_FA_DATABASE " Integer Variable", {}, { NodeType_VARIABLE_INTEGER, create_variable_node_signature() } ); - event_manager.register_action( ICON_FA_DATABASE " String Variable", {}, { NodeType_VARIABLE_STRING, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_DATABASE " Boolean Variable", {}, { NodeType_VARIABLE_BOOLEAN, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_DATABASE " Double Variable", {}, { NodeType_VARIABLE_DOUBLE, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_DATABASE " Integer Variable", {}, { NodeType_VARIABLE_INTEGER, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_DATABASE " String Variable", {}, { NodeType_VARIABLE_STRING, create_variable_node_signature() } ); // 3) Literals - event_manager.register_action( ICON_FA_FILE " Boolean Literal", {}, { NodeType_LITERAL_BOOLEAN, create_variable_node_signature() } ); - event_manager.register_action( ICON_FA_FILE " Double Literal", {}, { NodeType_LITERAL_DOUBLE, create_variable_node_signature() } ); - event_manager.register_action( ICON_FA_FILE " Integer Literal", {}, { NodeType_LITERAL_INTEGER, create_variable_node_signature() } ); - event_manager.register_action( ICON_FA_FILE " String Literal", {}, { NodeType_LITERAL_STRING, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_FILE " Boolean Literal", {}, { NodeType_LITERAL_BOOLEAN, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_FILE " Double Literal", {}, { NodeType_LITERAL_DOUBLE, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_FILE " Integer Literal", {}, { NodeType_LITERAL_INTEGER, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_FILE " String Literal", {}, { NodeType_LITERAL_STRING, create_variable_node_signature() } ); // 4) Functions/Operators from the API const Nodlang& language = Nodlang::get_instance(); @@ -218,7 +158,7 @@ bool Nodable::on_init() const fw::func_type* func_type = each_fct->get_type(); std::string label; language.serialize_func_sig( label, func_type ); - event_manager.register_action( label.c_str(), {}, { NodeType_INVOKABLE, func_type } ); + event_manager.bind( label.c_str(), {}, { NodeType_INVOKABLE, func_type } ); } } return true; @@ -275,7 +215,7 @@ void Nodable::on_update() GraphView* graph_view = current_file ? current_file->get_graph_view() : nullptr; History* curr_file_history = current_file ? current_file->get_history() : nullptr; - Event* event = nullptr; + BaseEvent* event = nullptr; while( (event = event_manager.poll_event()) && event != nullptr ) { switch ( event->id ) @@ -366,40 +306,47 @@ void Nodable::on_update() break; } - case fw::EventID_REQUEST_SHOW_SLASHSCREEN: + case Event_ShowWindow::id: { - config.common.splashscreen = true; + auto _event = reinterpret_cast(event); + if ( _event->data.window_id == "splashscreen" ) + { + config.common.splashscreen = _event->data.visible; + } break; } - case EventID_REQUEST_FRAME_SELECTION: + case Event_FrameSelection::id: { if (graph_view) graph_view->frame_selected_node_views(); break; } - case EventID_REQUEST_FRAME_ALL: + case Event_FrameAll::id: { if (graph_view) graph_view->frame_all_node_views(); break; } - case EventID_NODE_SELECTION_CHANGE: + + case NodeViewSelectionChangeEvent::id: { - auto _event = dynamic_cast( event ); + auto _event = reinterpret_cast( event ); current_file->view.clear_overlay(); - Condition_ condition = _event->payload.new_selection ? Condition_ENABLE_IF_HAS_SELECTION - : Condition_ENABLE_IF_HAS_NO_SELECTION; + Condition_ condition = _event->data.new_selection ? Condition_ENABLE_IF_HAS_SELECTION + : Condition_ENABLE_IF_HAS_NO_SELECTION; push_overlay_shortcuts(current_file->view, condition ); break; } - case fw::EventID_FILE_OPENED: + case EventID_FILE_OPENED: { if (!current_file) break; current_file->view.clear_overlay(); push_overlay_shortcuts(current_file->view, Condition_ENABLE_IF_HAS_NO_SELECTION); break; } - case EventID_REQUEST_DELETE_NODE: + case Event_DeleteNode::id: { + // TODO: store a ref to the view in the event, use selected as fallback if not present + if ( selected_view && !ImGui::IsAnyItemFocused() ) { Node* selected_node = selected_view->get_owner().get(); @@ -408,14 +355,16 @@ void Nodable::on_update() break; } - case EventID_REQUEST_ARRANGE_HIERARCHY: + case Event_ArrangeNode::id: { if ( selected_view ) selected_view->arrange_recursively(); break; } - case EventID_REQUEST_SELECT_SUCCESSOR: + case Event_SelectNext::id: { + // TODO: store a ref to the view in the event, use selected as fallback if not present + if (!selected_view) break; std::vector> successors = selected_view->get_owner()->successors(); if (!successors.empty()) @@ -427,20 +376,21 @@ void Nodable::on_update() } break; } - case EventID_REQUEST_TOGGLE_FOLDING: + + case Event_ToggleFolding::id: { if ( !selected_view ) break; - auto _event = dynamic_cast(event); - _event->payload.recursive ? selected_view->expand_toggle_rec() - : selected_view->expand_toggle(); + auto _event = reinterpret_cast(event); + _event->data.mode == RECURSIVELY ? selected_view->expand_toggle_rec() + : selected_view->expand_toggle(); break; } - case EventID_SLOT_DROPPED: + case Event_SlotDropped::id: { - auto _event = dynamic_cast(event); - SlotRef tail = _event->payload.first; - SlotRef head = _event->payload.second; + auto _event = reinterpret_cast(event); + SlotRef tail = _event->data.first; + SlotRef head = _event->data.second; if (head.flags & SlotFlag_ORDER_SECOND ) std::swap(tail, head); // guarantee src to be the output DirectedEdge edge(tail, head); auto cmd = std::make_shared(edge); @@ -449,10 +399,10 @@ void Nodable::on_update() break; } - case EventID_SLOT_DISCONNECTED: + case Event_SlotDisconnected::id: { - auto _event = dynamic_cast(event); - SlotRef slot = _event->payload.first; + auto _event = reinterpret_cast(event); + SlotRef slot = _event->data.first; auto cmd_grp = std::make_shared("Disconnect All Edges"); for( const auto& adjacent_slot: slot->adjacent() ) @@ -466,28 +416,29 @@ void Nodable::on_update() break; } - case EventID_REQUEST_CREATE_NODE: + case Event_CreateBlock::id: + case Event_CreateNode::id: { - auto _event = dynamic_cast(event); + auto _event = reinterpret_cast(event); // 1) create the node - PoolID new_node_id = current_file->get_graph()->create_node( _event->payload.node_type, _event->payload.node_signature ); + PoolID new_node_id = current_file->get_graph()->create_node( _event->data.node_type, _event->data.node_signature ); // 2) handle connections - if ( !_event->payload.dragged_slot ) + if ( !_event->data.dragged_slot ) { // Experimental: we try to connect a parent-less child - if ( new_node_id != _event->payload.graph->get_root() && config.experimental_graph_autocompletion ) + if ( new_node_id != _event->data.graph->get_root() && config.experimental_graph_autocompletion ) { - _event->payload.graph->ensure_has_root(); + _event->data.graph->ensure_has_root(); // m_graph->connect( new_node, m_graph->get_root(), RelType::CHILD ); } } else { Slot* complementary_slot = new_node_id->find_slot_by_property_type( - get_complementary_flags( _event->payload.dragged_slot->slot().static_flags() ), - _event->payload.dragged_slot->get_property()->get_type() ); + get_complementary_flags( _event->data.dragged_slot->slot().static_flags() ), + _event->data.dragged_slot->get_property()->get_type() ); if ( !complementary_slot ) { @@ -496,19 +447,19 @@ void Nodable::on_update() } else { - Slot* out = &_event->payload.dragged_slot->slot(); + Slot* out = &_event->data.dragged_slot->slot(); Slot* in = complementary_slot; if ( out->has_flags( SlotFlag_ORDER_SECOND ) ) std::swap( out, in ); - _event->payload.graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); + _event->data.graph->connect( *out, *in, ConnectFlag_ALLOW_SIDE_EFFECTS ); } } // set new_node's view position, select it if ( auto view = new_node_id->get_component() ) { - view->set_position( _event->payload.node_view_local_pos, fw::Space_Local ); + view->set_position( _event->data.node_view_local_pos, fw::Space_Local ); NodeView::set_selected( view ); } break; diff --git a/src/nodable/gui/NodableView.cpp b/src/nodable/gui/NodableView.cpp index df9311f56..255785c74 100644 --- a/src/nodable/gui/NodableView.cpp +++ b/src/nodable/gui/NodableView.cpp @@ -104,9 +104,7 @@ void NodableView::on_draw() if (ImGui::MenuItem("Expand/Collapse recursive", nullptr, false, has_selection)) { - ToggleFoldingEventPayload payload; - payload.recursive = true; - event_manager.dispatch(payload); + event_manager.dispatch( { RECURSIVELY } ); } ImGui::EndMenu(); } diff --git a/src/nodable/gui/SlotView.cpp b/src/nodable/gui/SlotView.cpp index c18752f15..738364f18 100644 --- a/src/nodable/gui/SlotView.cpp +++ b/src/nodable/gui/SlotView.cpp @@ -85,7 +85,8 @@ void SlotView::behavior(SlotView& _view, bool _readonly) { if ( ImGui::MenuItem(ICON_FA_TRASH " Disconnect")) { - fw::EventManager::get_instance().dispatch( { _view.m_slot } ); + auto& event_manager = fw::EventManager::get_instance(); + event_manager.dispatch({ _view.slot() }); } ImGui::EndPopup(); @@ -145,10 +146,8 @@ void SlotView::drop_behavior(bool &require_new_node, bool _enable_edition) { if ( s_hovered ) { - SlotEventPayload payload; - payload.first = s_dragged->m_slot; - payload.second = s_hovered->m_slot; - fw::EventManager::get_instance().dispatch(payload); + auto& event_manager = fw::EventManager::get_instance(); + event_manager.dispatch({ s_dragged->m_slot, s_hovered->m_slot}); reset_hovered(); reset_dragged(); From 5882ea0ee628d0f70eebaeb82f346f5a521babdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Fri, 1 Mar 2024 14:59:32 -0500 Subject: [PATCH 11/27] fix(GraphView): contextual menu 8/N (large refactor) --- src/fw/gui/Action.h | 53 ++++++++++++++++----------- src/fw/gui/Event.h | 20 +++++------ src/fw/gui/EventManager.cpp | 23 +++++++----- src/fw/gui/EventManager.h | 57 ++++++++++++++++++------------ src/fw/gui/ImGuiEx.cpp | 2 +- src/nodable/gui/Action.h | 20 +++++------ src/nodable/gui/Event.h | 11 ++++-- src/nodable/gui/FrameMode.h | 10 ++++++ src/nodable/gui/GraphView.cpp | 10 ++++++ src/nodable/gui/GraphView.h | 2 ++ src/nodable/gui/HybridFile.cpp | 2 +- src/nodable/gui/HybridFileView.cpp | 2 +- src/nodable/gui/Nodable.cpp | 52 ++++++++++----------------- 13 files changed, 153 insertions(+), 111 deletions(-) create mode 100644 src/nodable/gui/FrameMode.h diff --git a/src/fw/gui/Action.h b/src/fw/gui/Action.h index 2eb2eb3c1..940c67a71 100644 --- a/src/fw/gui/Action.h +++ b/src/fw/gui/Action.h @@ -15,60 +15,71 @@ namespace fw }; /** Basic action defining which event has to be triggered when a given shortcut is detected */ - class BaseAction + class IAction { public: - BaseAction( + IAction( EventID event_id, const char* label, - const Shortcut& shortcut = {}) + const Shortcut& shortcut = {}, + u64_t userdata = {} + ) : label(label) , event_id(event_id) , shortcut(shortcut) + , userdata( userdata ) {} std::string label; EventID event_id; Shortcut shortcut; - virtual BaseEvent* make_event() const { return new BaseEvent(event_id); } + u64_t userdata; + virtual IEvent* make_event() const { return new IEvent(event_id); } }; /** Generic Action to trigger a given EventT */ template - class SimpleAction : public BaseAction + class BasicAction : public IAction { public: - explicit SimpleAction( const char* label, Shortcut shortcut = {} ) - : BaseAction(event_id, label, shortcut) + using event_t = fw::BasicEvent<_event_id>; + using event_data_t = typename event_t::data_t; + explicit BasicAction( + const char* label, + Shortcut shortcut = {}, + u64_t userdata = {} + ) + : IAction(event_id, label, shortcut, userdata) {} }; /** Generic Action able to make a given EventT from an ActionConfigT */ template - class CustomAction : public BaseAction + class CustomAction : public IAction { public: - static_assert( !std::is_base_of_v ); - using event_t = EventT; + static_assert( !std::is_base_of_v ); + using event_t = EventT; using event_data_t = typename EventT::data_t; CustomAction( const char* label, - Shortcut shortcut = {}, - event_data_t event_initial_state = {} + Shortcut shortcut, + u64_t userdata, + event_data_t event_initial_state = {} ) - : BaseAction(EventT::id, label, shortcut) + : IAction(EventT::id, label, shortcut, userdata) , event_initial_state( event_initial_state ) {} EventT* make_event() const override { return new EventT( event_initial_state ); } event_data_t event_initial_state; // Custom data to attach }; - using Action_FileSave = SimpleAction; - using Action_FileSaveAs = SimpleAction; - using Action_FileClose = SimpleAction; - using Action_FileBrowse = SimpleAction; - using Action_FileNew = SimpleAction; - using Action_Exit = SimpleAction; - using Action_Undo = SimpleAction; - using Action_Redo = SimpleAction; + using Action_FileSave = BasicAction; + using Action_FileSaveAs = BasicAction; + using Action_FileClose = BasicAction; + using Action_FileBrowse = BasicAction; + using Action_FileNew = BasicAction; + using Action_Exit = BasicAction; + using Action_Undo = BasicAction; + using Action_Redo = BasicAction; using Action_ShowWindow = CustomAction; } \ No newline at end of file diff --git a/src/fw/gui/Event.h b/src/fw/gui/Event.h index 14d13ff89..c3387d6cd 100644 --- a/src/fw/gui/Event.h +++ b/src/fw/gui/Event.h @@ -30,29 +30,29 @@ namespace fw }; /** Basic event, can be extended via CustomEvent */ - class BaseEvent + class IEvent { public: const EventID id; - constexpr explicit BaseEvent(EventID id): id(id) {} - virtual ~BaseEvent() = default; + constexpr explicit IEvent(EventID id): id(id) {} + virtual ~IEvent() = default; }; template - class SimpleEvent : public fw::BaseEvent + class BasicEvent : public fw::IEvent { public: - using data_t = void; + using data_t = struct {}; constexpr static EventID id = static_cast(event_id); - SimpleEvent() - : BaseEvent(event_id) + BasicEvent() + : IEvent(event_id) {} }; - /** Template to extend BaseEvent with a specific payload */ + /** Template to extend IEvent with a specific payload */ template - class CustomEvent : public fw::BaseEvent + class CustomEvent : public fw::IEvent { public: constexpr static EventID id = event_id; @@ -61,7 +61,7 @@ namespace fw DataT data; CustomEvent( DataT _data = {}) - : BaseEvent(event_id) + : IEvent(event_id) , data( _data ) {} }; diff --git a/src/fw/gui/EventManager.cpp b/src/fw/gui/EventManager.cpp index 67c08ab0b..ee92af243 100644 --- a/src/fw/gui/EventManager.cpp +++ b/src/fw/gui/EventManager.cpp @@ -35,29 +35,36 @@ EventManager& EventManager::get_instance() return *s_instance; } -void EventManager::dispatch( BaseEvent* _event) +void EventManager::dispatch(IEvent* _event) { m_events.push(_event); } -BaseEvent* EventManager::poll_event() +IEvent* EventManager::poll_event() { - return m_events.empty() ? nullptr : m_events.front(); + if ( m_events.empty() ) + { + return nullptr; + } + + IEvent* next_event = m_events.front(); + m_events.pop(); + return next_event; } -BaseEvent* EventManager::dispatch( EventID _event_id ) +IEvent* EventManager::dispatch( EventID _event_id ) { - auto new_event = new BaseEvent{ _event_id }; + auto new_event = new IEvent{ _event_id }; dispatch(new_event ); return new_event; } -const BaseAction* EventManager::get_action_by_type( u16_t type ) +const IAction* EventManager::get_action_by_type( u16_t type ) { return m_actions_by_event_type.at(type); } -const std::vector& EventManager::get_actions() const +const std::vector& EventManager::get_actions() const { return m_actions; } @@ -72,7 +79,7 @@ void EventManager::dispatch_delayed( EventID type, u64_t delay) ); } -void EventManager::add_action( BaseAction* _action )// Add a new action (can be triggered via shortcut) +void EventManager::add_action( IAction* _action )// Add a new action (can be triggered via shortcut) { m_actions.push_back( _action ); m_actions_by_event_type.insert({ _action->event_id, _action }); diff --git a/src/fw/gui/EventManager.h b/src/fw/gui/EventManager.h index 1b563b561..1dd38873f 100644 --- a/src/fw/gui/EventManager.h +++ b/src/fw/gui/EventManager.h @@ -20,46 +20,57 @@ namespace fw EventManager(const EventManager&) = delete; ~EventManager(); - BaseEvent* dispatch( EventID ); // Create and push a basic event to the queue - void dispatch_delayed( EventID, u64_t); // Does the same as dispatch(EventID) with a delay in millisecond. A delay of 0ms will be processed after a regular dispatch though. - void dispatch( BaseEvent* _event); // Push an existing event to the queue. - BaseEvent* poll_event(); // Pop the first event in the queue - const std::vector& get_actions() const; // Get all the actions bound to any event + IEvent* dispatch( EventID ); // Create and push a basic event to the queue + void dispatch_delayed( EventID, u64_t); // Does the same as dispatch(EventID) with a delay in millisecond. A delay of 0ms will be processed after a regular dispatch though. + void dispatch( IEvent* _event); // Push an existing event to the queue. + IEvent* poll_event(); // Pop the first event in the queue + const std::vector& get_actions() const; // Get all the actions bound to any event template - void bind( const char* label, Shortcut shortcut = {}, typename ActionT::event_data_t&& event_state = {} ) - { - static_assert( std::is_base_of_v ); - BaseAction* action = new ActionT(label, shortcut, event_state ); + void bind( + const char* label, + Shortcut shortcut, + typename ActionT::event_data_t&& event_state, + u64_t custom_data = {} + ) { + static_assert( std::is_base_of_v ); + IAction* action = new ActionT(label, shortcut, custom_data, event_state ); add_action(action); } template - void bind( const char* label, Shortcut shortcut = {} ) - { - static_assert( std::is_base_of_v ); - BaseAction* action = new ActionT(label, shortcut); + void bind( + const char* label, + Shortcut shortcut, + u64_t custom_data = 0 + ){ + static_assert( std::is_base_of_v ); + IAction* action = new ActionT(label, shortcut, custom_data ); add_action(action); } - void bind( const char* label, EventID event_id, const Shortcut& shortcut = {} ) - { - auto* action = new BaseAction(event_id, label, shortcut); + void bind( + const char* label, + EventID event_id, + const Shortcut& shortcut = {}, + u64_t custom_data = {} + ){ + auto* action = new IAction(event_id, label, shortcut, custom_data); add_action(action); } template - BaseEvent* dispatch(typename EventT::data_t&& state = {} ) + IEvent* dispatch(typename EventT::data_t&& state = {} ) { - static_assert( std::is_base_of_v ); + static_assert( std::is_base_of_v ); auto new_event = new EventT(state); dispatch(new_event); return new_event; } - const BaseAction* get_action_by_type(u16_t type); // Get the action bound to a given event type + const IAction* get_action_by_type(u16_t type); // Get the action bound to a given event type static EventManager& get_instance(); private: - void add_action( BaseAction* _action); + void add_action( IAction* _action); static EventManager* s_instance; - std::queue m_events; - std::vector m_actions; - std::map m_actions_by_event_type; + std::queue m_events; + std::vector m_actions; + std::map m_actions_by_event_type; }; } \ No newline at end of file diff --git a/src/fw/gui/ImGuiEx.cpp b/src/fw/gui/ImGuiEx.cpp index a3cab7b18..33e87c6f7 100644 --- a/src/fw/gui/ImGuiEx.cpp +++ b/src/fw/gui/ImGuiEx.cpp @@ -229,7 +229,7 @@ void ImGuiEx::BeginFrame() void ImGuiEx::MenuItem(uint16_t type, bool selected, bool enable) { - const BaseAction* action = EventManager::get_instance().get_action_by_type( type ); + const IAction* action = EventManager::get_instance().get_action_by_type( type ); if (ImGui::MenuItem( action->label.c_str(), action->shortcut.to_string().c_str(), selected, enable)) { EventManager::get_instance().dispatch( action->event_id ); diff --git a/src/nodable/gui/Action.h b/src/nodable/gui/Action.h index c788ecce2..1e7699434 100644 --- a/src/nodable/gui/Action.h +++ b/src/nodable/gui/Action.h @@ -6,19 +6,19 @@ namespace ndbl { - using fw::SimpleAction; + using fw::BasicAction; using fw::CustomAction; // Actions specific to Nodable, more actions defined in framework's Action.h - using Action_DeleteNode = SimpleAction; - using Action_ArrangeNode = SimpleAction; - using Action_ToggleFolding = SimpleAction; - using Action_SelectNext = SimpleAction; - using Action_Isolate = SimpleAction; - using Action_SelectionChange = SimpleAction; - using Action_MoveGraph = SimpleAction; - using Action_FrameGraph = SimpleAction; + using Action_DeleteNode = BasicAction; + using Action_ArrangeNode = BasicAction; + using Action_ToggleFolding = BasicAction; + using Action_SelectNext = BasicAction; + using Action_Isolate = BasicAction; + using Action_SelectionChange = BasicAction; + using Action_MoveGraph = BasicAction; + using Action_FrameGraph = BasicAction; using Action_CreateBlock = CustomAction; - using Action_CreateNode = CustomAction ; + using Action_CreateNode = CustomAction; } \ No newline at end of file diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index cca320280..c95ff8dea 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -1,6 +1,7 @@ #pragma once #include +#include "FrameMode.h" #include "SlotView.h" #include "core/Graph.h" #include "fw/core/Pool.h" @@ -19,7 +20,6 @@ namespace ndbl EventID_REQUEST_ARRANGE_HIERARCHY, EventID_REQUEST_SELECT_SUCCESSOR, EventID_REQUEST_TOGGLE_FOLDING, - EventID_REQUEST_FRAME_ALL, EventID_REQUEST_CREATE_NODE, EventID_REQUEST_CREATE_BLOCK, EventID_REQUEST_FRAME_SELECTION, @@ -30,8 +30,13 @@ namespace ndbl EventID_NODE_SELECTION_CHANGE, }; - using Event_FrameSelection = fw::SimpleEvent; - using Event_FrameAll = fw::SimpleEvent; + class GraphView; + struct EventPayload_FrameNodeViews + { + FrameMode mode; + GraphView* graph_view = nullptr; // Will be deduced my the Nodable if nullptr + }; + using Event_FrameNodeViews = fw::CustomEvent; struct EventPayload_SlotPair { SlotRef first; diff --git a/src/nodable/gui/FrameMode.h b/src/nodable/gui/FrameMode.h new file mode 100644 index 000000000..838b2a408 --- /dev/null +++ b/src/nodable/gui/FrameMode.h @@ -0,0 +1,10 @@ +#pragma once + +namespace ndbl +{ + enum FrameMode + { + FRAME_SELECTION_ONLY = 0, + FRAME_ALL = 1 + }; +} \ No newline at end of file diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index b5aa08fac..9623c45aa 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -503,6 +503,16 @@ void GraphView::add_action_to_context_menu( Action_CreateNode* _action ) m_create_node_context_menu.items.push_back(_action); } +void GraphView::frame( FrameMode mode ) +{ + // TODO: use an ImRect instead of a FrameMode enum, it will be easier to handle undo/redo + if ( mode == FRAME_ALL ) + { + return frame_all_node_views(); + } + return frame_selected_node_views(); +} + Action_CreateNode* CreateNodeContextMenu::draw_search_input( size_t _result_max_count ) { bool validated; diff --git a/src/nodable/gui/GraphView.h b/src/nodable/gui/GraphView.h index e10e051a4..c211d014d 100644 --- a/src/nodable/gui/GraphView.h +++ b/src/nodable/gui/GraphView.h @@ -55,6 +55,8 @@ namespace ndbl void translate_all(ImVec2 /* delta */, const std::vector&); void unfold(); // unfold the graph until it is stabilized void add_action_to_context_menu( Action_CreateNode* _action); + void frame( FrameMode mode ); + private: void draw_grid( ImDrawList*, const Config& ) const; void frame_views(const std::vector &_views, bool _align_top_left_corner); diff --git a/src/nodable/gui/HybridFile.cpp b/src/nodable/gui/HybridFile.cpp index ec7c54920..4b80a6d49 100644 --- a/src/nodable/gui/HybridFile.cpp +++ b/src/nodable/gui/HybridFile.cpp @@ -39,7 +39,7 @@ HybridFile::HybridFile(std::string _name) m_graph = new Graph(&Nodable::get_instance().node_factory); m_graph_view = new GraphView(m_graph); - for( BaseAction* action : EventManager::get_instance().get_actions() ) // Fill the "create node" context menu + for( IAction* action : EventManager::get_instance().get_actions() ) // Fill the "create node" context menu { if ( auto create_node_action = dynamic_cast(action)) { diff --git a/src/nodable/gui/HybridFileView.cpp b/src/nodable/gui/HybridFileView.cpp index 329e92195..eb133b4be 100644 --- a/src/nodable/gui/HybridFileView.cpp +++ b/src/nodable/gui/HybridFileView.cpp @@ -51,7 +51,7 @@ HybridFileView::HybridFileView(HybridFile& _file) graph_view->translate_all( ImVec2(-1000.f, -1000.0f) , views); // frame all (33ms delayed) - fw::EventManager::get_instance().dispatch_delayed( EventID_REQUEST_FRAME_ALL, 33 ); + fw::EventManager::get_instance().dispatch_delayed( EventID_REQUEST_FRAME_SELECTION, 33 ); } } }); diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 0eb739895..68de8e528 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -112,8 +112,8 @@ bool Nodable::on_init() // Bind commands to shortcuts using fw::EventID; event_manager.bind( "Delete", { SDLK_DELETE, KMOD_NONE } ); - event_manager.bind( "Arrange", { SDLK_a, KMOD_NONE } /*, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR */ ); - event_manager.bind( "Fold", { SDLK_x, KMOD_NONE } /*, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR */ ); + event_manager.bind( "Arrange", { SDLK_a, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind( "Fold", { SDLK_x, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); event_manager.bind( "Next", { SDLK_n, KMOD_NONE } ); event_manager.bind( ICON_FA_SAVE " Save", { SDLK_s, KMOD_CTRL } ); event_manager.bind( ICON_FA_SAVE " Save as", { SDLK_s, KMOD_CTRL } ); @@ -124,16 +124,16 @@ bool Nodable::on_init() event_manager.bind( ICON_FA_SIGN_OUT_ALT " Exit", { SDLK_F4, KMOD_ALT } ); event_manager.bind( "Undo", { SDLK_z, KMOD_CTRL } ); event_manager.bind( "Redo", { SDLK_y, KMOD_CTRL } ); - event_manager.bind( "Isolate", { SDLK_i, KMOD_CTRL } /*, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR */ ); - event_manager.bind("Deselect", { 0, KMOD_NONE, "Double click on bg" } /*, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR }*/ ); - event_manager.bind("Move Graph", { 0, KMOD_NONE, "Drag background" } /*, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR }*/ ); - event_manager.bind("Frame Selection", { SDLK_f, KMOD_NONE } /*, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR }*/ ); + event_manager.bind( "Isolate", { SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR ); + event_manager.bind("Deselect", { 0, KMOD_NONE, "Double click on bg" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind("Move Graph", { 0, KMOD_NONE, "Drag background" }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind("Frame Selection", { SDLK_f, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); event_manager.bind("Frame All", { SDLK_f, KMOD_LCTRL } ); // Prepare context menu items { // 1) Blocks - event_manager.bind( ICON_FA_CODE " Condition", {}, { NodeType_BLOCK_CONDITION } ); + event_manager.bind( ICON_FA_CODE " Condition", {}, NodeType_BLOCK_CONDITION ); event_manager.bind( ICON_FA_CODE " For Loop", {}, NodeType_BLOCK_FOR_LOOP ); event_manager.bind( ICON_FA_CODE " While Loop", {}, NodeType_BLOCK_WHILE_LOOP ); event_manager.bind( ICON_FA_CODE " Scope", {}, NodeType_BLOCK_SCOPE ); @@ -187,26 +187,16 @@ void Nodable::on_update() // shorthand to push all shortcuts to a file view overlay depending on conditions auto push_overlay_shortcuts = [&](ndbl::HybridFileView& _view, Condition _condition) -> void { - for (const auto& _action: event_manager.get_actions()) + for (const auto _action: event_manager.get_actions()) { - if( ( _action.condition & _condition) == _condition) + if( ( _action->userdata & _condition) == _condition && (_action->userdata & Condition_HIGHLIGHTED_IN_GRAPH_EDITOR|Condition_HIGHLIGHTED_IN_TEXT_EDITOR) ) { - if ( _action.condition & Condition_HIGHLIGHTED_IN_GRAPH_EDITOR) - { - _view.push_overlay( - { _action.label.substr(0, 12), - _action.shortcut.to_string() - }, OverlayType_GRAPH); - } - if ( _action.condition & Condition_HIGHLIGHTED_IN_TEXT_EDITOR) - { - _view.push_overlay( - { _action.label.substr(0,12), - _action.shortcut.to_string() - }, OverlayType_TEXT); - } + std::string label = _action->label.substr(0, 12); + std::string shortcut_str = _action->shortcut.to_string(); + OverlayType_ overlay_type = _action->userdata & Condition_HIGHLIGHTED_IN_TEXT_EDITOR ? OverlayType_GRAPH + : OverlayType_TEXT; + _view.push_overlay({label, shortcut_str}, overlay_type); } - } }; @@ -215,8 +205,8 @@ void Nodable::on_update() GraphView* graph_view = current_file ? current_file->get_graph_view() : nullptr; History* curr_file_history = current_file ? current_file->get_history() : nullptr; - BaseEvent* event = nullptr; - while( (event = event_manager.poll_event()) && event != nullptr ) + IEvent* event = nullptr; + while( (event = event_manager.poll_event()) ) { switch ( event->id ) { @@ -315,15 +305,11 @@ void Nodable::on_update() } break; } - case Event_FrameSelection::id: - { - if (graph_view) graph_view->frame_selected_node_views(); - break; - } - case Event_FrameAll::id: + case Event_FrameNodeViews::id: { - if (graph_view) graph_view->frame_all_node_views(); + auto _event = reinterpret_cast( event ); + _event->data.graph_view->frame(_event->data.mode); break; } From 95079a49a6d84396c09c27a8ec7949ec9260c107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Fri, 1 Mar 2024 19:30:19 -0500 Subject: [PATCH 12/27] fix(GraphView): contextual menu 9/N (large refactor) --- src/fw/gui/Action.h | 60 +++++++++---------- src/fw/gui/Event.h | 4 +- src/fw/gui/EventManager.cpp | 19 ++++-- src/fw/gui/EventManager.h | 54 +++++++++-------- src/fw/gui/ImGuiEx.cpp | 7 ++- src/fw/gui/ImGuiEx.h | 2 +- src/nodable/gui/Action.h | 18 +++--- src/nodable/gui/Event.h | 28 ++++++--- src/nodable/gui/HybridFileView.cpp | 2 +- src/nodable/gui/Nodable.cpp | 94 +++++++++++++++--------------- src/nodable/gui/NodableView.cpp | 7 +-- 11 files changed, 158 insertions(+), 137 deletions(-) diff --git a/src/fw/gui/Action.h b/src/fw/gui/Action.h index 940c67a71..238809cdd 100644 --- a/src/fw/gui/Action.h +++ b/src/fw/gui/Action.h @@ -19,34 +19,34 @@ namespace fw { public: IAction( - EventID event_id, - const char* label, - const Shortcut& shortcut = {}, - u64_t userdata = {} - ) - : label(label) - , event_id(event_id) - , shortcut(shortcut) - , userdata( userdata ) + EventID event_id, + const char* label, + const Shortcut& shortcut = {}, + u64_t userdata = {} + ) + : label(label) + , event_id(event_id) + , shortcut(shortcut) + , userdata(userdata) {} - std::string label; - EventID event_id; - Shortcut shortcut; - u64_t userdata; + std::string label; + EventID event_id; + Shortcut shortcut; + u64_t userdata; virtual IEvent* make_event() const { return new IEvent(event_id); } }; /** Generic Action to trigger a given EventT */ template - class BasicAction : public IAction + class Action : public IAction { public: - using event_t = fw::BasicEvent<_event_id>; + using event_t = fw::Event<_event_id>; using event_data_t = typename event_t::data_t; - explicit BasicAction( - const char* label, - Shortcut shortcut = {}, - u64_t userdata = {} + explicit Action( + const char* label, + Shortcut shortcut = {}, + u64_t userdata = {} ) : IAction(event_id, label, shortcut, userdata) {} @@ -63,9 +63,9 @@ namespace fw CustomAction( const char* label, Shortcut shortcut, - u64_t userdata, - event_data_t event_initial_state = {} - ) + event_data_t event_initial_state = {}, + u64_t userdata = {} + ) : IAction(EventT::id, label, shortcut, userdata) , event_initial_state( event_initial_state ) {} @@ -73,13 +73,13 @@ namespace fw event_data_t event_initial_state; // Custom data to attach }; - using Action_FileSave = BasicAction; - using Action_FileSaveAs = BasicAction; - using Action_FileClose = BasicAction; - using Action_FileBrowse = BasicAction; - using Action_FileNew = BasicAction; - using Action_Exit = BasicAction; - using Action_Undo = BasicAction; - using Action_Redo = BasicAction; + using Action_FileSave = Action; + using Action_FileSaveAs = Action; + using Action_FileClose = Action; + using Action_FileBrowse = Action; + using Action_FileNew = Action; + using Action_Exit = Action; + using Action_Undo = Action; + using Action_Redo = Action; using Action_ShowWindow = CustomAction; } \ No newline at end of file diff --git a/src/fw/gui/Event.h b/src/fw/gui/Event.h index c3387d6cd..d4f525d9d 100644 --- a/src/fw/gui/Event.h +++ b/src/fw/gui/Event.h @@ -39,13 +39,13 @@ namespace fw }; template - class BasicEvent : public fw::IEvent + class Event : public fw::IEvent { public: using data_t = struct {}; constexpr static EventID id = static_cast(event_id); - BasicEvent() + Event() : IEvent(event_id) {} }; diff --git a/src/fw/gui/EventManager.cpp b/src/fw/gui/EventManager.cpp index ee92af243..f27776205 100644 --- a/src/fw/gui/EventManager.cpp +++ b/src/fw/gui/EventManager.cpp @@ -59,9 +59,16 @@ IEvent* EventManager::dispatch( EventID _event_id ) return new_event; } -const IAction* EventManager::get_action_by_type( u16_t type ) +const IAction* EventManager::get_action_by_event_id(EventID id) { - return m_actions_by_event_type.at(type); + auto found = m_actions_by_event_type.find(id); + if ( found == m_actions_by_event_type.end() ) + { + string128 str; + str.append_fmt("Unable to find an action bound to EventId %i\n", id); + FW_EXPECT(false, str.c_str() ); + } + return found->second; } const std::vector& EventManager::get_actions() const @@ -69,12 +76,12 @@ const std::vector& EventManager::get_actions() const return m_actions; } -void EventManager::dispatch_delayed( EventID type, u64_t delay) +void EventManager::dispatch_delayed(u64_t delay, IEvent* event) { fw::async::get_instance().add_task( - std::async(std::launch::async, [this, type, delay]() -> void { + std::async(std::launch::async, [this, event, delay]() -> void { std::this_thread::sleep_for(std::chrono::milliseconds{delay}); - dispatch( type ); + dispatch(event); }) ); } @@ -82,7 +89,7 @@ void EventManager::dispatch_delayed( EventID type, u64_t delay) void EventManager::add_action( IAction* _action )// Add a new action (can be triggered via shortcut) { m_actions.push_back( _action ); - m_actions_by_event_type.insert({ _action->event_id, _action }); + m_actions_by_event_type.emplace( _action->event_id, _action ); } std::string Shortcut::to_string() const diff --git a/src/fw/gui/EventManager.h b/src/fw/gui/EventManager.h index 1dd38873f..9c420edb8 100644 --- a/src/fw/gui/EventManager.h +++ b/src/fw/gui/EventManager.h @@ -20,32 +20,36 @@ namespace fw EventManager(const EventManager&) = delete; ~EventManager(); - IEvent* dispatch( EventID ); // Create and push a basic event to the queue - void dispatch_delayed( EventID, u64_t); // Does the same as dispatch(EventID) with a delay in millisecond. A delay of 0ms will be processed after a regular dispatch though. - void dispatch( IEvent* _event); // Push an existing event to the queue. + IEvent* dispatch(EventID); // Create and push a basic event to the queue + void dispatch(IEvent* _event); // Push an existing event to the queue. + void dispatch_delayed(u64_t, IEvent* ); // Does the same as dispatch(Event*) with a delay in millisecond. A delay of 0ms will be processed after a regular dispatch though. IEvent* poll_event(); // Pop the first event in the queue const std::vector& get_actions() const; // Get all the actions bound to any event - template - void bind( - const char* label, - Shortcut shortcut, - typename ActionT::event_data_t&& event_state, - u64_t custom_data = {} - ) { - static_assert( std::is_base_of_v ); - IAction* action = new ActionT(label, shortcut, custom_data, event_state ); - add_action(action); + + template + void dispatch(Args... args) + { + static_assert(std::is_base_of_v ); + IEvent* event = new EventT(args...); + dispatch(event); } - template - void bind( - const char* label, - Shortcut shortcut, - u64_t custom_data = 0 - ){ - static_assert( std::is_base_of_v ); - IAction* action = new ActionT(label, shortcut, custom_data ); + + template + void dispatch_delayed(u64_t delay_in_ms, typename EventT::data_t data) + { + static_assert(std::is_base_of_v ); + IEvent* event = new EventT(data); + dispatch_delayed(delay_in_ms, event); + } + + template + void bind(Args... args) + { + static_assert(std::is_base_of_v ); + IAction* action = new ActionT(args...); add_action(action); } + void bind( const char* label, EventID event_id, @@ -64,11 +68,11 @@ namespace fw return new_event; } - const IAction* get_action_by_type(u16_t type); // Get the action bound to a given event type - static EventManager& get_instance(); + const IAction* get_action_by_event_id(EventID id); // Get the action bound to a given event type + static EventManager& get_instance(); private: - void add_action( IAction* _action); - static EventManager* s_instance; + void add_action( IAction* _action); + static EventManager* s_instance; std::queue m_events; std::vector m_actions; std::map m_actions_by_event_type; diff --git a/src/fw/gui/ImGuiEx.cpp b/src/fw/gui/ImGuiEx.cpp index 33e87c6f7..c7e12993a 100644 --- a/src/fw/gui/ImGuiEx.cpp +++ b/src/fw/gui/ImGuiEx.cpp @@ -227,12 +227,13 @@ void ImGuiEx::BeginFrame() s_is_any_tooltip_open = false; } -void ImGuiEx::MenuItem(uint16_t type, bool selected, bool enable) +void ImGuiEx::MenuItem(EventID id, bool selected, bool enable) { - const IAction* action = EventManager::get_instance().get_action_by_type( type ); + const IAction* action = EventManager::get_instance().get_action_by_event_id( id ); if (ImGui::MenuItem( action->label.c_str(), action->shortcut.to_string().c_str(), selected, enable)) { - EventManager::get_instance().dispatch( action->event_id ); + auto event = action->make_event(); + EventManager::get_instance().dispatch( event ); } } diff --git a/src/fw/gui/ImGuiEx.h b/src/fw/gui/ImGuiEx.h index 2a265c83b..105a2bbd5 100644 --- a/src/fw/gui/ImGuiEx.h +++ b/src/fw/gui/ImGuiEx.h @@ -106,7 +106,7 @@ namespace fw static void EndTooltip(); static ImRect& EnlargeToInclude(ImRect& _rect, ImRect _other); - static void MenuItem(uint16_t type, bool selected = false, bool enable = true); + static void MenuItem(EventID id, bool selected = false, bool enable = true); static void BulletTextWrapped(const char*); static ImRect GetContentRegion(Space); diff --git a/src/nodable/gui/Action.h b/src/nodable/gui/Action.h index 1e7699434..5c8089806 100644 --- a/src/nodable/gui/Action.h +++ b/src/nodable/gui/Action.h @@ -6,19 +6,19 @@ namespace ndbl { - using fw::BasicAction; + using fw::Action; using fw::CustomAction; // Actions specific to Nodable, more actions defined in framework's Action.h - using Action_DeleteNode = BasicAction; - using Action_ArrangeNode = BasicAction; - using Action_ToggleFolding = BasicAction; - using Action_SelectNext = BasicAction; - using Action_Isolate = BasicAction; - using Action_SelectionChange = BasicAction; - using Action_MoveGraph = BasicAction; - using Action_FrameGraph = BasicAction; + using Action_DeleteNode = Action; + using Action_ArrangeNode = Action; + using Action_ToggleFolding = Action; + using Action_SelectNext = Action; + using Action_ToggleIsolate = Action; + using Action_SelectionChange = Action; + using Action_MoveGraph = Action; + using Action_FrameGraph = Action; using Action_CreateBlock = CustomAction; using Action_CreateNode = CustomAction; } \ No newline at end of file diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index c95ff8dea..9f5e2428e 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -24,7 +24,7 @@ namespace ndbl EventID_REQUEST_CREATE_BLOCK, EventID_REQUEST_FRAME_SELECTION, EventID_REQUEST_MOVE_SELECTION, - EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, + EventID_REQUEST_TOGGLE_ISOLATE, EventID_SLOT_DROPPED, EventID_SLOT_DISCONNECTED, EventID_NODE_SELECTION_CHANGE, @@ -34,7 +34,12 @@ namespace ndbl struct EventPayload_FrameNodeViews { FrameMode mode; - GraphView* graph_view = nullptr; // Will be deduced my the Nodable if nullptr + GraphView* graph_view; // Will be deduced my the Nodable if nullptr + + EventPayload_FrameNodeViews(FrameMode mode, GraphView* graph_view) + : mode(mode) + , graph_view(graph_view) + {} }; using Event_FrameNodeViews = fw::CustomEvent; @@ -75,15 +80,22 @@ namespace ndbl }; using NodeViewSelectionChangeEvent = fw::CustomEvent; + using ToggleIsolateSelectionEvent = fw::Event; + struct EventPayload_CreateNode { - NodeType node_type; // The note type to create - const fw::func_type* node_signature{}; // The signature of the node that must be created - SlotView* dragged_slot{}; // The slot view being dragged. - Graph* graph = nullptr; // The graph to create the node into - ImVec2 node_view_local_pos; // The desired position for the new node view + NodeType node_type; // The note type to create + const fw::func_type* node_signature; // The signature of the node that must be created + SlotView* dragged_slot = nullptr; // The slot view being dragged. + Graph* graph = nullptr; // The graph to create the node into + ImVec2 node_view_local_pos; // The desired position for the new node view + + explicit EventPayload_CreateNode(NodeType node_type ) + : node_type(node_type) + , node_signature(nullptr) + {} - EventPayload_CreateNode(NodeType node_type, const fw::func_type* signature = nullptr ) + EventPayload_CreateNode(NodeType node_type, const fw::func_type* signature ) : node_type(node_type) , node_signature(signature) {} diff --git a/src/nodable/gui/HybridFileView.cpp b/src/nodable/gui/HybridFileView.cpp index eb133b4be..7f4bb7dfc 100644 --- a/src/nodable/gui/HybridFileView.cpp +++ b/src/nodable/gui/HybridFileView.cpp @@ -51,7 +51,7 @@ HybridFileView::HybridFileView(HybridFile& _file) graph_view->translate_all( ImVec2(-1000.f, -1000.0f) , views); // frame all (33ms delayed) - fw::EventManager::get_instance().dispatch_delayed( EventID_REQUEST_FRAME_SELECTION, 33 ); + fw::EventManager::get_instance().dispatch_delayed( 33, {FRAME_ALL, graph_view} ); } } }); diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 68de8e528..8c551ffb1 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -110,56 +110,53 @@ bool Nodable::on_init() fw::Pool::init(); // Bind commands to shortcuts - using fw::EventID; - event_manager.bind( "Delete", { SDLK_DELETE, KMOD_NONE } ); - event_manager.bind( "Arrange", { SDLK_a, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind( "Fold", { SDLK_x, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind( "Next", { SDLK_n, KMOD_NONE } ); - event_manager.bind( ICON_FA_SAVE " Save", { SDLK_s, KMOD_CTRL } ); - event_manager.bind( ICON_FA_SAVE " Save as", { SDLK_s, KMOD_CTRL } ); - event_manager.bind( ICON_FA_TIMES " Close", { SDLK_w, KMOD_CTRL } ); - event_manager.bind( ICON_FA_FOLDER_OPEN " Open", { SDLK_o, KMOD_CTRL } ); - event_manager.bind( ICON_FA_FILE " New", { SDLK_n, KMOD_CTRL } ); - event_manager.bind( "Splashscreen", { SDLK_F1 }, { "splashscreen" } ); - event_manager.bind( ICON_FA_SIGN_OUT_ALT " Exit", { SDLK_F4, KMOD_ALT } ); - event_manager.bind( "Undo", { SDLK_z, KMOD_CTRL } ); - event_manager.bind( "Redo", { SDLK_y, KMOD_CTRL } ); - event_manager.bind( "Isolate", { SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR ); - event_manager.bind("Deselect", { 0, KMOD_NONE, "Double click on bg" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind("Move Graph", { 0, KMOD_NONE, "Drag background" }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind("Frame Selection", { SDLK_f, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind("Frame All", { SDLK_f, KMOD_LCTRL } ); + event_manager.bind( "Delete", Shortcut{ SDLK_DELETE, KMOD_NONE } ); + event_manager.bind( "Arrange", Shortcut{ SDLK_a, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind( "Fold", Shortcut{ SDLK_x, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind( "Next", Shortcut{ SDLK_n, KMOD_NONE } ); + event_manager.bind( ICON_FA_SAVE " Save", Shortcut{ SDLK_s, KMOD_CTRL } ); + event_manager.bind( ICON_FA_SAVE " Save as", Shortcut{ SDLK_s, KMOD_CTRL } ); + event_manager.bind( ICON_FA_TIMES " Close", Shortcut{ SDLK_w, KMOD_CTRL } ); + event_manager.bind( ICON_FA_FOLDER_OPEN " Open", Shortcut{ SDLK_o, KMOD_CTRL } ); + event_manager.bind( ICON_FA_FILE " New", Shortcut{ SDLK_n, KMOD_CTRL } ); + event_manager.bind( "Splashscreen", Shortcut{ SDLK_F1 }, EventPayload_ShowWindow{ "splashscreen" } ); + event_manager.bind( ICON_FA_SIGN_OUT_ALT " Exit", Shortcut{ SDLK_F4, KMOD_ALT } ); + event_manager.bind( "Undo", Shortcut{ SDLK_z, KMOD_CTRL } ); + event_manager.bind( "Redo", Shortcut{ SDLK_y, KMOD_CTRL } ); + event_manager.bind( "Isolate", Shortcut{ SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR ); + event_manager.bind("Deselect", Shortcut{ 0, KMOD_NONE, "Double click on bg" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind("Move Graph", Shortcut{ 0, KMOD_NONE, "Drag background" }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind("Frame Selection", Shortcut{ SDLK_f, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind("Frame All", Shortcut{ SDLK_f, KMOD_LCTRL } ); // Prepare context menu items + // 1) Blocks + event_manager.bind( ICON_FA_CODE " Condition", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_CONDITION} ); + event_manager.bind( ICON_FA_CODE " For Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_FOR_LOOP} ); + event_manager.bind( ICON_FA_CODE " While Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_WHILE_LOOP} ); + event_manager.bind( ICON_FA_CODE " Scope", Shortcut{}, EventPayload_CreateNode{NodeType_BLOCK_SCOPE} ); + event_manager.bind( ICON_FA_CODE " Program", Shortcut{}, EventPayload_CreateNode{NodeType_BLOCK_PROGRAM} ); + + // 2) Variables + event_manager.bind( ICON_FA_DATABASE " Boolean Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_BOOLEAN, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_DATABASE " Double Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_DOUBLE, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_DATABASE " Integer Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_INTEGER, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_DATABASE " String Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_STRING, create_variable_node_signature() } ); + + // 3) Literals + event_manager.bind( ICON_FA_FILE " Boolean Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_BOOLEAN, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_FILE " Double Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_DOUBLE, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_FILE " Integer Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_INTEGER, create_variable_node_signature() } ); + event_manager.bind( ICON_FA_FILE " String Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_STRING, create_variable_node_signature() } ); + + // 4) Functions/Operators from the API + const Nodlang& language = Nodlang::get_instance(); + for ( auto& each_fct: language.get_api() ) { - // 1) Blocks - event_manager.bind( ICON_FA_CODE " Condition", {}, NodeType_BLOCK_CONDITION ); - event_manager.bind( ICON_FA_CODE " For Loop", {}, NodeType_BLOCK_FOR_LOOP ); - event_manager.bind( ICON_FA_CODE " While Loop", {}, NodeType_BLOCK_WHILE_LOOP ); - event_manager.bind( ICON_FA_CODE " Scope", {}, NodeType_BLOCK_SCOPE ); - event_manager.bind( ICON_FA_CODE " Program", {}, NodeType_BLOCK_PROGRAM ); - - // 2) Variables - event_manager.bind( ICON_FA_DATABASE " Boolean Variable", {}, { NodeType_VARIABLE_BOOLEAN, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_DATABASE " Double Variable", {}, { NodeType_VARIABLE_DOUBLE, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_DATABASE " Integer Variable", {}, { NodeType_VARIABLE_INTEGER, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_DATABASE " String Variable", {}, { NodeType_VARIABLE_STRING, create_variable_node_signature() } ); - - // 3) Literals - event_manager.bind( ICON_FA_FILE " Boolean Literal", {}, { NodeType_LITERAL_BOOLEAN, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_FILE " Double Literal", {}, { NodeType_LITERAL_DOUBLE, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_FILE " Integer Literal", {}, { NodeType_LITERAL_INTEGER, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_FILE " String Literal", {}, { NodeType_LITERAL_STRING, create_variable_node_signature() } ); - - // 4) Functions/Operators from the API - const Nodlang& language = Nodlang::get_instance(); - for ( auto& each_fct: language.get_api() ) - { - const fw::func_type* func_type = each_fct->get_type(); - std::string label; - language.serialize_func_sig( label, func_type ); - event_manager.bind( label.c_str(), {}, { NodeType_INVOKABLE, func_type } ); - } + const fw::func_type* func_type = each_fct->get_type(); + std::string label; + language.serialize_func_sig( label, func_type ); + event_manager.bind( label.c_str(), Shortcut{}, EventPayload_CreateNode{ NodeType_INVOKABLE, func_type } ); } return true; } @@ -210,7 +207,7 @@ void Nodable::on_update() { switch ( event->id ) { - case EventID_REQUEST_TOGGLE_ISOLATE_SELECTION: + case EventID_REQUEST_TOGGLE_ISOLATE: { config.isolate_selection = !config.isolate_selection; if(current_file) @@ -309,6 +306,7 @@ void Nodable::on_update() case Event_FrameNodeViews::id: { auto _event = reinterpret_cast( event ); + FW_ASSERT(_event->data.graph_view); _event->data.graph_view->frame(_event->data.mode); break; } diff --git a/src/nodable/gui/NodableView.cpp b/src/nodable/gui/NodableView.cpp index 255785c74..5b6b984e1 100644 --- a/src/nodable/gui/NodableView.cpp +++ b/src/nodable/gui/NodableView.cpp @@ -99,8 +99,7 @@ void NodableView::on_draw() } fw::ImGuiEx::MenuItem( EventID_REQUEST_ARRANGE_HIERARCHY, false, has_selection ); - fw::ImGuiEx::MenuItem( EventID_REQUEST_TOGGLE_FOLDING, false, - has_selection ); + fw::ImGuiEx::MenuItem( EventID_REQUEST_TOGGLE_FOLDING, false,has_selection ); if (ImGui::MenuItem("Expand/Collapse recursive", nullptr, false, has_selection)) { @@ -144,7 +143,7 @@ void NodableView::on_draw() ImGui::Separator(); - fw::ImGuiEx::MenuItem( EventID_REQUEST_TOGGLE_ISOLATE_SELECTION, config.isolate_selection ); + fw::ImGuiEx::MenuItem( EventID_REQUEST_TOGGLE_ISOLATE, config.isolate_selection ); ImGui::EndMenu(); } @@ -838,7 +837,7 @@ void NodableView::draw_toolbar_window() { if (ImGui::Button( config.isolate_selection ? ICON_FA_CROP " isolation mode: ON " : ICON_FA_CROP " isolation mode: OFF", button_size)) { - m_app->event_manager.dispatch( EventID_REQUEST_TOGGLE_ISOLATE_SELECTION ); + m_app->event_manager.dispatch( EventID_REQUEST_TOGGLE_ISOLATE ); } ImGui::SameLine(); ImGui::EndGroup(); From 8ea3c572b613a5f37d78654183104538f35140c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Fri, 1 Mar 2024 19:43:46 -0500 Subject: [PATCH 13/27] fix(GraphView): contextual menu 10/N (large refactor) --- src/fw/gui/Action.h | 6 +++--- src/fw/gui/EventManager.cpp | 1 + src/fw/gui/ImGuiEx.cpp | 4 ++++ src/nodable/gui/Action.h | 2 +- src/nodable/gui/Event.h | 7 ++----- src/nodable/gui/HybridFileView.cpp | 2 +- src/nodable/gui/Nodable.cpp | 12 ++++++------ 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/fw/gui/Action.h b/src/fw/gui/Action.h index 238809cdd..b7a2cddcd 100644 --- a/src/fw/gui/Action.h +++ b/src/fw/gui/Action.h @@ -37,18 +37,18 @@ namespace fw }; /** Generic Action to trigger a given EventT */ - template + template class Action : public IAction { public: - using event_t = fw::Event<_event_id>; + using event_t = fw::Event; using event_data_t = typename event_t::data_t; explicit Action( const char* label, Shortcut shortcut = {}, u64_t userdata = {} ) - : IAction(event_id, label, shortcut, userdata) + : IAction(id, label, shortcut, userdata) {} }; diff --git a/src/fw/gui/EventManager.cpp b/src/fw/gui/EventManager.cpp index f27776205..282ad7999 100644 --- a/src/fw/gui/EventManager.cpp +++ b/src/fw/gui/EventManager.cpp @@ -90,6 +90,7 @@ void EventManager::add_action( IAction* _action )// Add a new action (can be tri { m_actions.push_back( _action ); m_actions_by_event_type.emplace( _action->event_id, _action ); + LOG_MESSAGE("EventManager", "Action '%s' bound to the event_id %i\n", _action->label.c_str(), _action->event_id); } std::string Shortcut::to_string() const diff --git a/src/fw/gui/ImGuiEx.cpp b/src/fw/gui/ImGuiEx.cpp index c7e12993a..360b02197 100644 --- a/src/fw/gui/ImGuiEx.cpp +++ b/src/fw/gui/ImGuiEx.cpp @@ -229,9 +229,13 @@ void ImGuiEx::BeginFrame() void ImGuiEx::MenuItem(EventID id, bool selected, bool enable) { + // Find the corresponding action const IAction* action = EventManager::get_instance().get_action_by_event_id( id ); + + // Draw a simple menu item if (ImGui::MenuItem( action->label.c_str(), action->shortcut.to_string().c_str(), selected, enable)) { + // Trigger the action auto event = action->make_event(); EventManager::get_instance().dispatch( event ); } diff --git a/src/nodable/gui/Action.h b/src/nodable/gui/Action.h index 5c8089806..06f1088f0 100644 --- a/src/nodable/gui/Action.h +++ b/src/nodable/gui/Action.h @@ -18,7 +18,7 @@ namespace ndbl using Action_ToggleIsolate = Action; using Action_SelectionChange = Action; using Action_MoveGraph = Action; - using Action_FrameGraph = Action; + using Action_FrameSelection = CustomAction; using Action_CreateBlock = CustomAction; using Action_CreateNode = CustomAction; } \ No newline at end of file diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index 9f5e2428e..8e8425f31 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -34,14 +34,11 @@ namespace ndbl struct EventPayload_FrameNodeViews { FrameMode mode; - GraphView* graph_view; // Will be deduced my the Nodable if nullptr - - EventPayload_FrameNodeViews(FrameMode mode, GraphView* graph_view) + EventPayload_FrameNodeViews(FrameMode mode) : mode(mode) - , graph_view(graph_view) {} }; - using Event_FrameNodeViews = fw::CustomEvent; + using Event_FrameSelection = fw::CustomEvent; struct EventPayload_SlotPair { SlotRef first; diff --git a/src/nodable/gui/HybridFileView.cpp b/src/nodable/gui/HybridFileView.cpp index 7f4bb7dfc..79118afae 100644 --- a/src/nodable/gui/HybridFileView.cpp +++ b/src/nodable/gui/HybridFileView.cpp @@ -51,7 +51,7 @@ HybridFileView::HybridFileView(HybridFile& _file) graph_view->translate_all( ImVec2(-1000.f, -1000.0f) , views); // frame all (33ms delayed) - fw::EventManager::get_instance().dispatch_delayed( 33, {FRAME_ALL, graph_view} ); + fw::EventManager::get_instance().dispatch_delayed( 33, {FRAME_ALL} ); } } }); diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 8c551ffb1..4ea07cfa5 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -126,8 +126,8 @@ bool Nodable::on_init() event_manager.bind( "Isolate", Shortcut{ SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR ); event_manager.bind("Deselect", Shortcut{ 0, KMOD_NONE, "Double click on bg" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); event_manager.bind("Move Graph", Shortcut{ 0, KMOD_NONE, "Drag background" }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind("Frame Selection", Shortcut{ SDLK_f, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind("Frame All", Shortcut{ SDLK_f, KMOD_LCTRL } ); + event_manager.bind("Frame Selection", Shortcut{ SDLK_f, KMOD_NONE }, EventPayload_FrameNodeViews{ FRAME_SELECTION_ONLY }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + event_manager.bind("Frame All", Shortcut{ SDLK_f, KMOD_LCTRL }, EventPayload_FrameNodeViews{ FRAME_ALL } ); // Prepare context menu items // 1) Blocks @@ -303,11 +303,11 @@ void Nodable::on_update() break; } - case Event_FrameNodeViews::id: + case Event_FrameSelection::id: { - auto _event = reinterpret_cast( event ); - FW_ASSERT(_event->data.graph_view); - _event->data.graph_view->frame(_event->data.mode); + auto _event = reinterpret_cast( event ); + FW_EXPECT(graph_view, "a graph_view is required"); + graph_view->frame(_event->data.mode); break; } From ca91e4f1e53b10684a41668de12965ab03a73b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sat, 2 Mar 2024 22:51:13 -0500 Subject: [PATCH 14/27] fix(GraphView): contextual menu 11/N (large refactor) --- src/fw/gui/Action.cpp | 15 ++++ src/fw/gui/Action.h | 90 ++++++++++++------------ src/fw/gui/ActionManager.cpp | 73 ++++++++++++++++++++ src/fw/gui/ActionManager.h | 56 +++++++++++++++ src/fw/gui/App.cpp | 5 +- src/fw/gui/App.h | 3 + src/fw/gui/CMakeLists.txt | 2 + src/fw/gui/Event.h | 61 ++++++++-------- src/fw/gui/EventManager.cpp | 43 +----------- src/fw/gui/EventManager.h | 22 ------ src/fw/gui/ImGuiEx.cpp | 16 +---- src/fw/gui/ImGuiEx.h | 15 +++- src/nodable/gui/Action.h | 27 +++++--- src/nodable/gui/Event.h | 40 ++++++----- src/nodable/gui/GraphView.cpp | 7 +- src/nodable/gui/HybridFile.cpp | 2 +- src/nodable/gui/HybridFileView.cpp | 15 ++++ src/nodable/gui/HybridFileView.h | 7 +- src/nodable/gui/Nodable.cpp | 107 +++++++++++++---------------- src/nodable/gui/NodableView.cpp | 38 +++++----- src/nodable/gui/NodeView.cpp | 15 +--- 21 files changed, 378 insertions(+), 281 deletions(-) create mode 100644 src/fw/gui/Action.cpp create mode 100644 src/fw/gui/ActionManager.cpp create mode 100644 src/fw/gui/ActionManager.h diff --git a/src/fw/gui/Action.cpp b/src/fw/gui/Action.cpp new file mode 100644 index 000000000..e4b21a530 --- /dev/null +++ b/src/fw/gui/Action.cpp @@ -0,0 +1,15 @@ +#include "Action.h" +#include "EventManager.h" + +using namespace fw; + +void IAction::trigger() const +{ + IEvent* event = make_event(); + EventManager::get_instance().dispatch( event ); +} + +IEvent* IAction::make_event() const +{ + return new IEvent(event_id); +} \ No newline at end of file diff --git a/src/fw/gui/Action.h b/src/fw/gui/Action.h index b7a2cddcd..9755a21af 100644 --- a/src/fw/gui/Action.h +++ b/src/fw/gui/Action.h @@ -2,6 +2,7 @@ #include "Event.h" #include "core/types.h" #include +#include namespace fw { @@ -14,72 +15,73 @@ namespace fw std::string to_string() const; }; - /** Basic action defining which event has to be triggered when a given shortcut is detected */ + /** + * The purpose of any IAction is to trigger a given basic event (identified by an EventID) + * when a key shortcut is pressed. + */ class IAction { public: - IAction( + explicit IAction( EventID event_id, - const char* label, - const Shortcut& shortcut = {}, + const char* label = "action", + Shortcut&& shortcut = {}, u64_t userdata = {} ) : label(label) , event_id(event_id) - , shortcut(shortcut) + , shortcut(std::move(shortcut)) , userdata(userdata) {} std::string label; EventID event_id; Shortcut shortcut; u64_t userdata; - virtual IEvent* make_event() const { return new IEvent(event_id); } + + void trigger() const; // Trigger action, will dispatch an event with default values + virtual IEvent* make_event() const; // Make a new event with default values }; - /** Generic Action to trigger a given EventT */ - template + /** + * The purpose of an Action is similar to IAction for events requiring some data to be constructed + */ + template class Action : public IAction { public: - using event_t = fw::Event; - using event_data_t = typename event_t::data_t; - explicit Action( - const char* label, - Shortcut shortcut = {}, - u64_t userdata = {} - ) - : IAction(id, label, shortcut, userdata) + static_assert( !std::is_base_of_v ); // Ensure EventT implements IEvent + + using event_t = EventT; // Type of the event triggered by this action + using event_data_t = typename EventT::data_t; // Type of the payload for the events triggered by this action + + event_data_t event_data; // Initial data used when making a new event + + Action( + const char* label, + Shortcut&& shortcut + ) + : IAction(EventT::id, label, std::move(shortcut) ) {} - }; - /** Generic Action able to make a given EventT from an ActionConfigT */ - template - class CustomAction : public IAction - { - public: - static_assert( !std::is_base_of_v ); - using event_t = EventT; - using event_data_t = typename EventT::data_t; - CustomAction( - const char* label, - Shortcut shortcut, - event_data_t event_initial_state = {}, - u64_t userdata = {} + Action( + const char* label, // Text to display for this action + Shortcut&& shortcut, // Shortcut able to trigger this action + u64_t userdata // Custom user data (typically to store flags) + ) + : IAction(EventT::id, label, std::move(shortcut), userdata) + {} + + Action( + const char* label, // Text to display for this action + Shortcut&& shortcut, // Shortcut able to trigger this action + event_data_t event_data, // Initial data of a default event + u64_t userdata = {} // Custom user data (typically to store flags) ) - : IAction(EventT::id, label, shortcut, userdata) - , event_initial_state( event_initial_state ) + : IAction(EventT::id, label, std::move(shortcut), userdata) + , event_data( event_data ) {} - EventT* make_event() const override { return new EventT( event_initial_state ); } - event_data_t event_initial_state; // Custom data to attach - }; - using Action_FileSave = Action; - using Action_FileSaveAs = Action; - using Action_FileClose = Action; - using Action_FileBrowse = Action; - using Action_FileNew = Action; - using Action_Exit = Action; - using Action_Undo = Action; - using Action_Redo = Action; - using Action_ShowWindow = CustomAction; + EventT* make_event() const override // Make a new event using default event_data + { return new EventT( event_data ); } + }; } \ No newline at end of file diff --git a/src/fw/gui/ActionManager.cpp b/src/fw/gui/ActionManager.cpp new file mode 100644 index 000000000..ff9ba4693 --- /dev/null +++ b/src/fw/gui/ActionManager.cpp @@ -0,0 +1,73 @@ +#include "ActionManager.h" +#include "core/assertions.h" +#include "core/async.h" +#include "core/log.h" +#include "core/reflection/type.h" +#include +#include +#include + +using namespace fw; + +ActionManager* ActionManager::s_instance = nullptr; + +ActionManager::ActionManager() +{ + LOG_VERBOSE("fw::ActionManager", "Constructor ...\n"); + FW_EXPECT(!s_instance, "cannot have two instances at a time"); + s_instance = this; + LOG_VERBOSE("fw::ActionManager", "Constructor " OK "\n"); +} + +ActionManager::~ActionManager() +{ + LOG_VERBOSE("fw::ActionManager", "Destructor ...\n"); + s_instance = nullptr; + for( auto action : m_actions ) + { + delete action; + } + LOG_VERBOSE("fw::ActionManager", "Destructor " OK "\n"); +} + +ActionManager& ActionManager::get_instance() +{ + FW_EXPECT(s_instance, "No instance found."); + return *s_instance; +} + +const IAction* ActionManager::get_action_with_id(EventID id) +{ + auto found = m_actions_by_id.find(id); + if ( found == m_actions_by_id.end() ) + { + string128 str; + str.append_fmt("Unable to find an action bound to EventId %i\n", id); + FW_EXPECT(false, str.c_str() ); + } + return found->second; +} + +const std::vector& ActionManager::get_actions() const +{ + return m_actions; +} + +void ActionManager::add_action( IAction* _action )// Add a new action (can be triggered via shortcut) +{ + m_actions.push_back( _action ); + m_actions_by_id.insert({_action->event_id, _action}); + LOG_MESSAGE("ActionManager", "Action '%s' bound to the event_id %i\n", _action->label.c_str(), _action->event_id); +} + +std::string Shortcut::to_string() const +{ + std::string result; + + if (mod & KMOD_CTRL) result += "Ctrl + "; + if (mod & KMOD_ALT) result += "Alt + "; + if (key) result += SDL_GetKeyName(key); + if (!description.empty()) result += description; + + return result; +} diff --git a/src/fw/gui/ActionManager.h b/src/fw/gui/ActionManager.h new file mode 100644 index 000000000..cce28a03c --- /dev/null +++ b/src/fw/gui/ActionManager.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Action.h" +#include "Event.h" +#include "core/reflection/func_type.h" +#include "core/types.h" + +namespace fw +{ + class ActionManager + { + public: + ActionManager(); + ActionManager(const ActionManager&) = delete; + ~ActionManager(); + + const std::vector& get_actions() const; // Get all the actions bound to any event + + template + Action* new_action(Args&&... args) + { + static_assert(std::is_base_of_v ); + auto* action = new Action(std::forward(args)...); + add_action(action); + return action; + } + + const IAction* get_action_with_id(EventID id); // Get the action bound to a given event type + static ActionManager& get_instance(); + + template + static ActionT* get_action() // Helper to get a given action type from the ActionManager instance + { return s_instance->get_action(); } + + private: + + template + ActionT* _get_action() const + { + static_assert( std::is_base_of_v ); + auto* action = get_action_with_id( ActionT::event_id ); + return dynamic_cast( action ); + } + + void add_action( IAction* _action); + static ActionManager* s_instance; + std::vector m_actions; + std::unordered_multimap m_actions_by_id; + }; +} \ No newline at end of file diff --git a/src/fw/gui/App.cpp b/src/fw/gui/App.cpp index dc62a3fc0..de3626507 100644 --- a/src/fw/gui/App.cpp +++ b/src/fw/gui/App.cpp @@ -18,6 +18,7 @@ App::App(Config& _config, AppView* _view) , should_stop(false) , font_manager(_config.font_manager) , event_manager() + , action_manager() , texture_manager() , m_sdl_window(nullptr) , m_sdl_gl_context() @@ -240,7 +241,7 @@ void App::handle_events() // With mode key only if( event.key.keysym.mod & (KMOD_CTRL | KMOD_ALT) ) { - for(const auto& _action: event_manager.get_actions() ) + for(const IAction* _action: action_manager.get_actions() ) { // first, priority to shortcuts with mod if ( _action->shortcut.mod != KMOD_NONE @@ -255,7 +256,7 @@ void App::handle_events() } else // without any mod key { - for(const auto& _action: event_manager.get_actions() ) + for(const IAction* _action: action_manager.get_actions() ) { // first, priority to shortcuts with mod if ( _action->shortcut.mod == KMOD_NONE diff --git a/src/fw/gui/App.h b/src/fw/gui/App.h index 82c5b13d6..c3158eb12 100644 --- a/src/fw/gui/App.h +++ b/src/fw/gui/App.h @@ -11,6 +11,8 @@ #include "FontManager.h" #include "AppView.h" #include "TextureManager.h" +#include "EventManager.h" +#include "ActionManager.h" namespace fw { @@ -28,6 +30,7 @@ namespace fw TextureManager texture_manager; // Manages Texture resources FontManager font_manager; // Manages Font resources EventManager event_manager; // Manages Events and BindedEvents (shortcuts/button triggered) + ActionManager action_manager; // Manages Events and BindedEvents (shortcuts/button triggered) bool should_stop; // Set this field true to tell the application to stop its main loop the next frame Config& config; // Application configuration (names, colors, fonts) diff --git a/src/fw/gui/CMakeLists.txt b/src/fw/gui/CMakeLists.txt index e09b297f2..0e0d649c7 100644 --- a/src/fw/gui/CMakeLists.txt +++ b/src/fw/gui/CMakeLists.txt @@ -6,6 +6,8 @@ ndbl_log_title_header() # add imgui executable add_library( framework-gui + Action.cpp + ActionManager.cpp App.cpp AppView.cpp EventManager.cpp diff --git a/src/fw/gui/Event.h b/src/fw/gui/Event.h index d4f525d9d..94d0da27b 100644 --- a/src/fw/gui/Event.h +++ b/src/fw/gui/Event.h @@ -1,5 +1,6 @@ #pragma once #include "core/types.h" +#include namespace fw { @@ -12,15 +13,15 @@ namespace fw { // Declare common event types - EventID_NONE = 0, + EventID_NULL = 0, - EventID_REQUEST_FILE_SAVE, - EventID_REQUEST_FILE_SAVE_AS, - EventID_REQUEST_FILE_NEW, - EventID_REQUEST_FILE_CLOSE, - EventID_REQUEST_FILE_BROWSE, - EventID_REQUEST_UNDO, - EventID_REQUEST_REDO, + EventID_FILE_SAVE, + EventID_FILE_SAVE_AS, + EventID_FILE_NEW, + EventID_FILE_CLOSE, + EventID_FILE_BROWSE, + EventID_UNDO, + EventID_REDO, EventID_REQUEST_EXIT, EventID_REQUEST_SHOW_WINDOW, @@ -38,38 +39,42 @@ namespace fw virtual ~IEvent() = default; }; - template - class Event : public fw::IEvent - { - public: - using data_t = struct {}; - constexpr static EventID id = static_cast(event_id); - - Event() - : IEvent(event_id) - {} - }; + struct null_data_t {}; /** Template to extend IEvent with a specific payload */ - template - class CustomEvent : public fw::IEvent + template + class Event : public fw::IEvent { public: - constexpr static EventID id = event_id; - using data_t = DataT; + constexpr static EventID id = id_value; + using data_t = DataT; // type required to construct this Event DataT data; - CustomEvent( DataT _data = {}) - : IEvent(event_id) + explicit Event(DataT _data = {}) + : IEvent(id_value) , data( _data ) {} }; + // Below, few basic events (not requiring any payload) + + using Event_NULL = Event; + using Event_FileSave = Event; + using Event_FileSaveAs = Event; + using Event_FileClose = Event; + using Event_FileBrowse = Event; + using Event_FileNew = Event; + using Event_Exit = Event; + using Event_Undo = Event; + using Event_Redo = Event; + + // Here, an event requiring the following payload + struct EventPayload_ShowWindow { - std::string window_id; // String identifying a given window (user defined) - bool visible; // Desired state + std::string window_id; // String identifying a given window (user defined) + bool visible = true; // Window visibility (desired state) }; - using Event_ShowWindow = CustomEvent; + using Event_ShowWindow = Event; } \ No newline at end of file diff --git a/src/fw/gui/EventManager.cpp b/src/fw/gui/EventManager.cpp index 282ad7999..3ef7d4e14 100644 --- a/src/fw/gui/EventManager.cpp +++ b/src/fw/gui/EventManager.cpp @@ -15,10 +15,6 @@ EventManager::~EventManager() { LOG_VERBOSE("fw::EventManager", "Destructor ...\n"); s_instance = nullptr; - for( auto action : m_actions ) - { - delete action; - } LOG_VERBOSE("fw::EventManager", "Destructor " OK "\n"); } EventManager::EventManager() @@ -59,23 +55,6 @@ IEvent* EventManager::dispatch( EventID _event_id ) return new_event; } -const IAction* EventManager::get_action_by_event_id(EventID id) -{ - auto found = m_actions_by_event_type.find(id); - if ( found == m_actions_by_event_type.end() ) - { - string128 str; - str.append_fmt("Unable to find an action bound to EventId %i\n", id); - FW_EXPECT(false, str.c_str() ); - } - return found->second; -} - -const std::vector& EventManager::get_actions() const -{ - return m_actions; -} - void EventManager::dispatch_delayed(u64_t delay, IEvent* event) { fw::async::get_instance().add_task( @@ -84,24 +63,4 @@ void EventManager::dispatch_delayed(u64_t delay, IEvent* event) dispatch(event); }) ); -} - -void EventManager::add_action( IAction* _action )// Add a new action (can be triggered via shortcut) -{ - m_actions.push_back( _action ); - m_actions_by_event_type.emplace( _action->event_id, _action ); - LOG_MESSAGE("EventManager", "Action '%s' bound to the event_id %i\n", _action->label.c_str(), _action->event_id); -} - -std::string Shortcut::to_string() const -{ - std::string result; - - if (mod & KMOD_CTRL) result += "Ctrl + "; - if (mod & KMOD_ALT) result += "Alt + "; - if (key) result += SDL_GetKeyName(key); - if (!description.empty()) result += description; - - return result; -} - +} \ No newline at end of file diff --git a/src/fw/gui/EventManager.h b/src/fw/gui/EventManager.h index 9c420edb8..7f7a5c4a4 100644 --- a/src/fw/gui/EventManager.h +++ b/src/fw/gui/EventManager.h @@ -24,7 +24,6 @@ namespace fw void dispatch(IEvent* _event); // Push an existing event to the queue. void dispatch_delayed(u64_t, IEvent* ); // Does the same as dispatch(Event*) with a delay in millisecond. A delay of 0ms will be processed after a regular dispatch though. IEvent* poll_event(); // Pop the first event in the queue - const std::vector& get_actions() const; // Get all the actions bound to any event template void dispatch(Args... args) @@ -42,23 +41,6 @@ namespace fw dispatch_delayed(delay_in_ms, event); } - template - void bind(Args... args) - { - static_assert(std::is_base_of_v ); - IAction* action = new ActionT(args...); - add_action(action); - } - - void bind( - const char* label, - EventID event_id, - const Shortcut& shortcut = {}, - u64_t custom_data = {} - ){ - auto* action = new IAction(event_id, label, shortcut, custom_data); - add_action(action); - } template IEvent* dispatch(typename EventT::data_t&& state = {} ) { @@ -68,13 +50,9 @@ namespace fw return new_event; } - const IAction* get_action_by_event_id(EventID id); // Get the action bound to a given event type static EventManager& get_instance(); private: - void add_action( IAction* _action); static EventManager* s_instance; std::queue m_events; - std::vector m_actions; - std::map m_actions_by_event_type; }; } \ No newline at end of file diff --git a/src/fw/gui/ImGuiEx.cpp b/src/fw/gui/ImGuiEx.cpp index 360b02197..a28c3a1a5 100644 --- a/src/fw/gui/ImGuiEx.cpp +++ b/src/fw/gui/ImGuiEx.cpp @@ -227,20 +227,6 @@ void ImGuiEx::BeginFrame() s_is_any_tooltip_open = false; } -void ImGuiEx::MenuItem(EventID id, bool selected, bool enable) -{ - // Find the corresponding action - const IAction* action = EventManager::get_instance().get_action_by_event_id( id ); - - // Draw a simple menu item - if (ImGui::MenuItem( action->label.c_str(), action->shortcut.to_string().c_str(), selected, enable)) - { - // Trigger the action - auto event = action->make_event(); - EventManager::get_instance().dispatch( event ); - } -} - void ImGuiEx::BulletTextWrapped(const char* str) { ImGui::Bullet(); ImGui::SameLine(); @@ -275,4 +261,4 @@ void ImGuiEx::Image(Texture* _texture) (float)_texture->height }; ImGui::Image((void *)(intptr_t)_texture->gl_handler, size); -} \ No newline at end of file +} diff --git a/src/fw/gui/ImGuiEx.h b/src/fw/gui/ImGuiEx.h index 105a2bbd5..2d29f8f9e 100644 --- a/src/fw/gui/ImGuiEx.h +++ b/src/fw/gui/ImGuiEx.h @@ -6,9 +6,10 @@ #define IMGUI_DEFINE_MATH_OPERATORS #endif -#include +#include "ActionManager.h" #include "EventManager.h" #include "core/types.h" +#include #include namespace fw @@ -106,7 +107,17 @@ namespace fw static void EndTooltip(); static ImRect& EnlargeToInclude(ImRect& _rect, ImRect _other); - static void MenuItem(EventID id, bool selected = false, bool enable = true); + template + static void MenuItem(bool selected = false, bool enable = true) // Shorthand to get a given action from the manager and draw a MenuItem from it. + { + const IAction* action = ActionManager::get_instance().get_action_with_id(EventT::id); + + if (ImGui::MenuItem( action->label.c_str(), action->shortcut.to_string().c_str(), selected, enable)) + { + action->trigger(); + } + }; + static void BulletTextWrapped(const char*); static ImRect GetContentRegion(Space); diff --git a/src/nodable/gui/Action.h b/src/nodable/gui/Action.h index 06f1088f0..08e4bff20 100644 --- a/src/nodable/gui/Action.h +++ b/src/nodable/gui/Action.h @@ -7,18 +7,23 @@ namespace ndbl { using fw::Action; - using fw::CustomAction; + using fw::Event; // Actions specific to Nodable, more actions defined in framework's Action.h - using Action_DeleteNode = Action; - using Action_ArrangeNode = Action; - using Action_ToggleFolding = Action; - using Action_SelectNext = Action; - using Action_ToggleIsolate = Action; - using Action_SelectionChange = Action; - using Action_MoveGraph = Action; - using Action_FrameSelection = CustomAction; - using Action_CreateBlock = CustomAction; - using Action_CreateNode = CustomAction; + // 1) Basic actions (simple events) + + using Action_DeleteNode = Action; + using Action_ArrangeNode = Action; + using Action_ToggleFolding = Action; + using Action_SelectNext = Action; + using Action_ToggleIsolate = Action; + using Action_SelectionChange = Action; + using Action_MoveGraph = Action; + + // 2) Advanced actions (custom events) + + using Action_FrameSelection = Action; + using Action_CreateBlock = Action; + using Action_CreateNode = Action; } \ No newline at end of file diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index 8e8425f31..ff5b32ec0 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -1,6 +1,7 @@ #pragma once #include +#include "Event.h" #include "FrameMode.h" #include "SlotView.h" #include "core/Graph.h" @@ -16,20 +17,23 @@ namespace ndbl enum EventID_ : fw::EventID { - EventID_REQUEST_DELETE_NODE = fw::EventID_USER_DEFINED, // operation on nodes - EventID_REQUEST_ARRANGE_HIERARCHY, - EventID_REQUEST_SELECT_SUCCESSOR, - EventID_REQUEST_TOGGLE_FOLDING, + EventID_DELETE_NODE = fw::EventID_USER_DEFINED, // operation on nodes + EventID_ARRANGE_NODE, + EventID_SELECT_NEXT, + EventID_TOGGLE_FOLDING, EventID_REQUEST_CREATE_NODE, EventID_REQUEST_CREATE_BLOCK, EventID_REQUEST_FRAME_SELECTION, - EventID_REQUEST_MOVE_SELECTION, - EventID_REQUEST_TOGGLE_ISOLATE, + EventID_MOVE_SELECTION, + EventID_TOGGLE_ISOLATE, EventID_SLOT_DROPPED, EventID_SLOT_DISCONNECTED, - EventID_NODE_SELECTION_CHANGE, + EventID_SELECTION_CHANGE, }; + using Event_ToggleIsolate = fw::Event; + using Event_MoveSelection = fw::Event; + class GraphView; struct EventPayload_FrameNodeViews { @@ -38,7 +42,7 @@ namespace ndbl : mode(mode) {} }; - using Event_FrameSelection = fw::CustomEvent; + using Event_FrameSelection = fw::Event; struct EventPayload_SlotPair { SlotRef first; @@ -48,16 +52,16 @@ namespace ndbl , second(second) {} }; - using Event_SlotDisconnected = fw::CustomEvent; - using Event_SlotDropped = fw::CustomEvent; + using Event_SlotDisconnected = fw::Event; + using Event_SlotDropped = fw::Event; struct EventPayload_Node { PoolID node; }; - using Event_DeleteNode = fw::CustomEvent; - using Event_ArrangeNode = fw::CustomEvent; - using Event_SelectNext = fw::CustomEvent; + using Event_DeleteNode = fw::Event; + using Event_ArrangeNode = fw::Event; + using Event_SelectNext = fw::Event; enum ToggleFoldingMode { @@ -68,16 +72,14 @@ namespace ndbl { ToggleFoldingMode mode; }; - using Event_ToggleFolding = fw::CustomEvent; + using Event_ToggleFolding = fw::Event; struct EventPayload_NodeViewSelectionChange { PoolID new_selection; PoolID old_selection; }; - using NodeViewSelectionChangeEvent = fw::CustomEvent; - - using ToggleIsolateSelectionEvent = fw::Event; + using Event_SelectionChange = fw::Event; struct EventPayload_CreateNode { @@ -97,7 +99,7 @@ namespace ndbl , node_signature(signature) {} }; - using Event_CreateNode = fw::CustomEvent; - using Event_CreateBlock = fw::CustomEvent; + using Event_CreateNode = fw::Event; + using Event_CreateBlock = fw::Event; }// namespace ndbl diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 9623c45aa..f2b6fc083 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -607,16 +607,16 @@ void CreateNodeContextMenu::update_cache_based_on_signature() { const type* dragged_property_type = dragged_slot->get_property_type(); - if ( action->event_initial_state.node_signature ) + if ( action->event_data.node_signature ) { if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) { - if ( !action->event_initial_state.node_signature->has_an_arg_of_type(dragged_property_type) ) + if ( !action->event_data.node_signature->has_an_arg_of_type(dragged_property_type) ) { continue; } } - else if ( !action->event_initial_state.node_signature->get_return_type()->equals(dragged_property_type) ) + else if ( !action->event_data.node_signature->get_return_type()->equals(dragged_property_type) ) { continue; } @@ -629,6 +629,7 @@ void CreateNodeContextMenu::update_cache_based_on_signature() } } } + void CreateNodeContextMenu::update_cache_based_on_user_input( size_t _limit ) { items_matching_search.clear(); diff --git a/src/nodable/gui/HybridFile.cpp b/src/nodable/gui/HybridFile.cpp index 4b80a6d49..260fbd145 100644 --- a/src/nodable/gui/HybridFile.cpp +++ b/src/nodable/gui/HybridFile.cpp @@ -39,7 +39,7 @@ HybridFile::HybridFile(std::string _name) m_graph = new Graph(&Nodable::get_instance().node_factory); m_graph_view = new GraphView(m_graph); - for( IAction* action : EventManager::get_instance().get_actions() ) // Fill the "create node" context menu + for( IAction* action : ActionManager::get_instance().get_actions() ) // Fill the "create node" context menu { if ( auto create_node_action = dynamic_cast(action)) { diff --git a/src/nodable/gui/HybridFileView.cpp b/src/nodable/gui/HybridFileView.cpp index 79118afae..c928b8e9e 100644 --- a/src/nodable/gui/HybridFileView.cpp +++ b/src/nodable/gui/HybridFileView.cpp @@ -353,3 +353,18 @@ size_t HybridFileView::size() const { return m_text_editor.Size(); } + +void HybridFileView::refresh_overlay(Condition _condition ) +{ + for (const IAction* _action: ActionManager::get_instance().get_actions()) + { + if( ( _action->userdata & _condition) == _condition && (_action->userdata & Condition_HIGHLIGHTED) ) + { + std::string label = _action->label.substr(0, 12); + std::string shortcut_str = _action->shortcut.to_string(); + OverlayType_ overlay_type = _action->userdata & Condition_HIGHLIGHTED_IN_TEXT_EDITOR ? OverlayType_TEXT + : OverlayType_GRAPH; + push_overlay({label, shortcut_str}, overlay_type); + } + } +} diff --git a/src/nodable/gui/HybridFileView.h b/src/nodable/gui/HybridFileView.h index 4f28581cf..01b8e5299 100644 --- a/src/nodable/gui/HybridFileView.h +++ b/src/nodable/gui/HybridFileView.h @@ -6,6 +6,7 @@ #include "fw/core/reflection/reflection" #include "fw/gui/View.h" +#include "Condition.h" #include "types.h" namespace ndbl @@ -22,7 +23,8 @@ namespace ndbl }; using OverlayType = int; - enum OverlayType_ { + enum OverlayType_ + { OverlayType_TEXT, OverlayType_GRAPH, OverlayType_COUNT @@ -58,8 +60,9 @@ namespace ndbl void draw_info_panel()const; void experimental_clipboard_auto_paste(bool); bool experimental_clipboard_auto_paste()const { return m_experimental_clipboard_auto_paste; } - void push_overlay(OverlayData, OverlayType) ; void clear_overlay(); + void push_overlay(OverlayData, OverlayType) ; + void refresh_overlay(Condition condition); void draw_overlay(const char* title, const std::vector& overlay_data, ImRect rect, ImVec2 position); size_t size() const; diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 4ea07cfa5..0b29007d2 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -110,44 +110,44 @@ bool Nodable::on_init() fw::Pool::init(); // Bind commands to shortcuts - event_manager.bind( "Delete", Shortcut{ SDLK_DELETE, KMOD_NONE } ); - event_manager.bind( "Arrange", Shortcut{ SDLK_a, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind( "Fold", Shortcut{ SDLK_x, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind( "Next", Shortcut{ SDLK_n, KMOD_NONE } ); - event_manager.bind( ICON_FA_SAVE " Save", Shortcut{ SDLK_s, KMOD_CTRL } ); - event_manager.bind( ICON_FA_SAVE " Save as", Shortcut{ SDLK_s, KMOD_CTRL } ); - event_manager.bind( ICON_FA_TIMES " Close", Shortcut{ SDLK_w, KMOD_CTRL } ); - event_manager.bind( ICON_FA_FOLDER_OPEN " Open", Shortcut{ SDLK_o, KMOD_CTRL } ); - event_manager.bind( ICON_FA_FILE " New", Shortcut{ SDLK_n, KMOD_CTRL } ); - event_manager.bind( "Splashscreen", Shortcut{ SDLK_F1 }, EventPayload_ShowWindow{ "splashscreen" } ); - event_manager.bind( ICON_FA_SIGN_OUT_ALT " Exit", Shortcut{ SDLK_F4, KMOD_ALT } ); - event_manager.bind( "Undo", Shortcut{ SDLK_z, KMOD_CTRL } ); - event_manager.bind( "Redo", Shortcut{ SDLK_y, KMOD_CTRL } ); - event_manager.bind( "Isolate", Shortcut{ SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR ); - event_manager.bind("Deselect", Shortcut{ 0, KMOD_NONE, "Double click on bg" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind("Move Graph", Shortcut{ 0, KMOD_NONE, "Drag background" }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind("Frame Selection", Shortcut{ SDLK_f, KMOD_NONE }, EventPayload_FrameNodeViews{ FRAME_SELECTION_ONLY }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); - event_manager.bind("Frame All", Shortcut{ SDLK_f, KMOD_LCTRL }, EventPayload_FrameNodeViews{ FRAME_ALL } ); + action_manager.new_action( "Delete", Shortcut{ SDLK_DELETE, KMOD_NONE } ); + action_manager.new_action( "Arrange", Shortcut{ SDLK_a, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + action_manager.new_action( "Fold", Shortcut{ SDLK_x, KMOD_NONE }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + action_manager.new_action( "Next", Shortcut{ SDLK_n, KMOD_NONE } ); + action_manager.new_action( ICON_FA_SAVE " Save", Shortcut{ SDLK_s, KMOD_CTRL } ); + action_manager.new_action( ICON_FA_SAVE " Save as", Shortcut{ SDLK_s, KMOD_CTRL } ); + action_manager.new_action( ICON_FA_TIMES " Close", Shortcut{ SDLK_w, KMOD_CTRL } ); + action_manager.new_action( ICON_FA_FOLDER_OPEN " Open", Shortcut{ SDLK_o, KMOD_CTRL } ); + action_manager.new_action( ICON_FA_FILE " New", Shortcut{ SDLK_n, KMOD_CTRL } ); + action_manager.new_action( "Splashscreen", Shortcut{ SDLK_F1 }, EventPayload_ShowWindow{ "splashscreen" } ); + action_manager.new_action( ICON_FA_SIGN_OUT_ALT " Exit", Shortcut{ SDLK_F4, KMOD_ALT } ); + action_manager.new_action( "Undo", Shortcut{ SDLK_z, KMOD_CTRL } ); + action_manager.new_action( "Redo", Shortcut{ SDLK_y, KMOD_CTRL } ); + action_manager.new_action( "Isolate", Shortcut{ SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR ); + action_manager.new_action( "Deselect", Shortcut{ 0, KMOD_NONE, "Double click on bg" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + action_manager.new_action( "Move Graph", Shortcut{ 0, KMOD_NONE, "Drag background" }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + action_manager.new_action( "Frame Selection", Shortcut{ SDLK_f, KMOD_NONE }, EventPayload_FrameNodeViews{ FRAME_SELECTION_ONLY }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); + action_manager.new_action( "Frame All", Shortcut{ SDLK_f, KMOD_LCTRL }, EventPayload_FrameNodeViews{ FRAME_ALL } ); // Prepare context menu items // 1) Blocks - event_manager.bind( ICON_FA_CODE " Condition", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_CONDITION} ); - event_manager.bind( ICON_FA_CODE " For Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_FOR_LOOP} ); - event_manager.bind( ICON_FA_CODE " While Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_WHILE_LOOP} ); - event_manager.bind( ICON_FA_CODE " Scope", Shortcut{}, EventPayload_CreateNode{NodeType_BLOCK_SCOPE} ); - event_manager.bind( ICON_FA_CODE " Program", Shortcut{}, EventPayload_CreateNode{NodeType_BLOCK_PROGRAM} ); + action_manager.new_action( ICON_FA_CODE " Condition", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_CONDITION } ); + action_manager.new_action( ICON_FA_CODE " For Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_FOR_LOOP } ); + action_manager.new_action( ICON_FA_CODE " While Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_WHILE_LOOP } ); + action_manager.new_action( ICON_FA_CODE " Scope", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_SCOPE } ); + action_manager.new_action( ICON_FA_CODE " Program", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_PROGRAM } ); // 2) Variables - event_manager.bind( ICON_FA_DATABASE " Boolean Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_BOOLEAN, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_DATABASE " Double Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_DOUBLE, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_DATABASE " Integer Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_INTEGER, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_DATABASE " String Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_STRING, create_variable_node_signature() } ); + action_manager.new_action( ICON_FA_DATABASE " Boolean Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_BOOLEAN, create_variable_node_signature() } ); + action_manager.new_action( ICON_FA_DATABASE " Double Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_DOUBLE, create_variable_node_signature() } ); + action_manager.new_action( ICON_FA_DATABASE " Integer Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_INTEGER, create_variable_node_signature() } ); + action_manager.new_action( ICON_FA_DATABASE " String Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_STRING, create_variable_node_signature() } ); // 3) Literals - event_manager.bind( ICON_FA_FILE " Boolean Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_BOOLEAN, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_FILE " Double Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_DOUBLE, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_FILE " Integer Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_INTEGER, create_variable_node_signature() } ); - event_manager.bind( ICON_FA_FILE " String Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_STRING, create_variable_node_signature() } ); + action_manager.new_action( ICON_FA_FILE " Boolean Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_BOOLEAN, create_variable_node_signature() } ); + action_manager.new_action( ICON_FA_FILE " Double Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_DOUBLE, create_variable_node_signature() } ); + action_manager.new_action( ICON_FA_FILE " Integer Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_INTEGER, create_variable_node_signature() } ); + action_manager.new_action( ICON_FA_FILE " String Literal", Shortcut{}, EventPayload_CreateNode{ NodeType_LITERAL_STRING, create_variable_node_signature() } ); // 4) Functions/Operators from the API const Nodlang& language = Nodlang::get_instance(); @@ -156,7 +156,7 @@ bool Nodable::on_init() const fw::func_type* func_type = each_fct->get_type(); std::string label; language.serialize_func_sig( label, func_type ); - event_manager.bind( label.c_str(), Shortcut{}, EventPayload_CreateNode{ NodeType_INVOKABLE, func_type } ); + action_manager.new_action( label.c_str(), Shortcut{}, EventPayload_CreateNode{ NodeType_INVOKABLE, func_type } ); } return true; } @@ -182,21 +182,6 @@ void Nodable::on_update() // 2. Handle events - // shorthand to push all shortcuts to a file view overlay depending on conditions - auto push_overlay_shortcuts = [&](ndbl::HybridFileView& _view, Condition _condition) -> void { - for (const auto _action: event_manager.get_actions()) - { - if( ( _action->userdata & _condition) == _condition && (_action->userdata & Condition_HIGHLIGHTED_IN_GRAPH_EDITOR|Condition_HIGHLIGHTED_IN_TEXT_EDITOR) ) - { - std::string label = _action->label.substr(0, 12); - std::string shortcut_str = _action->shortcut.to_string(); - OverlayType_ overlay_type = _action->userdata & Condition_HIGHLIGHTED_IN_TEXT_EDITOR ? OverlayType_GRAPH - : OverlayType_TEXT; - _view.push_overlay({label, shortcut_str}, overlay_type); - } - } - }; - // Nodable events auto selected_view = NodeView::get_selected(); GraphView* graph_view = current_file ? current_file->get_graph_view() : nullptr; @@ -207,7 +192,7 @@ void Nodable::on_update() { switch ( event->id ) { - case EventID_REQUEST_TOGGLE_ISOLATE: + case EventID_TOGGLE_ISOLATE: { config.isolate_selection = !config.isolate_selection; if(current_file) @@ -223,24 +208,24 @@ void Nodable::on_update() break; } - case fw::EventID_REQUEST_FILE_CLOSE: + case fw::EventID_FILE_CLOSE: { if(current_file) close_file(current_file); break; } - case fw::EventID_REQUEST_UNDO: + case fw::EventID_UNDO: { if(curr_file_history) curr_file_history->undo(); break; } - case fw::EventID_REQUEST_REDO: + case fw::EventID_REDO: { if(curr_file_history) curr_file_history->redo(); break; } - case fw::EventID_REQUEST_FILE_BROWSE: + case fw::EventID_FILE_BROWSE: { std::string path; if( m_view->pick_file_path(path, fw::AppView::DIALOG_Browse)) @@ -253,13 +238,13 @@ void Nodable::on_update() } - case fw::EventID_REQUEST_FILE_NEW: + case fw::EventID_FILE_NEW: { new_file(); break; } - case fw::EventID_REQUEST_FILE_SAVE_AS: + case fw::EventID_FILE_SAVE_AS: { if (current_file) { @@ -273,7 +258,7 @@ void Nodable::on_update() break; } - case fw::EventID_REQUEST_FILE_SAVE: + case fw::EventID_FILE_SAVE: { if (current_file) { @@ -311,20 +296,20 @@ void Nodable::on_update() break; } - case NodeViewSelectionChangeEvent::id: + case Event_SelectionChange::id: { - auto _event = reinterpret_cast( event ); - current_file->view.clear_overlay(); + auto _event = reinterpret_cast( event ); + Condition_ condition = _event->data.new_selection ? Condition_ENABLE_IF_HAS_SELECTION : Condition_ENABLE_IF_HAS_NO_SELECTION; - push_overlay_shortcuts(current_file->view, condition ); + current_file->view.clear_overlay(); + current_file->view.refresh_overlay( condition ); break; } case EventID_FILE_OPENED: { - if (!current_file) break; current_file->view.clear_overlay(); - push_overlay_shortcuts(current_file->view, Condition_ENABLE_IF_HAS_NO_SELECTION); + current_file->view.refresh_overlay( Condition_ENABLE_IF_HAS_NO_SELECTION ); break; } case Event_DeleteNode::id: diff --git a/src/nodable/gui/NodableView.cpp b/src/nodable/gui/NodableView.cpp index 5b6b984e1..7c7517c6b 100644 --- a/src/nodable/gui/NodableView.cpp +++ b/src/nodable/gui/NodableView.cpp @@ -8,6 +8,7 @@ #include "core/NodeUtils.h" #include "Config.h" #include "Event.h" +#include "Action.h" #include "HybridFile.h" #include "HybridFileView.h" #include "History.h" @@ -19,6 +20,7 @@ using namespace ndbl; using namespace ndbl::assembly; +using namespace fw; NodableView::NodableView(Nodable * _app) : fw::AppView(_app) @@ -65,13 +67,13 @@ void NodableView::on_draw() if (ImGui::BeginMenu("File")) { bool has_file = current_file; bool changed = current_file != nullptr && current_file->changed; - fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_NEW ); - fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_BROWSE ); + ImGuiEx::MenuItem(); + ImGuiEx::MenuItem(); ImGui::Separator(); - fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_SAVE_AS, false, has_file ); - fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_SAVE, false, has_file && changed ); + ImGuiEx::MenuItem(false, has_file); + ImGuiEx::MenuItem(false, has_file && changed); ImGui::Separator(); - fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_FILE_CLOSE, false, has_file ); + ImGuiEx::MenuItem(false, has_file); auto auto_paste = has_file && current_file->view.experimental_clipboard_auto_paste(); @@ -79,27 +81,29 @@ void NodableView::on_draw() current_file->view.experimental_clipboard_auto_paste(!auto_paste); } - fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_EXIT ); + fw::ImGuiEx::MenuItem(); ImGui::EndMenu(); } bool vm_is_stopped = virtual_machine.is_program_stopped(); - if (ImGui::BeginMenu("Edit")) { - if (current_file_history) { - fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_UNDO ); - fw::ImGuiEx::MenuItem( fw::EventID_REQUEST_REDO ); + if (ImGui::BeginMenu("Edit")) + { + if (current_file_history) + { + ImGuiEx::MenuItem(); + ImGuiEx::MenuItem(); ImGui::Separator(); } auto has_selection = NodeView::is_any_selected(); if (ImGui::MenuItem("Delete", "Del.", false, has_selection && vm_is_stopped)) { - event_manager.dispatch( EventID_REQUEST_DELETE_NODE ); + event_manager.dispatch( EventID_DELETE_NODE ); } - fw::ImGuiEx::MenuItem( EventID_REQUEST_ARRANGE_HIERARCHY, false, has_selection ); - fw::ImGuiEx::MenuItem( EventID_REQUEST_TOGGLE_FOLDING, false,has_selection ); + fw::ImGuiEx::MenuItem( false, has_selection ); + fw::ImGuiEx::MenuItem( false,has_selection ); if (ImGui::MenuItem("Expand/Collapse recursive", nullptr, false, has_selection)) { @@ -143,7 +147,7 @@ void NodableView::on_draw() ImGui::Separator(); - fw::ImGuiEx::MenuItem( EventID_REQUEST_TOGGLE_ISOLATE, config.isolate_selection ); + fw::ImGuiEx::MenuItem(config.isolate_selection ); ImGui::EndMenu(); } @@ -499,10 +503,10 @@ void NodableView::draw_startup_window(ImGuiID dockspace_id) { ImVec2 btn_size(center_area.x * 0.44f, 40.0f); if (ImGui::Button(ICON_FA_FILE" New File", btn_size)) - event_manager.dispatch( fw::EventID_REQUEST_FILE_NEW ); + event_manager.dispatch( fw::EventID_FILE_NEW ); ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN" Open ...", btn_size)) - event_manager.dispatch( fw::EventID_REQUEST_FILE_BROWSE ); + event_manager.dispatch( fw::EventID_FILE_BROWSE ); ImGui::NewLine(); ImGui::Separator(); @@ -837,7 +841,7 @@ void NodableView::draw_toolbar_window() { if (ImGui::Button( config.isolate_selection ? ICON_FA_CROP " isolation mode: ON " : ICON_FA_CROP " isolation mode: OFF", button_size)) { - m_app->event_manager.dispatch( EventID_REQUEST_TOGGLE_ISOLATE ); + m_app->event_manager.dispatch( EventID_TOGGLE_ISOLATE ); } ImGui::SameLine(); ImGui::EndGroup(); diff --git a/src/nodable/gui/NodeView.cpp b/src/nodable/gui/NodeView.cpp index 915364c2e..ad0d64e9e 100644 --- a/src/nodable/gui/NodeView.cpp +++ b/src/nodable/gui/NodeView.cpp @@ -165,19 +165,10 @@ void NodeView::set_selected(PoolID new_selection) if( s_selected == new_selection ) return; - // Handle de-selection - if( s_selected ) - { - event_manager.dispatch( { s_selected } ); - s_selected.reset(); - } + EventPayload_NodeViewSelectionChange event{ new_selection, s_selected }; + event_manager.dispatch(event); - // Handle selection - if( new_selection ) - { - event_manager.dispatch( { new_selection }); - s_selected = new_selection; - } + s_selected = new_selection; } PoolID NodeView::get_selected() From be977c708be379814390f54d590080e16a5b30a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Mon, 4 Mar 2024 09:34:48 -0500 Subject: [PATCH 15/27] feat(GraphView): ActionManagerView --- src/fw/gui-example/AppExampleView.h | 14 +++--- src/fw/gui/Action.cpp | 4 +- src/fw/gui/ActionManagerView.cpp | 33 ++++++++++++++ src/fw/gui/ActionManagerView.h | 12 +++++ src/fw/gui/App.cpp | 6 +-- src/fw/gui/AppView.cpp | 25 +++++++---- src/fw/gui/AppView.h | 9 ++-- src/fw/gui/CMakeLists.txt | 1 + src/fw/gui/Config.h | 5 +++ src/nodable/gui/NodableView.cpp | 68 +++++++++++++++++------------ src/nodable/gui/NodableView.h | 2 +- 11 files changed, 128 insertions(+), 51 deletions(-) create mode 100644 src/fw/gui/ActionManagerView.cpp create mode 100644 src/fw/gui/ActionManagerView.h diff --git a/src/fw/gui-example/AppExampleView.h b/src/fw/gui-example/AppExampleView.h index 1f81bdb62..918c26b27 100644 --- a/src/fw/gui-example/AppExampleView.h +++ b/src/fw/gui-example/AppExampleView.h @@ -18,12 +18,16 @@ namespace fw dock_window( "top", fw::AppView::Dockspace_TOP ); } - void on_draw_splashscreen() override + void draw_splashscreen() override { - ImGui::TextWrapped( "Welcome to the framework-gui-example app.\nThis demonstrates how to use the framework-gui library." ); - ImGui::Separator(); - ImGui::TextWrapped( "\nFor your information, this is the splashscreen window of the app.\n" - "You can inject your custom code by editing Example::on_draw_splashscreen()\n" ); + if ( AppView::begin_splashscreen() ) + { + ImGui::TextWrapped( "Welcome to the framework-gui-example app.\nThis demonstrates how to use the framework-gui library." ); + ImGui::Separator(); + ImGui::TextWrapped( "\nFor your information, this is the splashscreen window of the app.\n" + "You can inject your custom code by editing Example::draw_splashscreen()\n" ); + AppView::end_splashscreen(); + } } }; } \ No newline at end of file diff --git a/src/fw/gui/Action.cpp b/src/fw/gui/Action.cpp index e4b21a530..d6734f0fa 100644 --- a/src/fw/gui/Action.cpp +++ b/src/fw/gui/Action.cpp @@ -5,8 +5,8 @@ using namespace fw; void IAction::trigger() const { - IEvent* event = make_event(); - EventManager::get_instance().dispatch( event ); + EventManager& event_manager = EventManager::get_instance(); + event_manager.dispatch( make_event() ); } IEvent* IAction::make_event() const diff --git a/src/fw/gui/ActionManagerView.cpp b/src/fw/gui/ActionManagerView.cpp new file mode 100644 index 000000000..3fb5ec3db --- /dev/null +++ b/src/fw/gui/ActionManagerView.cpp @@ -0,0 +1,33 @@ +#include "ActionManagerView.h" +#include "ActionManager.h" +#include "imgui.h" + +using namespace fw; + +void ActionManagerView::draw(ActionManager* manager) +{ + if ( ImGui::BeginTable("Actions", 2) ) + { + ImGui::TableSetupColumn("Action"); + ImGui::TableSetupColumn("Shortcut"); + ImGui::TableHeadersRow(); + + for( auto& action : manager->get_actions()) + { + ImGui::PushID(action); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if( ImGui::SmallButton("trigger") ) + { + action->trigger(); + } + ImGui::SameLine(); + ImGui::Text("%s", action->label.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s", action->shortcut.to_string().c_str()); // TODO: handle shortcut edition + ImGui::PopID(); + } + ImGui::EndTable(); + } +} + diff --git a/src/fw/gui/ActionManagerView.h b/src/fw/gui/ActionManagerView.h new file mode 100644 index 000000000..d0e0a792b --- /dev/null +++ b/src/fw/gui/ActionManagerView.h @@ -0,0 +1,12 @@ +#pragma once + +namespace fw +{ + // Forward declaration + class ActionManager; + + class ActionManagerView + { + public: static void draw(ActionManager* manager); + }; +} \ No newline at end of file diff --git a/src/fw/gui/App.cpp b/src/fw/gui/App.cpp index de3626507..4bf1c7291 100644 --- a/src/fw/gui/App.cpp +++ b/src/fw/gui/App.cpp @@ -339,9 +339,9 @@ int App::main(int argc, char *argv[]) if( all_time <= 0 ) all_time = 1; dt = u32_t(0.9f*float(dt) + 0.1f*float(all_time)); // Smooth value u32_t fps = 1000 / dt; - char title[255]; - snprintf( title, 255, "%s | %i fps (dt %d ms, frame %d ms)", config.app_window_label.c_str(), fps, dt, frame_time ); - title[254] = '\0'; + char title[256]; + snprintf( title, 256, "%s | %i fps (dt %d ms, frame %d ms)", config.app_window_label.c_str(), fps, dt, frame_time ); + title[255] = '\0'; SDL_SetWindowTitle( m_sdl_window, title ); } } diff --git a/src/fw/gui/AppView.cpp b/src/fw/gui/AppView.cpp index 577c20ea0..ba2a7eea3 100644 --- a/src/fw/gui/AppView.cpp +++ b/src/fw/gui/AppView.cpp @@ -43,7 +43,7 @@ bool AppView::draw() } // Splashscreen - draw_splashscreen_window(); + draw_splashscreen(); // Setup main window @@ -182,7 +182,15 @@ void AppView::dock_window(const char* window_name, Dockspace dockspace)const ImGui::DockBuilderDockWindow(window_name, m_dockspaces[dockspace]); } -void AppView::draw_splashscreen_window() +void AppView::draw_splashscreen() +{ + if ( AppView::begin_splashscreen() ) + { + AppView::end_splashscreen(); + } +} + +bool AppView::begin_splashscreen() { if (m_app->config.splashscreen && !ImGui::IsPopupOpen(m_app->config.splashscreen_window_label)) { @@ -194,14 +202,15 @@ void AppView::draw_splashscreen_window() auto flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize; - if (ImGui::BeginPopupModal(m_app->config.splashscreen_window_label, &m_app->config.splashscreen, flags)) - { - on_draw_splashscreen(); - ImGui::EndPopup(); - } + return ImGui::BeginPopupModal(m_app->config.splashscreen_window_label, &m_app->config.splashscreen, flags); +} + +void AppView::end_splashscreen() +{ + ImGui::EndPopup(); } -void AppView::draw_status_window() const +void AppView::draw_status_window() { if (ImGui::Begin(k_status_window_name)) { diff --git a/src/fw/gui/AppView.h b/src/fw/gui/AppView.h index 8fc0174de..eb12aee11 100644 --- a/src/fw/gui/AppView.h +++ b/src/fw/gui/AppView.h @@ -68,16 +68,19 @@ namespace fw virtual void on_init() {}; virtual void on_draw() {}; virtual void on_reset_layout() {}; - virtual void on_draw_splashscreen() {}; + virtual void draw_splashscreen(); // If needed, use begin/end_splashscreen static methods to override this. Ex: if ( AppView::begin_splashscreen(m_app->config) ) { /* your code here */; AppView::end_splashscreen(); } bool draw(); + void draw_status_window(); ImGuiID get_dockspace(Dockspace)const; bool pick_file_path(std::string& _out_path, DialogType) const; // pick a file and store its path in _out_path void dock_window(const char* window_name, Dockspace)const; // Must be called ON_RESET_LAYOUT void set_layout_initialized(bool b); + + protected: + bool begin_splashscreen(); + void end_splashscreen(); private: - void draw_splashscreen_window(); - void draw_status_window() const; App * m_app; bool m_is_layout_initialized; std::array m_dockspaces{}; diff --git a/src/fw/gui/CMakeLists.txt b/src/fw/gui/CMakeLists.txt index 0e0d649c7..3448f6167 100644 --- a/src/fw/gui/CMakeLists.txt +++ b/src/fw/gui/CMakeLists.txt @@ -8,6 +8,7 @@ add_library( framework-gui Action.cpp ActionManager.cpp + ActionManagerView.cpp App.cpp AppView.cpp EventManager.cpp diff --git a/src/fw/gui/Config.h b/src/fw/gui/Config.h index 2f8f4030a..a57401c18 100644 --- a/src/fw/gui/Config.h +++ b/src/fw/gui/Config.h @@ -112,6 +112,11 @@ namespace fw colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.55f); + colors[ImGuiCol_TableBorderLight] = ImVec4(0.20f, 0.20f, 0.20f, 0.80f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.20f, 0.20f, 0.20f, 0.90f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.60f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.40f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.20f, 0.20f, 0.20f, 0.20f); _style.WindowBorderSize = 1.0f; _style.FrameBorderSize = 1.0f; diff --git a/src/nodable/gui/NodableView.cpp b/src/nodable/gui/NodableView.cpp index 7c7517c6b..cac3f9f4f 100644 --- a/src/nodable/gui/NodableView.cpp +++ b/src/nodable/gui/NodableView.cpp @@ -2,21 +2,22 @@ #include -#include "fw/core/log.h" -#include "fw/core/system.h" -#include "fw/gui/Texture.h" -#include "core/NodeUtils.h" +#include "Action.h" #include "Config.h" #include "Event.h" -#include "Action.h" +#include "History.h" #include "HybridFile.h" #include "HybridFileView.h" -#include "History.h" #include "Nodable.h" #include "NodeView.h" -#include "build_info.h" #include "Physics.h" #include "PropertyView.h" +#include "build_info.h" +#include "core/NodeUtils.h" +#include "fw/core/log.h" +#include "fw/core/system.h" +#include "fw/gui/Texture.h" +#include "gui/ActionManagerView.h" using namespace ndbl; using namespace ndbl::assembly; @@ -672,6 +673,11 @@ void NodableView::draw_config_window() { ImGui::SliderInt("grid subdivisions", &config.ui_graph_grid_subdivs, 1, 16); } + if (ImGui::CollapsingHeader("Shortcuts", ImGuiTreeNodeFlags_SpanAvailWidth)) + { + ActionManagerView::draw(&m_app->action_manager); + } + if ( m_app->config.common.debug && ImGui::CollapsingHeader("Pool")) { ImGui::Text("Pool stats:"); @@ -685,37 +691,41 @@ void NodableView::draw_config_window() { ImGui::End(); } -void NodableView::on_draw_splashscreen() +void NodableView::draw_splashscreen() { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if ( AppView::begin_splashscreen() ) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - // Image - ImGui::SameLine((ImGui::GetContentRegionAvail().x - m_logo->width) * 0.5f); // center img - fw::ImGuiEx::Image(m_logo); + // Image + ImGui::SameLine((ImGui::GetContentRegionAvail().x - m_logo->width) * 0.5f); // center img + fw::ImGuiEx::Image(m_logo); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(50.0f, 30.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(50.0f, 30.0f)); - // disclaimer - ImGui::TextWrapped( - "DISCLAIMER: This software is a prototype, do not expect too much from it. Use at your own risk."); + // disclaimer + ImGui::TextWrapped( + "DISCLAIMER: This software is a prototype, do not expect too much from it. Use at your own risk."); - ImGui::NewLine(); - ImGui::NewLine(); + ImGui::NewLine(); + ImGui::NewLine(); - // credits - const char *credit = "by Berdal84"; - ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(credit).x); - ImGui::TextWrapped("%s", credit); + // credits + const char *credit = "by Berdal84"; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(credit).x); + ImGui::TextWrapped("%s", credit); - // build version - ImGui::TextWrapped("%s", BuildInfo::version); + // build version + ImGui::TextWrapped("%s", BuildInfo::version); - // close on left/rightmouse btn click - if (ImGui::IsMouseClicked(0) || ImGui::IsMouseClicked(1)) - { - m_app->config.common.splashscreen = false; + // close on left/rightmouse btn click + if (ImGui::IsMouseClicked(0) || ImGui::IsMouseClicked(1)) + { + m_app->config.common.splashscreen = false; + } + ImGui::PopStyleVar(); // ImGuiStyleVar_FramePadding + AppView::end_splashscreen(); } - ImGui::PopStyleVar(); // ImGuiStyleVar_FramePadding } void NodableView::draw_history_bar(History *currentFileHistory) { diff --git a/src/nodable/gui/NodableView.h b/src/nodable/gui/NodableView.h index 00316faa9..f8ef6d042 100644 --- a/src/nodable/gui/NodableView.h +++ b/src/nodable/gui/NodableView.h @@ -31,7 +31,7 @@ namespace ndbl void on_init() override; void on_draw() override; - void on_draw_splashscreen() override; + void draw_splashscreen() override; void on_reset_layout() override; // draw_xxx_window From 8594127acf4602bf9aefa5c7198f8a90665d2afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Wed, 24 Apr 2024 18:43:43 -0400 Subject: [PATCH 16/27] refactor(NodeViewConstrain): extract methods from apply() --- src/nodable/gui/NodeView.cpp | 20 +++++++- src/nodable/gui/NodeView.h | 3 ++ src/nodable/gui/NodeViewConstraint.cpp | 71 +++++++++++--------------- src/nodable/gui/NodeViewConstraint.h | 8 ++- 4 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/nodable/gui/NodeView.cpp b/src/nodable/gui/NodeView.cpp index ad0d64e9e..3a80a9192 100644 --- a/src/nodable/gui/NodeView.cpp +++ b/src/nodable/gui/NodeView.cpp @@ -1045,6 +1045,18 @@ NodeView* NodeView::substitute_with_parent_if_not_visible(NodeView* _view, bool return parent_view; } +std::vector NodeView::substitute_with_parent_if_not_visible(const std::vector& _in, bool _recursive) +{ + std::vector out; + out.reserve(_in.size()); // Wort but more probable case + for(auto each : _in) + { + auto each_or_substitute = NodeView::substitute_with_parent_if_not_visible(each, _recursive); + out.push_back(each_or_substitute); + } + return std::move(out); +}; + void NodeView::expand_toggle_rec() { return set_expanded_rec(!m_expanded); @@ -1118,4 +1130,10 @@ void NodeView::set_color( const ImVec4* _color, ColorType _type ) ImColor NodeView::get_color( ColorType _type ) const { return ImColor(*m_colors[_type]); -} \ No newline at end of file +} + +bool NodeView::none_is_visible( std::vector _views ) +{ + auto is_visible = [](const NodeView* view) { return view->is_visible(); }; + return std::find_if(_views.begin(), _views.end(), is_visible) == _views.end(); +} diff --git a/src/nodable/gui/NodeView.h b/src/nodable/gui/NodeView.h index bef8d69e9..147c8a3b3 100644 --- a/src/nodable/gui/NodeView.h +++ b/src/nodable/gui/NodeView.h @@ -99,12 +99,15 @@ namespace ndbl static void set_view_detail(NodeViewDetail _viewDetail); // Change view detail globally static NodeViewDetail get_view_detail() { return s_view_detail; } static NodeView* substitute_with_parent_if_not_visible(NodeView* _view, bool _recursive = true); + static std::vector substitute_with_parent_if_not_visible(const std::vector& _in, bool _recurse = true ); ImVec2 get_slot_pos( const Slot& ); ImRect get_slot_rect( const Slot& _slot, const Config& _config, i8_t _count ) const; ImRect get_slot_rect( const SlotView &_slot_view, const Config &_config, i8_t _pos ) const; ImVec2 get_slot_normal( const Slot& slot) const; void set_color( const ImVec4* _color, ColorType _type = Color_FILL ); ImColor get_color(ColorType _type) const; + static bool none_is_visible( std::vector vector1 ); + private: void set_adjacent_visible(SlotFlags flags, bool _visible, bool _recursive); diff --git a/src/nodable/gui/NodeViewConstraint.cpp b/src/nodable/gui/NodeViewConstraint.cpp index 47477bc8e..690cb9e1b 100644 --- a/src/nodable/gui/NodeViewConstraint.cpp +++ b/src/nodable/gui/NodeViewConstraint.cpp @@ -13,7 +13,7 @@ using namespace fw; NodeViewConstraint::NodeViewConstraint(const char* _name, ConstrainFlags _flags) : m_flags(_flags) -, m_filter(always) +, m_should_apply(always) , m_is_active(true) , m_name(_name) { @@ -21,50 +21,20 @@ NodeViewConstraint::NodeViewConstraint(const char* _name, ConstrainFlags _flags) void NodeViewConstraint::apply(float _dt) { - bool should_apply = m_is_active && m_filter(this); - if(!should_apply) - { - return; - } + // Check if this constrain should apply + if(!m_is_active && m_should_apply(this)) return; - /* - * To get a clean list of node views. - * Substitute each not visible view by their respective parent. - */ - auto get_clean = [](std::vector _in) - { - std::vector out; - out.reserve(_in.size()); - for(auto each : _in) - { - out.push_back(NodeView::substitute_with_parent_if_not_visible(each)); - } - return std::move(out); - }; + // Gather only visible views or their parent (recursively) + auto pool = Pool::get_pool(); + std::vector clean_drivers = NodeView::substitute_with_parent_if_not_visible( pool->get( m_drivers ), true ); + std::vector clean_targets = NodeView::substitute_with_parent_if_not_visible( pool->get( m_targets ), true ); - std::vector clean_drivers = get_clean( Pool::get_pool()->get( m_drivers ) ); - std::vector clean_targets = get_clean( Pool::get_pool()->get( m_targets ) ); + // If we still have no targets or drivers visible, it's not necessary to go further + if ( NodeView::none_is_visible(clean_targets)) return; + if ( NodeView::none_is_visible(clean_drivers)) return; - //debug - if( fw::ImGuiEx::debug ) - { - for (auto each_target: clean_targets) - { - for (auto each_driver: clean_drivers) - { - fw::ImGuiEx::DebugLine( - each_driver->get_position(fw::Space_Screen), - each_target->get_position(fw::Space_Screen), - IM_COL32(0, 0, 255, 30), 1.0f); - } - } - } - - auto none_is_visible = [](const std::vector& _views)-> bool { - auto is_visible = [](const NodeView* view) { return view->is_visible(); }; - return std::find_if(_views.begin(), _views.end(), is_visible) == _views.end(); - }; - if (none_is_visible(clean_targets) || none_is_visible(clean_drivers)) return; + // To control visually + draw_debug_lines( clean_drivers, clean_targets ); const Config& config = Nodable::get_instance().config; @@ -197,6 +167,23 @@ void NodeViewConstraint::apply(float _dt) } } +void NodeViewConstraint::draw_debug_lines(const std::vector& _drivers,const std::vector& _targets ) +{ + if( ImGuiEx::debug ) + { + for (auto each_target: _targets ) + { + for (auto each_driver: _drivers ) + { + ImGuiEx::DebugLine( + each_driver->get_position( Space_Screen), + each_target->get_position( Space_Screen), + IM_COL32(0, 0, 255, 30), 1.0f); + } + } + } +} + void NodeViewConstraint::add_target(PoolID _target) { FW_ASSERT( _target ); diff --git a/src/nodable/gui/NodeViewConstraint.h b/src/nodable/gui/NodeViewConstraint.h index 03b3e9868..65ea102c8 100644 --- a/src/nodable/gui/NodeViewConstraint.h +++ b/src/nodable/gui/NodeViewConstraint.h @@ -41,11 +41,12 @@ namespace ndbl { */ class NodeViewConstraint { public: + // Lambda returning true if this constrain should apply. using Filter = std::function; NodeViewConstraint(const char* _name, ConstrainFlags ); void apply(float _dt); - void apply_when(const Filter& _lambda) { m_filter = _lambda; } + void apply_when(const Filter& _lambda) { m_should_apply = _lambda; } void add_target(PoolID); void add_driver(PoolID); void add_targets(const std::vector>&); @@ -61,9 +62,12 @@ namespace ndbl { private: const char* m_name; bool m_is_active; - Filter m_filter; // Lambda returning true if this constrain should apply. + Filter m_should_apply; ConstrainFlags m_flags; std::vector> m_drivers; // driving the targets std::vector> m_targets; + static void draw_debug_lines( + const std::vector& _drivers, + const std::vector& _targets); }; } \ No newline at end of file From c1db16d553e570e55ddfa6f5077ee6a4ae93253f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Wed, 24 Apr 2024 19:06:25 -0400 Subject: [PATCH 17/27] refactor(NodeViewConstrain): extract methods from apply() 2/N --- src/nodable/gui/NodeViewConstraint.cpp | 35 +++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/nodable/gui/NodeViewConstraint.cpp b/src/nodable/gui/NodeViewConstraint.cpp index 690cb9e1b..a477b74a2 100644 --- a/src/nodable/gui/NodeViewConstraint.cpp +++ b/src/nodable/gui/NodeViewConstraint.cpp @@ -19,6 +19,22 @@ NodeViewConstraint::NodeViewConstraint(const char* _name, ConstrainFlags _flags) { } +/** TODO: move this in a class (not NodeViewConstrain) */ +std::vector get_rect(const std::vector& _in_views) +{ + std::vector _out; + for (auto each_target : _in_views ) + { + ImRect rect; + if( !(each_target->pinned() || !each_target->is_visible()) ) + { + rect = each_target->get_rect( true ); + } + _out.push_back(rect); + } + return std::move(_out); +} + void NodeViewConstraint::apply(float _dt) { // Check if this constrain should apply @@ -48,24 +64,13 @@ void NodeViewConstraint::apply(float _dt) NodeView* driver = clean_drivers[0]; const bool align_bbox_bottom = m_flags & ConstrainFlag_ALIGN_BBOX_BOTTOM; const float y_direction = align_bbox_bottom ? 1.0f : -1.0f; - float size_x_total = 0.0f; ImVec2 driver_pos = driver->get_position(fw::Space_Local); ImVec2 cursor_pos = driver_pos; const Node& driver_owner = *driver->get_owner(); - std::vector target_rects; - - // Compute each target_rect and size_x_total : - //----------------------- - for (auto each_target : clean_targets) - { - ImRect rect; - if( !(each_target->pinned() || !each_target->is_visible()) ) - { - rect = each_target->get_rect( true ); - } - target_rects.push_back(rect); - size_x_total += rect.GetWidth(); - } + auto target_rects = get_rect( clean_targets ); + float size_x_total = 0.0f; + std::for_each( target_rects.begin(), target_rects.end(), + [&](auto each ) { size_x_total += each.GetSize().x; }); // Determine x position start: //--------------------------- From 4ece7f549a5b25f7bb499f8c56d5b6fe7c1c260d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Wed, 24 Apr 2024 20:25:28 -0400 Subject: [PATCH 18/27] refactor(NodeViewConstrain): extract methods from apply() 3/N --- src/nodable/gui/NodeViewConstraint.cpp | 107 ++++++++++++++----------- src/nodable/gui/NodeViewConstraint.h | 11 +++ 2 files changed, 72 insertions(+), 46 deletions(-) diff --git a/src/nodable/gui/NodeViewConstraint.cpp b/src/nodable/gui/NodeViewConstraint.cpp index a477b74a2..bdd86133d 100644 --- a/src/nodable/gui/NodeViewConstraint.cpp +++ b/src/nodable/gui/NodeViewConstraint.cpp @@ -64,65 +64,80 @@ void NodeViewConstraint::apply(float _dt) NodeView* driver = clean_drivers[0]; const bool align_bbox_bottom = m_flags & ConstrainFlag_ALIGN_BBOX_BOTTOM; const float y_direction = align_bbox_bottom ? 1.0f : -1.0f; - ImVec2 driver_pos = driver->get_position(fw::Space_Local); - ImVec2 cursor_pos = driver_pos; + ImVec2 virtual_cursor = driver->get_position(fw::Space_Local); const Node& driver_owner = *driver->get_owner(); auto target_rects = get_rect( clean_targets ); - float size_x_total = 0.0f; - std::for_each( target_rects.begin(), target_rects.end(), - [&](auto each ) { size_x_total += each.GetSize().x; }); - - // Determine x position start: - //--------------------------- - - // x alignment - // - // We add an indentation when driver is an instruction without being connected to a predecessor - const bool align_right = driver_owner.is_instruction() && !driver_owner.predecessors().empty() && not align_bbox_bottom; - if ( align_right ) + + // Determine horizontal alignment + //------------------------------- + + Align halign = Align_CENTER; + + // Align right when driver is an instruction without being connected to a predecessor + if ( driver_owner.is_instruction() && !driver_owner.predecessors().empty() && not align_bbox_bottom ) { - cursor_pos.x += driver->get_size().x / 4.0f - + config.ui_node_spacing; + halign = Align_END; + } + + // Determine virtual_cursor.x from alignment + //---------------------------------- - // Otherwise we simply align vertically - } else { - cursor_pos.x -= size_x_total / 2.0f; + switch( halign ) + { + case Align_START: + { + FW_EXPECT(false, "not implemented") + } + + case Align_END: + { + virtual_cursor.x += driver->get_size().x / 4.0f + config.ui_node_spacing; + break; + } + + case Align_CENTER: + { + float size_x_total = 0.0f; + std::for_each( target_rects.begin(), target_rects.end(),[&](auto each ) { size_x_total += each.GetSize().x; }); + virtual_cursor.x -= size_x_total / 2.0f; + } } // Constraint in row: //------------------- - cursor_pos.y += y_direction * driver->get_size().y / 2.0f; + virtual_cursor.y += y_direction * driver->get_size().y / 2.0f; for (int target_index = 0; target_index < clean_targets.size(); target_index++) { NodeView* each_target = clean_targets[target_index]; - if ( !each_target->pinned() && each_target->is_visible() ) + const Node& target_owner = *each_target->get_owner(); + + // Guards + if ( !each_target->is_visible() ) continue; + if ( each_target->pinned() ) continue; + if ( !target_owner.should_be_constrain_to_follow_output( driver_owner.poolid() ) && !align_bbox_bottom ) continue; + + // Compute new position for this input view + ImRect& target_rect = target_rects[target_index]; + + ImVec2 relative_pos( + target_rect.GetWidth() / 2.0f, + y_direction * (target_rect.GetHeight() / 2.0f + config.ui_node_spacing) + ); + + if ( align_bbox_bottom ) relative_pos += y_direction * config.ui_node_spacing; + + // Add a vertical space to avoid having too much wires aligned on x-axis + // useful for "for" nodes. + if( halign == Align_END && clean_targets.size() > 1 ) { - // Compute new position for this input view - ImRect& target_rect = target_rects[target_index]; - - ImVec2 relative_pos( - target_rect.GetWidth() / 2.0f, - y_direction * (target_rect.GetHeight() / 2.0f + config.ui_node_spacing) - ); - - if ( align_bbox_bottom ) relative_pos += y_direction * config.ui_node_spacing; - - if( align_right && clean_targets.size() > 1 ) - { - // add a vertical space to avoid having too much wires aligned on x-axis - int reverse_y_spacing = (clean_targets.size() - 1 - target_index) * config.ui_node_spacing * 1.5f; - relative_pos.y += y_direction * reverse_y_spacing; - } - - const Node& target_owner = *each_target->get_owner(); - const bool constrained = target_owner.should_be_constrain_to_follow_output( driver_owner.poolid() ); - if ( constrained || align_bbox_bottom ) - { - auto target_physics = target_owner.get_component(); - target_physics->add_force_to_translate_to(cursor_pos + relative_pos + m_offset, config.ui_node_speed, true); - cursor_pos.x += target_rect.GetWidth() + config.ui_node_spacing; - } + float reverse_y_spacing = float(clean_targets.size() - 1 - target_index) * config.ui_node_spacing * 1.5f; + relative_pos.y += y_direction * reverse_y_spacing; } + + auto target_physics = target_owner.get_component(); + target_physics->add_force_to_translate_to( virtual_cursor + relative_pos + m_offset, config.ui_node_speed, true); + virtual_cursor.x += target_rect.GetWidth() + config.ui_node_spacing; + } break; } diff --git a/src/nodable/gui/NodeViewConstraint.h b/src/nodable/gui/NodeViewConstraint.h index 65ea102c8..0394a7d2e 100644 --- a/src/nodable/gui/NodeViewConstraint.h +++ b/src/nodable/gui/NodeViewConstraint.h @@ -11,6 +11,17 @@ namespace ndbl { class NodeView; using fw::PoolID; + enum Align { + Align_START, + Align_CENTER, + Align_END, + }; + + enum Direction { + Direction_ROW , + Direction_COLUMN, + }; + typedef int ConstrainFlags; enum ConstrainFlag_ { From 30f586abc337e7d8f5057544e63a1de73242cabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Wed, 24 Apr 2024 23:09:11 -0400 Subject: [PATCH 19/27] fix|refactor(HybridFile): ensure Graph is dirty when modified, refresh text, naming changes --- src/nodable/core/Graph.cpp | 7 +++-- src/nodable/core/language/Nodlang.cpp | 3 ++ src/nodable/gui/HybridFile.cpp | 43 +++++++++++++++++---------- src/nodable/gui/HybridFile.h | 2 +- src/nodable/gui/HybridFileView.cpp | 6 ++-- src/nodable/gui/HybridFileView.h | 11 ++++--- src/nodable/gui/NodableView.cpp | 10 +++---- 7 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/nodable/core/Graph.cpp b/src/nodable/core/Graph.cpp index 4054ebd6f..9a02e8ff2 100644 --- a/src/nodable/core/Graph.cpp +++ b/src/nodable/core/Graph.cpp @@ -95,6 +95,7 @@ void Graph::add(PoolID _node) FW_ASSERT(std::find(m_node_registry.begin(), m_node_registry.end(), _node->poolid()) == m_node_registry.end()) m_node_registry.push_back(_node->poolid()); _node->parent_graph = this; + set_dirty(); // To express this graph changed LOG_VERBOSE("Graph", "registerNode %s (%s)\n", _node->name.c_str(), _node->get_type()->get_name()) } @@ -103,6 +104,7 @@ void Graph::remove(PoolID _node) auto found = std::find(m_node_registry.begin(), m_node_registry.end(), _node); FW_ASSERT(found != m_node_registry.end()); m_node_registry.erase(found); + set_dirty(); // To express this graph changed } void Graph::ensure_has_root() @@ -254,6 +256,7 @@ void Graph::remove(DirectedEdge edge) { LOG_WARNING("Graph", "Unable to unregister edge\n") } + set_dirty(); // To express this graph changed } DirectedEdge* Graph::connect_to_variable(Slot& _out, VariableNode& _variable ) @@ -412,7 +415,7 @@ DirectedEdge* Graph::connect(Slot& _first, Slot& _second, ConnectFlags _flags) FW_ASSERT(false);// This connection type is not yet implemented } } - set_dirty(); + set_dirty(); // To express this graph changed return &edge; } @@ -458,7 +461,7 @@ void Graph::disconnect( const DirectedEdge& _edge, ConnectFlags flags) FW_EXPECT(!type, "Not yet implemented yet"); } - set_dirty(); + set_dirty(); // To express this graph changed } PoolID Graph::create_scope() diff --git a/src/nodable/core/language/Nodlang.cpp b/src/nodable/core/language/Nodlang.cpp index 572632e8a..91eee7a8b 100644 --- a/src/nodable/core/language/Nodlang.cpp +++ b/src/nodable/core/language/Nodlang.cpp @@ -615,6 +615,9 @@ PoolID Nodlang::parse_program() parser_state.scope.pop(); commit_transaction(); + // Avoid an unnecessary serialization of the graph (would happen once after each parsing) + parser_state.graph->set_dirty(false); + return root; } diff --git a/src/nodable/gui/HybridFile.cpp b/src/nodable/gui/HybridFile.cpp index 260fbd145..63dfb7582 100644 --- a/src/nodable/gui/HybridFile.cpp +++ b/src/nodable/gui/HybridFile.cpp @@ -17,7 +17,7 @@ using namespace fw; HybridFile::HybridFile(std::string _name) : name(std::move(_name)) - , changed(true) + , is_content_dirty(true) , view(*this) , m_history(&Nodable::get_instance().config.experimental_hybrid_history) { @@ -64,22 +64,23 @@ HybridFile::~HybridFile() bool HybridFile::write_to_disk() { - if(path.empty() ) + if( path.empty() ) { + LOG_WARNING("File", "No path defined, unable to save file\n"); return false; } - if (changed) - { - std::ofstream out_fstream(path.c_str()); - std::string content = view.get_text(); - out_fstream.write(content.c_str(), content.size()); - changed = false; - LOG_MESSAGE("File", "%s saved\n", name.c_str()); - } else { + if ( !is_content_dirty ) + { LOG_MESSAGE("File", "Nothing to save\n"); } + std::ofstream out_fstream(path.c_str()); + std::string content = view.get_text(); + out_fstream.write(content.c_str(), content.size()); // TODO: size can exceed fstream! + is_content_dirty = false; + LOG_MESSAGE("File", "%s saved\n", name.c_str()); + return true; } @@ -102,7 +103,7 @@ bool HybridFile::load() std::string content((std::istreambuf_iterator(file_stream)), std::istreambuf_iterator()); set_text(content); - changed = false; + is_content_dirty = false; LOG_MESSAGE("HybridFile", "\"%s\" loaded (%s).\n", name.c_str(), path.c_str()) @@ -141,13 +142,16 @@ UpdateResult HybridFile::update() // bool isolate_selection = Nodable::get_instance().config.isolate_selection; + // 1) Handle when view changes (graph or text) + //-------------------------------------------- + if( view.changed() ) { - if( view.focused_text_changed() && !view.graph_changed() ) + if( view.focused_text_changed() && !view.is_graph_dirty() ) { update_graph_from_text(isolate_selection); } - else if ( view.graph_changed() ) + else if ( view.is_graph_dirty() ) { update_text_from_graph(isolate_selection); } @@ -157,10 +161,19 @@ UpdateResult HybridFile::update() // This is not supposed to happens, that's why there is an assert to be aware of is FW_ASSERT(false); } - view.changed(false); + view.set_dirty( false ); + FW_ASSERT( m_graph->is_dirty() == false); + } + + // 2) Handle when graph (not the graph view) changes + //-------------------------------------------------- + + if ( m_graph->is_dirty() ) + { + update_text_from_graph(isolate_selection); } - return m_graph->update(); + return m_graph->update(); // ~ garbage collection } UpdateResult HybridFile::update_graph_from_text(bool isolate_selection) diff --git a/src/nodable/gui/HybridFile.h b/src/nodable/gui/HybridFile.h index 6b071367c..a7bc40dd0 100644 --- a/src/nodable/gui/HybridFile.h +++ b/src/nodable/gui/HybridFile.h @@ -38,7 +38,7 @@ namespace ndbl observe::Event graph_changed; ghc::filesystem::path path; // file path on disk std::string name; // friendly name - bool changed; // true if changes needs to be saved + bool is_content_dirty; // true if changes needs to be saved HybridFileView view; explicit HybridFile(std::string _name); diff --git a/src/nodable/gui/HybridFileView.cpp b/src/nodable/gui/HybridFileView.cpp index c928b8e9e..179878d52 100644 --- a/src/nodable/gui/HybridFileView.cpp +++ b/src/nodable/gui/HybridFileView.cpp @@ -21,7 +21,7 @@ HybridFileView::HybridFileView(HybridFile& _file) : fw::View() , m_text_editor() , m_focused_text_changed(false) - , m_graph_changed(false) + , m_is_graph_dirty(false) , m_file(_file) , m_child1_size(0.3f) , m_child2_size(0.7f) @@ -153,7 +153,7 @@ bool HybridFileView::draw() m_text_editor.IsTextChanged() || (app.config.isolate_selection && is_selected_text_modified); - if (m_text_editor.IsTextChanged()) m_file.changed = true; + if (m_text_editor.IsTextChanged()) m_file.is_content_dirty = true; } ImGui::EndChild(); @@ -175,7 +175,7 @@ bool HybridFileView::draw() { // Draw graph View::use_available_region(graph_view); - m_graph_changed = graph_view->draw(); + m_is_graph_dirty = graph_view->draw(); // Draw overlay: shortcuts ImRect overlay_rect = fw::ImGuiEx::GetContentRegion(fw::Space_Screen); diff --git a/src/nodable/gui/HybridFileView.h b/src/nodable/gui/HybridFileView.h index 01b8e5299..7b9c14e60 100644 --- a/src/nodable/gui/HybridFileView.h +++ b/src/nodable/gui/HybridFileView.h @@ -44,10 +44,10 @@ namespace ndbl void init(); bool draw() override; - bool changed() const { return m_focused_text_changed || m_graph_changed; } + bool changed() const { return m_focused_text_changed || m_is_graph_dirty; } bool focused_text_changed() const { return m_focused_text_changed; } - bool graph_changed() const { return m_graph_changed; } - void changed(bool b) { m_focused_text_changed = m_graph_changed = b; } + bool is_graph_dirty() const { return m_is_graph_dirty; } + void set_dirty(bool b) { m_focused_text_changed = m_is_graph_dirty = b; } void set_text(const std::string&); std::string get_selected_text()const; std::string get_text()const; @@ -64,13 +64,12 @@ namespace ndbl void push_overlay(OverlayData, OverlayType) ; void refresh_overlay(Condition condition); void draw_overlay(const char* title, const std::vector& overlay_data, ImRect rect, ImVec2 position); - - size_t size() const; + size_t size() const; private: std::array, OverlayType_COUNT> m_overlay_data; bool m_focused_text_changed; - bool m_graph_changed; + bool m_is_graph_dirty; HybridFile& m_file; std::string m_text_overlay_window_name; std::string m_graph_overlay_window_name; diff --git a/src/nodable/gui/NodableView.cpp b/src/nodable/gui/NodableView.cpp index cac3f9f4f..918b385f0 100644 --- a/src/nodable/gui/NodableView.cpp +++ b/src/nodable/gui/NodableView.cpp @@ -66,19 +66,19 @@ void NodableView::on_draw() History* current_file_history = current_file ? current_file->get_history() : nullptr; if (ImGui::BeginMenu("File")) { - bool has_file = current_file; - bool changed = current_file != nullptr && current_file->changed; + bool has_file = current_file != nullptr; + bool is_current_file_content_dirty = current_file != nullptr && current_file->is_content_dirty; ImGuiEx::MenuItem(); ImGuiEx::MenuItem(); ImGui::Separator(); ImGuiEx::MenuItem(false, has_file); - ImGuiEx::MenuItem(false, has_file && changed); + ImGuiEx::MenuItem(false, has_file && is_current_file_content_dirty ); ImGui::Separator(); ImGuiEx::MenuItem(false, has_file); auto auto_paste = has_file && current_file->view.experimental_clipboard_auto_paste(); - if (ImGui::MenuItem(ICON_FA_COPY " Auto-paste clipboard", "", auto_paste, has_file ) && current_file ) { + if (ImGui::MenuItem(ICON_FA_COPY " Auto-paste clipboard", "", auto_paste, has_file ) && has_file ) { current_file->view.experimental_clipboard_auto_paste(!auto_paste); } @@ -551,7 +551,7 @@ void NodableView::draw_file_window(ImGuiID dockspace_id, bool redock_all, Hybrid ImGui::SetNextWindowDockID(dockspace_id, redock_all ? ImGuiCond_Always : ImGuiCond_Appearing); ImGuiWindowFlags window_flags = - (file->changed ? ImGuiWindowFlags_UnsavedDocument : 0) | ImGuiWindowFlags_NoScrollbar; + (file->is_content_dirty ? ImGuiWindowFlags_UnsavedDocument : 0) | ImGuiWindowFlags_NoScrollbar; auto child_bg = ImGui::GetStyle().Colors[ImGuiCol_ChildBg]; child_bg.w = 0; From 3e4bee4c1228a8257ed06b864df9f590c34c35bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Wed, 24 Apr 2024 23:18:40 -0400 Subject: [PATCH 20/27] fix(HybridFile|Graph): ensure Graph physics constraints are refreshed --- src/nodable/core/Graph.cpp | 2 -- src/nodable/gui/HybridFile.cpp | 11 +++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/nodable/core/Graph.cpp b/src/nodable/core/Graph.cpp index 9a02e8ff2..707c32795 100644 --- a/src/nodable/core/Graph.cpp +++ b/src/nodable/core/Graph.cpp @@ -85,8 +85,6 @@ UpdateResult Graph::update() } - set_dirty(false); - return result; } diff --git a/src/nodable/gui/HybridFile.cpp b/src/nodable/gui/HybridFile.cpp index 63dfb7582..d5fd59f54 100644 --- a/src/nodable/gui/HybridFile.cpp +++ b/src/nodable/gui/HybridFile.cpp @@ -170,7 +170,15 @@ UpdateResult HybridFile::update() if ( m_graph->is_dirty() ) { + // Refresh text update_text_from_graph(isolate_selection); + + // Refresh constraints + auto physics_components = NodeUtils::get_components( m_graph->get_node_registry() ); + Physics::destroy_constraints( physics_components ); + Physics::create_constraints( m_graph->get_node_registry() ); + + m_graph->set_dirty(false); } return m_graph->update(); // ~ garbage collection @@ -178,7 +186,7 @@ UpdateResult HybridFile::update() UpdateResult HybridFile::update_graph_from_text(bool isolate_selection) { - // Destroy all physics' constraints + // Destroy all physics constraints auto physics_components = NodeUtils::get_components( m_graph->get_node_registry() ); Physics::destroy_constraints( physics_components ); @@ -191,7 +199,6 @@ UpdateResult HybridFile::update_graph_from_text(bool isolate_selection) return UpdateResult::SUCCESS_WITH_CHANGES; } return UpdateResult::SUCCES_WITHOUT_CHANGES; - } size_t HybridFile::size() const From e510d96807cf229883cde9b1eb75c6dc4af139aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Wed, 24 Apr 2024 23:22:13 -0400 Subject: [PATCH 21/27] fix(GraphView): compilation warning --- src/nodable/gui/GraphView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index f2b6fc083..053cc3536 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -559,7 +559,7 @@ Action_CreateNode* CreateNodeContextMenu::draw_search_input( size_t _result_max_ auto it = items_matching_search.begin(); while( it != items_matching_search.end() && std::distance(items_matching_search.begin(), it) != _result_max_count) { - auto& action = *it; + auto* action = *it; if ( ImGui::Button( action->label.c_str()) || // User can click on the button... (ImGui::IsKeyDown( ImGuiKey_Enter ) && ImGui::IsItemFocused() ) // ...or press enter if this item is the first ) From 3aab9479f8a03b46ae884c76e5209ed55c6c1a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Wed, 24 Apr 2024 23:30:17 -0400 Subject: [PATCH 22/27] refactor(GraphView): clean update_cache_based_on_signature method --- src/nodable/gui/GraphView.cpp | 66 ++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index 053cc3536..ca826cdcc 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -585,48 +585,56 @@ void CreateNodeContextMenu::update_cache_based_on_signature() { items_with_compatible_signature.clear(); + // 1) When NO slot is dragged + //--------------------------- + if ( !dragged_slot ) { // When no slot is dragged, user can create any node items_with_compatible_signature = items; + return; } - else + + // 2) When a slot is dragged + //-------------------------- + + for (auto& action: items ) { - for (auto& action: items ) + // 2.a - items able to create a new block (if/else/scope/for/etc..) + //----------------------------------------------------------------- + + if ( action->event_id == EventID_REQUEST_CREATE_BLOCK ) + { + if ( dragged_slot->is_this() ) + { + items_with_compatible_signature.push_back( action ); + } + continue; + } + + // 2.b - items able to create a new node (variable/literal/function/etc..) + //------------------------------------------------------------------------ + + if ( dragged_slot->is_this() ) continue; // A "this" slot cannot be connected to these items + + const type* dragged_property_type = dragged_slot->get_property_type(); + + if ( action->event_data.node_signature ) { - if ( action->event_id == EventID_REQUEST_CREATE_BLOCK ) + if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) { - if ( dragged_slot->is_this() ) + if ( !action->event_data.node_signature->has_an_arg_of_type(dragged_property_type) ) { - items_with_compatible_signature.push_back( action ); + continue; } } - else + else if ( !action->event_data.node_signature->get_return_type()->equals(dragged_property_type) ) { - if ( !dragged_slot->is_this() ) - { - const type* dragged_property_type = dragged_slot->get_property_type(); - - if ( action->event_data.node_signature ) - { - if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) - { - if ( !action->event_data.node_signature->has_an_arg_of_type(dragged_property_type) ) - { - continue; - } - } - else if ( !action->event_data.node_signature->get_return_type()->equals(dragged_property_type) ) - { - continue; - } - } else { - // by default, we accept any item not having a signature - } - items_with_compatible_signature.push_back( action ); - } + continue; } - } + } // by default, we accept any item not having a signature + + items_with_compatible_signature.push_back( action ); } } From e109b7cf67883e299838a6603a140e1d2dd4def7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Thu, 25 Apr 2024 00:01:55 -0400 Subject: [PATCH 23/27] fix(GraphView): node search too works for blocks --- src/fw/gui/ActionManager.cpp | 2 +- src/nodable/gui/Action.h | 1 - src/nodable/gui/Event.h | 1 - src/nodable/gui/GraphView.cpp | 53 +++++++++++++++++------------------ src/nodable/gui/Nodable.cpp | 11 ++++---- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/fw/gui/ActionManager.cpp b/src/fw/gui/ActionManager.cpp index ff9ba4693..09c27314b 100644 --- a/src/fw/gui/ActionManager.cpp +++ b/src/fw/gui/ActionManager.cpp @@ -56,7 +56,7 @@ const std::vector& ActionManager::get_actions() const void ActionManager::add_action( IAction* _action )// Add a new action (can be triggered via shortcut) { m_actions.push_back( _action ); - m_actions_by_id.insert({_action->event_id, _action}); + m_actions_by_id.insert(std::pair{_action->event_id, _action}); LOG_MESSAGE("ActionManager", "Action '%s' bound to the event_id %i\n", _action->label.c_str(), _action->event_id); } diff --git a/src/nodable/gui/Action.h b/src/nodable/gui/Action.h index 08e4bff20..7c9dc85f6 100644 --- a/src/nodable/gui/Action.h +++ b/src/nodable/gui/Action.h @@ -24,6 +24,5 @@ namespace ndbl // 2) Advanced actions (custom events) using Action_FrameSelection = Action; - using Action_CreateBlock = Action; using Action_CreateNode = Action; } \ No newline at end of file diff --git a/src/nodable/gui/Event.h b/src/nodable/gui/Event.h index ff5b32ec0..91bd6acca 100644 --- a/src/nodable/gui/Event.h +++ b/src/nodable/gui/Event.h @@ -100,6 +100,5 @@ namespace ndbl {} }; using Event_CreateNode = fw::Event; - using Event_CreateBlock = fw::Event; }// namespace ndbl diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index ca826cdcc..dbfe813df 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -600,40 +600,37 @@ void CreateNodeContextMenu::update_cache_based_on_signature() for (auto& action: items ) { - // 2.a - items able to create a new block (if/else/scope/for/etc..) - //----------------------------------------------------------------- + const type* dragged_property_type = dragged_slot->get_property_type(); - if ( action->event_id == EventID_REQUEST_CREATE_BLOCK ) + switch ( action->event_data.node_type ) { - if ( dragged_slot->is_this() ) - { - items_with_compatible_signature.push_back( action ); - } - continue; - } - - // 2.b - items able to create a new node (variable/literal/function/etc..) - //------------------------------------------------------------------------ - - if ( dragged_slot->is_this() ) continue; // A "this" slot cannot be connected to these items + case NodeType_BLOCK_CONDITION: + case NodeType_BLOCK_FOR_LOOP: + case NodeType_BLOCK_WHILE_LOOP: + case NodeType_BLOCK_SCOPE: + case NodeType_BLOCK_PROGRAM: + // Blocks are only for code flow slots + if ( !dragged_slot->allows(SlotFlag_TYPE_CODEFLOW) ) + continue; + break; - const type* dragged_property_type = dragged_slot->get_property_type(); + default: + if ( dragged_slot->allows(SlotFlag_TYPE_CODEFLOW) ) + continue; - if ( action->event_data.node_signature ) - { - if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) ) - { - if ( !action->event_data.node_signature->has_an_arg_of_type(dragged_property_type) ) + if ( action->event_data.node_signature ) { - continue; - } - } - else if ( !action->event_data.node_signature->get_return_type()->equals(dragged_property_type) ) - { - continue; - } - } // by default, we accept any item not having a signature + if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) && + !action->event_data.node_signature->has_an_arg_of_type(dragged_property_type) + ) + continue; + + if ( !action->event_data.node_signature->get_return_type()->equals(dragged_property_type) ) + continue; + } // by default, we accept any item not having a signature + break; + } items_with_compatible_signature.push_back( action ); } } diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 0b29007d2..919881203 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -131,11 +131,11 @@ bool Nodable::on_init() // Prepare context menu items // 1) Blocks - action_manager.new_action( ICON_FA_CODE " Condition", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_CONDITION } ); - action_manager.new_action( ICON_FA_CODE " For Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_FOR_LOOP } ); - action_manager.new_action( ICON_FA_CODE " While Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_WHILE_LOOP } ); - action_manager.new_action( ICON_FA_CODE " Scope", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_SCOPE } ); - action_manager.new_action( ICON_FA_CODE " Program", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_PROGRAM } ); + action_manager.new_action( ICON_FA_CODE " Condition", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_CONDITION } ); + action_manager.new_action( ICON_FA_CODE " For Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_FOR_LOOP } ); + action_manager.new_action( ICON_FA_CODE " While Loop", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_WHILE_LOOP } ); + action_manager.new_action( ICON_FA_CODE " Scope", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_SCOPE } ); + action_manager.new_action( ICON_FA_CODE " Program", Shortcut{}, EventPayload_CreateNode{ NodeType_BLOCK_PROGRAM } ); // 2) Variables action_manager.new_action( ICON_FA_DATABASE " Boolean Variable", Shortcut{}, EventPayload_CreateNode{ NodeType_VARIABLE_BOOLEAN, create_variable_node_signature() } ); @@ -385,7 +385,6 @@ void Nodable::on_update() break; } - case Event_CreateBlock::id: case Event_CreateNode::id: { auto _event = reinterpret_cast(event); From d8860aa1e8f2e10a011eaed29472d548ac10105c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Thu, 25 Apr 2024 00:25:53 -0400 Subject: [PATCH 24/27] fix(GraphView): hide graph flow connectors in some cases --- src/nodable/core/Node.cpp | 6 ++++++ src/nodable/core/Node.h | 1 + src/nodable/gui/GraphView.cpp | 13 ++++++++----- src/nodable/gui/Nodable.cpp | 2 +- src/nodable/gui/NodeView.cpp | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/nodable/core/Node.cpp b/src/nodable/core/Node.cpp index e4490b830..d59c50674 100644 --- a/src/nodable/core/Node.cpp +++ b/src/nodable/core/Node.cpp @@ -355,3 +355,9 @@ bool Node::should_be_constrain_to_follow_output( PoolID _output ) co const auto& _outputs = outputs(); return predecessors().empty() && _outputs.size() >= 1 && _outputs.back() == _output->m_id; } + +bool Node::can_be_instruction() const +{ + // TODO: handle case where a variable has inputs/outputs but not connected to the code flow + return slot_count(SlotFlag_TYPE_CODEFLOW) > 0 && inputs().empty() && outputs().empty(); +} diff --git a/src/nodable/core/Node.h b/src/nodable/core/Node.h index d15e6fee4..c8c60bf11 100644 --- a/src/nodable/core/Node.h +++ b/src/nodable/core/Node.h @@ -64,6 +64,7 @@ namespace ndbl { virtual void init(); bool is_instruction() const; + bool can_be_instruction() const; // Slot related //------------- diff --git a/src/nodable/gui/GraphView.cpp b/src/nodable/gui/GraphView.cpp index dbfe813df..c6c54c0da 100644 --- a/src/nodable/gui/GraphView.cpp +++ b/src/nodable/gui/GraphView.cpp @@ -615,11 +615,15 @@ void CreateNodeContextMenu::update_cache_based_on_signature() break; default: - if ( dragged_slot->allows(SlotFlag_TYPE_CODEFLOW) ) - continue; - if ( action->event_data.node_signature ) + if ( dragged_slot->allows(SlotFlag_TYPE_CODEFLOW)) + { + // we can connect anything to a code flow slot + } + else if ( action->event_data.node_signature ) { + // discard incompatible signatures + if ( dragged_slot->allows( SlotFlag_ORDER_FIRST ) && !action->event_data.node_signature->has_an_arg_of_type(dragged_property_type) ) @@ -628,8 +632,7 @@ void CreateNodeContextMenu::update_cache_based_on_signature() if ( !action->event_data.node_signature->get_return_type()->equals(dragged_property_type) ) continue; - } // by default, we accept any item not having a signature - break; + } } items_with_compatible_signature.push_back( action ); } diff --git a/src/nodable/gui/Nodable.cpp b/src/nodable/gui/Nodable.cpp index 919881203..1e6ae43b2 100644 --- a/src/nodable/gui/Nodable.cpp +++ b/src/nodable/gui/Nodable.cpp @@ -360,7 +360,7 @@ void Nodable::on_update() auto _event = reinterpret_cast(event); SlotRef tail = _event->data.first; SlotRef head = _event->data.second; - if (head.flags & SlotFlag_ORDER_SECOND ) std::swap(tail, head); // guarantee src to be the output + if (tail.flags & SlotFlag_ORDER_SECOND ) std::swap(tail, head); // guarantee src to be the output DirectedEdge edge(tail, head); auto cmd = std::make_shared(edge); curr_file_history->push_command(cmd); diff --git a/src/nodable/gui/NodeView.cpp b/src/nodable/gui/NodeView.cpp index 3a80a9192..4c5f272a3 100644 --- a/src/nodable/gui/NodeView.cpp +++ b/src/nodable/gui/NodeView.cpp @@ -286,7 +286,7 @@ bool NodeView::draw() std::unordered_map count_by_flags{{SlotFlag_NEXT, 0}, {SlotFlag_PREV, 0}}; for ( SlotView& slot_view : m_slot_views ) { - if( slot_view.slot().capacity() && slot_view.slot().type() == SlotFlag_TYPE_CODEFLOW ) + if( slot_view.slot().capacity() && slot_view.slot().type() == SlotFlag_TYPE_CODEFLOW && (node->is_instruction() || node->can_be_instruction() ) ) { int& count = count_by_flags[slot_view.slot().static_flags()]; ImRect rect = get_slot_rect( slot_view, config, count ); From ea9d1e597b23045ce5717af32bf133d152f9052e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Thu, 25 Apr 2024 00:35:36 -0400 Subject: [PATCH 25/27] fix(HybridFile): remove assert --- src/nodable/gui/HybridFile.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nodable/gui/HybridFile.cpp b/src/nodable/gui/HybridFile.cpp index d5fd59f54..fc1b83702 100644 --- a/src/nodable/gui/HybridFile.cpp +++ b/src/nodable/gui/HybridFile.cpp @@ -162,7 +162,6 @@ UpdateResult HybridFile::update() FW_ASSERT(false); } view.set_dirty( false ); - FW_ASSERT( m_graph->is_dirty() == false); } // 2) Handle when graph (not the graph view) changes From 0e953f67c094ccf31d4cb8ab38e4553920b1adb5 Mon Sep 17 00:00:00 2001 From: "berdal84 (win11)" Date: Fri, 26 Apr 2024 01:03:35 -0400 Subject: [PATCH 26/27] fix(fw): hash casts --- src/fw/core/hash.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fw/core/hash.h b/src/fw/core/hash.h index f476f5321..31ea402f1 100644 --- a/src/fw/core/hash.h +++ b/src/fw/core/hash.h @@ -9,10 +9,10 @@ namespace fw { using type = size_t; - inline static size_t hash(char* buffer, size_t buf_size, size_t seed = 0) - { return XXHash32::hash(buffer, buf_size, seed); } + inline static size_t hash(char* buffer, size_t buf_size, uint32_t seed = 0) + { return XXHash32::hash(buffer, (uint64_t)buf_size, seed); } - inline static size_t hash(const char* str, size_t seed = 0) - { return XXHash32::hash(str, strlen(str), seed); } + inline static size_t hash(const char* str, uint32_t seed = 0) + { return XXHash32::hash(str, (uint64_t)strlen(str), seed); } }; } \ No newline at end of file From 5b4899974250d74e6a72b02de1c2e5389922bbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Thu, 25 Apr 2024 23:04:53 -0400 Subject: [PATCH 27/27] fix(Nodlang): hash type --- src/fw/core/hash.h | 6 +++--- src/nodable/core/language/Nodlang.cpp | 7 ++++--- src/nodable/core/language/Nodlang.h | 5 +++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/fw/core/hash.h b/src/fw/core/hash.h index 31ea402f1..8dbf30f49 100644 --- a/src/fw/core/hash.h +++ b/src/fw/core/hash.h @@ -7,12 +7,12 @@ namespace fw { namespace hash { - using type = size_t; + using hash_t = u32_t; - inline static size_t hash(char* buffer, size_t buf_size, uint32_t seed = 0) + inline static hash_t hash(char* buffer, size_t buf_size, uint32_t seed = 0) { return XXHash32::hash(buffer, (uint64_t)buf_size, seed); } - inline static size_t hash(const char* str, uint32_t seed = 0) + inline static hash_t hash(const char* str, uint32_t seed = 0) { return XXHash32::hash(str, (uint64_t)strlen(str), seed); } }; } \ No newline at end of file diff --git a/src/nodable/core/language/Nodlang.cpp b/src/nodable/core/language/Nodlang.cpp index 91eee7a8b..cbd140d2c 100644 --- a/src/nodable/core/language/Nodlang.cpp +++ b/src/nodable/core/language/Nodlang.cpp @@ -1014,7 +1014,8 @@ Token Nodlang::parse_token(char* buffer, size_t buffer_size, size_t& global_curs Token_t type = Token_t::identifier; - auto keyword_found = m_token_t_by_keyword.find( hash::hash(buffer + start_pos, cursor - start_pos) ); + auto hash = hash::hash(buffer + start_pos, cursor - start_pos); + auto keyword_found = m_token_t_by_keyword.find( hash ); if (keyword_found != m_token_t_by_keyword.end()) { // a keyword has priority over identifier @@ -1883,11 +1884,11 @@ std::shared_ptr Nodlang::find_function(const char* _signature_ return nullptr; } - fw::hash::type hash = fw::hash::hash(_signature_hint); + auto hash = fw::hash::hash(_signature_hint); return find_function( hash ); } -std::shared_ptr Nodlang::find_function(fw::hash::type _hash) const +std::shared_ptr Nodlang::find_function(fw::hash::hash_t _hash) const { auto found = m_functions_by_signature.find(_hash); if ( found != m_functions_by_signature.end()) diff --git a/src/nodable/core/language/Nodlang.h b/src/nodable/core/language/Nodlang.h index e42e7f9d6..b3db583f1 100644 --- a/src/nodable/core/language/Nodlang.h +++ b/src/nodable/core/language/Nodlang.h @@ -9,6 +9,7 @@ #include "core/VariableNode.h" #include "fw/core/reflection/reflection" #include "fw/core/system.h" +#include "fw/core/hash.h" #include "core/Token.h" #include "core/TokenRibbon.h" @@ -142,7 +143,7 @@ namespace ndbl{ template void load_library(); // Instantiate a library from its type (uses reflection to get all its static methods). private: - invokable_ptr find_function( unsigned long _hash ) const; + invokable_ptr find_function(fw::hash::hash_t _hash) const; private: struct { std::vector> keywords; @@ -154,7 +155,7 @@ namespace ndbl{ operators_vec m_operators; // the allowed operators (!= implementations). Invokable_vec m_operators_impl; // operators' implementations. Invokable_vec m_functions; // all the functions (including operator's). - std::unordered_map> m_functions_by_signature; // Functions indexed by signature hash + std::unordered_map> m_functions_by_signature; // Functions indexed by signature hash std::unordered_map m_single_char_by_keyword; std::unordered_map m_keyword_by_token_t; // token_t to string (ex: Token_t::keyword_double => "double"). std::unordered_map m_keyword_by_type_id;