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.

330 lines
7.6KB

  1. #include <app/PortWidget.hpp>
  2. #include <app/Scene.hpp>
  3. #include <window.hpp>
  4. #include <context.hpp>
  5. #include <history.hpp>
  6. #include <engine/Engine.hpp>
  7. #include <settings.hpp>
  8. #include <componentlibrary.hpp>
  9. namespace rack {
  10. namespace app {
  11. struct PortTooltip : ui::Tooltip {
  12. PortWidget* portWidget;
  13. void step() override {
  14. if (portWidget->module) {
  15. engine::Port* port = portWidget->getPort();
  16. engine::PortInfo* portInfo = portWidget->getPortInfo();
  17. // Label
  18. text = portInfo->getName();
  19. text += " ";
  20. text += (portWidget->type == engine::Port::INPUT) ? "input" : "output";
  21. // Description
  22. std::string description = portInfo->getDescription();
  23. if (description != "") {
  24. text += "\n";
  25. text += description;
  26. }
  27. // Voltage, number of channels
  28. int channels = port->getChannels();
  29. for (int i = 0; i < channels; i++) {
  30. // Add newline or comma
  31. text += "\n";
  32. if (channels > 1)
  33. text += string::f("%d: ", i + 1);
  34. text += string::f("% .3fV", port->getVoltage(i));
  35. }
  36. // Connected to
  37. std::list<CableWidget*> cables = APP->scene->rack->getCablesOnPort(portWidget);
  38. for (CableWidget* cable : cables) {
  39. PortWidget* otherPw = (portWidget->type == engine::Port::INPUT) ? cable->outputPort : cable->inputPort;
  40. if (!otherPw)
  41. continue;
  42. text += "\n";
  43. text += "Connected to ";
  44. text += otherPw->module->model->getFullName();
  45. text += ": ";
  46. text += otherPw->getPortInfo()->getName();
  47. text += " ";
  48. text += (otherPw->type == engine::Port::INPUT) ? "input" : "output";
  49. }
  50. }
  51. Tooltip::step();
  52. // Position at bottom-right of parameter
  53. box.pos = portWidget->getAbsoluteOffset(portWidget->box.size).round();
  54. // Fit inside parent (copied from Tooltip.cpp)
  55. assert(parent);
  56. box = box.nudge(parent->box.zeroPos());
  57. }
  58. };
  59. struct PlugLight : MultiLightWidget {
  60. PlugLight() {
  61. addBaseColor(componentlibrary::SCHEME_GREEN);
  62. addBaseColor(componentlibrary::SCHEME_RED);
  63. addBaseColor(componentlibrary::SCHEME_BLUE);
  64. box.size = math::Vec(8, 8);
  65. bgColor = componentlibrary::SCHEME_BLACK_TRANSPARENT;
  66. }
  67. };
  68. PortWidget::PortWidget() {
  69. plugLight = new PlugLight;
  70. }
  71. PortWidget::~PortWidget() {
  72. // HACK: In case onDragDrop() is called but not onLeave() afterwards...
  73. destroyTooltip();
  74. // plugLight is not a child and is thus owned by the PortWidget, so we need to delete it here
  75. delete plugLight;
  76. // HACK
  77. if (module)
  78. APP->scene->rack->clearCablesOnPort(this);
  79. }
  80. engine::Port* PortWidget::getPort() {
  81. if (!module)
  82. return NULL;
  83. if (type == engine::Port::INPUT)
  84. return &module->inputs[portId];
  85. else
  86. return &module->outputs[portId];
  87. }
  88. engine::PortInfo* PortWidget::getPortInfo() {
  89. if (!module)
  90. return NULL;
  91. if (type == engine::Port::INPUT)
  92. return module->inputInfos[portId];
  93. else
  94. return module->outputInfos[portId];
  95. }
  96. void PortWidget::createTooltip() {
  97. if (!settings::tooltips)
  98. return;
  99. if (this->tooltip)
  100. return;
  101. if (!module)
  102. return;
  103. PortTooltip* tooltip = new PortTooltip;
  104. tooltip->portWidget = this;
  105. APP->scene->addChild(tooltip);
  106. this->tooltip = tooltip;
  107. }
  108. void PortWidget::destroyTooltip() {
  109. if (!tooltip)
  110. return;
  111. APP->scene->removeChild(tooltip);
  112. delete tooltip;
  113. tooltip = NULL;
  114. }
  115. void PortWidget::step() {
  116. if (!module)
  117. return;
  118. std::vector<float> values(3);
  119. for (int i = 0; i < 3; i++) {
  120. if (type == engine::Port::OUTPUT)
  121. values[i] = module->outputs[portId].plugLights[i].getBrightness();
  122. else
  123. values[i] = module->inputs[portId].plugLights[i].getBrightness();
  124. }
  125. plugLight->setBrightnesses(values);
  126. Widget::step();
  127. }
  128. void PortWidget::draw(const DrawArgs& args) {
  129. CableWidget* cw = APP->scene->rack->incompleteCable;
  130. if (cw) {
  131. // Dim the PortWidget if the active cable cannot plug into this PortWidget
  132. if (type == engine::Port::OUTPUT ? cw->outputPort : cw->inputPort)
  133. nvgGlobalAlpha(args.vg, 0.5);
  134. }
  135. Widget::draw(args);
  136. }
  137. void PortWidget::onButton(const event::Button& e) {
  138. OpaqueWidget::onButton(e);
  139. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  140. CableWidget* cw = APP->scene->rack->getTopCable(this);
  141. if (cw) {
  142. // history::CableRemove
  143. history::CableRemove* h = new history::CableRemove;
  144. h->setCable(cw);
  145. APP->history->push(h);
  146. APP->scene->rack->removeCable(cw);
  147. delete cw;
  148. }
  149. e.consume(this);
  150. }
  151. }
  152. void PortWidget::onEnter(const event::Enter& e) {
  153. createTooltip();
  154. }
  155. void PortWidget::onLeave(const event::Leave& e) {
  156. destroyTooltip();
  157. }
  158. void PortWidget::onDragStart(const event::DragStart& e) {
  159. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  160. return;
  161. CableWidget* cw = NULL;
  162. if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  163. if (type == engine::Port::OUTPUT) {
  164. // Ctrl-clicking an output creates a new cable.
  165. // Keep cable NULL. Will be created below
  166. }
  167. else {
  168. // Ctrl-clicking an input clones the cable already patched to it.
  169. CableWidget* topCw = APP->scene->rack->getTopCable(this);
  170. if (topCw) {
  171. cw = new CableWidget;
  172. cw->color = topCw->color;
  173. cw->outputPort = topCw->outputPort;
  174. cw->updateCable();
  175. }
  176. }
  177. }
  178. else {
  179. // Grab cable on top of stack
  180. cw = APP->scene->rack->getTopCable(this);
  181. if (cw) {
  182. // history::CableRemove
  183. history::CableRemove* h = new history::CableRemove;
  184. h->setCable(cw);
  185. APP->history->push(h);
  186. // Disconnect and reuse existing cable
  187. APP->scene->rack->removeCable(cw);
  188. if (type == engine::Port::OUTPUT)
  189. cw->outputPort = NULL;
  190. else
  191. cw->inputPort = NULL;
  192. cw->updateCable();
  193. }
  194. }
  195. if (!cw) {
  196. // Create a new cable
  197. cw = new CableWidget;
  198. cw->setNextCableColor();
  199. if (type == engine::Port::OUTPUT)
  200. cw->outputPort = this;
  201. else
  202. cw->inputPort = this;
  203. cw->updateCable();
  204. }
  205. APP->scene->rack->setIncompleteCable(cw);
  206. }
  207. void PortWidget::onDragEnd(const event::DragEnd& e) {
  208. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  209. return;
  210. CableWidget* cw = APP->scene->rack->releaseIncompleteCable();
  211. if (!cw)
  212. return;
  213. if (cw->isComplete()) {
  214. APP->scene->rack->addCable(cw);
  215. // history::CableAdd
  216. history::CableAdd* h = new history::CableAdd;
  217. h->setCable(cw);
  218. APP->history->push(h);
  219. }
  220. else {
  221. delete cw;
  222. }
  223. }
  224. void PortWidget::onDragDrop(const event::DragDrop& e) {
  225. // HACK: Only delete tooltip if we're not (normal) dragging it.
  226. if (e.origin == this)
  227. createTooltip();
  228. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  229. return;
  230. // Reject ports if this is an input port and something is already plugged into it
  231. if (type == engine::Port::INPUT) {
  232. if (APP->scene->rack->getTopCable(this))
  233. return;
  234. }
  235. CableWidget* cw = APP->scene->rack->incompleteCable;
  236. if (cw) {
  237. cw->hoveredOutputPort = cw->hoveredInputPort = NULL;
  238. if (type == engine::Port::OUTPUT)
  239. cw->outputPort = this;
  240. else
  241. cw->inputPort = this;
  242. cw->updateCable();
  243. }
  244. }
  245. void PortWidget::onDragEnter(const event::DragEnter& e) {
  246. PortWidget* pw = dynamic_cast<PortWidget*>(e.origin);
  247. if (pw) {
  248. createTooltip();
  249. }
  250. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  251. return;
  252. // Reject ports if this is an input port and something is already plugged into it
  253. if (type == engine::Port::INPUT) {
  254. if (APP->scene->rack->getTopCable(this))
  255. return;
  256. }
  257. CableWidget* cw = APP->scene->rack->incompleteCable;
  258. if (cw) {
  259. if (type == engine::Port::OUTPUT)
  260. cw->hoveredOutputPort = this;
  261. else
  262. cw->hoveredInputPort = this;
  263. }
  264. }
  265. void PortWidget::onDragLeave(const event::DragLeave& e) {
  266. destroyTooltip();
  267. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  268. return;
  269. PortWidget* originPort = dynamic_cast<PortWidget*>(e.origin);
  270. if (!originPort)
  271. return;
  272. CableWidget* cw = APP->scene->rack->incompleteCable;
  273. if (cw) {
  274. if (type == engine::Port::OUTPUT)
  275. cw->hoveredOutputPort = NULL;
  276. else
  277. cw->hoveredInputPort = NULL;
  278. }
  279. }
  280. } // namespace app
  281. } // namespace rack