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.

992 lines
32KB

  1. /*
  2. * DISTRHO Ildaeil Plugin
  3. * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. #include "CarlaNativePlugin.h"
  18. #include "CarlaBackendUtils.hpp"
  19. #include "DistrhoUI.hpp"
  20. #include "DistrhoPlugin.hpp"
  21. #include "PluginHostWindow.hpp"
  22. #include "extra/Thread.hpp"
  23. // IDE helper
  24. #include "DearImGui.hpp"
  25. #include <vector>
  26. // strcasestr
  27. #ifdef DISTRHO_OS_WINDOWS
  28. # include <shlwapi.h>
  29. namespace ildaeil {
  30. inline const char* strcasestr(const char* const haystack, const char* const needle)
  31. {
  32. return StrStrIA(haystack, needle);
  33. }
  34. // using strcasestr = StrStrIA;
  35. }
  36. #else
  37. namespace ildaeil {
  38. using ::strcasestr;
  39. }
  40. #endif
  41. // generates a warning if this is defined as anything else
  42. #define CARLA_API
  43. START_NAMESPACE_DISTRHO
  44. // --------------------------------------------------------------------------------------------------------------------
  45. class IldaeilPlugin : public Plugin
  46. {
  47. public:
  48. const NativePluginDescriptor* fCarlaPluginDescriptor;
  49. NativePluginHandle fCarlaPluginHandle;
  50. NativeHostDescriptor fCarlaHostDescriptor;
  51. CarlaHostHandle fCarlaHostHandle;
  52. void* fUI;
  53. // ...
  54. };
  55. // --------------------------------------------------------------------------------------------------------------------
  56. void ildaeilParameterChangeForUI(void* ui, uint32_t index, float value);
  57. const char* ildaeilOpenFileForUI(void* ui, bool isDir, const char* title, const char* filter);
  58. // --------------------------------------------------------------------------------------------------------------------
  59. using namespace CarlaBackend;
  60. // shared resource pointer
  61. // carla_juce_init();
  62. class IldaeilUI : public UI,
  63. public Thread,
  64. public PluginHostWindow::Callbacks
  65. {
  66. static constexpr const uint kInitialWidth = 1220;
  67. static constexpr const uint kInitialHeight = 640;
  68. static constexpr const uint kGenericWidth = 360;
  69. static constexpr const uint kGenericHeight = 400;
  70. static constexpr const uint kButtonHeight = 20;
  71. enum {
  72. kDrawingInit,
  73. kDrawingErrorInit,
  74. kDrawingErrorDraw,
  75. kDrawingLoading,
  76. kDrawingPluginList,
  77. kDrawingPluginEmbedUI,
  78. kDrawingPluginGenericUI,
  79. kDrawingPluginPendingFromInit
  80. } fDrawingState;
  81. struct PluginInfoCache {
  82. char* name;
  83. char* label;
  84. PluginInfoCache()
  85. : name(nullptr),
  86. label(nullptr) {}
  87. ~PluginInfoCache()
  88. {
  89. std::free(name);
  90. std::free(label);
  91. }
  92. };
  93. struct PluginGenericUI {
  94. char* title;
  95. uint parameterCount;
  96. struct Parameter {
  97. char* name;
  98. char* format;
  99. uint32_t rindex;
  100. bool boolean, bvalue, log;
  101. float min, max, power;
  102. Parameter()
  103. : name(nullptr),
  104. format(nullptr),
  105. rindex(0),
  106. boolean(false),
  107. bvalue(false),
  108. log(false),
  109. min(0.0f),
  110. max(1.0f) {}
  111. ~Parameter()
  112. {
  113. std::free(name);
  114. std::free(format);
  115. }
  116. }* parameters;
  117. float* values;
  118. PluginGenericUI()
  119. : title(nullptr),
  120. parameterCount(0),
  121. parameters(nullptr),
  122. values(nullptr) {}
  123. ~PluginGenericUI()
  124. {
  125. std::free(title);
  126. delete[] parameters;
  127. delete[] values;
  128. }
  129. };
  130. IldaeilPlugin* const fPlugin;
  131. PluginHostWindow fPluginHostWindow;
  132. PluginType fPluginType;
  133. uint fPluginCount;
  134. uint fPluginSelected;
  135. bool fPluginScanningFinished;
  136. bool fPluginHasCustomUI;
  137. bool fPluginHasEmbedUI;
  138. bool fPluginWillRunInBridgeMode;
  139. PluginInfoCache* fPlugins;
  140. ScopedPointer<PluginGenericUI> fPluginGenericUI;
  141. bool fPluginSearchActive;
  142. bool fPluginSearchFirstShow;
  143. char fPluginSearchString[0xff];
  144. String fPopupError;
  145. Size<uint> fNextSize;
  146. public:
  147. IldaeilUI()
  148. : UI(kInitialWidth, kInitialHeight),
  149. Thread("IldaeilScanner"),
  150. fDrawingState(kDrawingInit),
  151. fPlugin((IldaeilPlugin*)getPluginInstancePointer()),
  152. fPluginHostWindow(getWindow(), this),
  153. fPluginType(PLUGIN_LV2),
  154. fPluginCount(0),
  155. fPluginSelected(0),
  156. fPluginScanningFinished(false),
  157. fPluginHasCustomUI(false),
  158. fPluginHasEmbedUI(false),
  159. fPluginWillRunInBridgeMode(false),
  160. fPlugins(nullptr),
  161. fPluginSearchActive(false),
  162. fPluginSearchFirstShow(false)
  163. {
  164. const double scaleFactor = getScaleFactor();
  165. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  166. {
  167. fDrawingState = kDrawingErrorInit;
  168. fPopupError = "Ildaeil backend failed to init properly, cannot continue.";
  169. setSize(kInitialWidth * scaleFactor * 0.5, kInitialHeight * scaleFactor * 0.5);
  170. return;
  171. }
  172. std::strcpy(fPluginSearchString, "Search...");
  173. ImGuiStyle& style(ImGui::GetStyle());
  174. style.FrameRounding = 4;
  175. const double padding = style.WindowPadding.y * 2;
  176. if (d_isNotEqual(scaleFactor, 1.0))
  177. {
  178. setSize(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  179. fPluginHostWindow.setPositionAndSize(0, kButtonHeight * scaleFactor + padding,
  180. kInitialWidth * scaleFactor,
  181. (kInitialHeight - kButtonHeight) * scaleFactor - padding);
  182. }
  183. else
  184. {
  185. fPluginHostWindow.setPositionAndSize(0, kButtonHeight + padding,
  186. kInitialWidth, kInitialHeight - kButtonHeight - padding);
  187. }
  188. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  189. char winIdStr[24];
  190. std::snprintf(winIdStr, sizeof(winIdStr), "%lx", (ulong)getWindow().getNativeWindowHandle());
  191. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr);
  192. carla_set_engine_option(handle, ENGINE_OPTION_FRONTEND_UI_SCALE, getScaleFactor()*1000, nullptr);
  193. if (carla_get_current_plugin_count(handle) != 0)
  194. {
  195. const uint hints = carla_get_plugin_info(handle, 0)->hints;
  196. fDrawingState = kDrawingPluginPendingFromInit;
  197. fPluginHasCustomUI = hints & PLUGIN_HAS_CUSTOM_UI;
  198. fPluginHasEmbedUI = hints & PLUGIN_HAS_CUSTOM_EMBED_UI;
  199. }
  200. fPlugin->fUI = this;
  201. }
  202. ~IldaeilUI() override
  203. {
  204. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr)
  205. {
  206. fPlugin->fUI = nullptr;
  207. carla_set_engine_option(fPlugin->fCarlaHostHandle, ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0");
  208. }
  209. if (isThreadRunning())
  210. stopThread(-1);
  211. hidePluginUI();
  212. fPluginGenericUI = nullptr;
  213. delete[] fPlugins;
  214. }
  215. void changeParameterFromDSP(const uint32_t index, const float value)
  216. {
  217. if (PluginGenericUI* const ui = fPluginGenericUI)
  218. {
  219. for (uint32_t i=0; i < ui->parameterCount; ++i)
  220. {
  221. if (ui->parameters[i].rindex != index)
  222. continue;
  223. ui->values[i] = value;
  224. if (ui->parameters[i].boolean)
  225. ui->parameters[i].bvalue = value > ui->parameters[i].min;
  226. break;
  227. }
  228. }
  229. }
  230. const char* openFileFromDSP(const bool /*isDir*/, const char* const title, const char* const /*filter*/)
  231. {
  232. Window::FileBrowserOptions opts;
  233. opts.title = title;
  234. getWindow().openFileBrowser(opts);
  235. return nullptr;
  236. }
  237. void showPluginUI(const CarlaHostHandle handle)
  238. {
  239. const CarlaPluginInfo* const info = carla_get_plugin_info(handle, 0);
  240. if (info->hints & PLUGIN_HAS_CUSTOM_EMBED_UI)
  241. {
  242. fDrawingState = kDrawingPluginEmbedUI;
  243. fPluginHasCustomUI = true;
  244. fPluginHasEmbedUI = true;
  245. carla_embed_custom_ui(handle, 0, fPluginHostWindow.attachAndGetWindowHandle());
  246. }
  247. else
  248. {
  249. fDrawingState = kDrawingPluginGenericUI;
  250. fPluginHasCustomUI = info->hints & PLUGIN_HAS_CUSTOM_UI;
  251. fPluginHasEmbedUI = false;
  252. if (fPluginGenericUI == nullptr)
  253. createPluginGenericUI(handle, info);
  254. else
  255. updatePluginGenericUI(handle);
  256. ImGuiStyle& style(ImGui::GetStyle());
  257. const double scaleFactor = getScaleFactor();
  258. fNextSize = Size<uint>(kGenericWidth * scaleFactor, (kGenericHeight + style.FramePadding.x) * scaleFactor);
  259. }
  260. repaint();
  261. }
  262. void hidePluginUI()
  263. {
  264. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  265. return;
  266. fPluginHostWindow.hide();
  267. if (fDrawingState == kDrawingPluginGenericUI || fDrawingState == kDrawingPluginEmbedUI)
  268. carla_show_custom_ui(fPlugin->fCarlaHostHandle, 0, false);
  269. }
  270. void createPluginGenericUI(const CarlaHostHandle handle, const CarlaPluginInfo* const info)
  271. {
  272. PluginGenericUI* const ui = new PluginGenericUI;
  273. String title(info->name);
  274. title += " by ";
  275. title += info->maker;
  276. ui->title = title.getAndReleaseBuffer();
  277. const uint32_t pcount = ui->parameterCount = carla_get_parameter_count(handle, 0);
  278. // make count of valid parameters
  279. for (uint32_t i=0; i < pcount; ++i)
  280. {
  281. const ParameterData* const pdata = carla_get_parameter_data(handle, 0, i);
  282. if (pdata->type != PARAMETER_INPUT ||
  283. (pdata->hints & PARAMETER_IS_ENABLED) == 0x0 ||
  284. (pdata->hints & PARAMETER_IS_READ_ONLY) != 0x0)
  285. {
  286. --ui->parameterCount;
  287. continue;
  288. }
  289. }
  290. ui->parameters = new PluginGenericUI::Parameter[ui->parameterCount];
  291. ui->values = new float[ui->parameterCount];
  292. // now safely fill in details
  293. for (uint32_t i=0, j=0; i < pcount; ++i)
  294. {
  295. const ParameterData* const pdata = carla_get_parameter_data(handle, 0, i);
  296. if (pdata->type != PARAMETER_INPUT ||
  297. (pdata->hints & PARAMETER_IS_ENABLED) == 0x0 ||
  298. (pdata->hints & PARAMETER_IS_READ_ONLY) != 0x0)
  299. continue;
  300. const CarlaParameterInfo* const pinfo = carla_get_parameter_info(handle, 0, i);
  301. const ::ParameterRanges* const pranges = carla_get_parameter_ranges(handle, 0, i);
  302. String format;
  303. if (pdata->hints & PARAMETER_IS_INTEGER)
  304. format = "%.0f ";
  305. else
  306. format = "%.3f ";
  307. format += pinfo->unit;
  308. PluginGenericUI::Parameter& param(ui->parameters[j]);
  309. param.name = strdup(pinfo->name);
  310. param.format = format.getAndReleaseBuffer();
  311. param.rindex = i;
  312. param.boolean = pdata->hints & PARAMETER_IS_BOOLEAN;
  313. param.log = pdata->hints & PARAMETER_IS_LOGARITHMIC;
  314. param.min = pranges->min;
  315. param.max = pranges->max;
  316. ui->values[j] = carla_get_current_parameter_value(handle, 0, i);
  317. if (param.boolean)
  318. param.bvalue = ui->values[j] > param.min;
  319. else
  320. param.bvalue = false;
  321. ++j;
  322. }
  323. fPluginGenericUI = ui;
  324. }
  325. void updatePluginGenericUI(const CarlaHostHandle handle)
  326. {
  327. PluginGenericUI* const ui = fPluginGenericUI;
  328. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  329. for (uint32_t i=0; i < ui->parameterCount; ++i)
  330. {
  331. ui->values[i] = carla_get_current_parameter_value(handle, 0, ui->parameters[i].rindex);
  332. if (ui->parameters[i].boolean)
  333. ui->parameters[i].bvalue = ui->values[i] > ui->parameters[i].min;
  334. }
  335. }
  336. bool loadPlugin(const CarlaHostHandle handle, const char* const label)
  337. {
  338. if (carla_get_current_plugin_count(handle) != 0)
  339. {
  340. hidePluginUI();
  341. carla_replace_plugin(handle, 0);
  342. }
  343. carla_set_engine_option(handle, ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, fPluginWillRunInBridgeMode, nullptr);
  344. if (carla_add_plugin(handle, BINARY_NATIVE, fPluginType, nullptr, nullptr,
  345. label, 0, 0x0, PLUGIN_OPTIONS_NULL))
  346. {
  347. fPluginGenericUI = nullptr;
  348. showPluginUI(handle);
  349. return true;
  350. }
  351. else
  352. {
  353. fPopupError = carla_get_last_error(handle);
  354. d_stdout("got error: %s", fPopupError.buffer());
  355. ImGui::OpenPopup("Plugin Error");
  356. }
  357. return false;
  358. }
  359. protected:
  360. void pluginWindowResized(const uint width, const uint height) override
  361. {
  362. fNextSize = Size<uint>(width,
  363. height + kButtonHeight * getScaleFactor() + ImGui::GetStyle().WindowPadding.y * 2);
  364. }
  365. void uiIdle() override
  366. {
  367. switch (fDrawingState)
  368. {
  369. case kDrawingInit:
  370. fDrawingState = kDrawingLoading;
  371. startThread();
  372. repaint();
  373. break;
  374. case kDrawingPluginPendingFromInit:
  375. showPluginUI(fPlugin->fCarlaHostHandle);
  376. startThread();
  377. break;
  378. case kDrawingPluginEmbedUI:
  379. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  380. fPluginHostWindow.idle();
  381. break;
  382. case kDrawingPluginGenericUI:
  383. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  384. break;
  385. default:
  386. break;
  387. }
  388. if (fNextSize.isValid())
  389. {
  390. setSize(fNextSize);
  391. fNextSize = Size<uint>();
  392. return;
  393. }
  394. }
  395. void uiFileBrowserSelected(const char* const filename) override
  396. {
  397. if (fPlugin != nullptr && fPlugin->fCarlaHostHandle != nullptr && filename != nullptr)
  398. carla_set_custom_data(fPlugin->fCarlaHostHandle, 0, CUSTOM_DATA_TYPE_STRING, "file", filename);
  399. }
  400. void run() override
  401. {
  402. if (const uint count = carla_get_cached_plugin_count(fPluginType, nullptr))
  403. {
  404. fPluginCount = 0;
  405. fPlugins = new PluginInfoCache[count];
  406. if (fDrawingState == kDrawingLoading)
  407. {
  408. fDrawingState = kDrawingPluginList;
  409. fPluginSearchFirstShow = true;
  410. }
  411. for (uint i=0, j; i < count && ! shouldThreadExit(); ++i)
  412. {
  413. const CarlaCachedPluginInfo* const info = carla_get_cached_plugin_info(fPluginType, i);
  414. DISTRHO_SAFE_ASSERT_CONTINUE(info != nullptr);
  415. if (! info->valid)
  416. continue;
  417. #if DISTRHO_PLUGIN_IS_SYNTH
  418. if (info->midiIns != 1 || (info->audioOuts != 1 && info->audioOuts != 2))
  419. continue;
  420. if ((info->hints & PLUGIN_IS_SYNTH) == 0x0)
  421. continue;
  422. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  423. if (info->midiIns != 1 || info->midiOuts != 1)
  424. continue;
  425. if (info->audioIns != 0 || info->audioOuts != 0)
  426. continue;
  427. #else
  428. if (info->audioIns != 2 || info->audioOuts != 2)
  429. continue;
  430. #endif
  431. j = fPluginCount;
  432. fPlugins[j].name = strdup(info->name);
  433. fPlugins[j].label = strdup(info->label);
  434. ++fPluginCount;
  435. }
  436. }
  437. else
  438. {
  439. String error("There are no ");
  440. error += getPluginTypeAsString(fPluginType);
  441. error += " audio plugins on this system.";
  442. fPopupError = error;
  443. fDrawingState = kDrawingErrorInit;
  444. }
  445. if (! shouldThreadExit())
  446. fPluginScanningFinished = true;
  447. }
  448. void onImGuiDisplay() override
  449. {
  450. switch (fDrawingState)
  451. {
  452. case kDrawingInit:
  453. case kDrawingLoading:
  454. case kDrawingPluginPendingFromInit:
  455. drawLoading();
  456. break;
  457. case kDrawingPluginList:
  458. drawPluginList();
  459. break;
  460. case kDrawingErrorInit:
  461. fDrawingState = kDrawingErrorDraw;
  462. drawError(true);
  463. break;
  464. case kDrawingErrorDraw:
  465. drawError(false);
  466. break;
  467. case kDrawingPluginGenericUI:
  468. drawGenericUI();
  469. // fall-through
  470. case kDrawingPluginEmbedUI:
  471. drawTopBar();
  472. break;
  473. }
  474. }
  475. void drawError(const bool open)
  476. {
  477. ImGui::SetNextWindowPos(ImVec2(0, 0));
  478. ImGui::SetNextWindowSize(ImVec2(getWidth(), getHeight()));
  479. const int flags = ImGuiWindowFlags_NoSavedSettings
  480. | ImGuiWindowFlags_NoTitleBar
  481. | ImGuiWindowFlags_NoResize
  482. | ImGuiWindowFlags_NoCollapse
  483. | ImGuiWindowFlags_NoScrollbar
  484. | ImGuiWindowFlags_NoScrollWithMouse
  485. | ImGuiWindowFlags_NoCollapse;
  486. if (ImGui::Begin("Error Window", nullptr, flags))
  487. {
  488. if (open)
  489. ImGui::OpenPopup("Engine Error");
  490. const int pflags = ImGuiWindowFlags_NoSavedSettings
  491. | ImGuiWindowFlags_NoResize
  492. | ImGuiWindowFlags_NoCollapse
  493. | ImGuiWindowFlags_NoScrollbar
  494. | ImGuiWindowFlags_NoScrollWithMouse
  495. | ImGuiWindowFlags_NoCollapse
  496. | ImGuiWindowFlags_AlwaysAutoResize
  497. | ImGuiWindowFlags_AlwaysUseWindowPadding;
  498. if (ImGui::BeginPopupModal("Engine Error", nullptr, pflags))
  499. {
  500. ImGui::TextUnformatted(fPopupError.buffer(), nullptr);
  501. ImGui::EndPopup();
  502. }
  503. }
  504. ImGui::End();
  505. }
  506. void drawTopBar()
  507. {
  508. const float padding = ImGui::GetStyle().WindowPadding.y * 2;
  509. ImGui::SetNextWindowPos(ImVec2(0, 0));
  510. ImGui::SetNextWindowSize(ImVec2(getWidth(), kButtonHeight * getScaleFactor() + padding));
  511. const int flags = ImGuiWindowFlags_NoSavedSettings
  512. | ImGuiWindowFlags_NoTitleBar
  513. | ImGuiWindowFlags_NoResize
  514. | ImGuiWindowFlags_NoCollapse
  515. | ImGuiWindowFlags_NoScrollbar
  516. | ImGuiWindowFlags_NoScrollWithMouse
  517. | ImGuiWindowFlags_NoCollapse;
  518. if (ImGui::Begin("Current Plugin", nullptr, flags))
  519. {
  520. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  521. if (ImGui::Button("Pick Another..."))
  522. {
  523. hidePluginUI();
  524. fDrawingState = kDrawingPluginList;
  525. const double scaleFactor = getScaleFactor();
  526. fNextSize = Size<uint>(kInitialWidth * scaleFactor, kInitialHeight * scaleFactor);
  527. }
  528. ImGui::SameLine();
  529. if (ImGui::Button("Reset"))
  530. {
  531. loadPlugin(handle, carla_get_plugin_info(handle, 0)->label);
  532. }
  533. if (fDrawingState == kDrawingPluginGenericUI && fPluginHasCustomUI)
  534. {
  535. ImGui::SameLine();
  536. if (ImGui::Button("Show Custom GUI"))
  537. {
  538. if (fPluginHasEmbedUI)
  539. {
  540. fDrawingState = kDrawingPluginEmbedUI;
  541. carla_embed_custom_ui(handle, 0, fPluginHostWindow.attachAndGetWindowHandle());
  542. }
  543. else
  544. {
  545. carla_show_custom_ui(handle, 0, true);
  546. }
  547. ImGui::End();
  548. return;
  549. }
  550. }
  551. if (fDrawingState == kDrawingPluginEmbedUI)
  552. {
  553. ImGui::SameLine();
  554. if (ImGui::Button("Show Generic GUI"))
  555. {
  556. hidePluginUI();
  557. fDrawingState = kDrawingPluginGenericUI;
  558. if (fPluginGenericUI == nullptr)
  559. createPluginGenericUI(handle, carla_get_plugin_info(handle, 0));
  560. else
  561. updatePluginGenericUI(handle);
  562. const double scaleFactor = getScaleFactor();
  563. const double padding = ImGui::GetStyle().WindowPadding.y * 2;
  564. fNextSize = Size<uint>(std::max(getWidth(), static_cast<uint>(kGenericWidth * scaleFactor + 0.5)),
  565. (kGenericHeight + kButtonHeight) * scaleFactor + padding);
  566. }
  567. }
  568. }
  569. ImGui::End();
  570. }
  571. void setupMainWindowPos()
  572. {
  573. float y = 0;
  574. float height = getHeight();
  575. if (fDrawingState == kDrawingPluginGenericUI)
  576. {
  577. y = kButtonHeight * getScaleFactor() + ImGui::GetStyle().WindowPadding.y * 2 - 1;
  578. height -= y;
  579. }
  580. ImGui::SetNextWindowPos(ImVec2(0, y));
  581. ImGui::SetNextWindowSize(ImVec2(getWidth(), height));
  582. }
  583. void drawGenericUI()
  584. {
  585. setupMainWindowPos();
  586. PluginGenericUI* const ui = fPluginGenericUI;
  587. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  588. // ImGui::SetNextWindowFocus();
  589. if (ImGui::Begin(ui->title, nullptr, ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoCollapse))
  590. {
  591. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  592. for (uint32_t i=0; i < ui->parameterCount; ++i)
  593. {
  594. PluginGenericUI::Parameter& param(ui->parameters[i]);
  595. if (param.boolean)
  596. {
  597. if (ImGui::Checkbox(param.name, &ui->parameters[i].bvalue))
  598. {
  599. if (ImGui::IsItemActivated())
  600. {
  601. carla_set_parameter_touch(handle, 0, param.rindex, true);
  602. // editParameter(0, true);
  603. }
  604. ui->values[i] = ui->parameters[i].bvalue ? ui->parameters[i].max : ui->parameters[i].min;
  605. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  606. // setParameterValue(0, ui->values[i]);
  607. }
  608. }
  609. else
  610. {
  611. const bool ret = param.log
  612. ? ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.format, 2.0f)
  613. : ImGui::SliderFloat(param.name, &ui->values[i], param.min, param.max, param.format);
  614. if (ret)
  615. {
  616. if (ImGui::IsItemActivated())
  617. {
  618. carla_set_parameter_touch(handle, 0, param.rindex, true);
  619. // editParameter(0, true);
  620. }
  621. carla_set_parameter_value(handle, 0, param.rindex, ui->values[i]);
  622. // setParameterValue(0, ui->values[i]);
  623. }
  624. }
  625. if (ImGui::IsItemDeactivated())
  626. {
  627. carla_set_parameter_touch(handle, 0, param.rindex, false);
  628. // editParameter(0, false);
  629. }
  630. }
  631. }
  632. ImGui::End();
  633. }
  634. void drawLoading()
  635. {
  636. setupMainWindowPos();
  637. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  638. {
  639. ImGui::TextUnformatted("Loading...", nullptr);
  640. }
  641. ImGui::End();
  642. }
  643. void drawPluginList()
  644. {
  645. setupMainWindowPos();
  646. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  647. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  648. {
  649. const int pflags = ImGuiWindowFlags_NoSavedSettings
  650. | ImGuiWindowFlags_NoResize
  651. | ImGuiWindowFlags_NoCollapse
  652. | ImGuiWindowFlags_NoScrollbar
  653. | ImGuiWindowFlags_NoScrollWithMouse
  654. | ImGuiWindowFlags_NoCollapse
  655. | ImGuiWindowFlags_AlwaysAutoResize
  656. | ImGuiWindowFlags_AlwaysUseWindowPadding;
  657. if (ImGui::BeginPopupModal("Plugin Error", nullptr, pflags))
  658. {
  659. ImGui::TextWrapped("Failed to load plugin, error was:\n%s", fPopupError.buffer());
  660. ImGui::Separator();
  661. if (ImGui::Button("Ok"))
  662. {
  663. ImGui::CloseCurrentPopup();
  664. }
  665. ImGui::SameLine();
  666. ImGui::Dummy(ImVec2(500 * getScaleFactor(), 1));
  667. ImGui::EndPopup();
  668. }
  669. else if (fPluginSearchFirstShow)
  670. {
  671. fPluginSearchFirstShow = false;
  672. ImGui::SetKeyboardFocusHere();
  673. }
  674. if (ImGui::InputText("", fPluginSearchString, sizeof(fPluginSearchString)-1, ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll))
  675. fPluginSearchActive = true;
  676. if (ImGui::IsKeyDown(ImGuiKey_Escape))
  677. fPluginSearchActive = false;
  678. ImGui::BeginDisabled(!fPluginScanningFinished);
  679. if (ImGui::Button("Load Plugin"))
  680. {
  681. do {
  682. const PluginInfoCache& info(fPlugins[fPluginSelected]);
  683. const char* label = nullptr;
  684. switch (fPluginType)
  685. {
  686. case PLUGIN_INTERNAL:
  687. label = info.label;
  688. break;
  689. case PLUGIN_LV2: {
  690. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  691. DISTRHO_SAFE_ASSERT_BREAK(slash != nullptr);
  692. label = slash+1;
  693. break;
  694. }
  695. default:
  696. break;
  697. }
  698. DISTRHO_SAFE_ASSERT_BREAK(label != nullptr);
  699. d_stdout("Loading %s...", info.name);
  700. if (loadPlugin(handle, label))
  701. {
  702. ImGui::EndDisabled();
  703. ImGui::End();
  704. return;
  705. }
  706. } while (false);
  707. }
  708. ImGui::SameLine();
  709. ImGui::Checkbox("Run in bridge mode", &fPluginWillRunInBridgeMode);
  710. if (carla_get_current_plugin_count(handle) != 0)
  711. {
  712. ImGui::SameLine();
  713. if (ImGui::Button("Cancel"))
  714. {
  715. showPluginUI(handle);
  716. }
  717. }
  718. ImGui::EndDisabled();
  719. if (ImGui::BeginChild("pluginlistwindow"))
  720. {
  721. if (ImGui::BeginTable("pluginlist",
  722. fPluginType == PLUGIN_LV2 ? 3 : 2, ImGuiTableFlags_NoSavedSettings))
  723. {
  724. const char* const search = fPluginSearchActive && fPluginSearchString[0] != '\0' ? fPluginSearchString : nullptr;
  725. switch (fPluginType)
  726. {
  727. case PLUGIN_INTERNAL:
  728. ImGui::TableSetupColumn("Name");
  729. ImGui::TableSetupColumn("Label");
  730. ImGui::TableHeadersRow();
  731. break;
  732. case PLUGIN_LV2:
  733. ImGui::TableSetupColumn("Name");
  734. ImGui::TableSetupColumn("Bundle");
  735. ImGui::TableSetupColumn("URI");
  736. ImGui::TableHeadersRow();
  737. break;
  738. default:
  739. break;
  740. }
  741. for (uint i=0; i<fPluginCount; ++i)
  742. {
  743. const PluginInfoCache& info(fPlugins[i]);
  744. if (search != nullptr && ildaeil::strcasestr(info.name, search) == nullptr)
  745. continue;
  746. bool selected = fPluginSelected == i;
  747. switch (fPluginType)
  748. {
  749. case PLUGIN_INTERNAL:
  750. ImGui::TableNextRow();
  751. ImGui::TableSetColumnIndex(0);
  752. ImGui::Selectable(info.name, &selected);
  753. ImGui::TableSetColumnIndex(1);
  754. ImGui::Selectable(info.label, &selected);
  755. break;
  756. case PLUGIN_LV2: {
  757. const char* const slash = std::strchr(info.label, DISTRHO_OS_SEP);
  758. DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
  759. ImGui::TableNextRow();
  760. ImGui::TableSetColumnIndex(0);
  761. ImGui::Selectable(info.name, &selected);
  762. ImGui::TableSetColumnIndex(1);
  763. ImGui::Selectable(slash+1, &selected);
  764. ImGui::TableSetColumnIndex(2);
  765. ImGui::TextUnformatted(info.label, slash);
  766. break;
  767. }
  768. default:
  769. break;
  770. }
  771. if (selected)
  772. fPluginSelected = i;
  773. }
  774. ImGui::EndTable();
  775. }
  776. ImGui::EndChild();
  777. }
  778. }
  779. ImGui::End();
  780. }
  781. protected:
  782. /* --------------------------------------------------------------------------------------------------------
  783. * DSP/Plugin Callbacks */
  784. void parameterChanged(uint32_t, float) override
  785. {
  786. }
  787. void stateChanged(const char* const key, const char* const) override
  788. {
  789. if (std::strcmp(key, "project") == 0)
  790. hidePluginUI();
  791. }
  792. // -------------------------------------------------------------------------------------------------------
  793. private:
  794. /**
  795. Set our UI class as non-copyable and add a leak detector just in case.
  796. */
  797. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  798. };
  799. // --------------------------------------------------------------------------------------------------------------------
  800. void ildaeilParameterChangeForUI(void* const ui, const uint32_t index, const float value)
  801. {
  802. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  803. static_cast<IldaeilUI*>(ui)->changeParameterFromDSP(index, value);
  804. }
  805. const char* ildaeilOpenFileForUI(void* const ui, const bool isDir, const char* const title, const char* const filter)
  806. {
  807. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, nullptr);
  808. return static_cast<IldaeilUI*>(ui)->openFileFromDSP(isDir, title, filter);
  809. }
  810. /* --------------------------------------------------------------------------------------------------------------------
  811. * UI entry point, called by DPF to create a new UI instance. */
  812. UI* createUI()
  813. {
  814. return new IldaeilUI();
  815. }
  816. // --------------------------------------------------------------------------------------------------------------------
  817. END_NAMESPACE_DISTRHO