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.

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