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.

559 lines
13KB

  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 CableColorItem : ColorMenuItem {
  105. CableWidget* cw;
  106. void onAction(const ActionEvent& e) override {
  107. // history::CableColorChange
  108. history::CableColorChange* h = new history::CableColorChange;
  109. h->setCable(cw);
  110. h->newColor = color;
  111. h->oldColor = cw->color;
  112. APP->history->push(h);
  113. cw->color = color;
  114. }
  115. };
  116. struct PortCableItem : ColorMenuItem {
  117. PortWidget* pw;
  118. CableWidget* cw;
  119. void onButton(const ButtonEvent& e) override {
  120. OpaqueWidget::onButton(e);
  121. if (disabled)
  122. return;
  123. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0) {
  124. // Set PortWidget::onDragStart overrides
  125. pw->internal->overrideCw = cw;
  126. // Pretend the PortWidget was clicked
  127. e.consume(pw);
  128. // Deletes `this`
  129. doAction();
  130. }
  131. }
  132. ui::Menu* createChildMenu() override {
  133. ui::Menu* menu = new ui::Menu;
  134. for (NVGcolor color : settings::cableColors) {
  135. // Include extra leading spaces for the color circle
  136. CableColorItem* item = createMenuItem<CableColorItem>(" Set color");
  137. item->disabled = color::isEqual(color, cw->color);
  138. item->cw = cw;
  139. item->color = color;
  140. menu->addChild(item);
  141. }
  142. return menu;
  143. }
  144. };
  145. struct PortCreateCableItem : ColorMenuItem {
  146. PortWidget* pw;
  147. void onButton(const ButtonEvent& e) override {
  148. OpaqueWidget::onButton(e);
  149. if (disabled)
  150. return;
  151. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0) {
  152. // Set PortWidget::onDragStart overrides
  153. pw->internal->overrideCreateCable = true;
  154. pw->internal->overrideColor = color;
  155. // Pretend the PortWidget was clicked
  156. e.consume(pw);
  157. // Deletes `this`
  158. doAction();
  159. }
  160. }
  161. };
  162. PortWidget::PortWidget() {
  163. internal = new Internal;
  164. }
  165. PortWidget::~PortWidget() {
  166. // The port shouldn't have any cables when destroyed, but just to make sure.
  167. if (module)
  168. APP->scene->rack->clearCablesOnPort(this);
  169. // HACK: In case onDragDrop() is called but not onLeave() afterwards...
  170. destroyTooltip();
  171. delete internal;
  172. }
  173. engine::Port* PortWidget::getPort() {
  174. if (!module)
  175. return NULL;
  176. if (type == engine::Port::INPUT)
  177. return &module->inputs[portId];
  178. else
  179. return &module->outputs[portId];
  180. }
  181. engine::PortInfo* PortWidget::getPortInfo() {
  182. if (!module)
  183. return NULL;
  184. if (type == engine::Port::INPUT)
  185. return module->inputInfos[portId];
  186. else
  187. return module->outputInfos[portId];
  188. }
  189. void PortWidget::createTooltip() {
  190. if (!settings::tooltips)
  191. return;
  192. if (internal->tooltip)
  193. return;
  194. if (!module)
  195. return;
  196. PortTooltip* tooltip = new PortTooltip;
  197. tooltip->portWidget = this;
  198. APP->scene->addChild(tooltip);
  199. internal->tooltip = tooltip;
  200. }
  201. void PortWidget::destroyTooltip() {
  202. if (!internal->tooltip)
  203. return;
  204. APP->scene->removeChild(internal->tooltip);
  205. delete internal->tooltip;
  206. internal->tooltip = NULL;
  207. }
  208. void PortWidget::createContextMenu() {
  209. ui::Menu* menu = createMenu();
  210. WeakPtr<PortWidget> weakThis = this;
  211. engine::PortInfo* portInfo = getPortInfo();
  212. assert(portInfo);
  213. menu->addChild(createMenuLabel(portInfo->getFullName()));
  214. std::vector<CableWidget*> cws = APP->scene->rack->getCompleteCablesOnPort(this);
  215. CableWidget* topCw = cws.empty() ? NULL : cws.back();
  216. menu->addChild(createMenuItem("Delete top cable", RACK_MOD_SHIFT_NAME "+click",
  217. [=]() {
  218. if (!weakThis)
  219. return;
  220. weakThis->deleteTopCableAction();
  221. },
  222. !topCw
  223. ));
  224. if (type == engine::Port::INPUT) {
  225. PortCloneCableItem* item = createMenuItem<PortCloneCableItem>("Duplicate top cable", RACK_MOD_CTRL_NAME "+drag");
  226. item->disabled = !topCw;
  227. item->pw = this;
  228. item->cw = topCw;
  229. menu->addChild(item);
  230. }
  231. menu->addChild(new ui::MenuSeparator);
  232. // New cable items
  233. bool createCableDisabled = (type == engine::Port::INPUT) && topCw;
  234. for (NVGcolor color : settings::cableColors) {
  235. // Include extra leading spaces for the color circle
  236. PortCreateCableItem* item = createMenuItem<PortCreateCableItem>(" New cable", "Click+drag");
  237. item->disabled = createCableDisabled;
  238. item->pw = this;
  239. item->color = color;
  240. menu->addChild(item);
  241. }
  242. if (!cws.empty()) {
  243. menu->addChild(new ui::MenuSeparator);
  244. menu->addChild(createMenuLabel("Click+drag to grab cable"));
  245. // Cable items
  246. for (auto it = cws.rbegin(); it != cws.rend(); it++) {
  247. CableWidget* cw = *it;
  248. PortWidget* pw = (type == engine::Port::INPUT) ? cw->outputPort : cw->inputPort;
  249. engine::PortInfo* portInfo = pw->getPortInfo();
  250. PortCableItem* item = createMenuItem<PortCableItem>(" " + portInfo->module->model->name + ": " + portInfo->getName(), RIGHT_ARROW);
  251. item->color = cw->color;
  252. item->pw = this;
  253. item->cw = cw;
  254. menu->addChild(item);
  255. }
  256. }
  257. appendContextMenu(menu);
  258. }
  259. void PortWidget::deleteTopCableAction() {
  260. CableWidget* cw = APP->scene->rack->getTopCable(this);
  261. if (!cw)
  262. return;
  263. // history::CableRemove
  264. history::CableRemove* h = new history::CableRemove;
  265. h->setCable(cw);
  266. APP->history->push(h);
  267. APP->scene->rack->removeCable(cw);
  268. delete cw;
  269. }
  270. void PortWidget::step() {
  271. Widget::step();
  272. }
  273. void PortWidget::draw(const DrawArgs& args) {
  274. CableWidget* cw = APP->scene->rack->getIncompleteCable();
  275. if (cw) {
  276. // Dim the PortWidget if the active cable cannot plug into this PortWidget
  277. if (type == engine::Port::OUTPUT ? cw->outputPort : cw->inputPort) {
  278. nvgTint(args.vg, nvgRGBf(0.33, 0.33, 0.33));
  279. }
  280. }
  281. Widget::draw(args);
  282. }
  283. void PortWidget::onButton(const ButtonEvent& e) {
  284. OpaqueWidget::onButton(e);
  285. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  286. createContextMenu();
  287. e.consume(this);
  288. return;
  289. }
  290. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  291. deleteTopCableAction();
  292. // Consume null so onDragStart isn't triggered
  293. e.consume(NULL);
  294. return;
  295. }
  296. }
  297. void PortWidget::onEnter(const EnterEvent& e) {
  298. createTooltip();
  299. }
  300. void PortWidget::onLeave(const LeaveEvent& e) {
  301. destroyTooltip();
  302. }
  303. void PortWidget::onDragStart(const DragStartEvent& e) {
  304. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  305. return;
  306. DEFER({
  307. // Reset overrides
  308. internal->overrideCw = NULL;
  309. internal->overrideCloneCw = NULL;
  310. internal->overrideCreateCable = false;
  311. internal->overrideColor = color::BLACK_TRANSPARENT;
  312. });
  313. CableWidget* cw = NULL;
  314. if (internal->overrideCreateCable) {
  315. // Keep cable NULL. Will be created below
  316. }
  317. else if (internal->overrideCloneCw || (APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  318. if (type == engine::Port::OUTPUT) {
  319. // Ctrl-clicking an output creates a new cable.
  320. // Keep cable NULL. Will be created below
  321. }
  322. else {
  323. // Ctrl-clicking an input clones the cable already patched to it.
  324. CableWidget* cloneCw;
  325. if (internal->overrideCloneCw)
  326. cloneCw = internal->overrideCloneCw;
  327. else
  328. cloneCw = APP->scene->rack->getTopCable(this);
  329. if (cloneCw) {
  330. cw = new CableWidget;
  331. cw->color = cloneCw->color;
  332. cw->outputPort = cloneCw->outputPort;
  333. cw->updateCable();
  334. }
  335. }
  336. }
  337. else {
  338. // Grab cable on top of stack
  339. if (internal->overrideCw)
  340. cw = internal->overrideCw;
  341. else
  342. cw = APP->scene->rack->getTopCable(this);
  343. if (cw) {
  344. // history::CableRemove
  345. history::CableRemove* h = new history::CableRemove;
  346. h->setCable(cw);
  347. APP->history->push(h);
  348. // Disconnect and reuse existing cable
  349. APP->scene->rack->removeCable(cw);
  350. if (type == engine::Port::OUTPUT)
  351. cw->outputPort = NULL;
  352. else
  353. cw->inputPort = NULL;
  354. cw->updateCable();
  355. }
  356. }
  357. if (!cw) {
  358. // Check that inputs don't already have a cable
  359. if (type == engine::Port::INPUT) {
  360. CableWidget* topCw = APP->scene->rack->getTopCable(this);
  361. if (topCw)
  362. return;
  363. }
  364. // Create a new cable
  365. cw = new CableWidget;
  366. // Set color
  367. if (internal->overrideCreateCable)
  368. cw->color = internal->overrideColor;
  369. else
  370. cw->color = APP->scene->rack->getNextCableColor();
  371. // Set port
  372. if (type == engine::Port::OUTPUT)
  373. cw->outputPort = this;
  374. else
  375. cw->inputPort = this;
  376. cw->updateCable();
  377. }
  378. APP->scene->rack->setIncompleteCable(cw);
  379. }
  380. void PortWidget::onDragEnd(const DragEndEvent& e) {
  381. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  382. return;
  383. CableWidget* cw = APP->scene->rack->releaseIncompleteCable();
  384. if (!cw)
  385. return;
  386. if (cw->isComplete()) {
  387. APP->scene->rack->addCable(cw);
  388. // history::CableAdd
  389. history::CableAdd* h = new history::CableAdd;
  390. h->setCable(cw);
  391. APP->history->push(h);
  392. }
  393. else {
  394. delete cw;
  395. }
  396. }
  397. void PortWidget::onDragDrop(const DragDropEvent& e) {
  398. // HACK: Only delete tooltip if we're not (normal) dragging it.
  399. if (e.origin == this)
  400. createTooltip();
  401. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  402. return;
  403. // Reject ports if this is an input port and something is already plugged into it
  404. if (type == engine::Port::INPUT) {
  405. if (APP->scene->rack->getTopCable(this))
  406. return;
  407. }
  408. CableWidget* cw = APP->scene->rack->getIncompleteCable();
  409. if (cw) {
  410. cw->hoveredOutputPort = cw->hoveredInputPort = NULL;
  411. if (type == engine::Port::OUTPUT)
  412. cw->outputPort = this;
  413. else
  414. cw->inputPort = this;
  415. cw->updateCable();
  416. }
  417. }
  418. void PortWidget::onDragEnter(const DragEnterEvent& e) {
  419. PortWidget* pw = dynamic_cast<PortWidget*>(e.origin);
  420. if (pw) {
  421. createTooltip();
  422. }
  423. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  424. return;
  425. // Reject ports if this is an input port and something is already plugged into it
  426. if (type == engine::Port::INPUT) {
  427. if (APP->scene->rack->getTopCable(this))
  428. return;
  429. }
  430. CableWidget* cw = APP->scene->rack->getIncompleteCable();
  431. if (cw) {
  432. if (type == engine::Port::OUTPUT)
  433. cw->hoveredOutputPort = this;
  434. else
  435. cw->hoveredInputPort = this;
  436. }
  437. }
  438. void PortWidget::onDragLeave(const DragLeaveEvent& e) {
  439. destroyTooltip();
  440. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  441. return;
  442. PortWidget* originPort = dynamic_cast<PortWidget*>(e.origin);
  443. if (!originPort)
  444. return;
  445. CableWidget* cw = APP->scene->rack->getIncompleteCable();
  446. if (cw) {
  447. if (type == engine::Port::OUTPUT)
  448. cw->hoveredOutputPort = NULL;
  449. else
  450. cw->hoveredInputPort = NULL;
  451. }
  452. }
  453. } // namespace app
  454. } // namespace rack