From 76de6c4a9065669bb74771a2bab1ad94a2990f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sun, 1 Dec 2024 17:09:50 -0500 Subject: [PATCH 1/5] refactor(tools::hash): renamed Hash, uses constexpr in a unified function --- CMakeLists.txt | 2 +- src/ndbl/core/language/Nodlang.cpp | 14 +++++------ src/ndbl/core/language/Nodlang.h | 2 +- src/tools/core/Hash.h | 39 ++++++++++++++++++++++++++++++ src/tools/core/StateMachine.cpp | 4 +-- src/tools/core/StateMachine.h | 2 +- src/tools/core/hash.h | 16 ------------ 7 files changed, 51 insertions(+), 28 deletions(-) create mode 100644 src/tools/core/Hash.h delete mode 100644 src/tools/core/hash.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f4979ce1..6f63b25e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,7 +161,7 @@ add_library( src/tools/core/assertions.h src/tools/core/format.cpp src/tools/core/format.h - src/tools/core/hash.h + src/tools/core/Hash.h src/tools/core/log.cpp src/tools/core/log.h src/tools/core/math.h diff --git a/src/ndbl/core/language/Nodlang.cpp b/src/ndbl/core/language/Nodlang.cpp index 44010e280..1ad69ca8a 100644 --- a/src/ndbl/core/language/Nodlang.cpp +++ b/src/ndbl/core/language/Nodlang.cpp @@ -17,7 +17,7 @@ #include "tools/core/reflection/reflection" #include "tools/core/format.h" #include "tools/core/log.h" -#include "tools/core/hash.h" +#include "tools/core/Hash.h" #include "ndbl/core/Utils.h" #include "ndbl/core/DirectedEdge.h" @@ -120,7 +120,7 @@ Nodlang::Nodlang(bool _strict) for( auto [keyword, token_t] : m_definition.keywords) { - m_token_t_by_keyword.insert({hash::hash_cstr(keyword), token_t}); + m_token_t_by_keyword.insert({Hash::hash32(keyword), token_t}); m_keyword_by_token_t.insert({token_t, keyword}); } @@ -128,7 +128,7 @@ Nodlang::Nodlang(bool _strict) { m_keyword_by_token_t.insert({token_t, keyword}); m_keyword_by_type_id.insert({type->id(), keyword}); - m_token_t_by_keyword.insert({hash::hash_cstr(keyword), token_t}); + m_token_t_by_keyword.insert({Hash::hash32(keyword), token_t}); m_token_t_by_type_id.insert({type->id(), token_t}); m_type_by_token_t.insert({token_t, type}); } @@ -1029,8 +1029,8 @@ Token Nodlang::parse_token(const char* buffer, size_t buffer_size, size_t& globa Token_t type = Token_t::identifier; - auto hash = hash::hash( buffer + start_pos, cursor - start_pos ); - auto keyword_found = m_token_t_by_keyword.find( hash ); + const u32_t key = Hash::hash32( buffer + start_pos, cursor - start_pos ); + auto keyword_found = m_token_t_by_keyword.find( key ); if (keyword_found != m_token_t_by_keyword.end()) { // a keyword has priority over identifier @@ -1813,8 +1813,8 @@ const IInvokable* Nodlang::find_function(const char* _signature_hint) const return nullptr; } - auto hash = hash::hash_cstr(_signature_hint); - return find_function( hash ); + const u32_t key = Hash::hash32(_signature_hint); + return find_function( key ); } const tools::IInvokable* Nodlang::find_function(u32_t _hash) const diff --git a/src/ndbl/core/language/Nodlang.h b/src/ndbl/core/language/Nodlang.h index 08a773ce0..856c8191d 100644 --- a/src/ndbl/core/language/Nodlang.h +++ b/src/ndbl/core/language/Nodlang.h @@ -7,7 +7,7 @@ #include "tools/core/reflection/reflection" #include "tools/core/System.h" -#include "tools/core/hash.h" +#include "tools/core/Hash.h" #include "tools/core/Optional.h" #include "ndbl/core/VariableNode.h" diff --git a/src/tools/core/Hash.h b/src/tools/core/Hash.h new file mode 100644 index 000000000..fdba7ccea --- /dev/null +++ b/src/tools/core/Hash.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace tools +{ + struct Hash + { + constexpr static u32_t DEFAULT_UNSIZED = -1; + constexpr static u32_t DEFAULT_SEED = 20241984; + + template + static u32_t hash32(const T& data, u32_t size = DEFAULT_UNSIZED ) + { + if constexpr ( !std::is_pointer_v ) // objects + { + if( size == DEFAULT_UNSIZED ) + size = 1; + return _hash32(&data, sizeof(T) * size); + } + else if constexpr ( std::is_same_v ) // cstring + { + if( size == DEFAULT_UNSIZED ) + size = strlen(data); + return _hash32(data, size ); + } + else + { + return _hash32(data, sizeof(void*)); // raw pointers + } + } + + static u32_t _hash32(const void* str, u32_t size, u32_t seed = DEFAULT_SEED) + { + return XXHash32::hash(str, size, seed); + } + }; +} diff --git a/src/tools/core/StateMachine.cpp b/src/tools/core/StateMachine.cpp index 92014cf04..31083900e 100644 --- a/src/tools/core/StateMachine.cpp +++ b/src/tools/core/StateMachine.cpp @@ -67,7 +67,7 @@ State* StateMachine::add_state(const char* _name) void StateMachine::add_state(State* state) { - u32_t key = hash::hash_cstr(state->name); + const u32_t key = Hash::hash32( state->name ); VERIFY(m_state.find(key) == m_state.end(), "State name already exists"); m_state.insert({key, state}); } @@ -86,7 +86,7 @@ void StateMachine::exit_state() State *StateMachine::get_state(const char *name) { - auto it = m_state.find( hash::hash_cstr(name) ); + auto it = m_state.find( Hash::hash32(name) ); if( it != m_state.end()) return it->second; return nullptr; diff --git a/src/tools/core/StateMachine.h b/src/tools/core/StateMachine.h index 339559014..5304cc84c 100644 --- a/src/tools/core/StateMachine.h +++ b/src/tools/core/StateMachine.h @@ -3,7 +3,7 @@ #include #include "types.h" #include "tools/core/assertions.h" -#include "tools/core/hash.h" +#include "tools/core/Hash.h" #include "tools/core/Delegate.h" #ifdef TOOLS_DEBUG diff --git a/src/tools/core/hash.h b/src/tools/core/hash.h deleted file mode 100644 index 63917af4c..000000000 --- a/src/tools/core/hash.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -namespace tools -{ - namespace hash - { - inline static u32_t hash(const void* buf, size_t len, u32_t seed = 0) - { return XXHash32::hash( buf, (uint64_t)len, seed); } - - inline static u32_t hash_cstr(const char* str, u32_t seed = 0) - { return XXHash32::hash(str, (uint64_t)strlen(str), seed); } - }; -} From 93fccc3cca89a1ec5b2e9960643072c964081f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sun, 1 Dec 2024 17:10:30 -0500 Subject: [PATCH 2/5] feat(tools::VariantVector): add Vector and VariantVector --- src/tools/core/VariantVector.h | 217 +++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 src/tools/core/VariantVector.h diff --git a/src/tools/core/VariantVector.h b/src/tools/core/VariantVector.h new file mode 100644 index 000000000..2fb373840 --- /dev/null +++ b/src/tools/core/VariantVector.h @@ -0,0 +1,217 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include // for forward_iterator, see append +#include "Signals.h" +#include "Hash.h" + +namespace tools +{ + // from https://stackoverflow.com/questions/52303316/get-index-by-type-in-stdvariant + template + static constexpr size_t index_of() + { + constexpr size_t SIZE = std::tuple_size_v; + static_assert( INDEX < SIZE, "Type not found in variant"); + + if constexpr ( std::is_same_v< T, std::tuple_element_t>) + return INDEX; + else + return index_of(); + } + + // note: we use std::monostate as first alternative type, when index() == 0, we are in that default state. + template + struct Variant + { + static constexpr size_t index_null = 0; // mono state's + + Variant() = default; + + template + Variant(T data) + : _data( data ) + {} + + template + Variant& operator=(T data) + { + this->_data = data; + return *this; + } + + Variant& operator=(const Variant& other) = default; + + template + T get() const + { return std::get(_data); } + + template + T get_if() const + { + if ( holds_alternative() ) + return std::get(_data); + return {}; + } + + template + constexpr bool holds_alternative() const + { return Variant::index_of() == index(); } + + size_t index() const + { return _data.index(); } + + bool operator==(void* ptr) const // check whether *ptr is held by this AnyPointer, useful for std::find + { return _data == ptr; }; + + bool operator==(const Variant& other) const + { return Hash::hash32(_data) == Hash::hash32(other._data ); } + + bool operator!=(const Variant& other) const + { return !(*this == other); } + + bool empty() const + { return _data.index() == index_null; } + + template + static constexpr size_t index_of() + { return tools::index_of >(); } + + private: + std::variant _data; + }; + + template + struct VariantVector + { + using element_t = Variant; + + enum EventT + { + EventT_Append, + EventT_Remove, + }; + + SIGNAL(on_change, EventT, element_t); + + bool empty() const + { + return _unique_hash.empty(); + } + + bool contains(const element_t& data) const // Constant on average, worst case linear in the size of the container. + { + return _unique_hash.contains( Hash::hash32(data)); + } + + const std::list& data() const + { + return _ordered_elem; + } + + void clear() // O(n) + { + for( const element_t& elem : _ordered_elem ) + { + on_change.emit( EventT_Remove, elem ); + } + _unique_hash.clear(); + _ordered_elem.clear(); + _count_by_index.clear(); + } + + template size_t append( Iterator begin, Iterator end ) + { + size_t count = 0; + + for(auto it = begin; it != end; ++it) + if ( append( element_t{*it} ) ) + ++count; + + return count; + } + + template bool contains() const // O(1), read from cache. + { + constexpr size_t index = element_t::template index_of(); + return _count_by_index.contains( index ); + } + + template size_t count() const // O(1), read from cache. + { + constexpr size_t index = element_t::template index_of(); + if ( _count_by_index.contains( index ) ) + { + return _count_by_index.at( index ); + } + return 0; + } + + bool append(element_t elem) // Constant on average, worst case linear in the size of the container. + { + const auto& [_, inserted] = _unique_hash.insert( Hash::hash32(elem) ); + if ( inserted ) + { + _ordered_elem.push_back(elem); + on_change.emit( EventT_Append, elem ); + _count_by_index[elem.index()]++; + return true; + } + return false; + } + + bool remove(const element_t& data)// Constant on average, worst case linear in the size of the container. + { + if ( _unique_hash.erase( Hash::hash32(data) ) ) + { + auto it = std::find( _ordered_elem.begin(), _ordered_elem.end(), data ); + on_change.emit( EventT_Remove, data ); + _ordered_elem.erase( it ); + _count_by_index[it->index()]--; + return true; + } + return false; + } + + template + T first_of() const // O(n), I suggest you to use contains() once first + { + const size_t _count = count(); + if ( _count == 0 ) + return {}; + + for ( const element_t& elem : _ordered_elem ) + if ( T ptr = elem.template get_if() ) + return ptr; + + return nullptr; + } + + template + std::vector collect() const // O(n), do only a single allocation when necessary + { + const size_t _count = count(); + if ( _count == 0 ) + return {}; + + std::vector result; + const std::type_index type_index{ typeid(T) }; + result.reserve( _count ); // 1 allocation max :) + + // OPTIM: we could use a cache per type_index if necessary ( type_index => list ) + for ( const element_t& elem : _ordered_elem ) + if ( T data = elem.template get_if() ) + result.push_back( data ); + + return result; + } + private: + std::unordered_set _unique_hash{}; // unordered_set because we need uniqueness, furthermore unordered_set::contains is faster than std::find on a list + std::list _ordered_elem{}; // list, because it " supports constant time insertion and removal of elements from anywhere in the container." (see https://devdocs.io/cpp/container/list) + std::unordered_map _count_by_index{}; + }; +} \ No newline at end of file From 2553268c07bb761c7405e2d46bc827d9bbf686a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sun, 1 Dec 2024 17:11:46 -0500 Subject: [PATCH 3/5] feat(ndbl::Graph): add missing destroy_xxx methods --- src/ndbl/core/Graph.cpp | 24 +++++++++++++++++++++++- src/ndbl/core/Graph.h | 4 +++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/ndbl/core/Graph.cpp b/src/ndbl/core/Graph.cpp index 059b930e6..6e628029e 100644 --- a/src/ndbl/core/Graph.cpp +++ b/src/ndbl/core/Graph.cpp @@ -611,4 +611,26 @@ std::vector Graph::scopes() if ( node->scope() ) result.push_back( node->scope() ); return result; -} \ No newline at end of file +} + +void Graph::destroy_next_frame(Scope *scope) +{ + const bool with_inputs = true; + destroy_next_frame_ex( scope->owner(), with_inputs ); + + for ( Node* node : scope->child() ) + destroy_next_frame_ex( node, with_inputs ); + + for ( Scope* scope : scope->partition() ) + destroy_next_frame( scope ); +} + +void Graph::destroy_next_frame_ex(Node *node, bool with_inputs) +{ + m_node_to_delete.insert(node); + + if ( with_inputs ) + for ( auto input : node->inputs() ) + if ( node->scope() == input->scope() ) + destroy_next_frame_ex( input, with_inputs ); +} diff --git a/src/ndbl/core/Graph.h b/src/ndbl/core/Graph.h index bf44aa771..326025911 100644 --- a/src/ndbl/core/Graph.h +++ b/src/ndbl/core/Graph.h @@ -95,7 +95,9 @@ namespace ndbl std::set root_scopes(); NodeRegistry& nodes() {return m_node_registry;} const NodeRegistry& nodes()const {return m_node_registry;} - void destroy_next_frame(Node* node) { m_node_to_delete.insert(node ); } + void destroy_next_frame_ex(Node* node, bool with_inputs ); + void destroy_next_frame(Node* node) { destroy_next_frame_ex( node, false ); } + void destroy_next_frame(Scope* scope); template inline VariableNode* create_variable_decl(const char* _name = "var"){ return create_variable_decl(tools::type::get(), _name); } template inline LiteralNode* create_literal() { return create_literal( tools::type::get()); } From ea94a2e3daed7fadc35036ef5afac5f87917109b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sun, 1 Dec 2024 17:12:38 -0500 Subject: [PATCH 4/5] feat(ndbl): handle ScopeView/NodeView delete/arrange, uses tools::Variant/VariantVector --- src/ndbl/gui/Action.h | 4 +- src/ndbl/gui/Event.h | 5 +- src/ndbl/gui/GraphView.cpp | 245 ++++++++++++++++++++--------------- src/ndbl/gui/GraphView.h | 21 ++- src/ndbl/gui/Nodable.cpp | 45 +++++-- src/ndbl/gui/NodableView.cpp | 29 +++-- src/ndbl/gui/ScopeView.cpp | 9 ++ src/ndbl/gui/ScopeView.h | 2 + src/ndbl/gui/Selection.h | 66 ---------- src/ndbl/gui/ViewItem.h | 70 ---------- 10 files changed, 217 insertions(+), 279 deletions(-) delete mode 100644 src/ndbl/gui/Selection.h delete mode 100644 src/ndbl/gui/ViewItem.h diff --git a/src/ndbl/gui/Action.h b/src/ndbl/gui/Action.h index e9febda4c..7719cd9ea 100644 --- a/src/ndbl/gui/Action.h +++ b/src/ndbl/gui/Action.h @@ -12,8 +12,8 @@ namespace ndbl // 1) Basic actions (simple events) - using Action_DeleteNode = Action; - using Action_ArrangeNode = Action; + using Action_DeleteNode = Action; + using Action_ArrangeNode = Action; using Action_ToggleFolding = Action; using Action_SelectNext = Action; using Action_ToggleIsolate = Action; diff --git a/src/ndbl/gui/Event.h b/src/ndbl/gui/Event.h index f104d3548..6080a28d5 100644 --- a/src/ndbl/gui/Event.h +++ b/src/ndbl/gui/Event.h @@ -9,7 +9,6 @@ #include "Event.h" #include "FrameMode.h" #include "SlotView.h" -#include "Selection.h" namespace ndbl { @@ -65,8 +64,8 @@ namespace ndbl Node* node; }; using Event_DeleteEdge = tools::Event; - using Event_DeleteNode = tools::Event; - using Event_ArrangeNode = tools::Event; + using Event_DeleteSelection = tools::Event; + using Event_ArrangeSelection = tools::Event; using Event_SelectNext = tools::Event; enum ToggleFoldingMode diff --git a/src/ndbl/gui/GraphView.cpp b/src/ndbl/gui/GraphView.cpp index e43207cb1..904c71150 100644 --- a/src/ndbl/gui/GraphView.cpp +++ b/src/ndbl/gui/GraphView.cpp @@ -70,6 +70,8 @@ GraphView::GraphView(Graph* graph) CONNECT(graph->on_add , &GraphView::decorate_node ); CONNECT(graph->on_change , &GraphView::_on_graph_change); CONNECT(graph->on_reset , &GraphView::reset); + + CONNECT(m_selection.on_change, &GraphView::_on_selection_change); } GraphView::~GraphView() @@ -251,7 +253,9 @@ bool GraphView::draw(float dt) }; ImGuiEx::DrawWire(id, draw_list, segment, code_flow_style); if (ImGui::GetHoveredID() == id ) - m_hovered = {tail, head}; + { + m_hovered = EdgeView{tail, head}; + } } } } @@ -331,7 +335,9 @@ bool GraphView::draw(float dt) ImGuiID id = make_wire_id(slot_view_out->slot, slot_in); ImGuiEx::DrawWire(id, draw_list, segment, style); if (ImGui::GetHoveredID() == id) - m_hovered = {slot_view_out, slot_view_in}; + { + m_hovered = EdgeView{slot_view_out, slot_view_in}; + } } } } @@ -353,7 +359,7 @@ bool GraphView::draw(float dt) { if ( nodeview->m_hovered_slotview != nullptr) { - m_hovered = {nodeview->m_hovered_slotview}; + m_hovered = nodeview->m_hovered_slotview; } else m_hovered = {nodeview}; @@ -395,9 +401,9 @@ bool GraphView::draw(float dt) { if (ImGui::Begin("GraphViewToolStateMachine")) { - ImGui::Text("current_tool: %s", m_state_machine.get_current_state_name()); - ImGui::Text("m_focused.type: %i", m_focused.type ); - ImGui::Text("m_hovered.type: %i", m_hovered.type ); + ImGui::Text("current_tool: %s" , m_state_machine.get_current_state_name()); + ImGui::Text("m_focused.type: %zu", m_focused.index() ); + ImGui::Text("m_hovered.type: %zu", m_hovered.index() ); Vec2 mouse_pos = ImGui::GetMousePos(); ImGui::Text("m_mouse_pos: (%f, %f)", mouse_pos.x, mouse_pos.y); } @@ -624,8 +630,10 @@ void GraphView::frame_nodes(FrameMode mode ) case FRAME_SELECTION_ONLY: { - if ( !m_selection.node().empty()) - frame_views(m_selection.node(), CENTER); + if ( m_selection.contains()) + { + frame_views( m_selection.collect(), CENTER ); + } break; } default: @@ -638,6 +646,33 @@ void GraphView::_on_graph_change() m_physics_dirty = true; } +void GraphView::_on_selection_change(Selection::EventT type, Element elem) +{ + bool selected = type == Selection::EventT_Append; + + switch ( elem.index() ) + { + case Element::index_of(): + { + elem.get()->state().selected = selected; + break; + } + case Element::index_of(): + { + elem.get()->set_selected( selected ); + break; + } + case Element::index_of(): + { + break; + } + default: + { + ASSERT(false); // unhandled case + } + } +} + void GraphView::reset() { if ( m_graph->is_empty() ) @@ -697,10 +732,17 @@ void GraphView::draw_create_node_context_menu(CreateNodeCtxMenu& menu, SlotView* void GraphView::drag_state_enter() { - for ( NodeView* view : m_selection.node() ) - view->set_pinned(); - for ( ScopeView* view : m_selection.scope() ) - view->set_pinned(); + for( const Element& elem : m_selection.data() ) + { + if ( auto nodeview = elem.get_if() ) + { + nodeview->set_pinned(); + } + else if ( auto scopeview = elem.get_if() ) + { + scopeview->set_pinned(); + } + } } void GraphView::drag_state_tick() @@ -708,19 +750,15 @@ void GraphView::drag_state_tick() const Vec2 delta = ImGui::GetMouseDragDelta(); ImGui::ResetMouseDragDelta(); - switch ( m_focused.type ) + if ( auto scopeview = m_focused.get_if() ) { - case ViewItemType_SCOPE: - { - m_focused.scopeview->translate( delta ); - break; - } - - default: + scopeview->translate( delta ); + } + else if ( auto nodeview = m_focused.get_if() ) + { + for ( NodeView* view : m_selection.collect() ) { - for (NodeView* view : m_selection.node() ) - view->spatial_node().translate( delta ); - break; + view->spatial_node().translate(delta); } } @@ -758,17 +796,18 @@ void GraphView::cursor_state_tick() if ( ImGui::IsWindowAppearing()) m_create_node_menu.flag_to_be_reset(); - switch ( m_focused.type ) + switch ( m_focused.index() ) { - case ViewItemType_NULL: + case Element::index_null: { draw_create_node_context_menu(m_create_node_menu); break; } - case ViewItemType_SCOPE: + case Element::index_of(): { - Node* node = m_focused.scopeview->scope()->node(); + auto scopeview = m_focused.get(); + Node* node = scopeview->scope()->node(); NodeView* nodeview = node->get_component(); if ( ImGui::MenuItem( nodeview->expanded() ? "Collapse Scope" : "Expand Scope" ) ) { @@ -777,7 +816,7 @@ void GraphView::cursor_state_tick() if ( ImGui::MenuItem("Delete Scope") ) { - auto event = new Event_DeleteNode({ m_focused.scopeview->node() }); + auto event = new Event_DeleteSelection({scopeview->node() }); get_event_manager()->dispatch(event); } @@ -785,7 +824,7 @@ void GraphView::cursor_state_tick() { // Get descendent scopes std::set children; - Scope::get_descendent( children, m_focused.scopeview->scope(), ScopeFlags_INCLUDE_SELF ); + Scope::get_descendent( children, scopeview->scope(), ScopeFlags_INCLUDE_SELF ); // Extract node views from each descendent std::vector views; @@ -802,8 +841,7 @@ void GraphView::cursor_state_tick() } // Replace selection m_selection.clear(); - for(NodeView* view : views) - m_selection.append(view ) ; + m_selection.append( views.begin(), views.end() ) ; get_event_manager()->dispatch({this}); } @@ -812,47 +850,52 @@ void GraphView::cursor_state_tick() break; } - case ViewItemType_EDGE: + + case Element::index_of(): { + auto edge = m_focused.get(); if ( ImGui::MenuItem(ICON_FA_TRASH " Delete Edge") ) { auto* event = new Event_DeleteEdge(); - event->data.first = m_focused.edge.slot[0]->slot; - event->data.second = m_focused.edge.slot[1]->slot; + event->data.first = edge.head->slot; + event->data.second = edge.tail->slot; get_event_manager()->dispatch( event ); } break; } - case ViewItemType_SLOT: + case Element::index_of(): { if ( ImGui::MenuItem(ICON_FA_TRASH " Disconnect Edges") ) { auto* event = new Event_SlotDisconnectAll(); - event->data.first = m_focused.slotview->slot; + event->data.first = m_focused.get()->slot; get_event_manager()->dispatch( event ); } break; } - case ViewItemType_NODE: + + case Element::index_of(): { + auto nodeview = m_focused.get(); + if ( ImGui::MenuItem(ICON_FA_TRASH " Delete Node") ) { - auto* event = new Event_DeleteNode (); - event->data.node = m_focused.nodeview->node(); + auto* event = new Event_DeleteSelection (); + event->data.node = nodeview->node(); get_event_manager()->dispatch( event ); } if ( ImGui::MenuItem(ICON_FA_MAP_PIN " Pin/Unpin Node") ) { - m_focused.nodeview->set_pinned( !m_focused.nodeview->pinned() ); + nodeview->set_pinned( !nodeview->pinned() ); } if ( ImGui::MenuItem(ICON_FA_WINDOW_RESTORE " Arrange Node") ) { - m_focused.nodeview->arrange_recursively(); + nodeview->arrange_recursively(); } break; @@ -865,10 +908,10 @@ void GraphView::cursor_state_tick() if ( !m_focused.empty() ) return; } - - switch (m_hovered.type) + bool select_hovered = false; + switch ( m_hovered.index() ) { - case ViewItemType_SLOT: + case Element::index_of(): { if ( ImGui::IsMouseClicked(1) ) { @@ -883,9 +926,8 @@ void GraphView::cursor_state_tick() break; } - case ViewItemType_NODE: + case Element::index_of(): { - bool select_hovered = false; if ( ImGui::IsMouseClicked(1) ) { m_focused = m_hovered; @@ -898,7 +940,7 @@ void GraphView::cursor_state_tick() } else if (ImGui::IsMouseDoubleClicked(0)) { - m_hovered.nodeview->expand_toggle(); + m_hovered.get()->expand_toggle(); m_focused = m_hovered; } else if (ImGui::IsMouseDragging(0)) @@ -907,19 +949,10 @@ void GraphView::cursor_state_tick() m_focused = m_hovered; m_state_machine.change_state(DRAG_STATE); } - - if ( select_hovered ) - { - const bool ctrl_pressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); - if ( !ctrl_pressed ) // replace when Ctrl is pressed - m_selection.clear(); - m_selection.append( m_hovered.nodeview ) ; - get_event_manager()->dispatch({this}); - } break; } - case ViewItemType_EDGE: + case Element::index_of(): { if (ImGui::IsMouseDragging(0, 0.1f)) { @@ -933,9 +966,8 @@ void GraphView::cursor_state_tick() break; } - case ViewItemType_SCOPE: + case Element::index_of(): { - bool select_hovered = false; if (ImGui::IsMouseReleased(0) ) { select_hovered = true; @@ -952,19 +984,10 @@ void GraphView::cursor_state_tick() select_hovered = true; m_state_machine.change_state(DRAG_STATE); } - - if ( select_hovered ) - { - const bool ctrl_pressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); - if ( !ctrl_pressed ) // replace when Ctrl is pressed - m_selection.clear(); - m_selection.append( m_hovered.scopeview ) ; - get_event_manager()->dispatch({this}); - } break; } - case ViewItemType_NULL: + case Element::index_null: { if ( ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows) ) { @@ -989,20 +1012,46 @@ void GraphView::cursor_state_tick() default: VERIFY(false, "Unhandled case, must be implemented!"); } + + if ( select_hovered ) + { + const bool ctrl_pressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + + if ( ctrl_pressed ) + { + if ( m_selection.contains(m_hovered) ) + { + m_selection.remove( m_hovered ); + } + else + { + m_selection.append( m_hovered ); + } + } + else + { + m_selection.clear(); + m_selection.append( m_hovered ); + } + + get_event_manager()->dispatch({this}); + } } //----------------------------------------------------------------------------- void GraphView::line_state_enter() { - ASSERT(m_focused.type == ViewItemType_SLOT); - ASSERT(m_focused.slotview != nullptr); + ASSERT( m_focused.holds_alternative() ); } void GraphView::line_state_tick() { - Vec2 mouse_pos_snapped = m_hovered.type == ViewItemType_SLOT ? m_hovered.slotview->spatial_node().position(WORLD_SPACE) - : Vec2{ImGui::GetMousePos()}; + Vec2 mouse_pos_snapped = Vec2{ImGui::GetMousePos()}; + if ( auto slotview = m_hovered.get_if() ) + { + mouse_pos_snapped = slotview->spatial_node().position(WORLD_SPACE); + } // Contextual menu if ( ImGui::BeginPopup(CONTEXT_POPUP) ) @@ -1013,7 +1062,7 @@ void GraphView::line_state_tick() m_create_node_menu.flag_to_be_reset(); if ( m_hovered.empty() ) - draw_create_node_context_menu(m_create_node_menu, m_focused.slotview); + draw_create_node_context_menu(m_create_node_menu, m_focused.get() ); if ( ImGui::IsMouseClicked(0) || ImGui::IsMouseClicked(1) ) m_state_machine.exit_state(); @@ -1022,33 +1071,25 @@ void GraphView::line_state_tick() } else if ( ImGui::IsMouseReleased(0) ) { - switch (m_hovered.type) + if ( m_hovered.holds_alternative() ) { - case ViewItemType_SLOT: + if ( m_focused != m_hovered ) { - if ( m_focused.slotview == m_hovered.slotview ) - break; auto event = new Event_SlotDropped(); - event->data.first = m_focused.slotview->slot; - event->data.second = m_hovered.slotview->slot; + event->data.first = m_focused.get()->slot; + event->data.second = m_hovered.get()->slot; get_event_manager()->dispatch(event); m_state_machine.exit_state(); - break; - } - - case ViewItemType_SCOPE: - case ViewItemType_EDGE: - case ViewItemType_NODE: - case ViewItemType_NULL: // ...on background - { - ImGui::OpenPopup(CONTEXT_POPUP); - break; } } + else + { + ImGui::OpenPopup(CONTEXT_POPUP); + } } // Draw a temporary wire from focused/dragged slotview to the mouse cursor - draw_wire_from_slot_to_pos(m_focused.slotview, mouse_pos_snapped ); + draw_wire_from_slot_to_pos( m_focused.get(), mouse_pos_snapped ); } void GraphView::line_state_leave() @@ -1082,16 +1123,18 @@ void GraphView::roi_state_tick() if (ImGui::IsMouseReleased(0)) { + // Get the views included in the ROI + std::deque nodeviews_inside_roi; + for ( Node* node : graph()->nodes() ) + if ( auto view = node->get_component() ) + if ( Rect::contains(roi, view->get_rect()) ) + nodeviews_inside_roi.push_back( view ); + + // Select them const bool ctrl_pressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); if ( !ctrl_pressed ) m_selection.clear(); - - // Select the views included in the ROI - for ( Node* node : graph()->nodes() ) - if ( auto v = node->get_component() ) - if (Rect::contains(roi, v->get_rect())) - m_selection.append(v); - + m_selection.append(nodeviews_inside_roi.begin(), nodeviews_inside_roi.end() ); get_event_manager()->dispatch({this}); m_state_machine.exit_state(); @@ -1119,11 +1162,11 @@ void GraphView::update(float dt) void GraphView::_set_hovered(ScopeView* scope_view) { - if ( m_hovered.type != ViewItemType_SCOPE ) + if ( !m_hovered.holds_alternative() ) m_hovered = scope_view; - else if ( !m_hovered.scopeview ) + else if ( m_hovered.empty() ) m_hovered = scope_view; - else if ( scope_view->depth() >= m_hovered.scopeview->depth() ) + else if ( scope_view->depth() >= m_hovered.get()->depth() ) m_hovered = scope_view; } diff --git a/src/ndbl/gui/GraphView.h b/src/ndbl/gui/GraphView.h index a62c45171..c0e601c6b 100644 --- a/src/ndbl/gui/GraphView.h +++ b/src/ndbl/gui/GraphView.h @@ -5,22 +5,21 @@ #include #include -#include "tools/gui/ViewState.h" // base class #include "tools/core/reflection/reflection" +#include "tools/core/VariantVector.h" +#include "tools/gui/ViewState.h" +#include "tools/gui/geometry/Pivots.h" #include "ndbl/core/NodeComponent.h" // base class #include "ndbl/core/Scope.h" #include "Action.h" -#include "Selection.h" #include "NodeView.h" #include "SlotView.h" #include "types.h" -#include "ViewItem.h" #include "tools/core/StateMachine.h" #include "CreateNodeCtxMenu.h" #include "ScopeView.h" -#include "tools/gui/geometry/Pivots.h" namespace ndbl { @@ -29,11 +28,10 @@ namespace ndbl class Graph; using tools::Vec2; - enum SelectionMode - { - SelectionMode_ADD = 0, - SelectionMode_REPLACE = 1, - }; + struct EdgeView { SlotView* tail = nullptr; SlotView* head = nullptr; } ; + using Selection = tools::VariantVector ; + using Element = Selection::element_t; + class GraphView { @@ -63,8 +61,8 @@ namespace ndbl static void draw_wire_from_slot_to_pos(SlotView *from, const Vec2 &end_pos); private: CreateNodeCtxMenu m_create_node_menu; - ViewItem m_hovered; - ViewItem m_focused; + Element m_hovered; + Element m_focused; Selection m_selection; tools::ViewState m_view_state; Graph* m_graph; @@ -75,6 +73,7 @@ namespace ndbl void _update(float dt, u16_t iterations); void _update(float dt); void _on_graph_change(); + void _on_selection_change(Selection::EventT, Element); void frame_views(const std::vector&, const Vec2& pivot ); void draw_create_node_context_menu(CreateNodeCtxMenu& menu, SlotView* dragged_slotview = nullptr ); void create_constraints__align_top_recursively(const std::vector& unfiltered_follower, ndbl::Node *leader); diff --git a/src/ndbl/gui/Nodable.cpp b/src/ndbl/gui/Nodable.cpp index b94c874a5..10ac6e3e2 100644 --- a/src/ndbl/gui/Nodable.cpp +++ b/src/ndbl/gui/Nodable.cpp @@ -197,33 +197,52 @@ void Nodable::update() m_current_file->view.refresh_overlay(Condition_ENABLE_IF_HAS_NO_SELECTION ); break; } - case Event_DeleteNode::id: + case Event_DeleteSelection::id: { if ( graph_view ) - for(NodeView* view : graph_view->selection().node() ) - graph_view->graph()->destroy_next_frame(view->node()); + { + for(const Element& elem : graph_view->selection().data() ) + { + if ( auto nodeview = elem.get_if() ) + graph_view->graph()->destroy_next_frame( nodeview->node() ); + else if ( auto scopeview = elem.get_if() ) + graph_view->graph()->destroy_next_frame( scopeview->scope() ); + } + } + break; } - case Event_ArrangeNode::id: + case Event_ArrangeSelection::id: { if ( graph_view ) - for(NodeView* view : graph_view->selection().node() ) - view->arrange_recursively(); + { + for(const Element& elem : graph_view->selection().data() ) + { + switch ( elem.index() ) + { + case Element::index_of(): + elem.get()->arrange_recursively(); + break; + case Element::index_of(): + elem.get()->arrange_content(); + break; + } + } + } + break; } case Event_SelectNext::id: { - if (graph_view && !graph_view->selection().empty()) + if ( graph_view && graph_view->selection().contains() ) { graph_view->selection().clear(); - if (!graph_view->selection().empty()) - for(NodeView* view : graph_view->selection().node() ) - for (NodeView* successor : Utils::get_components( view->node()->flow_outputs() ) ) - graph_view->selection().append( successor ); + for(NodeView* view : graph_view->selection().collect() ) + for (NodeView* successor : Utils::get_components( view->node()->flow_outputs() ) ) + graph_view->selection().append( successor ); } - break; } @@ -232,7 +251,7 @@ void Nodable::update() if ( graph_view ) break; - for(NodeView* view : graph_view->selection().node()) + for( NodeView* view : graph_view->selection().collect() ) { auto _event = reinterpret_cast(event); _event->data.mode == RECURSIVELY ? view->expand_toggle_rec() diff --git a/src/ndbl/gui/NodableView.cpp b/src/ndbl/gui/NodableView.cpp index bbe82cc1d..388f82507 100644 --- a/src/ndbl/gui/NodableView.cpp +++ b/src/ndbl/gui/NodableView.cpp @@ -63,8 +63,8 @@ void NodableView::init(Nodable * _app) tools::ActionManager* action_manager = get_action_manager(); ASSERT(action_manager != nullptr); // initialized by base_view // (With shortcut) - 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("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 } ); @@ -280,7 +280,7 @@ void NodableView::draw() if (ImGui::MenuItem("Reset", NULL, false, interpreter_is_stopped)) event_manager->dispatch( EventID_RESET_GRAPH ); - ImGuiEx::MenuItem_EventTrigger(false, has_selection); + ImGuiEx::MenuItem_EventTrigger(false, has_selection); ImGuiEx::MenuItem_EventTrigger(false, has_selection); if (ImGui::MenuItem("Expand/Collapse recursive", nullptr, false, has_selection)) @@ -526,17 +526,20 @@ bool NodableView::draw_node_properties_window() if( File* current_file = m_app->get_current_file() ) { const GraphView* graph_view = current_file->graph().view(); // Graph can't be null - const size_t selection_size = graph_view->selection().node().size(); - if ( selection_size == 1) + switch ( graph_view->selection().count() ) { - ImGui::Indent(10.0f); - NodeView* first_node_view = graph_view->selection().node().front(); - changed |= NodeView::draw_as_properties_panel(first_node_view, &m_show_advanced_node_properties); - } - else if ( selection_size > 1) - { - ImGui::Indent(10.0f); - ImGui::Text("Multi-Selection"); + case 0: + break; + case 1: + { + ImGui::Indent(10.0f); + auto* first_nodeview = graph_view->selection().first_of(); + changed |= NodeView::draw_as_properties_panel(first_nodeview, &m_show_advanced_node_properties); + break; + } + default: + ImGui::Indent(10.0f); + ImGui::Text("Multi-Selection"); } } } diff --git a/src/ndbl/gui/ScopeView.cpp b/src/ndbl/gui/ScopeView.cpp index eddce0432..baeb1d336 100644 --- a/src/ndbl/gui/ScopeView.cpp +++ b/src/ndbl/gui/ScopeView.cpp @@ -60,6 +60,7 @@ void ScopeView::update(float dt, ScopeViewFlags flags) | NodeViewFlag_WITH_PINNED; Rect node_rect = nodeview->get_rect_ex(WORLD_SPACE, nodeview_flags); m_content_rect = Rect::merge(m_content_rect, node_rect); + m_wrapped_node_view.push_back(nodeview); }; if ( !m_scope->is_partition() ) @@ -237,3 +238,11 @@ void ScopeView::draw_scope_tree_ex(Scope *scope) } ImGui::PopID(); } + +void ScopeView::arrange_content() +{ + for( NodeView* view : m_wrapped_node_view ) + { + view->arrange_recursively(); + } +} diff --git a/src/ndbl/gui/ScopeView.h b/src/ndbl/gui/ScopeView.h index f014b7ce9..92611f290 100644 --- a/src/ndbl/gui/ScopeView.h +++ b/src/ndbl/gui/ScopeView.h @@ -46,7 +46,9 @@ namespace ndbl bool pinned() const; void set_pinned(bool b = true); const Rect& content_rect() const { return m_content_rect; } + void arrange_content(); static void draw_scope_tree(Scope* scope); + private: static void draw_scope_tree_ex(Scope* scope); void on_reset_parent(Scope*); diff --git a/src/ndbl/gui/Selection.h b/src/ndbl/gui/Selection.h deleted file mode 100644 index 431938cbd..000000000 --- a/src/ndbl/gui/Selection.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#include "NodeView.h" -#include "ScopeView.h" -#include - -namespace ndbl -{ - struct Selection - { - void append(NodeView* view) - { - _node.push_back(view); - view->state().selected = true; - } - - void append(ScopeView* view) - { - _scope.push_back(view); - view->state().selected = true; - } - - void remove(NodeView* view) - { - view->state().selected = false; - _node.erase( std::find(_node.begin(), _node.end(), view) ); - } - - void remove(ScopeView* view) - { - view->state().selected = false; - _scope.erase( std::find(_scope.begin(), _scope.end(), view) ); - } - - bool empty() const - { - return _node.empty() && _scope.empty(); - } - - bool contains(NodeView* view) const - { - return std::find(_node.begin(), _node.end(), view) != _node.end(); - } - - void clear() - { - for( NodeView* view : _node ) - { - view->state().selected = false; - } - - for( ScopeView* view : _scope ) - { - view->state().selected = false; - } - - _node.clear(); - _scope.clear(); - } - - const std::vector& node() const { return _node; }; - const std::vector& scope() const { return _scope; }; - private: - std::vector _node; - std::vector _scope; - }; -} \ No newline at end of file diff --git a/src/ndbl/gui/ViewItem.h b/src/ndbl/gui/ViewItem.h deleted file mode 100644 index c81917667..000000000 --- a/src/ndbl/gui/ViewItem.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once -#include -#include -#include - -namespace ndbl -{ - // forward declarations - struct SlotView; - class NodeView; - - enum ViewItemType - { - ViewItemType_NULL = 0, - ViewItemType_SLOT, - ViewItemType_EDGE, - ViewItemType_SCOPE, - ViewItemType_NODE, - }; - - // Simple structure to store a NodeView, a SlotView, an Edge, or nothing. - struct ViewItem - { - ViewItemType type = ViewItemType_NULL; - - union - { - struct { - void *ptr1; - void *ptr2; - } raw_data; - - NodeView* nodeview; - SlotView* slotview; - ScopeView* scopeview; - - struct { - SlotView* slot[2]; - } edge; - - }; - - ViewItem() - : raw_data({nullptr, nullptr}) - {} - - ViewItem(SlotView* slotview) - : type(ViewItemType_SLOT) - , raw_data({slotview, nullptr}) - {} - - ViewItem(ScopeView* scopeview) - : type(ViewItemType_SCOPE) - , raw_data({scopeview, nullptr}) - {} - - ViewItem(NodeView* nodeview) - : type(ViewItemType_NODE) - , raw_data({nodeview, nullptr}) - {} - - ViewItem(SlotView* edge_start, SlotView* edge_end) - : type(ViewItemType_EDGE) - , raw_data({edge_start, edge_end}) - {} - - bool empty() const - { return type == ViewItemType_NULL; } - }; -} \ No newline at end of file From 4c53440b59e2129750febfaa5e64fa453f41c5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9renger=20Dalle-Cort?= Date: Sun, 1 Dec 2024 18:39:42 -0500 Subject: [PATCH 5/5] fix(GraphView): issues with dragging and selection --- src/ndbl/gui/Action.h | 1 - src/ndbl/gui/Event.h | 7 --- src/ndbl/gui/GraphView.cpp | 110 ++++++++++++----------------------- src/ndbl/gui/NodableView.cpp | 1 - 4 files changed, 38 insertions(+), 81 deletions(-) diff --git a/src/ndbl/gui/Action.h b/src/ndbl/gui/Action.h index 7719cd9ea..2072ae9ed 100644 --- a/src/ndbl/gui/Action.h +++ b/src/ndbl/gui/Action.h @@ -17,7 +17,6 @@ namespace ndbl 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) diff --git a/src/ndbl/gui/Event.h b/src/ndbl/gui/Event.h index 6080a28d5..f40b33908 100644 --- a/src/ndbl/gui/Event.h +++ b/src/ndbl/gui/Event.h @@ -79,13 +79,6 @@ namespace ndbl }; using Event_ToggleFolding = tools::Event; - struct EventPayload_SelectionChange - { - GraphView* graph_view; - // Selection old_selection; was unused - }; - using Event_GraphViewSelectionChanged = tools::Event; - struct EventPayload_CreateNode { CreateNodeType node_type; // The note type to create diff --git a/src/ndbl/gui/GraphView.cpp b/src/ndbl/gui/GraphView.cpp index 904c71150..3c44936a9 100644 --- a/src/ndbl/gui/GraphView.cpp +++ b/src/ndbl/gui/GraphView.cpp @@ -734,14 +734,10 @@ void GraphView::drag_state_enter() { for( const Element& elem : m_selection.data() ) { - if ( auto nodeview = elem.get_if() ) - { + if ( auto* nodeview = elem.get_if() ) nodeview->set_pinned(); - } - else if ( auto scopeview = elem.get_if() ) - { + else if ( auto* scopeview = elem.get_if() ) scopeview->set_pinned(); - } } } @@ -750,16 +746,12 @@ void GraphView::drag_state_tick() const Vec2 delta = ImGui::GetMouseDragDelta(); ImGui::ResetMouseDragDelta(); - if ( auto scopeview = m_focused.get_if() ) + for ( const Selection::element_t& elem : m_selection.data() ) { - scopeview->translate( delta ); - } - else if ( auto nodeview = m_focused.get_if() ) - { - for ( NodeView* view : m_selection.collect() ) - { - view->spatial_node().translate(delta); - } + if ( auto* nodeview = elem.get_if() ) + nodeview->spatial_node().translate(delta); + else if ( auto* scopeview = elem.get_if() ) + scopeview->translate(delta); } if ( ImGui::IsMouseReleased(0) ) @@ -842,7 +834,6 @@ void GraphView::cursor_state_tick() // Replace selection m_selection.clear(); m_selection.append( views.begin(), views.end() ) ; - get_event_manager()->dispatch({this}); } ImGui::Separator(); @@ -908,7 +899,7 @@ void GraphView::cursor_state_tick() if ( !m_focused.empty() ) return; } - bool select_hovered = false; + switch ( m_hovered.index() ) { case Element::index_of(): @@ -926,32 +917,6 @@ void GraphView::cursor_state_tick() break; } - case Element::index_of(): - { - if ( ImGui::IsMouseClicked(1) ) - { - m_focused = m_hovered; - ImGui::OpenPopup(CONTEXT_POPUP); - } - else if (ImGui::IsMouseReleased(0) ) - { - select_hovered = true; - m_focused = m_hovered; - } - else if (ImGui::IsMouseDoubleClicked(0)) - { - m_hovered.get()->expand_toggle(); - m_focused = m_hovered; - } - else if (ImGui::IsMouseDragging(0)) - { - select_hovered = true; - m_focused = m_hovered; - m_state_machine.change_state(DRAG_STATE); - } - break; - } - case Element::index_of(): { if (ImGui::IsMouseDragging(0, 0.1f)) @@ -966,11 +931,36 @@ void GraphView::cursor_state_tick() break; } + case Element::index_of(): case Element::index_of(): { + const bool ctrl_pressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + auto handle_selection = [&](Element& hovered_elem, bool want_selected, bool allow_multi_selection ) + { + if ( want_selected ) + { + if ( !allow_multi_selection ) + m_selection.clear(); + m_selection.append( hovered_elem ); + } + else if ( !allow_multi_selection ) + { + m_selection.remove( hovered_elem ); + } + else + { + m_selection.clear(); + } + }; + if (ImGui::IsMouseReleased(0) ) { - select_hovered = true; + bool want_selected = true; + if ( ctrl_pressed ) + { + want_selected = !m_selection.contains( m_hovered ); + } + handle_selection( m_hovered, want_selected, ctrl_pressed ); m_focused = m_hovered; } else if (ImGui::IsMouseClicked(1)) @@ -980,8 +970,9 @@ void GraphView::cursor_state_tick() } else if ( ImGui::IsMouseDragging(0) ) { + if ( bool wants_selection = !m_selection.contains( m_hovered ) ) + handle_selection( m_hovered, wants_selection, ctrl_pressed ); // always allow multi selection in that case m_focused = m_hovered; - select_hovered = true; m_state_machine.change_state(DRAG_STATE); } break; @@ -1012,30 +1003,6 @@ void GraphView::cursor_state_tick() default: VERIFY(false, "Unhandled case, must be implemented!"); } - - if ( select_hovered ) - { - const bool ctrl_pressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); - - if ( ctrl_pressed ) - { - if ( m_selection.contains(m_hovered) ) - { - m_selection.remove( m_hovered ); - } - else - { - m_selection.append( m_hovered ); - } - } - else - { - m_selection.clear(); - m_selection.append( m_hovered ); - } - - get_event_manager()->dispatch({this}); - } } //----------------------------------------------------------------------------- @@ -1124,18 +1091,17 @@ void GraphView::roi_state_tick() if (ImGui::IsMouseReleased(0)) { // Get the views included in the ROI - std::deque nodeviews_inside_roi; + std::set nodeviews_inside_roi; for ( Node* node : graph()->nodes() ) if ( auto view = node->get_component() ) if ( Rect::contains(roi, view->get_rect()) ) - nodeviews_inside_roi.push_back( view ); + nodeviews_inside_roi.insert( view ); // Select them const bool ctrl_pressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); if ( !ctrl_pressed ) m_selection.clear(); m_selection.append(nodeviews_inside_roi.begin(), nodeviews_inside_roi.end() ); - get_event_manager()->dispatch({this}); m_state_machine.exit_state(); } diff --git a/src/ndbl/gui/NodableView.cpp b/src/ndbl/gui/NodableView.cpp index 388f82507..24e8aae7a 100644 --- a/src/ndbl/gui/NodableView.cpp +++ b/src/ndbl/gui/NodableView.cpp @@ -77,7 +77,6 @@ void NodableView::init(Nodable * _app) action_manager->new_action("Undo", Shortcut{SDLK_z, KMOD_CTRL } ); action_manager->new_action("Redo", Shortcut{SDLK_y, KMOD_CTRL } ); action_manager->new_action("Isolation", Shortcut{SDLK_i, KMOD_CTRL }, Condition_ENABLE | Condition_HIGHLIGHTED_IN_TEXT_EDITOR ); - action_manager->new_action("Deselect", Shortcut{0, KMOD_NONE, "Click on background" }, Condition_ENABLE_IF_HAS_SELECTION | Condition_HIGHLIGHTED_IN_GRAPH_EDITOR ); action_manager->new_action("Drag whole graph", Shortcut{SDLK_SPACE, KMOD_NONE, "Space + Drag" }, 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 } );