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