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.

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