From c63eeed1b4945c08081a8427e0049b24b4b8dc99 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 29 Oct 2024 00:50:39 +0100 Subject: [PATCH] polar snapping (almost bug free) --- src/main.cpp | 26 ++++++++++-- src/midi.h | 1 + src/node-manager.h | 37 +++++++++++----- src/node.h | 5 ++- src/ui.h | 104 ++++++++++++++++++++++----------------------- 5 files changed, 106 insertions(+), 67 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 04060ee..3e3aab0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ /*Pink: (176, 82, 121); #b05279*/ /*Blue: (108, 153, 187); #6c99bb*/ +#include #define RAYGUI_IMPLEMENTATION #include "datatypes.h" @@ -127,12 +128,17 @@ int main(){ auto hovering_nodes = app.nm.nodes | std::ranges::views::values| std::ranges::views::filter([&mouse_pos](Node n) { return CheckCollisionPointCircle(mouse_pos, n.position, n.radius);}); auto hovering_connectors = app.nm.nodes | std::ranges::views::values| std::ranges::views::filter([&mouse_pos](Node n) { return CheckCollisionPointCircle(mouse_pos, (n.position - ((n.position - mouse_pos)).norm() * (n.radius + 14.0f)), 8.0f);}); + for(auto &n : app.nm.nodes | std::ranges::views::values){ + if(hovering_nodes.empty()) n.hovering = false; + if(hovering_connectors.empty()) n.hover_connect = false; + } + if(!mouse_down && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::HOVER_CANVAS; if((mouse_clicked || mouse_down || mouse_released) && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::DRAW_SELECTION; if(!hovering_nodes.empty() && !mouse_down && !mouse_right_down) istate = InteractionState::HOVER_NODE; if(!dragging_nodes.empty()) istate = InteractionState::DRAG_NODE; - if(!hovering_connectors.empty() && istate == InteractionState::HOVER_CANVAS) istate = InteractionState::HOVER_CONNECTOR; + if(!hovering_connectors.empty() && !mouse_right_down && istate == InteractionState::HOVER_CANVAS) istate = InteractionState::HOVER_CONNECTOR; if(!connecting_nodes.empty()) istate = InteractionState::CONNECT_NODE; if(!selected_nodes.empty() && IsKeyPressed(KEY_DELETE)) istate = InteractionState::DELETE_SELECTED_NODES; @@ -207,6 +213,10 @@ int main(){ if(IsKeyDown(KEY_LEFT_CONTROL)) { dragging_nodes.front().position = (Vec2D){round((mouse_pos.x+dragging_nodes.front().drag_offset.x)/app.settings.grid)*app.settings.grid, round((mouse_pos.y+dragging_nodes.front().drag_offset.y)/app.settings.grid)*app.settings.grid}; + } else if(IsKeyDown(KEY_LEFT_SHIFT)){ + float steps = round(dragging_nodes.front().neighbor->position.dist(mouse_pos) / 60.0f) * 60.0f; + Vec2D polar = dragging_nodes.front().neighbor->position - (dragging_nodes.front().neighbor->position - (mouse_pos + dragging_nodes.front().drag_offset)).norm() * steps; + dragging_nodes.front().position = polar; } else { dragging_nodes.front().position = mouse_pos + dragging_nodes.front().drag_offset; } @@ -230,8 +240,10 @@ int main(){ } case InteractionState::HOVER_CONNECTOR: { - hovering_connectors.front().hover_connect = true; - if(mouse_clicked) hovering_connectors.front().connecting = true; + if(!hovering_connectors.empty()){ + hovering_connectors.front().hover_connect = true; + if(mouse_clicked) hovering_connectors.front().connecting = true; + } break; } case InteractionState::CONNECT_NODE: @@ -240,8 +252,16 @@ int main(){ if(connecting_nodes.front().connecting && mouse_down){ DrawLine(connector_position.x, connector_position.y, mouse_pos.x, mouse_pos.y, (Color){229, 181, 103, 255}); + connecting_nodes.front().hovering = true; + connecting_nodes.front().hover_connect = true; } + if(!hovering_connectors.empty()) { + hovering_connectors.front().hovering = true; + hovering_connectors.front().hover_connect = true; + } + if(!hovering_nodes.empty()) hovering_nodes.front().hovering = true; + if(mouse_released) { if(&hovering_nodes.front() != &connecting_nodes.front() && &connecting_nodes.front() != &hovering_connectors.front()){ if(!hovering_nodes.empty()){ diff --git a/src/midi.h b/src/midi.h index 65454bb..22363ff 100644 --- a/src/midi.h +++ b/src/midi.h @@ -60,6 +60,7 @@ class Midi { void enumerate(){ std::cout << (int)obs.get_current_api() << std::endl; int i = 0; + midi_out_ports.clear(); for(const libremidi::output_port& port : obs.get_output_ports()){ midi_out_ports.push_back({port, i, false}); i++; diff --git a/src/node-manager.h b/src/node-manager.h index 73ed002..d320c07 100644 --- a/src/node-manager.h +++ b/src/node-manager.h @@ -20,6 +20,8 @@ class NodeManager{ std::vector keys; int iterator = 0; + std::vector fractions = {"1/32","1/16","3/32","1/8","5/32","3/16","7/32","1/4","9/32","5/16","11/32","3/8","13/32","7/16","15/32","1/2","17/32","9/16","19/32","5/8","21/32","11/16","23/32","3/4","25/32","13/16","27/32","7/8","29/32","15/16","31/32","1/1"}; + int getRandomNode(){ std::vector o; std::ranges::sample(keys, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); @@ -60,18 +62,33 @@ class NodeManager{ } void drawNodes(Vec2D *mouse_pos, Camera2D *camera){ - auto flat_nodes = nodes | std::ranges::views::values | std::ranges::to>(); + auto neighbors = nodes | std::ranges::views::values | std::ranges::views::filter([](Node n){ return !n.dragging && !n.selected;}) | std::ranges::to>(); + float min_d = 480; for(auto& n : nodes | std::ranges::views::values ){ if(n.dragging){ - std::sort(flat_nodes.begin(), flat_nodes.end(), [n](Node na, Node nb){ return na.position.dist(n.position) < nb.position.dist(n.position);}); - float d = flat_nodes.at(1).position.dist(n.position); - if(d > 0 && d < 240) { - Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f); - Vec2D middle_handle = flat_nodes.at(1).position + (n.position - flat_nodes.at(1).position) * ((d - text_length.x)*0.5f)/d; - Vec2D text_position = middle_handle + (flat_nodes.at(1).position - n.position).norm().orth() * 12.0f; - float rotation = Vector2Angle((Vector2){1,0}, (n.position - flat_nodes.at(1).position)) * RAD2DEG; - DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - DrawLineV(n.position, flat_nodes.at(1).position, VS_COLOR_PINK); + std::sort(neighbors.begin(), neighbors.end(), [n](Node na, Node nb){ return na.position.dist(n.position) < nb.position.dist(n.position);}); + if(neighbors.size() > 1){ + min_d = neighbors.front().position.dist(n.position); + n.neighbor = &nodes.at(neighbors.at(1).id); + int i = 0; + for(auto it = (neighbors.begin()); it != neighbors.end(); it++, i++){ + float d = it->position.dist(n.position); + if(d > 0 && d < min_d*1.5 && d < 960) { + std::string frac = fractions.at((int)Clamp(round(d/60) - 1, 0, 31)); + Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f); + Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); + + Vec2D middle_handle = it->position + (n.position - it->position) * ((d - text_length.x)*0.5f)/d; + Vec2D middle_handle_frac = it->position + (n.position - it->position) * ((d - text_length_frac.x)*0.5f)/d; + Vec2D text_position = middle_handle + (it->position - n.position).norm().orth() * 12.0f; + Vec2D text_position_frac = middle_handle_frac - (it->position - n.position).norm().orth() * 4.0f; + + float rotation = Vector2Angle((Vector2){1,0}, (n.position - it->position)) * RAD2DEG; + DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + if(i == 0 && IsKeyDown(KEY_LEFT_SHIFT)) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + DrawLineV(n.position, it->position, VS_COLOR_PINK); + } + } } } n.draw(mouse_pos, camera); diff --git a/src/node.h b/src/node.h index 625afd7..06ae5fb 100644 --- a/src/node.h +++ b/src/node.h @@ -22,6 +22,9 @@ class Node : public Movable{ Node(int id, float x, float y, float r) : Movable(Vec2D(x, y), 0.0f), radius(r), id(id) {} Node(int id, Vec2D p, float r) : Movable(p, 0.0f), radius(r), id(id) {} + + Node *neighbor = nullptr; + float radius; int id; @@ -86,6 +89,7 @@ class Node : public Movable{ DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); DrawRing(position, radius, radius+4, 0.0f - 90.0f, (360.0f/127.0f) * midi_velocity - 90.0f, 24, (Color){158, 134, 200, 255}); DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); + if(mode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); float midi_gate_map = round((255.f / midi_gate) * fade); Color gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115, (unsigned char)Clamp(255 - midi_gate_map, 0, 255)}, WHITE); @@ -102,7 +106,6 @@ class Node : public Movable{ int offset = MeasureText(MIDI::Notes[midi_note].c_str(), 12); DrawText(MIDI::Notes[midi_note].c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE ); } - if(mode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, (Color){229, 181, 103, 255}); Vec2D connector = position - ((position - *mouse)).norm() * (radius + R); float opacity = 150.0f - Clamp(position.dist(mouse), 0.0f, 150.0f); diff --git a/src/ui.h b/src/ui.h index b649507..59840ff 100644 --- a/src/ui.h +++ b/src/ui.h @@ -28,67 +28,65 @@ class AppUI{ void drawSideBar(){ GuiPanel((Rectangle){-1,-30,280,(float)settings->height + 31}, ""); - { - const int s_controls = 20; - //GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF); - if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start(); - if(GuiButton((Rectangle){120, (float)s_controls, 60, 30}, "STOP")) sm->stop(); - if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset(); + const int s_controls = 25; + //GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF); + if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start(); + if(GuiButton((Rectangle){120, (float)s_controls, 60, 30}, "STOP")) sm->stop(); + if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset(); - const int s_bpm = s_controls + 35; - GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING"); - GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",settings->bpm).c_str()); - float _bpm = (float)settings->bpm; - GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300); - settings->bpm = _bpm; + const int s_bpm = s_controls + 45; + GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING"); + GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",settings->bpm).c_str()); + float _bpm = (float)settings->bpm; + GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300); + settings->bpm = _bpm; - int s_midi = s_bpm + 55; - GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); - if(GuiButton((Rectangle){20, (float)s_midi, 60, 30}, "REload")) sm->midi.enumerate(); + int s_midi = s_bpm + 75; + GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); + if(GuiButton((Rectangle){200, (float)s_midi, 60, 20}, "RELOAD")) sm->midi.enumerate(); - int i = 0; - for(auto& mo : sm->midi.midi_out_ports){ - GuiCheckBox((Rectangle){20, (float)s_midi + 25 + i*20.0f, 20, 20}, mo.port.port_name.substr(0,32).c_str(), &mo.enabled); - i++; - } + int i = 0; + for(auto& mo : sm->midi.midi_out_ports){ + GuiCheckBox((Rectangle){20, (float)s_midi + 25 + i*20.0f, 20, 20}, mo.port.port_name.substr(0,32).c_str(), &mo.enabled); + i++; + } - if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) { - for(auto &midis : sm->midi.midi_out_ports){ - if(midis.enabled){ - for(auto &n : nm->nodes){ - n.second.setMidiPort(midis.number, true); - } + if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) { + for(auto &midis : sm->midi.midi_out_ports){ + if(midis.enabled){ + for(auto &n : nm->nodes){ + n.second.setMidiPort(midis.number, true); } } } - - int s_signal = s_midi + 90 + i*20; - GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS"); - if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){ - int ni = nm->getRandomNode(); - sm->addSignalAtNode(ni); - } - - int signal_configs = 0; - for(auto &s : sm->signals){ - if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(signal_configs); - DrawCircle(55, s_signal+80 + signal_configs*20, 6.0F, s.color); - float s_multiplier = round(s.multiplier*10.0f)/10.0f; - float signal_bpm = settings->bpm * s.multiplier; - GuiSlider((Rectangle){70, (float)s_signal+70 + signal_configs*20, 160, 20}, "", std::format("{:.1f}",signal_bpm).c_str(), &s_multiplier, 0.1, 10); - s.multiplier = s_multiplier; - s.speed = settings->speed; - signal_configs++; - } - - int s_nodes = s_signal + 60 + signal_configs*20; - GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES"); - if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){ - nm->addNode(200.0f, 200.0f); - } - DrawText(std::format("{}", nm->nodes.size()).c_str(), 20, settings->height - 70, 15, VS_COLOR_WHITE); - DrawFPS(20, settings->height - 30); } + + int s_signal = s_midi + 100 + i*20; + GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS"); + if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){ + int ni = nm->getRandomNode(); + sm->addSignalAtNode(ni); + } + + int signal_configs = 0; + for(auto &s : sm->signals){ + if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(signal_configs); + DrawCircle(55, s_signal+80 + signal_configs*20, 6.0F, s.color); + float s_multiplier = round(s.multiplier*10.0f)/10.0f; + float signal_bpm = settings->bpm * s.multiplier; + GuiSlider((Rectangle){70, (float)s_signal+70 + signal_configs*20, 160, 20}, "", std::format("{:.1f}",signal_bpm).c_str(), &s_multiplier, 0.1, 10); + s.multiplier = s_multiplier; + s.speed = settings->speed; + signal_configs++; + } + + int s_nodes = s_signal + 70 + signal_configs*20; + GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES"); + if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){ + nm->addNode(200.0f, 200.0f); + } + DrawText(std::format("{}", nm->nodes.size()).c_str(), 20, settings->height - 70, 15, VS_COLOR_WHITE); + DrawFPS(20, settings->height - 30); } void drawPerNodeConfig(Camera2D *camera){