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.

255 lines
6.5KB

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