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.

525 lines
12KB

  1. #include <app/PortWidget.hpp>
  2. #include <app/Scene.hpp>
  3. #include <ui/MenuItem.hpp>
  4. #include <ui/MenuSeparator.hpp>
  5. #include <window/Window.hpp>
  6. #include <context.hpp>
  7. #include <history.hpp>
  8. #include <engine/Engine.hpp>
  9. #include <settings.hpp>
  10. #include <helpers.hpp>
  11. namespace rack {
  12. namespace app {
  13. struct PortWidget::Internal {
  14. ui::Tooltip* tooltip = NULL;
  15. /** For overriding onDragStart behavior by menu items. */
  16. CableWidget* overrideCw = NULL;
  17. CableWidget* overrideCloneCw = NULL;
  18. bool overrideCreateCable = false;
  19. NVGcolor overrideColor = color::BLACK_TRANSPARENT;
  20. };
  21. struct PortTooltip : ui::Tooltip {
  22. PortWidget* portWidget;
  23. void step() override {
  24. if (portWidget->module) {
  25. engine::Port* port = portWidget->getPort();
  26. engine::PortInfo* portInfo = portWidget->getPortInfo();
  27. // Label
  28. text = portInfo->getFullName();
  29. // Description
  30. std::string description = portInfo->getDescription();
  31. if (description != "") {
  32. text += "\n";
  33. text += description;
  34. }
  35. // Voltage, number of channels
  36. int channels = port->getChannels();
  37. for (int i = 0; i < channels; i++) {
  38. float v = port->getVoltage(i);
  39. // Add newline or comma
  40. text += "\n";
  41. if (channels > 1)
  42. text += string::f("%d: ", i + 1);
  43. text += string::f("% .3fV", math::normalizeZero(v));
  44. }
  45. // Connected to
  46. std::vector<CableWidget*> cables = APP->scene->rack->getCompleteCablesOnPort(portWidget);
  47. for (auto it = cables.rbegin(); it != cables.rend(); it++) {
  48. CableWidget* cable = *it;
  49. PortWidget* otherPw = (portWidget->type == engine::Port::INPUT) ? cable->outputPort : cable->inputPort;
  50. if (!otherPw)
  51. continue;
  52. text += "\n";
  53. if (portWidget->type == engine::Port::INPUT)
  54. text += "From ";
  55. else
  56. text += "To ";
  57. text += otherPw->module->model->getFullName();
  58. text += ": ";
  59. text += otherPw->getPortInfo()->getName();
  60. text += " ";
  61. text += (otherPw->type == engine::Port::INPUT) ? "input" : "output";
  62. }
  63. }
  64. Tooltip::step();
  65. // Position at bottom-right of parameter
  66. box.pos = portWidget->getAbsoluteOffset(portWidget->box.size).round();
  67. // Fit inside parent (copied from Tooltip.cpp)
  68. assert(parent);
  69. box = box.nudge(parent->box.zeroPos());
  70. }
  71. };
  72. struct ColorMenuItem : ui::MenuItem {
  73. NVGcolor color;
  74. void draw(const DrawArgs& args) override {
  75. MenuItem::draw(args);
  76. // Color circle
  77. nvgBeginPath(args.vg);
  78. float radius = 6.0;
  79. nvgCircle(args.vg, 8.0 + radius, box.size.y / 2, radius);
  80. nvgFillColor(args.vg, color);
  81. nvgFill(args.vg);
  82. nvgStrokeWidth(args.vg, 1.0);
  83. nvgStrokeColor(args.vg, color::mult(color, 0.5));
  84. nvgStroke(args.vg);
  85. }
  86. };
  87. struct PortCloneCableItem : ui::MenuItem {
  88. PortWidget* pw;
  89. CableWidget* cw;
  90. void onButton(const ButtonEvent& e) override {
  91. OpaqueWidget::onButton(e);
  92. if (disabled)
  93. return;
  94. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0) {
  95. // Set PortWidget::onDragStart overrides
  96. pw->internal->overrideCloneCw = cw;
  97. // Pretend the PortWidget was clicked
  98. e.consume(pw);
  99. // Deletes `this`
  100. doAction();
  101. }
  102. }
  103. };
  104. struct PortCableItem : ColorMenuItem {
  105. PortWidget* pw;
  106. CableWidget* cw;
  107. void onButton(const ButtonEvent& e) override {
  108. OpaqueWidget::onButton(e);
  109. if (disabled)
  110. return;
  111. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0) {
  112. // Set PortWidget::onDragStart overrides
  113. pw->internal->overrideCw = cw;
  114. // Pretend the PortWidget was clicked
  115. e.consume(pw);
  116. // Deletes `this`
  117. doAction();
  118. }
  119. }
  120. };
  121. struct PortCreateCableItem : ColorMenuItem {
  122. PortWidget* pw;
  123. void onButton(const ButtonEvent& e) override {
  124. OpaqueWidget::onButton(e);
  125. if (disabled)
  126. return;
  127. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0) {
  128. // Set PortWidget::onDragStart overrides
  129. pw->internal->overrideCreateCable = true;
  130. pw->internal->overrideColor = color;
  131. // Pretend the PortWidget was clicked
  132. e.consume(pw);
  133. // Deletes `this`
  134. doAction();
  135. }
  136. }
  137. };
  138. PortWidget::PortWidget() {
  139. internal = new Internal;
  140. }
  141. PortWidget::~PortWidget() {
  142. // The port shouldn't have any cables when destroyed, but just to make sure.
  143. if (module)
  144. APP->scene->rack->clearCablesOnPort(this);
  145. // HACK: In case onDragDrop() is called but not onLeave() afterwards...
  146. destroyTooltip();
  147. delete internal;
  148. }
  149. engine::Port* PortWidget::getPort() {
  150. if (!module)
  151. return NULL;
  152. if (type == engine::Port::INPUT)
  153. return &module->inputs[portId];
  154. else
  155. return &module->outputs[portId];
  156. }
  157. engine::PortInfo* PortWidget::getPortInfo() {
  158. if (!module)
  159. return NULL;
  160. if (type == engine::Port::INPUT)
  161. return module->inputInfos[portId];
  162. else
  163. return module->outputInfos[portId];
  164. }
  165. void PortWidget::createTooltip() {
  166. if (!settings::tooltips)
  167. return;
  168. if (internal->tooltip)
  169. return;
  170. if (!module)
  171. return;
  172. PortTooltip* tooltip = new PortTooltip;
  173. tooltip->portWidget = this;
  174. APP->scene->addChild(tooltip);
  175. internal->tooltip = tooltip;
  176. }
  177. void PortWidget::destroyTooltip() {
  178. if (!internal->tooltip)
  179. return;
  180. APP->scene->removeChild(internal->tooltip);
  181. delete internal->tooltip;
  182. internal->tooltip = NULL;
  183. }
  184. void PortWidget::createContextMenu() {
  185. ui::Menu* menu = createMenu();
  186. WeakPtr<PortWidget> weakThis = this;
  187. engine::PortInfo* portInfo = getPortInfo();
  188. assert(portInfo);
  189. menu->addChild(createMenuLabel(portInfo->getFullName()));
  190. std::vector<CableWidget*> cws = APP->scene->rack->getCompleteCablesOnPort(this);
  191. CableWidget* topCw = cws.empty() ? NULL : cws.back();
  192. menu->addChild(createMenuItem("Delete top cable", RACK_MOD_SHIFT_NAME "+click",
  193. [=]() {
  194. if (!weakThis)
  195. return;
  196. weakThis->deleteTopCableAction();
  197. },
  198. !topCw
  199. ));
  200. if (type == engine::Port::INPUT) {
  201. PortCloneCableItem* item = createMenuItem<PortCloneCableItem>("Duplicate top cable", RACK_MOD_CTRL_NAME "+drag");
  202. item->disabled = !topCw;
  203. item->pw = this;
  204. item->cw = topCw;
  205. menu->addChild(item);
  206. }
  207. menu->addChild(new ui::MenuSeparator);
  208. // New cable items
  209. bool createCableDisabled = (type == engine::Port::INPUT) && topCw;
  210. for (NVGcolor color : settings::cableColors) {
  211. // Include extra leading spaces for the color circle
  212. PortCreateCableItem* item = createMenuItem<PortCreateCableItem>(" New cable", "Click+drag");
  213. item->disabled = createCableDisabled;
  214. item->pw = this;
  215. item->color = color;
  216. menu->addChild(item);
  217. }
  218. if (!cws.empty()) {
  219. menu->addChild(new ui::MenuSeparator);
  220. }
  221. // Cable items
  222. for (auto it = cws.rbegin(); it != cws.rend(); it++) {
  223. CableWidget* cw = *it;
  224. PortWidget* pw = (type == engine::Port::INPUT) ? cw->outputPort : cw->inputPort;
  225. engine::PortInfo* portInfo = pw->getPortInfo();
  226. PortCableItem* item = createMenuItem<PortCableItem>(" " + portInfo->module->model->name + ": " + portInfo->getName());
  227. item->color = cw->color;
  228. item->pw = this;
  229. item->cw = cw;
  230. menu->addChild(item);
  231. }
  232. }
  233. void PortWidget::deleteTopCableAction() {
  234. CableWidget* cw = APP->scene->rack->getTopCable(this);
  235. if (!cw)
  236. return;
  237. // history::CableRemove
  238. history::CableRemove* h = new history::CableRemove;
  239. h->setCable(cw);
  240. APP->history->push(h);
  241. APP->scene->rack->removeCable(cw);
  242. delete cw;
  243. }
  244. void PortWidget::step() {
  245. Widget::step();
  246. }
  247. void PortWidget::draw(const DrawArgs& args) {
  248. CableWidget* cw = APP->scene->rack->getIncompleteCable();
  249. if (cw) {
  250. // Dim the PortWidget if the active cable cannot plug into this PortWidget
  251. if (type == engine::Port::OUTPUT ? cw->outputPort : cw->inputPort) {
  252. nvgTint(args.vg, nvgRGBf(0.33, 0.33, 0.33));
  253. }
  254. }
  255. Widget::draw(args);
  256. }
  257. void PortWidget::onButton(const ButtonEvent& e) {
  258. OpaqueWidget::onButton(e);
  259. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  260. createContextMenu();
  261. e.consume(this);
  262. return;
  263. }
  264. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  265. deleteTopCableAction();
  266. // Consume null so onDragStart isn't triggered
  267. e.consume(NULL);
  268. return;
  269. }
  270. }
  271. void PortWidget::onEnter(const EnterEvent& e) {
  272. createTooltip();
  273. }
  274. void PortWidget::onLeave(const LeaveEvent& e) {
  275. destroyTooltip();
  276. }
  277. void PortWidget::onDragStart(const DragStartEvent& e) {
  278. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  279. return;
  280. DEFER({
  281. // Reset overrides
  282. internal->overrideCw = NULL;
  283. internal->overrideCloneCw = NULL;
  284. internal->overrideCreateCable = false;
  285. internal->overrideColor = color::BLACK_TRANSPARENT;
  286. });
  287. CableWidget* cw = NULL;
  288. if (internal->overrideCreateCable) {
  289. // Keep cable NULL. Will be created below
  290. }
  291. else if (internal->overrideCloneCw || (APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  292. if (type == engine::Port::OUTPUT) {
  293. // Ctrl-clicking an output creates a new cable.
  294. // Keep cable NULL. Will be created below
  295. }
  296. else {
  297. // Ctrl-clicking an input clones the cable already patched to it.
  298. CableWidget* cloneCw;
  299. if (internal->overrideCloneCw)
  300. cloneCw = internal->overrideCloneCw;
  301. else
  302. cloneCw = APP->scene->rack->getTopCable(this);
  303. if (cloneCw) {
  304. cw = new CableWidget;
  305. cw->color = cloneCw->color;
  306. cw->outputPort = cloneCw->outputPort;
  307. cw->updateCable();
  308. }
  309. }
  310. }
  311. else {
  312. // Grab cable on top of stack
  313. if (internal->overrideCw)
  314. cw = internal->overrideCw;
  315. else
  316. cw = APP->scene->rack->getTopCable(this);
  317. if (cw) {
  318. // history::CableRemove
  319. history::CableRemove* h = new history::CableRemove;
  320. h->setCable(cw);
  321. APP->history->push(h);
  322. // Disconnect and reuse existing cable
  323. APP->scene->rack->removeCable(cw);
  324. if (type == engine::Port::OUTPUT)
  325. cw->outputPort = NULL;
  326. else
  327. cw->inputPort = NULL;
  328. cw->updateCable();
  329. }
  330. }
  331. if (!cw) {
  332. // Check that inputs don't already have a cable
  333. if (type == engine::Port::INPUT) {
  334. CableWidget* topCw = APP->scene->rack->getTopCable(this);
  335. if (topCw)
  336. return;
  337. }
  338. // Create a new cable
  339. cw = new CableWidget;
  340. // Set color
  341. if (internal->overrideColor.a > 0.f)
  342. cw->color = internal->overrideColor;
  343. else
  344. cw->color = APP->scene->rack->getNextCableColor();
  345. // Set port
  346. if (type == engine::Port::OUTPUT)
  347. cw->outputPort = this;
  348. else
  349. cw->inputPort = this;
  350. cw->updateCable();
  351. }
  352. APP->scene->rack->setIncompleteCable(cw);
  353. }
  354. void PortWidget::onDragEnd(const DragEndEvent& e) {
  355. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  356. return;
  357. CableWidget* cw = APP->scene->rack->releaseIncompleteCable();
  358. if (!cw)
  359. return;
  360. if (cw->isComplete()) {
  361. APP->scene->rack->addCable(cw);
  362. // history::CableAdd
  363. history::CableAdd* h = new history::CableAdd;
  364. h->setCable(cw);
  365. APP->history->push(h);
  366. }
  367. else {
  368. delete cw;
  369. }
  370. }
  371. void PortWidget::onDragDrop(const DragDropEvent& e) {
  372. // HACK: Only delete tooltip if we're not (normal) dragging it.
  373. if (e.origin == this)
  374. createTooltip();
  375. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  376. return;
  377. // Reject ports if this is an input port and something is already plugged into it
  378. if (type == engine::Port::INPUT) {
  379. if (APP->scene->rack->getTopCable(this))
  380. return;
  381. }
  382. CableWidget* cw = APP->scene->rack->getIncompleteCable();
  383. if (cw) {
  384. cw->hoveredOutputPort = cw->hoveredInputPort = NULL;
  385. if (type == engine::Port::OUTPUT)
  386. cw->outputPort = this;
  387. else
  388. cw->inputPort = this;
  389. cw->updateCable();
  390. }
  391. }
  392. void PortWidget::onDragEnter(const DragEnterEvent& e) {
  393. PortWidget* pw = dynamic_cast<PortWidget*>(e.origin);
  394. if (pw) {
  395. createTooltip();
  396. }
  397. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  398. return;
  399. // Reject ports if this is an input port and something is already plugged into it
  400. if (type == engine::Port::INPUT) {
  401. if (APP->scene->rack->getTopCable(this))
  402. return;
  403. }
  404. CableWidget* cw = APP->scene->rack->getIncompleteCable();
  405. if (cw) {
  406. if (type == engine::Port::OUTPUT)
  407. cw->hoveredOutputPort = this;
  408. else
  409. cw->hoveredInputPort = this;
  410. }
  411. }
  412. void PortWidget::onDragLeave(const DragLeaveEvent& e) {
  413. destroyTooltip();
  414. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  415. return;
  416. PortWidget* originPort = dynamic_cast<PortWidget*>(e.origin);
  417. if (!originPort)
  418. return;
  419. CableWidget* cw = APP->scene->rack->getIncompleteCable();
  420. if (cw) {
  421. if (type == engine::Port::OUTPUT)
  422. cw->hoveredOutputPort = NULL;
  423. else
  424. cw->hoveredInputPort = NULL;
  425. }
  426. }
  427. } // namespace app
  428. } // namespace rack