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.

373 lines
9.3KB

  1. #include <app/CableWidget.hpp>
  2. #include <widget/SvgWidget.hpp>
  3. #include <widget/TransformWidget.hpp>
  4. #include <app/Scene.hpp>
  5. #include <app/RackWidget.hpp>
  6. #include <app/ModuleWidget.hpp>
  7. #include <context.hpp>
  8. #include <asset.hpp>
  9. #include <settings.hpp>
  10. #include <engine/Engine.hpp>
  11. #include <engine/Port.hpp>
  12. #include <app/MultiLightWidget.hpp>
  13. #include <componentlibrary.hpp>
  14. namespace rack {
  15. namespace app {
  16. struct TintWidget : widget::Widget {
  17. NVGcolor color = color::WHITE;
  18. void draw(const DrawArgs& args) override {
  19. nvgTint(args.vg, color);
  20. Widget::draw(args);
  21. }
  22. };
  23. struct PlugLight : componentlibrary::TRedGreenBlueLight<app::MultiLightWidget> {
  24. PlugLight() {
  25. box.size = math::Vec(9, 9);
  26. }
  27. };
  28. struct PlugWidget : widget::Widget {
  29. float angle = 0.5f * M_PI;
  30. PortWidget* portWidget = NULL;
  31. widget::FramebufferWidget* fb;
  32. widget::TransformWidget* plugTransform;
  33. TintWidget* plugTint;
  34. widget::SvgWidget* plug;
  35. widget::SvgWidget* plugPort;
  36. app::MultiLightWidget* plugLight;
  37. PlugWidget() {
  38. fb = new widget::FramebufferWidget;
  39. addChild(fb);
  40. plugTransform = new widget::TransformWidget;
  41. fb->addChild(plugTransform);
  42. plugTint = new TintWidget;
  43. plugTransform->addChild(plugTint);
  44. plug = new widget::SvgWidget;
  45. plug->setSvg(window::Svg::load(asset::system("res/ComponentLibrary/Plug.svg")));
  46. plugTint->addChild(plug);
  47. plugTransform->setSize(plug->getSize());
  48. plugTransform->setPosition(plug->getSize().mult(-0.5));
  49. plugTint->setSize(plug->getSize());
  50. plugPort = new widget::SvgWidget;
  51. plugPort->setSvg(window::Svg::load(asset::system("res/ComponentLibrary/PlugPort.svg")));
  52. plugPort->setPosition(plugPort->getSize().mult(-0.5));
  53. fb->addChild(plugPort);
  54. plugLight = new PlugLight;
  55. plugLight->setPosition(plugLight->getSize().mult(-0.5));
  56. addChild(plugLight);
  57. setSize(plug->getSize());
  58. }
  59. void step() override {
  60. std::vector<float> values(3);
  61. if (portWidget && plugLight->isVisible()) {
  62. engine::Port* port = portWidget->getPort();
  63. if (port) {
  64. for (int i = 0; i < 3; i++) {
  65. values[i] = port->plugLights[i].getBrightness();
  66. }
  67. }
  68. }
  69. plugLight->setBrightnesses(values);
  70. Widget::step();
  71. }
  72. void setColor(NVGcolor color) {
  73. if (color::isEqual(color, plugTint->color))
  74. return;
  75. plugTint->color = color;
  76. fb->setDirty();
  77. }
  78. void setAngle(float angle) {
  79. if (angle == this->angle)
  80. return;
  81. this->angle = angle;
  82. plugTransform->identity();
  83. plugTransform->rotate(angle - 0.5f * M_PI, plug->getSize().div(2));
  84. fb->setDirty();
  85. }
  86. void setPortWidget(PortWidget* portWidget) {
  87. this->portWidget = portWidget;
  88. }
  89. void setTop(bool top) {
  90. plugLight->setVisible(top);
  91. }
  92. };
  93. struct CableWidget::Internal {
  94. };
  95. CableWidget::CableWidget() {
  96. internal = new Internal;
  97. color = color::BLACK_TRANSPARENT;
  98. inputPlug = new PlugWidget;
  99. addChild(inputPlug);
  100. outputPlug = new PlugWidget;
  101. addChild(outputPlug);
  102. }
  103. CableWidget::~CableWidget() {
  104. setCable(NULL);
  105. delete internal;
  106. }
  107. bool CableWidget::isComplete() {
  108. return outputPort && inputPort;
  109. }
  110. void CableWidget::updateCable() {
  111. if (cable) {
  112. APP->engine->removeCable(cable);
  113. delete cable;
  114. cable = NULL;
  115. }
  116. if (inputPort && outputPort) {
  117. cable = new engine::Cable;
  118. cable->inputModule = inputPort->module;
  119. cable->inputId = inputPort->portId;
  120. cable->outputModule = outputPort->module;
  121. cable->outputId = outputPort->portId;
  122. APP->engine->addCable(cable);
  123. }
  124. }
  125. void CableWidget::setCable(engine::Cable* cable) {
  126. if (this->cable) {
  127. APP->engine->removeCable(this->cable);
  128. delete this->cable;
  129. this->cable = NULL;
  130. }
  131. if (cable) {
  132. app::ModuleWidget* outputMw = APP->scene->rack->getModule(cable->outputModule->id);
  133. if (!outputMw)
  134. throw Exception("Cable cannot find output ModuleWidget %lld", (long long) cable->outputModule->id);
  135. outputPort = outputMw->getOutput(cable->outputId);
  136. if (!outputPort)
  137. throw Exception("Cable cannot find output port %d", cable->outputId);
  138. app::ModuleWidget* inputMw = APP->scene->rack->getModule(cable->inputModule->id);
  139. if (!inputMw)
  140. throw Exception("Cable cannot find input ModuleWidget %lld", (long long) cable->inputModule->id);
  141. inputPort = inputMw->getInput(cable->inputId);
  142. if (!inputPort)
  143. throw Exception("Cable cannot find input port %d", cable->inputId);
  144. this->cable = cable;
  145. }
  146. else {
  147. outputPort = NULL;
  148. inputPort = NULL;
  149. }
  150. }
  151. engine::Cable* CableWidget::getCable() {
  152. return cable;
  153. }
  154. math::Vec CableWidget::getInputPos() {
  155. if (inputPort) {
  156. return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), APP->scene->rack);
  157. }
  158. else if (hoveredInputPort) {
  159. return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), APP->scene->rack);
  160. }
  161. else {
  162. return APP->scene->rack->getMousePos();
  163. }
  164. }
  165. math::Vec CableWidget::getOutputPos() {
  166. if (outputPort) {
  167. return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), APP->scene->rack);
  168. }
  169. else if (hoveredOutputPort) {
  170. return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), APP->scene->rack);
  171. }
  172. else {
  173. return APP->scene->rack->getMousePos();
  174. }
  175. }
  176. void CableWidget::mergeJson(json_t* rootJ) {
  177. std::string s = color::toHexString(color);
  178. json_object_set_new(rootJ, "color", json_string(s.c_str()));
  179. }
  180. void CableWidget::fromJson(json_t* rootJ) {
  181. json_t* colorJ = json_object_get(rootJ, "color");
  182. if (colorJ && json_is_string(colorJ)) {
  183. color = color::fromHexString(json_string_value(colorJ));
  184. }
  185. else {
  186. // In <v0.6.0, cables used JSON objects instead of hex strings. Just ignore them if so and use the existing cable color.
  187. // In <=v1, cable colors were not serialized.
  188. color = APP->scene->rack->getNextCableColor();
  189. }
  190. }
  191. static math::Vec getSlumpPos(math::Vec pos1, math::Vec pos2) {
  192. float dist = pos1.minus(pos2).norm();
  193. math::Vec avg = pos1.plus(pos2).div(2);
  194. // Lower average point as distance increases
  195. avg.y += (1.0 - settings::cableTension) * (150.0 + 1.0 * dist);
  196. return avg;
  197. }
  198. void CableWidget::step() {
  199. math::Vec outputPos = getOutputPos();
  200. math::Vec inputPos = getInputPos();
  201. math::Vec slump = getSlumpPos(outputPos, inputPos);
  202. NVGcolor colorOpaque = color;
  203. colorOpaque.a = 1.f;
  204. // Draw output plug
  205. bool outputTop = !isComplete() || APP->scene->rack->getTopCable(outputPort) == this;
  206. outputPlug->setPosition(outputPos);
  207. outputPlug->setTop(outputTop);
  208. outputPlug->setAngle(slump.minus(outputPos).arg());
  209. outputPlug->setColor(colorOpaque);
  210. outputPlug->setPortWidget(outputPort);
  211. // Draw input plug
  212. bool inputTop = !isComplete() || APP->scene->rack->getTopCable(inputPort) == this;
  213. inputPlug->setPosition(inputPos);
  214. inputPlug->setTop(inputTop);
  215. inputPlug->setAngle(slump.minus(inputPos).arg());
  216. inputPlug->setColor(colorOpaque);
  217. inputPlug->setPortWidget(inputPort);
  218. Widget::step();
  219. }
  220. void CableWidget::draw(const DrawArgs& args) {
  221. // Draw plugs
  222. Widget::draw(args);
  223. }
  224. void CableWidget::drawLayer(const DrawArgs& args, int layer) {
  225. // Cable shadow and cable
  226. if (layer == 2 || layer == 3) {
  227. float opacity = settings::cableOpacity;
  228. bool thick = false;
  229. if (isComplete()) {
  230. engine::Output* output = &cable->outputModule->outputs[cable->outputId];
  231. // Increase thickness if output port is polyphonic
  232. if (output->isPolyphonic()) {
  233. thick = true;
  234. }
  235. // Draw opaque if mouse is hovering over a connected port
  236. Widget* hoveredWidget = APP->event->hoveredWidget;
  237. if (outputPort == hoveredWidget || inputPort == hoveredWidget) {
  238. opacity = 1.0;
  239. }
  240. // Draw translucent cable if not active (i.e. 0 channels)
  241. else if (output->getChannels() == 0) {
  242. opacity *= 0.5;
  243. }
  244. }
  245. else {
  246. // Draw opaque if the cable is incomplete
  247. opacity = 1.0;
  248. }
  249. if (opacity <= 0.0)
  250. return;
  251. nvgAlpha(args.vg, std::pow(opacity, 1.5));
  252. math::Vec outputPos = getOutputPos();
  253. math::Vec inputPos = getInputPos();
  254. float thickness = thick ? 9.0 : 6.0;
  255. // The endpoints are off-center
  256. math::Vec slump = getSlumpPos(outputPos, inputPos);
  257. outputPos = outputPos.plus(slump.minus(outputPos).normalize().mult(13.0));
  258. inputPos = inputPos.plus(slump.minus(inputPos).normalize().mult(13.0));
  259. nvgLineCap(args.vg, NVG_ROUND);
  260. // Avoids glitches when cable is bent
  261. nvgLineJoin(args.vg, NVG_ROUND);
  262. if (layer == 2) {
  263. // Draw cable shadow
  264. math::Vec shadowSlump = slump.plus(math::Vec(0, 30));
  265. nvgBeginPath(args.vg);
  266. nvgMoveTo(args.vg, VEC_ARGS(outputPos));
  267. nvgQuadTo(args.vg, VEC_ARGS(shadowSlump), VEC_ARGS(inputPos));
  268. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.10);
  269. nvgStrokeColor(args.vg, shadowColor);
  270. nvgStrokeWidth(args.vg, thickness - 1.0);
  271. nvgStroke(args.vg);
  272. }
  273. else if (layer == 3) {
  274. // Draw cable outline
  275. nvgBeginPath(args.vg);
  276. nvgMoveTo(args.vg, VEC_ARGS(outputPos));
  277. nvgQuadTo(args.vg, VEC_ARGS(slump), VEC_ARGS(inputPos));
  278. // nvgStrokePaint(args.vg, nvgLinearGradient(args.vg, VEC_ARGS(outputPos), VEC_ARGS(inputPos), color::mult(color, 0.5), color));
  279. nvgStrokeColor(args.vg, color::mult(color, 0.8));
  280. nvgStrokeWidth(args.vg, thickness);
  281. nvgStroke(args.vg);
  282. // Draw cable
  283. nvgStrokeColor(args.vg, color::mult(color, 0.95));
  284. nvgStrokeWidth(args.vg, thickness - 1.0);
  285. nvgStroke(args.vg);
  286. }
  287. }
  288. Widget::drawLayer(args, layer);
  289. }
  290. engine::Cable* CableWidget::releaseCable() {
  291. engine::Cable* cable = this->cable;
  292. this->cable = NULL;
  293. return cable;
  294. }
  295. } // namespace app
  296. } // namespace rack