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.

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