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.

274 lines
7.4KB

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