7644a50d00
Phase 1 — graph_layouts:
- New module cpp/functions/viz/graph_layouts.{h,cpp,md} v1.0.0
- layout_grid, layout_circular, layout_random (migrated from graph_force_layout.cpp)
- layout_radial: BFS rings from root, hop k -> circle of radius k*ring_spacing
- layout_hierarchical: Sugiyama-style heuristic (longest-path levels + barycenter ordering)
- layout_fixed: no-op
- All respect NF_PINNED. graph_layout_circular/grid kept as deprecated wrappers.
Phase 2-3 — graph_viewport v1.2.0:
- Multi-selection via state.selection (vector<int>); NF_SELECTED kept in sync
- Lasso: Shift+Drag on empty area; AABB hit-test on release
- Drag of N-selection: all selected pinned + moved by mouse delta
- Ctrl+click toggle, Esc clears selection
- Right-click on node -> on_context_menu callback
- Double-click on node -> on_double_click callback
- Helpers exposed: graph_viewport_clear/add_to/toggle/is_selected (own TU for tests)
Phase 4 — tests:
- test_graph_layouts: 12 cases / 364 assertions covering geometry, pin, edges
- test_graph_viewport: 5 cases for selection helpers (pure logic, no GL)
Phase 5 — demo (primitives_gallery):
- Layout combo (force/grid/circular/radial/hierarchical/fixed) + Apply button
- Right-click popup with Pin/Unpin/Add-to-selection
- Status overlay shows [N selected] when selection non-empty
- Updated golden images
Issue moved to dev/issues/completed/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
105 lines
3.3 KiB
C++
105 lines
3.3 KiB
C++
// Smoke tests for graph_viewport (issue 0049i).
|
|
// El widget completo necesita un contexto OpenGL + ImGui — eso vive en el
|
|
// demo y en el test visual. Aqui solo cubrimos los helpers de seleccion
|
|
// (logica pura sobre GraphData / GraphViewportState) que no dependen de GL
|
|
// ni de ImGui frame state.
|
|
|
|
#define CATCH_CONFIG_MAIN
|
|
#include "catch_amalgamated.hpp"
|
|
|
|
#include "viz/graph_viewport.h"
|
|
#include "viz/graph_types.h"
|
|
|
|
#include <vector>
|
|
|
|
namespace {
|
|
struct TG {
|
|
std::vector<GraphNode> nodes;
|
|
GraphData data{};
|
|
void make(int N) {
|
|
nodes.clear();
|
|
for (int i = 0; i < N; ++i) nodes.push_back(graph_node());
|
|
data.nodes = nodes.data();
|
|
data.node_count = (int)nodes.size();
|
|
data.node_capacity = (int)nodes.capacity();
|
|
data.edges = nullptr; data.edge_count = 0; data.edge_capacity = 0;
|
|
data.types = nullptr; data.type_count = 0;
|
|
data.rel_types = nullptr; data.rel_type_count = 0;
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
TEST_CASE("selection: add sets NF_SELECTED and selected_node", "[viewport][selection]") {
|
|
TG g; g.make(5);
|
|
GraphViewportState st;
|
|
|
|
graph_viewport_add_to_selection(g.data, st, 2);
|
|
REQUIRE(st.selection.size() == 1);
|
|
REQUIRE(st.selection[0] == 2);
|
|
REQUIRE(st.selected_node == 2);
|
|
REQUIRE((g.nodes[2].flags & NF_SELECTED) != 0);
|
|
|
|
// Anadir el mismo no duplica
|
|
graph_viewport_add_to_selection(g.data, st, 2);
|
|
REQUIRE(st.selection.size() == 1);
|
|
|
|
graph_viewport_add_to_selection(g.data, st, 4);
|
|
REQUIRE(st.selection.size() == 2);
|
|
REQUIRE(st.selected_node == 4);
|
|
}
|
|
|
|
TEST_CASE("selection: clear unsets all NF_SELECTED", "[viewport][selection]") {
|
|
TG g; g.make(3);
|
|
GraphViewportState st;
|
|
graph_viewport_add_to_selection(g.data, st, 0);
|
|
graph_viewport_add_to_selection(g.data, st, 1);
|
|
graph_viewport_add_to_selection(g.data, st, 2);
|
|
|
|
graph_viewport_clear_selection(g.data, st);
|
|
|
|
REQUIRE(st.selection.empty());
|
|
REQUIRE(st.selected_node == -1);
|
|
for (int i = 0; i < 3; ++i) {
|
|
REQUIRE((g.nodes[i].flags & NF_SELECTED) == 0);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("selection: toggle adds then removes", "[viewport][selection]") {
|
|
TG g; g.make(3);
|
|
GraphViewportState st;
|
|
|
|
graph_viewport_toggle_selection(g.data, st, 1);
|
|
REQUIRE(graph_viewport_is_selected(st, 1));
|
|
REQUIRE((g.nodes[1].flags & NF_SELECTED) != 0);
|
|
|
|
graph_viewport_toggle_selection(g.data, st, 1);
|
|
REQUIRE_FALSE(graph_viewport_is_selected(st, 1));
|
|
REQUIRE((g.nodes[1].flags & NF_SELECTED) == 0);
|
|
REQUIRE(st.selected_node == -1);
|
|
}
|
|
|
|
TEST_CASE("selection: out-of-range indices are ignored", "[viewport][selection]") {
|
|
TG g; g.make(2);
|
|
GraphViewportState st;
|
|
|
|
graph_viewport_add_to_selection(g.data, st, -1);
|
|
graph_viewport_add_to_selection(g.data, st, 5);
|
|
graph_viewport_toggle_selection(g.data, st, -3);
|
|
|
|
REQUIRE(st.selection.empty());
|
|
REQUIRE(st.selected_node == -1);
|
|
}
|
|
|
|
TEST_CASE("selection: is_selected reports membership", "[viewport][selection]") {
|
|
TG g; g.make(4);
|
|
GraphViewportState st;
|
|
|
|
graph_viewport_add_to_selection(g.data, st, 0);
|
|
graph_viewport_add_to_selection(g.data, st, 3);
|
|
|
|
REQUIRE(graph_viewport_is_selected(st, 0));
|
|
REQUIRE_FALSE(graph_viewport_is_selected(st, 1));
|
|
REQUIRE_FALSE(graph_viewport_is_selected(st, 2));
|
|
REQUIRE(graph_viewport_is_selected(st, 3));
|
|
}
|