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.

271 lines
7.6KB

  1. #include "app/CableWidget.hpp"
  2. #include "app/Scene.hpp"
  3. #include "componentlibrary.hpp"
  4. #include "window.hpp"
  5. #include "event.hpp"
  6. #include "app.hpp"
  7. #include "patch.hpp"
  8. #include "settings.hpp"
  9. namespace rack {
  10. static void drawPlug(NVGcontext *vg, math::Vec pos, NVGcolor color) {
  11. NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5);
  12. // Plug solid
  13. nvgBeginPath(vg);
  14. nvgCircle(vg, pos.x, pos.y, 9);
  15. nvgFillColor(vg, color);
  16. nvgFill(vg);
  17. // Border
  18. nvgStrokeWidth(vg, 1.0);
  19. nvgStrokeColor(vg, colorOutline);
  20. nvgStroke(vg);
  21. // Hole
  22. nvgBeginPath(vg);
  23. nvgCircle(vg, pos.x, pos.y, 5);
  24. nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0));
  25. nvgFill(vg);
  26. }
  27. static void drawCable(NVGcontext *vg, math::Vec pos1, math::Vec pos2, NVGcolor color, float thickness, float tension, float opacity) {
  28. NVGcolor colorShadow = nvgRGBAf(0, 0, 0, 0.10);
  29. NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5);
  30. // Cable
  31. if (opacity > 0.0) {
  32. nvgSave(vg);
  33. // This power scaling looks more linear than actual linear scaling
  34. nvgGlobalAlpha(vg, std::pow(opacity, 1.5));
  35. float dist = pos1.minus(pos2).norm();
  36. math::Vec slump;
  37. slump.y = (1.0 - tension) * (150.0 + 1.0*dist);
  38. math::Vec pos3 = pos1.plus(pos2).div(2).plus(slump);
  39. nvgLineJoin(vg, NVG_ROUND);
  40. // Shadow
  41. math::Vec pos4 = pos3.plus(slump.mult(0.08));
  42. nvgBeginPath(vg);
  43. nvgMoveTo(vg, pos1.x, pos1.y);
  44. nvgQuadTo(vg, pos4.x, pos4.y, pos2.x, pos2.y);
  45. nvgStrokeColor(vg, colorShadow);
  46. nvgStrokeWidth(vg, thickness);
  47. nvgStroke(vg);
  48. // Cable outline
  49. nvgBeginPath(vg);
  50. nvgMoveTo(vg, pos1.x, pos1.y);
  51. nvgQuadTo(vg, pos3.x, pos3.y, pos2.x, pos2.y);
  52. nvgStrokeColor(vg, colorOutline);
  53. nvgStrokeWidth(vg, thickness);
  54. nvgStroke(vg);
  55. // Cable solid
  56. nvgStrokeColor(vg, color);
  57. nvgStrokeWidth(vg, thickness - 2);
  58. nvgStroke(vg);
  59. nvgRestore(vg);
  60. }
  61. }
  62. static const NVGcolor cableColors[] = {
  63. nvgRGB(0xc9, 0xb7, 0x0e), // yellow
  64. nvgRGB(0xc9, 0x18, 0x47), // red
  65. nvgRGB(0x0c, 0x8e, 0x15), // green
  66. nvgRGB(0x09, 0x86, 0xad), // blue
  67. };
  68. static int lastCableColorId = -1;
  69. CableWidget::CableWidget() {
  70. lastCableColorId = (lastCableColorId + 1) % LENGTHOF(cableColors);
  71. color = cableColors[lastCableColorId];
  72. cable = new Cable;
  73. }
  74. CableWidget::~CableWidget() {
  75. delete cable;
  76. }
  77. bool CableWidget::isComplete() {
  78. return outputPort && inputPort;
  79. }
  80. void CableWidget::setOutput(PortWidget *outputPort) {
  81. this->outputPort = outputPort;
  82. if (outputPort) {
  83. assert(outputPort->type == PortWidget::OUTPUT);
  84. cable->outputModule = outputPort->module;
  85. cable->outputId = outputPort->portId;
  86. }
  87. }
  88. void CableWidget::setInput(PortWidget *inputPort) {
  89. this->inputPort = inputPort;
  90. if (inputPort) {
  91. assert(inputPort->type == PortWidget::INPUT);
  92. cable->inputModule = inputPort->module;
  93. cable->inputId = inputPort->portId;
  94. }
  95. }
  96. math::Vec CableWidget::getOutputPos() {
  97. if (outputPort) {
  98. return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), app()->scene->rackWidget);
  99. }
  100. else if (hoveredOutputPort) {
  101. return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), app()->scene->rackWidget);
  102. }
  103. else {
  104. return app()->scene->rackWidget->mousePos;
  105. }
  106. }
  107. math::Vec CableWidget::getInputPos() {
  108. if (inputPort) {
  109. return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), app()->scene->rackWidget);
  110. }
  111. else if (hoveredInputPort) {
  112. return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), app()->scene->rackWidget);
  113. }
  114. else {
  115. return app()->scene->rackWidget->mousePos;
  116. }
  117. }
  118. json_t *CableWidget::toJson() {
  119. assert(isComplete());
  120. json_t *rootJ = json_object();
  121. // This is just here for fun. It is not used in fromJson(), since cableIds are not preserved across multiple launches of Rack.
  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, const std::map<int, ModuleWidget*> &moduleWidgets) {
  132. int outputModuleId = json_integer_value(json_object_get(rootJ, "outputModuleId"));
  133. int outputId = json_integer_value(json_object_get(rootJ, "outputId"));
  134. int inputModuleId = json_integer_value(json_object_get(rootJ, "inputModuleId"));
  135. int inputId = json_integer_value(json_object_get(rootJ, "inputId"));
  136. // Get module widgets
  137. auto outputModuleIt = moduleWidgets.find(outputModuleId);
  138. auto inputModuleIt = moduleWidgets.find(inputModuleId);
  139. if (outputModuleIt == moduleWidgets.end() || inputModuleIt == moduleWidgets.end())
  140. return;
  141. ModuleWidget *outputModule = outputModuleIt->second;
  142. ModuleWidget *inputModule = inputModuleIt->second;
  143. // Set ports
  144. if (app()->patch->isLegacy(1)) {
  145. // Before 0.6, the index of the "ports" array was the index of the PortWidget in the `outputs` and `inputs` vector.
  146. setOutput(outputModule->outputs[outputId]);
  147. setInput(inputModule->inputs[inputId]);
  148. }
  149. else {
  150. for (PortWidget *port : outputModule->outputs) {
  151. if (port->portId == outputId) {
  152. setOutput(port);
  153. break;
  154. }
  155. }
  156. for (PortWidget *port : inputModule->inputs) {
  157. if (port->portId == inputId) {
  158. setInput(port);
  159. break;
  160. }
  161. }
  162. }
  163. if (!isComplete())
  164. return;
  165. json_t *colorJ = json_object_get(rootJ, "color");
  166. if (colorJ) {
  167. // v0.6.0 and earlier patches use JSON objects. Just ignore them if so and use the existing cable color.
  168. if (json_is_string(colorJ))
  169. color = color::fromHexString(json_string_value(colorJ));
  170. }
  171. }
  172. void CableWidget::draw(NVGcontext *vg) {
  173. float opacity = settings::cableOpacity;
  174. float tension = settings::cableTension;
  175. if (!isComplete()) {
  176. // Draw opaque if the cable is incomplete
  177. opacity = 1.0;
  178. }
  179. else {
  180. // Draw opaque if mouse is hovering over a connected port
  181. PortWidget *hoveredPort = dynamic_cast<PortWidget*>(app()->event->hoveredWidget);
  182. if (hoveredPort && (hoveredPort == outputPort || hoveredPort == inputPort))
  183. opacity = 1.0;
  184. }
  185. float thickness = 5;
  186. if (isComplete()) {
  187. Output *output = &cable->outputModule->outputs[cable->outputId];
  188. if (output->channels > 1) {
  189. // Increase thickness if output port is polyphonic
  190. thickness = 7;
  191. }
  192. // else if (output->channels == 0) {
  193. // // Draw translucent cable if not active (i.e. 0 channels)
  194. // opacity *= 0.5;
  195. // }
  196. }
  197. math::Vec outputPos = getOutputPos();
  198. math::Vec inputPos = getInputPos();
  199. drawCable(vg, outputPos, inputPos, color, thickness, tension, opacity);
  200. }
  201. void CableWidget::drawPlugs(NVGcontext *vg) {
  202. // TODO Figure out a way to draw plugs first and cables last, and cut the plug portion of the cable off.
  203. math::Vec outputPos = getOutputPos();
  204. math::Vec inputPos = getInputPos();
  205. // Draw plug if the cable is on top, or if the cable is incomplete
  206. if (!isComplete() || app()->scene->rackWidget->getTopCable(outputPort) == this) {
  207. drawPlug(vg, outputPos, color);
  208. if (isComplete()) {
  209. // Draw plug light
  210. nvgSave(vg);
  211. nvgTranslate(vg, outputPos.x - 4, outputPos.y - 4);
  212. outputPort->plugLight->draw(vg);
  213. nvgRestore(vg);
  214. }
  215. }
  216. if (!isComplete() || app()->scene->rackWidget->getTopCable(inputPort) == this) {
  217. drawPlug(vg, inputPos, color);
  218. if (isComplete()) {
  219. nvgSave(vg);
  220. nvgTranslate(vg, inputPos.x - 4, inputPos.y - 4);
  221. inputPort->plugLight->draw(vg);
  222. nvgRestore(vg);
  223. }
  224. }
  225. }
  226. } // namespace rack