You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

293 lines
7.7KB

  1. #include <app/CableWidget.hpp>
  2. #include <app/Scene.hpp>
  3. #include <app/RackWidget.hpp>
  4. #include <window.hpp>
  5. #include <app.hpp>
  6. #include <patch.hpp>
  7. #include <settings.hpp>
  8. #include <engine/Port.hpp>
  9. namespace rack {
  10. namespace app {
  11. static void drawPlug(NVGcontext* vg, math::Vec pos, NVGcolor color) {
  12. NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5);
  13. // Plug solid
  14. nvgBeginPath(vg);
  15. nvgCircle(vg, pos.x, pos.y, 9);
  16. nvgFillColor(vg, color);
  17. nvgFill(vg);
  18. // Border
  19. nvgStrokeWidth(vg, 1.0);
  20. nvgStrokeColor(vg, colorOutline);
  21. nvgStroke(vg);
  22. // Hole
  23. nvgBeginPath(vg);
  24. nvgCircle(vg, pos.x, pos.y, 5);
  25. nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0));
  26. nvgFill(vg);
  27. }
  28. static void drawCable(NVGcontext* vg, math::Vec pos1, math::Vec pos2, NVGcolor color, float thickness, float tension, float opacity) {
  29. NVGcolor colorShadow = nvgRGBAf(0, 0, 0, 0.10);
  30. NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5);
  31. // Cable
  32. if (opacity > 0.0) {
  33. nvgSave(vg);
  34. // This power scaling looks more linear than actual linear scaling
  35. nvgGlobalAlpha(vg, std::pow(opacity, 1.5));
  36. float dist = pos1.minus(pos2).norm();
  37. math::Vec slump;
  38. slump.y = (1.0 - tension) * (150.0 + 1.0 * dist);
  39. math::Vec pos3 = pos1.plus(pos2).div(2).plus(slump);
  40. // Adjust pos1 and pos2 to not draw over the plug
  41. pos1 = pos1.plus(pos3.minus(pos1).normalize().mult(9));
  42. pos2 = pos2.plus(pos3.minus(pos2).normalize().mult(9));
  43. nvgLineJoin(vg, NVG_ROUND);
  44. // Shadow
  45. math::Vec pos4 = pos3.plus(slump.mult(0.08));
  46. nvgBeginPath(vg);
  47. nvgMoveTo(vg, pos1.x, pos1.y);
  48. nvgQuadTo(vg, pos4.x, pos4.y, pos2.x, pos2.y);
  49. nvgStrokeColor(vg, colorShadow);
  50. nvgStrokeWidth(vg, thickness);
  51. nvgStroke(vg);
  52. // Cable outline
  53. nvgBeginPath(vg);
  54. nvgMoveTo(vg, pos1.x, pos1.y);
  55. nvgQuadTo(vg, pos3.x, pos3.y, pos2.x, pos2.y);
  56. nvgStrokeColor(vg, colorOutline);
  57. nvgStrokeWidth(vg, thickness);
  58. nvgStroke(vg);
  59. // Cable solid
  60. nvgStrokeColor(vg, color);
  61. nvgStrokeWidth(vg, thickness - 2);
  62. nvgStroke(vg);
  63. nvgRestore(vg);
  64. }
  65. }
  66. CableWidget::CableWidget() {
  67. cable = new engine::Cable;
  68. color = color::BLACK_TRANSPARENT;
  69. if (!settings::cableColors.empty()) {
  70. int id = APP->scene->rack->nextCableColorId++;
  71. APP->scene->rack->nextCableColorId %= settings::cableColors.size();
  72. color = settings::cableColors[id];
  73. }
  74. }
  75. CableWidget::~CableWidget() {
  76. delete cable;
  77. }
  78. bool CableWidget::isComplete() {
  79. return outputPort && inputPort;
  80. }
  81. void CableWidget::setOutput(PortWidget* outputPort) {
  82. this->outputPort = outputPort;
  83. if (outputPort) {
  84. assert(outputPort->type == PortWidget::OUTPUT);
  85. cable->outputModule = outputPort->module;
  86. cable->outputId = outputPort->portId;
  87. }
  88. }
  89. void CableWidget::setInput(PortWidget* inputPort) {
  90. this->inputPort = inputPort;
  91. if (inputPort) {
  92. assert(inputPort->type == PortWidget::INPUT);
  93. cable->inputModule = inputPort->module;
  94. cable->inputId = inputPort->portId;
  95. }
  96. }
  97. math::Vec CableWidget::getOutputPos() {
  98. if (outputPort) {
  99. return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), APP->scene->rack);
  100. }
  101. else if (hoveredOutputPort) {
  102. return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), APP->scene->rack);
  103. }
  104. else {
  105. return APP->scene->rack->mousePos;
  106. }
  107. }
  108. math::Vec CableWidget::getInputPos() {
  109. if (inputPort) {
  110. return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), APP->scene->rack);
  111. }
  112. else if (hoveredInputPort) {
  113. return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), APP->scene->rack);
  114. }
  115. else {
  116. return APP->scene->rack->mousePos;
  117. }
  118. }
  119. json_t* CableWidget::toJson() {
  120. assert(isComplete());
  121. json_t* rootJ = json_object();
  122. json_object_set_new(rootJ, "id", json_integer(cable->id));
  123. json_object_set_new(rootJ, "outputModuleId", json_integer(cable->outputModule->id));
  124. json_object_set_new(rootJ, "outputId", json_integer(cable->outputId));
  125. json_object_set_new(rootJ, "inputModuleId", json_integer(cable->inputModule->id));
  126. json_object_set_new(rootJ, "inputId", json_integer(cable->inputId));
  127. std::string s = color::toHexString(color);
  128. json_object_set_new(rootJ, "color", json_string(s.c_str()));
  129. return rootJ;
  130. }
  131. void CableWidget::fromJson(json_t* rootJ) {
  132. // outputModuleId
  133. json_t* outputModuleIdJ = json_object_get(rootJ, "outputModuleId");
  134. if (!outputModuleIdJ)
  135. return;
  136. int outputModuleId = json_integer_value(outputModuleIdJ);
  137. ModuleWidget* outputModule = APP->scene->rack->getModule(outputModuleId);
  138. if (!outputModule)
  139. return;
  140. // inputModuleId
  141. json_t* inputModuleIdJ = json_object_get(rootJ, "inputModuleId");
  142. if (!inputModuleIdJ)
  143. return;
  144. int inputModuleId = json_integer_value(inputModuleIdJ);
  145. ModuleWidget* inputModule = APP->scene->rack->getModule(inputModuleId);
  146. if (!inputModule)
  147. return;
  148. // outputId
  149. json_t* outputIdJ = json_object_get(rootJ, "outputId");
  150. if (!outputIdJ)
  151. return;
  152. int outputId = json_integer_value(outputIdJ);
  153. // inputId
  154. json_t* inputIdJ = json_object_get(rootJ, "inputId");
  155. if (!inputIdJ)
  156. return;
  157. int inputId = json_integer_value(inputIdJ);
  158. // Only set ID if unset
  159. if (cable->id < 0) {
  160. // id
  161. json_t* idJ = json_object_get(rootJ, "id");
  162. // Before 1.0, cables IDs were not used, so just leave it as default and Engine will assign one automatically.
  163. if (idJ) {
  164. cable->id = json_integer_value(idJ);
  165. }
  166. }
  167. // Set ports
  168. if (APP->patch->isLegacy(1)) {
  169. // Before 0.6, the index of the "ports" array was the index of the PortWidget in the `outputs` and `inputs` vector.
  170. setOutput(outputModule->outputs[outputId]);
  171. setInput(inputModule->inputs[inputId]);
  172. }
  173. else {
  174. for (PortWidget* port : outputModule->outputs) {
  175. if (port->portId == outputId) {
  176. setOutput(port);
  177. break;
  178. }
  179. }
  180. for (PortWidget* port : inputModule->inputs) {
  181. if (port->portId == inputId) {
  182. setInput(port);
  183. break;
  184. }
  185. }
  186. }
  187. if (!isComplete())
  188. return;
  189. json_t* colorJ = json_object_get(rootJ, "color");
  190. if (colorJ) {
  191. // v0.6.0 and earlier patches use JSON objects. Just ignore them if so and use the existing cable color.
  192. if (json_is_string(colorJ))
  193. color = color::fromHexString(json_string_value(colorJ));
  194. }
  195. }
  196. void CableWidget::draw(const DrawArgs& args) {
  197. float opacity = settings::cableOpacity;
  198. float tension = settings::cableTension;
  199. float thickness = 5;
  200. if (isComplete()) {
  201. engine::Output* output = &cable->outputModule->outputs[cable->outputId];
  202. // Draw opaque if mouse is hovering over a connected port
  203. if (output->channels > 1) {
  204. // Increase thickness if output port is polyphonic
  205. thickness = 9;
  206. }
  207. if (outputPort->hovered || inputPort->hovered) {
  208. opacity = 1.0;
  209. }
  210. else if (output->channels == 0) {
  211. // Draw translucent cable if not active (i.e. 0 channels)
  212. opacity *= 0.5;
  213. }
  214. }
  215. else {
  216. // Draw opaque if the cable is incomplete
  217. opacity = 1.0;
  218. }
  219. math::Vec outputPos = getOutputPos();
  220. math::Vec inputPos = getInputPos();
  221. drawCable(args.vg, outputPos, inputPos, color, thickness, tension, opacity);
  222. }
  223. void CableWidget::drawPlugs(const DrawArgs& args) {
  224. math::Vec outputPos = getOutputPos();
  225. math::Vec inputPos = getInputPos();
  226. // Draw plug if the cable is on top, or if the cable is incomplete
  227. if (!isComplete() || APP->scene->rack->getTopCable(outputPort) == this) {
  228. drawPlug(args.vg, outputPos, color);
  229. if (isComplete()) {
  230. // Draw plug light
  231. nvgSave(args.vg);
  232. nvgTranslate(args.vg, outputPos.x - 4, outputPos.y - 4);
  233. outputPort->plugLight->draw(args);
  234. nvgRestore(args.vg);
  235. }
  236. }
  237. if (!isComplete() || APP->scene->rack->getTopCable(inputPort) == this) {
  238. drawPlug(args.vg, inputPos, color);
  239. if (isComplete()) {
  240. nvgSave(args.vg);
  241. nvgTranslate(args.vg, inputPos.x - 4, inputPos.y - 4);
  242. inputPort->plugLight->draw(args);
  243. nvgRestore(args.vg);
  244. }
  245. }
  246. }
  247. } // namespace app
  248. } // namespace rack