From 813958002bd464fc46a9f67d1e90baf731ced406 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 26 Nov 2024 01:58:09 +0100 Subject: [PATCH] Drag selection and snap to grid ok. Deselect not working though... --- src/app-manager.h | 189 ++++++++++++++++++++++++++++++++++++---- src/canvas.h | 9 +- src/draggable.h | 28 +++--- src/graph-manager.h | 21 ++++- src/interactable.h | 7 +- src/interaction-event.h | 2 +- src/main.cpp | 150 ++----------------------------- src/node.h | 3 - src/selectable.h | 2 +- 9 files changed, 231 insertions(+), 180 deletions(-) diff --git a/src/app-manager.h b/src/app-manager.h index d028a62..8ef75c7 100644 --- a/src/app-manager.h +++ b/src/app-manager.h @@ -1,12 +1,15 @@ #pragma once +#include +#include +#include "nlohmann/json.hpp" #include "clipboard.h" #include "datatypes.h" +#include "canvas.h" #include "graph-manager.h" #include "interaction-event.h" #include "midi-node.h" #include "mouse.h" -#include "moveable.h" #include "node.h" #include "raylib.h" #include "signal-manager.h" @@ -16,15 +19,21 @@ #include #include +using json = nlohmann::json; + namespace VISEQ::APP { + class AppManager; + void to_json(json& j, const AppManager& A); + void from_json(const json& j, AppManager& A); class AppManager{ public: - + AppManager() : sm(&gm, &mutex), mouse(&gm), ui(&sm, &gm) {} std::mutex mutex; + std::shared_ptr canvas = std::make_shared(); VISEQ::GRAPH::GraphManager gm; VISEQ::SIGNAL::SignalManager sm; VISEQ::APP::Clipboard cb; @@ -34,6 +43,100 @@ namespace VISEQ::APP { UI::AppUI ui; + void init(){ + canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; + canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; + canvas->camera.rotation = 0.0f; + canvas->camera.zoom = 1.0f; + } + + void close(){ + std::ofstream file("state.json"); + file << json(*this); + std::cout << std::setw(4) << json(*this) << std::endl; + } + + void startSignals(){ + sm.startSignalThread(); + } + + void loadState(){ + if(std::filesystem::exists("state.json")){ + std::ifstream f("state.json"); + json graph = json::parse(f); + + int highest_id = 0; + std::shared_ptr hid; + for(auto n : graph.at("graphmanager").at("nodes")){ + std::shared_ptr mn = n; + if(mn->id > highest_id) { + highest_id = mn->id; + hid = mn; + } + } + + for(auto e : graph.at("graphmanager").at("edges")){ + if(e.at("id") > highest_id){ + highest_id = e.at("id"); + } + } + + for(auto n : graph.at("graphmanager").at("nodes")){ + std::shared_ptr mn = n; + gm.addNode(mn); + //app.gm.nodes.push_back(mn); + std::cout << std::format("adding node {}", mn->id) << std::endl; + } + + for(auto e : graph.at("graphmanager").at("edges")){ + int start_id = e.at("start").get(); + int end_id = e.at("end").get(); + + VEC_SPTR_Node start = gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to(); + VEC_SPTR_Node end = gm.nodes | RANGE_FILTER([end_id](SPTR_Node n){return n->id == end_id;}) | std::ranges::to(); + + if(!start.empty() && !end.empty()) { + std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; + gm.addConnection(start.front(), end.front()); + } + + } + + gm.edges.back()->newId(highest_id); // set static id counter in base class to highest value from json file + + for(auto s : graph.at("signalmanager").at("signals")){ + int start_id = s.at("start").get(); + int next_id = s.at("next").get(); + int last_id = s.at("last").get(); + std::shared_ptr ms = s; + + VEC_SPTR_Node start = gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to(); + VEC_SPTR_Node last = gm.nodes | RANGE_FILTER([last_id](SPTR_Node n){return n->id == last_id;}) | std::ranges::to(); + VEC_SPTR_Node next = gm.nodes | RANGE_FILTER([next_id](SPTR_Node n){return n->id == next_id;}) | std::ranges::to(); + ms->start = start.front(); + ms->next = next.front(); + ms->last = last.front(); + sm.signals.push_back(ms); + + std::cout << std::format("adding signal with start node {}", start_id) << std::endl; + } + + } else { + for(int i = 0; i < 4; ++i){ + gm.addMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4}); + } + for(int i = 3; i >= 0; --i){ + gm.addMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4}); + } + + for(int i = 0; i < 8; ++i){ + gm.addConnection(gm.nodes[i], gm.nodes[(i+1) % 8]); + } + sm.addSignalAtNode(gm.nodes.front()); + } + + } + void updateMidiPorts(){ for(auto&n : gm.nodes | RANGE_FILTER([](VISEQ::GRAPH::NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;})){ auto nc = std::dynamic_pointer_cast(n); @@ -58,23 +161,63 @@ namespace VISEQ::APP { gm.removeNodesAndEdges(nodes); } - void doInteraction(){ - while(event_queue.size() > 0){ + void doInteraction(){ + bool exit = false; + while(event_queue.size() > 0 && !exit){ auto e = event_queue.front(); - std::cout << e.action << " " << e.item->z_index << std::endl; - - if(e.item->getType() == GraphItemType::NODE || e.item->getType() == GraphItemType::CONNECTOR || e.item->getType() == GraphItemType::CANVAS) { + std::cout << e.item->getType() << " " << e.action << std::endl; + + if(e.item->getType() == GraphItemType::CANVAS) { + if(e.action == Interaction::SELECT) { + gm.select(); + std::cout << gm.selected_nodes.size() << std::endl; + canvas->clearSelectionRect(); + } + if(e.action == Interaction::DESELECT) gm.deselect(); + } + + if(e.item->getType() == GraphItemType::NODE){ + if(e.action == Interaction::PICKUP) { + e.item->pickUp(); + if(std::dynamic_pointer_cast(e.item)->selected){ + for(auto s : gm.selected_nodes){ + s->pickUp(std::dynamic_pointer_cast(e.item)->position); + } + } + exit = true; + } + if(e.action == Interaction::DRAG) { + e.item->drag(); + if(std::dynamic_pointer_cast(e.item)->selected){ + for(auto s : gm.selected_nodes){ + s->drag(); + } + } + exit = true; + } + if(e.action == Interaction::PUTDOWN) { + e.item->putDown(); + if(std::dynamic_pointer_cast(e.item)->selected){ + for(auto s : gm.selected_nodes){ + s->putDown(); + } + } + } + } + if(exit) break; + + if(e.item->getType() == GraphItemType::CONNECTOR || e.item->getType() == GraphItemType::CANVAS) { switch(e.action){ case VISEQ::APP::Interaction::PICKUP: { e.item->pickUp(); - goto exit_loop; + exit = true; break; } case VISEQ::APP::Interaction::DRAG: { e.item->drag(); - goto exit_loop; + exit = true; break; } case VISEQ::APP::Interaction::PUTDOWN: @@ -83,18 +226,23 @@ namespace VISEQ::APP { break; } default: + {} } } + event_queue.pop_front(); } - exit_loop: event_queue.clear(); } void update(){ + state->update(canvas->camera); + canvas->update(); + mutex.lock(); updateMidiPorts(); mutex.unlock(); + doInteraction(); mouse.update(); @@ -109,15 +257,24 @@ namespace VISEQ::APP { } } - void draw(Vec2Df mouse_pos, Camera2D camera){ - gm.drawNodes(mouse_pos, camera); - gm.drawEdges(mouse_pos, camera); - sm.drawSignals(mouse_pos, camera); + void draw(){ + BeginDrawing(); + ClearBackground((Color){46,46,46}); + canvas->drawStart(); + canvas->draw(); + + gm.drawNodes(SCREEN_MOUSE, canvas->camera); + gm.drawEdges(SCREEN_MOUSE, canvas->camera); + sm.drawSignals(SCREEN_MOUSE, canvas->camera); + + canvas->drawEnd(); + drawUI(); + EndDrawing(); } - void drawUI(Vec2Df mouse_pos, Camera2D camera){ + void drawUI(){ mutex.lock(); - ui.draw(mouse_pos, camera); + ui.draw(SCREEN_MOUSE, canvas->camera); mutex.unlock(); } }; diff --git a/src/canvas.h b/src/canvas.h index a6bb593..e871425 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -2,6 +2,7 @@ #include "datatypes.h" +#include "interaction-event.h" #include "state.h" #include "draggable.h" #include "raylib.h" @@ -25,17 +26,20 @@ namespace VISEQ::APP::CANVAS { Vec2Df select1 = {SCREEN_MOUSE.x > select_start.x ? select_start.x : SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? select_start.y : SCREEN_MOUSE.y}; Vec2Df select2 = {SCREEN_MOUSE.x > select_start.x ? SCREEN_MOUSE.x - select_start.x : select_start.x - SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? SCREEN_MOUSE.y - select_start.y : select_start.y - SCREEN_MOUSE.y}; state->selection = (Rectangle){select1.x, select1.y, select2.x, select2.y}; - DrawRectangleLines(state->selection.x, state->selection.y, state->selection.width, state->selection.height, (Color){150,150,150,150}); } virtual void pickUp() override { BASE::Draggable::pickUp(); - state->selection = (Rectangle){0,0,0,0}; select_start = SCREEN_MOUSE; + state->pushEvent({getPtr(), Interaction::DESELECT}); } virtual void putDown() override { BASE::Draggable::putDown(); + state->pushEvent({getPtr(), Interaction::SELECT}); + } + + void clearSelectionRect(){ state->selection = (Rectangle){0,0,0,0}; } @@ -84,6 +88,7 @@ namespace VISEQ::APP::CANVAS { DrawPixel(x, y, (Color){130,130,130,200}); } } + if(state->selection.width > 0 || state->selection.height > 0) DrawRectangleLines(state->selection.x, state->selection.y, state->selection.width, state->selection.height, (Color){150,150,150,150}); } }; } diff --git a/src/draggable.h b/src/draggable.h index 2d7a5d3..89bd484 100644 --- a/src/draggable.h +++ b/src/draggable.h @@ -36,26 +36,32 @@ namespace VISEQ::BASE { picked_up = true; drag_offset = position - SCREEN_MOUSE; } + + virtual void pickUp(Vec2Df selection_origin) override { + picked_up = true; + drag_offset = position - selection_origin; + } virtual void drag() override { - state->selection_origin = position; - if(KEYS_PRESSED.ctrl){ - selection_offset = position - state->selection_origin; - } - if(KEYS_DOWN.ctrl){ - position = MOD_MOUSE + selection_offset; - } else { +// state->selection_origin = position; + /*if(KEYS_PRESSED.ctrl){*/ + /* selection_offset = position - state->selection_origin;*/ + /*}*/ + /*if(KEYS_DOWN.ctrl){*/ + /* position = MOD_MOUSE + selection_offset;*/ + /*} else {*/ position = MOD_MOUSE + drag_offset; - } + /*}*/ } virtual void putDown() override { picked_up = false; } - virtual void select() override { - in_selection_rect = CheckCollisionPointRec(position, state->selection); - if(MOUSE_LEFT.released) selected = (KEYS_DOWN.shift && selected) || in_selection_rect; + virtual bool select() override { + /*in_selection_rect = */ + /*if(MOUSE_LEFT.released) selected = (KEYS_DOWN.shift && selected) || in_selection_rect;*/ + return CheckCollisionPointRec(position, state->selection); } }; diff --git a/src/graph-manager.h b/src/graph-manager.h index f80204d..9e8e446 100644 --- a/src/graph-manager.h +++ b/src/graph-manager.h @@ -46,7 +46,7 @@ namespace VISEQ::GRAPH { EDGES::VEC_SPTR_Edge remove_edges; EDGES::VEC_SPTR_Edge split_edges; - //VEC_SPTR_Node selected_nodes; + VEC_SPTR_Node selected_nodes; std::map counter; @@ -63,9 +63,24 @@ namespace VISEQ::GRAPH { std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); return std::dynamic_pointer_cast(o.front()); } - + + void select(){ + auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->select();}) | std::ranges::to(); + if(!KEYS_DOWN.shift) selected_nodes.clear(); + selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); + std::for_each(selected_nodes.begin(), selected_nodes.end(), [](SPTR_Node n){n->selected = true;}); + } + + void deselect(){ + for(auto s : selected_nodes){ + s->selected = false; + } + selected_nodes.clear(); + } + VEC_SPTR_Node getSelection(){ - return nodes | RANGE_FILTER([](SPTR_Node n){return n->selected;}) | std::ranges::to(); + return selected_nodes; + //return nodes | RANGE_FILTER([](SPTR_Node n){return n->selected;}) | std::ranges::to(); } void addNode(SPTR_Node node){ diff --git a/src/interactable.h b/src/interactable.h index f761d64..15ac897 100644 --- a/src/interactable.h +++ b/src/interactable.h @@ -2,6 +2,7 @@ #include "datatypes.h" #include "raylib.h" +#include "vmath.h" namespace VISEQ::BASE { class Interactable{ @@ -13,7 +14,7 @@ namespace VISEQ::BASE { } virtual ~Interactable() {} - + bool is_hovering = false; int z_index = 0; @@ -33,9 +34,11 @@ namespace VISEQ::BASE { virtual bool released() { return IsMouseButtonReleased(MOUSE_BUTTON_LEFT); - } + } + virtual GRAPH::GraphItemType getType() = 0; virtual void pickUp() = 0; + virtual void pickUp(Vec2Df position) = 0; virtual void drag() = 0; virtual void putDown() = 0; }; diff --git a/src/interaction-event.h b/src/interaction-event.h index 560f7f8..0d50e4c 100644 --- a/src/interaction-event.h +++ b/src/interaction-event.h @@ -6,7 +6,7 @@ namespace VISEQ::APP{ - enum Interaction {NONE, HOVER, CLICK, HOLD, RELEASE, PICKUP, DRAG, PUTDOWN, SELECTED}; + enum Interaction {NONE, HOVER, CLICK, HOLD, RELEASE, PICKUP, DRAG, PUTDOWN, SELECT, DESELECT}; struct InteractionEvent{ std::shared_ptr item; diff --git a/src/main.cpp b/src/main.cpp index 7af2c6f..99b91c1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,45 +5,23 @@ * 2024 Sebastian Kujas * */ -#include "canvas.h" -#include "state.h" -#include "datatypes.h" -#include "graph-manager.h" -#include "node.h" -#include "vmath.h" -#include -#include #define RAYGUI_IMPLEMENTATION #include #include +#include "state.h" #include "app-manager.h" #include "datatypes.h" -#include "midi-node.h" - -#include "nlohmann/json.hpp" #include #include #include -#include #include -using json = nlohmann::json; -std::shared_ptr canvas = std::make_shared(); VISEQ::APP::AppManager app; - std::shared_ptr state = VISEQ::APP::State::get(); -Vec2Df mouse_pos; -Vec2Df drag_start; -//Vec2Df global_mouse; - -bool mouse_clicked = false; -bool mouse_touched = false; -bool mouse_dragging = false; - int main(){ srand(time(NULL)); @@ -60,9 +38,7 @@ int main(){ SetWindowSize(monitor_width - 300, monitor_height - 300); //ToggleFullscreen(); - - app.ui.init(); - + state->settings.fps = 60.0f; state->settings.bpm = 120.0f; state->settings.grid = 60.0f; // 120px for a 16th note @@ -74,111 +50,16 @@ int main(){ SetTargetFPS(state->settings.fps); - canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; - canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; - canvas->camera.rotation = 0.0f; - canvas->camera.zoom = 1.0f; + app.init(); + app.ui.init(); - if(std::filesystem::exists("state.json")){ - std::ifstream f("state.json"); - json graph = json::parse(f); + app.loadState(); -// state->settings = graph.at("settings"); + app.startSignals(); - int highest_id = 0; - std::shared_ptr hid; - for(auto n : graph.at("graphmanager").at("nodes")){ - std::shared_ptr mn = n; - if(mn->id > highest_id) { - highest_id = mn->id; - hid = mn; - } - } - - for(auto e : graph.at("graphmanager").at("edges")){ - if(e.at("id") > highest_id){ - highest_id = e.at("id"); - } - } - - for(auto n : graph.at("graphmanager").at("nodes")){ - std::shared_ptr mn = n; - app.gm.addNode(mn); - //app.gm.nodes.push_back(mn); - std::cout << std::format("adding node {}", mn->id) << std::endl; - } - - for(auto e : graph.at("graphmanager").at("edges")){ - int start_id = e.at("start").get(); - int end_id = e.at("end").get(); - - VEC_SPTR_Node start = app.gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to(); - VEC_SPTR_Node end = app.gm.nodes | RANGE_FILTER([end_id](SPTR_Node n){return n->id == end_id;}) | std::ranges::to(); - - if(!start.empty() && !end.empty()) { - std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; - app.gm.addConnection(start.front(), end.front()); - } - - } - - app.gm.edges.back()->newId(highest_id); // set static id counter in base class to highest value from json file - - for(auto s : graph.at("signalmanager").at("signals")){ - int start_id = s.at("start").get(); - int next_id = s.at("next").get(); - int last_id = s.at("last").get(); - std::shared_ptr ms = s; - - VEC_SPTR_Node start = app.gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to(); - VEC_SPTR_Node last = app.gm.nodes | RANGE_FILTER([last_id](SPTR_Node n){return n->id == last_id;}) | std::ranges::to(); - VEC_SPTR_Node next = app.gm.nodes | RANGE_FILTER([next_id](SPTR_Node n){return n->id == next_id;}) | std::ranges::to(); - ms->start = start.front(); - ms->next = next.front(); - ms->last = last.front(); - app.sm.signals.push_back(ms); - - std::cout << std::format("adding signal with start node {}", start_id) << std::endl; - } - - } else { - for(int i = 0; i < 4; ++i){ - app.gm.addMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4}); - } - for(int i = 3; i >= 0; --i){ - app.gm.addMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4}); - } - - for(int i = 0; i < 8; ++i){ - app.gm.addConnection(app.gm.nodes[i], app.gm.nodes[(i+1) % 8]); - } - app.sm.addSignalAtNode(app.gm.nodes.front()); - } - - app.sm.startSignalThread(); - - enum InteractionState {HOVER_CANVAS, HOVER_NODE, HOVER_CONNECTOR, ADD_NODES_MENU, DRAG_NODE, CONNECT_NODE, DRAW_SELECTION, SCALE_SELECTION, DELETE_SELECTED_NODES, COPY_SELECTED_NODES, PASTE_SELECTED_NODES}; - InteractionState istate = HOVER_CANVAS; - - Vec2Df node_menu_position; - - Vec2Df select_start; - Rectangle selection; - - Vec2Df scale_start; - Vec2Df scale_rectangle; - - float drag_distance = 0; - - VEC_SPTR_Node copied_nodes; - while(!WindowShouldClose()){ - - BeginDrawing(); - ClearBackground((Color){46,46,46}); - canvas->drawStart(); - - state->update(canvas->camera); + app.update(); + app.draw(); /*else if(IsKeyDown(KEY_LEFT_SHIFT)){*/ /* float steps = round((dragNode->neighbors.front()->position.dist(mouse_pos)) / state->settings.grid) * state->settings.grid;*/ @@ -490,22 +371,9 @@ int main(){ /* }*/ /*};*/ - canvas->update(); - app.update(); - - canvas->draw(); - app.draw(SCREEN_MOUSE, canvas->camera); - canvas->drawEnd(); - - app.drawUI(SCREEN_MOUSE, canvas->camera); - - EndDrawing(); } + app.close(); - std::ofstream file("state.json"); - file << json(app); - - std::cout << std::setw(4) << json(app) << std::endl; CloseWindow(); diff --git a/src/node.h b/src/node.h index 404b4d6..01c75c4 100644 --- a/src/node.h +++ b/src/node.h @@ -71,9 +71,6 @@ namespace VISEQ::GRAPH::NODES { if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); - if(selected) state->pushEvent({getPtr(), APP::Interaction::SELECTED}); - - if(state->selection.width > 0) select(); int free_connectors = 0; for(auto c : connectors){ diff --git a/src/selectable.h b/src/selectable.h index 0acf119..d9884ab 100644 --- a/src/selectable.h +++ b/src/selectable.h @@ -20,6 +20,6 @@ namespace VISEQ::BASE { return (selected || hovering()) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); } - virtual void select() = 0; + virtual bool select() = 0; }; }