The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

554 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For the technical preview this file cannot be licensed commercially.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #pragma once
  14. //==============================================================================
  15. class ConcertinaHeader : public Component,
  16. public ChangeBroadcaster
  17. {
  18. public:
  19. ConcertinaHeader (String n, Path p)
  20. : Component (n), name (n), iconPath (p)
  21. {
  22. setTitle (getName());
  23. panelIcon = Icon (iconPath, Colours::white);
  24. nameLabel.setText (name, dontSendNotification);
  25. nameLabel.setJustificationType (Justification::centredLeft);
  26. nameLabel.setInterceptsMouseClicks (false, false);
  27. nameLabel.setAccessible (false);
  28. nameLabel.setColour (Label::textColourId, Colours::white);
  29. addAndMakeVisible (nameLabel);
  30. }
  31. void resized() override
  32. {
  33. auto b = getLocalBounds().toFloat();
  34. iconBounds = b.removeFromLeft (b.getHeight()).reduced (7, 7);
  35. arrowBounds = b.removeFromRight (b.getHeight());
  36. nameLabel.setBounds (b.toNearestInt());
  37. }
  38. void paint (Graphics& g) override
  39. {
  40. g.setColour (findColour (defaultButtonBackgroundColourId));
  41. g.fillRoundedRectangle (getLocalBounds().reduced (2, 3).toFloat(), 2.0f);
  42. g.setColour (Colours::white);
  43. g.fillPath (ProjucerLookAndFeel::getArrowPath (arrowBounds,
  44. getParentComponent()->getBoundsInParent().getY() == yPosition ? 2 : 0,
  45. true, Justification::centred));
  46. panelIcon.draw (g, iconBounds.toFloat(), false);
  47. }
  48. void mouseUp (const MouseEvent& e) override
  49. {
  50. if (! e.mouseWasDraggedSinceMouseDown())
  51. sendChangeMessage();
  52. }
  53. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  54. {
  55. return std::make_unique<AccessibilityHandler> (*this,
  56. AccessibilityRole::button,
  57. AccessibilityActions().addAction (AccessibilityActionType::press,
  58. [this] { sendChangeMessage(); }));
  59. }
  60. int direction = 0;
  61. int yPosition = 0;
  62. private:
  63. String name;
  64. Label nameLabel;
  65. Path iconPath;
  66. Icon panelIcon;
  67. Rectangle<float> arrowBounds, iconBounds;
  68. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaHeader)
  69. };
  70. //==============================================================================
  71. class FindPanel : public Component,
  72. private Timer,
  73. private FocusChangeListener
  74. {
  75. public:
  76. FindPanel (std::function<void (const String&)> cb)
  77. : callback (cb)
  78. {
  79. addAndMakeVisible (editor);
  80. editor.onTextChange = [this] { startTimer (250); };
  81. editor.onFocusLost = [this]
  82. {
  83. isFocused = false;
  84. repaint();
  85. };
  86. Desktop::getInstance().addFocusChangeListener (this);
  87. lookAndFeelChanged();
  88. }
  89. ~FindPanel() override
  90. {
  91. Desktop::getInstance().removeFocusChangeListener (this);
  92. }
  93. void paintOverChildren (Graphics& g) override
  94. {
  95. if (! isFocused)
  96. return;
  97. g.setColour (findColour (defaultHighlightColourId));
  98. Path p;
  99. p.addRoundedRectangle (getLocalBounds().reduced (2), 3.0f);
  100. g.strokePath (p, PathStrokeType (2.0f));
  101. }
  102. void resized() override
  103. {
  104. editor.setBounds (getLocalBounds().reduced (2));
  105. }
  106. private:
  107. TextEditor editor;
  108. bool isFocused = false;
  109. std::function<void (const String&)> callback;
  110. //==============================================================================
  111. void lookAndFeelChanged() override
  112. {
  113. editor.setTextToShowWhenEmpty ("Filter...", findColour (widgetTextColourId).withAlpha (0.3f));
  114. }
  115. void globalFocusChanged (Component* focusedComponent) override
  116. {
  117. if (focusedComponent == &editor)
  118. {
  119. isFocused = true;
  120. repaint();
  121. }
  122. }
  123. void timerCallback() override
  124. {
  125. stopTimer();
  126. callback (editor.getText());
  127. }
  128. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FindPanel)
  129. };
  130. //==============================================================================
  131. class ConcertinaTreeComponent : public Component
  132. {
  133. public:
  134. class AdditionalComponents
  135. {
  136. public:
  137. enum Type
  138. {
  139. addButton = (1 << 0),
  140. settingsButton = (1 << 1),
  141. findPanel = (1 << 2)
  142. };
  143. JUCE_NODISCARD AdditionalComponents with (Type t)
  144. {
  145. auto copy = *this;
  146. copy.componentTypes |= t;
  147. return copy;
  148. }
  149. bool has (Type t) const noexcept
  150. {
  151. return (componentTypes & t) != 0;
  152. }
  153. private:
  154. int componentTypes = 0;
  155. };
  156. ConcertinaTreeComponent (const String& name,
  157. TreePanelBase* tree,
  158. AdditionalComponents additionalComponents)
  159. : Component (name),
  160. treeToDisplay (tree)
  161. {
  162. setTitle (getName());
  163. setFocusContainerType (FocusContainerType::focusContainer);
  164. if (additionalComponents.has (AdditionalComponents::addButton))
  165. {
  166. addButton = std::make_unique<IconButton> ("Add", getIcons().plus);
  167. addAndMakeVisible (addButton.get());
  168. addButton->onClick = [this] { showAddMenu(); };
  169. }
  170. if (additionalComponents.has (AdditionalComponents::settingsButton))
  171. {
  172. settingsButton = std::make_unique<IconButton> ("Settings", getIcons().settings);
  173. addAndMakeVisible (settingsButton.get());
  174. settingsButton->onClick = [this] { showSettings(); };
  175. }
  176. if (additionalComponents.has (AdditionalComponents::findPanel))
  177. {
  178. findPanel = std::make_unique<FindPanel> ([this] (const String& filter) { treeToDisplay->rootItem->setSearchFilter (filter); });
  179. addAndMakeVisible (findPanel.get());
  180. }
  181. addAndMakeVisible (treeToDisplay.get());
  182. }
  183. void resized() override
  184. {
  185. auto bounds = getLocalBounds();
  186. if (addButton != nullptr || settingsButton != nullptr || findPanel != nullptr)
  187. {
  188. auto bottomSlice = bounds.removeFromBottom (25);
  189. bottomSlice.removeFromRight (3);
  190. if (addButton != nullptr)
  191. addButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
  192. if (settingsButton != nullptr)
  193. settingsButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
  194. if (findPanel != nullptr)
  195. findPanel->setBounds (bottomSlice.reduced (2));
  196. }
  197. treeToDisplay->setBounds (bounds);
  198. }
  199. TreePanelBase* getTree() const noexcept { return treeToDisplay.get(); }
  200. private:
  201. std::unique_ptr<TreePanelBase> treeToDisplay;
  202. std::unique_ptr<IconButton> addButton, settingsButton;
  203. std::unique_ptr<FindPanel> findPanel;
  204. void showAddMenu()
  205. {
  206. auto numSelected = treeToDisplay->tree.getNumSelectedItems();
  207. if (numSelected > 1)
  208. return;
  209. if (numSelected == 0)
  210. {
  211. if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
  212. root->showPopupMenu (addButton->getScreenBounds().getCentre());
  213. }
  214. else
  215. {
  216. if (auto* item = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getSelectedItem (0)))
  217. item->showAddMenu (addButton->getScreenBounds().getCentre());
  218. }
  219. }
  220. void showSettings()
  221. {
  222. if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
  223. {
  224. treeToDisplay->tree.clearSelectedItems();
  225. root->showDocument();
  226. }
  227. }
  228. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaTreeComponent)
  229. };
  230. //==============================================================================
  231. struct ProjectSettingsComponent : public Component,
  232. private ChangeListener
  233. {
  234. ProjectSettingsComponent (Project& p)
  235. : project (p),
  236. group (project.getProjectFilenameRootString(),
  237. Icon (getIcons().settings, Colours::transparentBlack))
  238. {
  239. setTitle ("Project Settings");
  240. setFocusContainerType (FocusContainerType::focusContainer);
  241. addAndMakeVisible (group);
  242. updatePropertyList();
  243. project.addChangeListener (this);
  244. }
  245. ~ProjectSettingsComponent() override
  246. {
  247. project.removeChangeListener (this);
  248. }
  249. void resized() override
  250. {
  251. group.updateSize (12, 0, getWidth() - 24);
  252. group.setBounds (getLocalBounds().reduced (12, 0));
  253. }
  254. void updatePropertyList()
  255. {
  256. PropertyListBuilder props;
  257. project.createPropertyEditors (props);
  258. group.setProperties (props);
  259. group.setName ("Project Settings");
  260. lastProjectType = project.getProjectTypeString();
  261. parentSizeChanged();
  262. }
  263. void changeListenerCallback (ChangeBroadcaster*) override
  264. {
  265. if (lastProjectType != project.getProjectTypeString())
  266. updatePropertyList();
  267. }
  268. void parentSizeChanged() override
  269. {
  270. auto width = jmax (550, getParentWidth());
  271. auto y = group.updateSize (12, 0, width - 12);
  272. y = jmax (getParentHeight(), y);
  273. setSize (width, y);
  274. }
  275. Project& project;
  276. var lastProjectType;
  277. PropertyGroupComponent group;
  278. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSettingsComponent)
  279. };
  280. //==============================================================================
  281. struct FileTreePanel : public TreePanelBase
  282. {
  283. FileTreePanel (Project& p)
  284. : TreePanelBase (&p, "fileTreeState")
  285. {
  286. tree.setMultiSelectEnabled (true);
  287. setRoot (std::make_unique<TreeItemTypes::GroupItem> (p.getMainGroup()));
  288. tree.setRootItemVisible (false);
  289. }
  290. void updateMissingFileStatuses()
  291. {
  292. if (auto* p = dynamic_cast<TreeItemTypes::FileTreeItemBase*> (rootItem.get()))
  293. p->checkFileStatus();
  294. }
  295. };
  296. struct ModuleTreePanel : public TreePanelBase
  297. {
  298. ModuleTreePanel (Project& p)
  299. : TreePanelBase (&p, "moduleTreeState")
  300. {
  301. tree.setMultiSelectEnabled (false);
  302. setRoot (std::make_unique<TreeItemTypes::EnabledModulesItem> (p));
  303. tree.setRootItemVisible (false);
  304. }
  305. };
  306. struct ExportersTreePanel : public TreePanelBase
  307. {
  308. ExportersTreePanel (Project& p)
  309. : TreePanelBase (&p, "exportersTreeState")
  310. {
  311. tree.setMultiSelectEnabled (false);
  312. setRoot (std::make_unique<TreeItemTypes::ExportersTreeRoot> (p));
  313. tree.setRootItemVisible (false);
  314. }
  315. };
  316. //==============================================================================
  317. class Sidebar : public Component,
  318. private ChangeListener
  319. {
  320. public:
  321. Sidebar (Project* p)
  322. : project (p)
  323. {
  324. setFocusContainerType (FocusContainerType::focusContainer);
  325. if (project != nullptr)
  326. buildConcertina();
  327. }
  328. ~Sidebar() override
  329. {
  330. TreePanelBase* panels[] = { getFileTreePanel(), getModuleTreePanel(), getExportersTreePanel() };
  331. for (auto* panel : panels)
  332. if (panel != nullptr)
  333. panel->saveOpenness();
  334. }
  335. void paint (Graphics& g) override
  336. {
  337. g.fillAll (findColour (secondaryBackgroundColourId));
  338. }
  339. void resized() override
  340. {
  341. concertinaPanel.setBounds (getLocalBounds().withTrimmedBottom (3));
  342. }
  343. TreePanelBase* getTreeWithSelectedItems()
  344. {
  345. for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
  346. {
  347. if (auto* treeComponent = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (i)))
  348. {
  349. if (auto* base = treeComponent->getTree())
  350. if (base->tree.getNumSelectedItems() != 0)
  351. return base;
  352. }
  353. }
  354. return nullptr;
  355. }
  356. FileTreePanel* getFileTreePanel() { return getPanel<FileTreePanel> (0); }
  357. ModuleTreePanel* getModuleTreePanel() { return getPanel<ModuleTreePanel> (1); }
  358. ExportersTreePanel* getExportersTreePanel() { return getPanel<ExportersTreePanel> (2); }
  359. void showPanel (int panelIndex)
  360. {
  361. jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
  362. concertinaPanel.expandPanelFully (concertinaPanel.getPanel (panelIndex), true);
  363. }
  364. private:
  365. //==============================================================================
  366. template <typename PanelType>
  367. PanelType* getPanel (int panelIndex)
  368. {
  369. if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (panelIndex)))
  370. return dynamic_cast<PanelType*> (panel->getTree());
  371. return nullptr;
  372. }
  373. void changeListenerCallback (ChangeBroadcaster* source) override
  374. {
  375. const auto pointerMatches = [source] (const std::unique_ptr<ConcertinaHeader>& header) { return header.get() == source; };
  376. const auto it = std::find_if (headers.begin(), headers.end(), pointerMatches);
  377. const auto index = (int) std::distance (headers.begin(), it);
  378. if (index != (int) headers.size())
  379. concertinaPanel.expandPanelFully (concertinaPanel.getPanel (index), true);
  380. }
  381. void buildConcertina()
  382. {
  383. for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0 ; --i)
  384. concertinaPanel.removePanel (concertinaPanel.getPanel (i));
  385. headers.clear();
  386. auto addPanel = [this] (const String& name,
  387. TreePanelBase* tree,
  388. ConcertinaTreeComponent::AdditionalComponents components,
  389. const Path& icon)
  390. {
  391. if (project != nullptr)
  392. concertinaPanel.addPanel (-1, new ConcertinaTreeComponent (name, tree, components), true);
  393. headers.push_back (std::make_unique<ConcertinaHeader> (name, icon));
  394. };
  395. using AdditionalComponents = ConcertinaTreeComponent::AdditionalComponents;
  396. addPanel ("File Explorer", new FileTreePanel (*project),
  397. AdditionalComponents{}
  398. .with (AdditionalComponents::addButton)
  399. .with (AdditionalComponents::findPanel),
  400. getIcons().fileExplorer);
  401. addPanel ("Modules", new ModuleTreePanel (*project),
  402. AdditionalComponents{}
  403. .with (AdditionalComponents::addButton)
  404. .with (AdditionalComponents::settingsButton),
  405. getIcons().modules);
  406. addPanel ("Exporters", new ExportersTreePanel (*project),
  407. AdditionalComponents{}.with (AdditionalComponents::addButton),
  408. getIcons().exporter);
  409. for (int i = 0; i < concertinaPanel.getNumPanels(); ++i)
  410. {
  411. auto* p = concertinaPanel.getPanel (i);
  412. auto* h = headers[(size_t) i].get();
  413. p->addMouseListener (this, true);
  414. h->addChangeListener (this);
  415. h->yPosition = i * 30;
  416. concertinaPanel.setCustomPanelHeader (p, h, false);
  417. concertinaPanel.setPanelHeaderSize (p, 30);
  418. }
  419. addAndMakeVisible (concertinaPanel);
  420. }
  421. void mouseDown (const MouseEvent& e) override
  422. {
  423. for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
  424. {
  425. if (auto* p = concertinaPanel.getPanel (i))
  426. {
  427. if (! (p->isParentOf (e.eventComponent)))
  428. {
  429. auto* base = dynamic_cast<TreePanelBase*> (p);
  430. if (base == nullptr)
  431. if (auto* concertina = dynamic_cast<ConcertinaTreeComponent*> (p))
  432. base = concertina->getTree();
  433. if (base != nullptr)
  434. base->tree.clearSelectedItems();
  435. }
  436. }
  437. }
  438. }
  439. //==============================================================================
  440. ConcertinaPanel concertinaPanel;
  441. std::vector<std::unique_ptr<ConcertinaHeader>> headers;
  442. Project* project = nullptr;
  443. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Sidebar)
  444. };