1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/extras/Projucer/Source/Project/UI/Sidebar/jucer_Sidebar.h
2021-05-14 12:54:07 +01:00

558 lines
18 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ConcertinaHeader : public Component,
public ChangeBroadcaster
{
public:
ConcertinaHeader (String n, Path p)
: Component (n), name (n), iconPath (p)
{
setTitle (getName());
panelIcon = Icon (iconPath, Colours::white);
nameLabel.setText (name, dontSendNotification);
nameLabel.setJustificationType (Justification::centredLeft);
nameLabel.setInterceptsMouseClicks (false, false);
nameLabel.setAccessible (false);
nameLabel.setColour (Label::textColourId, Colours::white);
addAndMakeVisible (nameLabel);
}
void resized() override
{
auto b = getLocalBounds().toFloat();
iconBounds = b.removeFromLeft (b.getHeight()).reduced (7, 7);
arrowBounds = b.removeFromRight (b.getHeight());
nameLabel.setBounds (b.toNearestInt());
}
void paint (Graphics& g) override
{
g.setColour (findColour (defaultButtonBackgroundColourId));
g.fillRoundedRectangle (getLocalBounds().reduced (2, 3).toFloat(), 2.0f);
g.setColour (Colours::white);
g.fillPath (ProjucerLookAndFeel::getArrowPath (arrowBounds,
getParentComponent()->getBoundsInParent().getY() == yPosition ? 2 : 0,
true, Justification::centred));
panelIcon.draw (g, iconBounds.toFloat(), false);
}
void mouseUp (const MouseEvent& e) override
{
if (! e.mouseWasDraggedSinceMouseDown())
sendChangeMessage();
}
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return std::make_unique<AccessibilityHandler> (*this,
AccessibilityRole::button,
AccessibilityActions().addAction (AccessibilityActionType::press,
[this] { sendChangeMessage(); }));
}
int direction = 0;
int yPosition = 0;
private:
String name;
Label nameLabel;
Path iconPath;
Icon panelIcon;
Rectangle<float> arrowBounds, iconBounds;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaHeader)
};
//==============================================================================
class FindPanel : public Component,
private Timer,
private FocusChangeListener
{
public:
FindPanel (std::function<void (const String&)> cb)
: callback (cb)
{
addAndMakeVisible (editor);
editor.onTextChange = [this] { startTimer (250); };
editor.onFocusLost = [this]
{
isFocused = false;
repaint();
};
Desktop::getInstance().addFocusChangeListener (this);
lookAndFeelChanged();
}
~FindPanel() override
{
Desktop::getInstance().removeFocusChangeListener (this);
}
void paintOverChildren (Graphics& g) override
{
if (! isFocused)
return;
g.setColour (findColour (defaultHighlightColourId));
Path p;
p.addRoundedRectangle (getLocalBounds().reduced (2), 3.0f);
g.strokePath (p, PathStrokeType (2.0f));
}
void resized() override
{
editor.setBounds (getLocalBounds().reduced (2));
}
private:
TextEditor editor;
bool isFocused = false;
std::function<void (const String&)> callback;
//==============================================================================
void lookAndFeelChanged() override
{
editor.setTextToShowWhenEmpty ("Filter...", findColour (widgetTextColourId).withAlpha (0.3f));
}
void globalFocusChanged (Component* focusedComponent) override
{
if (focusedComponent == &editor)
{
isFocused = true;
repaint();
}
}
void timerCallback() override
{
stopTimer();
callback (editor.getText());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FindPanel)
};
//==============================================================================
class ConcertinaTreeComponent : public Component
{
public:
class AdditionalComponents
{
public:
enum Type
{
addButton = (1 << 0),
settingsButton = (1 << 1),
findPanel = (1 << 2)
};
AdditionalComponents with (Type t)
{
auto copy = *this;
copy.componentTypes |= t;
return copy;
}
bool has (Type t) const noexcept
{
return (componentTypes & t) != 0;
}
private:
int componentTypes = 0;
};
ConcertinaTreeComponent (const String& name,
TreePanelBase* tree,
AdditionalComponents additionalComponents)
: Component (name),
treeToDisplay (tree)
{
setTitle (getName());
setFocusContainerType (FocusContainerType::focusContainer);
if (additionalComponents.has (AdditionalComponents::addButton))
{
addButton = std::make_unique<IconButton> ("Add", getIcons().plus);
addAndMakeVisible (addButton.get());
addButton->onClick = [this] { showAddMenu(); };
}
if (additionalComponents.has (AdditionalComponents::settingsButton))
{
settingsButton = std::make_unique<IconButton> ("Settings", getIcons().settings);
addAndMakeVisible (settingsButton.get());
settingsButton->onClick = [this] { showSettings(); };
}
if (additionalComponents.has (AdditionalComponents::findPanel))
{
findPanel = std::make_unique<FindPanel> ([this] (const String& filter) { treeToDisplay->rootItem->setSearchFilter (filter); });
addAndMakeVisible (findPanel.get());
}
addAndMakeVisible (treeToDisplay.get());
}
void resized() override
{
auto bounds = getLocalBounds();
if (addButton != nullptr || settingsButton != nullptr || findPanel != nullptr)
{
auto bottomSlice = bounds.removeFromBottom (25);
bottomSlice.removeFromRight (3);
if (addButton != nullptr)
addButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
if (settingsButton != nullptr)
settingsButton->setBounds (bottomSlice.removeFromRight (25).reduced (2));
if (findPanel != nullptr)
findPanel->setBounds (bottomSlice.reduced (2));
}
treeToDisplay->setBounds (bounds);
}
TreePanelBase* getTree() const noexcept { return treeToDisplay.get(); }
private:
std::unique_ptr<TreePanelBase> treeToDisplay;
std::unique_ptr<IconButton> addButton, settingsButton;
std::unique_ptr<FindPanel> findPanel;
void showAddMenu()
{
auto numSelected = treeToDisplay->tree.getNumSelectedItems();
if (numSelected > 1)
return;
if (numSelected == 0)
{
if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
root->showPopupMenu (addButton->getScreenBounds().getCentre());
}
else
{
if (auto* item = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getSelectedItem (0)))
item->showAddMenu (addButton->getScreenBounds().getCentre());
}
}
void showSettings()
{
if (auto* root = dynamic_cast<JucerTreeViewBase*> (treeToDisplay->tree.getRootItem()))
{
treeToDisplay->tree.clearSelectedItems();
root->showDocument();
}
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaTreeComponent)
};
//==============================================================================
struct ProjectSettingsComponent : public Component,
private ChangeListener
{
ProjectSettingsComponent (Project& p)
: project (p),
group (project.getProjectFilenameRootString(),
Icon (getIcons().settings, Colours::transparentBlack))
{
setTitle ("Project Settings");
setFocusContainerType (FocusContainerType::focusContainer);
addAndMakeVisible (group);
updatePropertyList();
project.addChangeListener (this);
}
~ProjectSettingsComponent() override
{
project.removeChangeListener (this);
}
void resized() override
{
group.updateSize (12, 0, getWidth() - 24);
group.setBounds (getLocalBounds().reduced (12, 0));
}
void updatePropertyList()
{
PropertyListBuilder props;
project.createPropertyEditors (props);
group.setProperties (props);
group.setName ("Project Settings");
lastProjectType = project.getProjectTypeString();
parentSizeChanged();
}
void changeListenerCallback (ChangeBroadcaster*) override
{
if (lastProjectType != project.getProjectTypeString())
updatePropertyList();
}
void parentSizeChanged() override
{
auto width = jmax (550, getParentWidth());
auto y = group.updateSize (12, 0, width - 12);
y = jmax (getParentHeight(), y);
setSize (width, y);
}
Project& project;
var lastProjectType;
PropertyGroupComponent group;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSettingsComponent)
};
//==============================================================================
struct FileTreePanel : public TreePanelBase
{
FileTreePanel (Project& p)
: TreePanelBase (&p, "fileTreeState")
{
tree.setMultiSelectEnabled (true);
setRoot (std::make_unique<TreeItemTypes::GroupItem> (p.getMainGroup()));
tree.setRootItemVisible (false);
}
void updateMissingFileStatuses()
{
if (auto* p = dynamic_cast<TreeItemTypes::FileTreeItemBase*> (rootItem.get()))
p->checkFileStatus();
}
};
struct ModuleTreePanel : public TreePanelBase
{
ModuleTreePanel (Project& p)
: TreePanelBase (&p, "moduleTreeState")
{
tree.setMultiSelectEnabled (false);
setRoot (std::make_unique<TreeItemTypes::EnabledModulesItem> (p));
tree.setRootItemVisible (false);
}
};
struct ExportersTreePanel : public TreePanelBase
{
ExportersTreePanel (Project& p)
: TreePanelBase (&p, "exportersTreeState")
{
tree.setMultiSelectEnabled (false);
setRoot (std::make_unique<TreeItemTypes::ExportersTreeRoot> (p));
tree.setRootItemVisible (false);
}
};
//==============================================================================
class Sidebar : public Component,
private ChangeListener
{
public:
Sidebar (Project* p)
: project (p)
{
setFocusContainerType (FocusContainerType::focusContainer);
if (project != nullptr)
buildConcertina();
}
~Sidebar() override
{
TreePanelBase* panels[] = { getFileTreePanel(), getModuleTreePanel(), getExportersTreePanel() };
for (auto* panel : panels)
if (panel != nullptr)
panel->saveOpenness();
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void resized() override
{
concertinaPanel.setBounds (getLocalBounds().withTrimmedBottom (3));
}
TreePanelBase* getTreeWithSelectedItems()
{
for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
{
if (auto* treeComponent = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (i)))
{
if (auto* base = treeComponent->getTree())
if (base->tree.getNumSelectedItems() != 0)
return base;
}
}
return nullptr;
}
FileTreePanel* getFileTreePanel() { return getPanel<FileTreePanel> (0); }
ModuleTreePanel* getModuleTreePanel() { return getPanel<ModuleTreePanel> (1); }
ExportersTreePanel* getExportersTreePanel() { return getPanel<ExportersTreePanel> (2); }
void showPanel (int panelIndex)
{
jassert (isPositiveAndBelow (panelIndex, concertinaPanel.getNumPanels()));
concertinaPanel.expandPanelFully (concertinaPanel.getPanel (panelIndex), true);
}
private:
//==============================================================================
template <typename PanelType>
PanelType* getPanel (int panelIndex)
{
if (auto* panel = dynamic_cast<ConcertinaTreeComponent*> (concertinaPanel.getPanel (panelIndex)))
return dynamic_cast<PanelType*> (panel->getTree());
return nullptr;
}
void changeListenerCallback (ChangeBroadcaster* source) override
{
const auto pointerMatches = [source] (const std::unique_ptr<ConcertinaHeader>& header) { return header.get() == source; };
const auto it = std::find_if (headers.begin(), headers.end(), pointerMatches);
const auto index = (int) std::distance (headers.begin(), it);
if (index != (int) headers.size())
concertinaPanel.expandPanelFully (concertinaPanel.getPanel (index), true);
}
void buildConcertina()
{
for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0 ; --i)
concertinaPanel.removePanel (concertinaPanel.getPanel (i));
headers.clear();
auto addPanel = [this] (const String& name,
TreePanelBase* tree,
ConcertinaTreeComponent::AdditionalComponents components,
const Path& icon)
{
if (project != nullptr)
concertinaPanel.addPanel (-1, new ConcertinaTreeComponent (name, tree, components), true);
headers.push_back (std::make_unique<ConcertinaHeader> (name, icon));
};
using AdditionalComponents = ConcertinaTreeComponent::AdditionalComponents;
addPanel ("File Explorer", new FileTreePanel (*project),
AdditionalComponents{}
.with (AdditionalComponents::addButton)
.with (AdditionalComponents::findPanel),
getIcons().fileExplorer);
addPanel ("Modules", new ModuleTreePanel (*project),
AdditionalComponents{}
.with (AdditionalComponents::addButton)
.with (AdditionalComponents::settingsButton),
getIcons().modules);
addPanel ("Exporters", new ExportersTreePanel (*project),
AdditionalComponents{}.with (AdditionalComponents::addButton),
getIcons().exporter);
for (int i = 0; i < concertinaPanel.getNumPanels(); ++i)
{
auto* p = concertinaPanel.getPanel (i);
auto* h = headers[(size_t) i].get();
p->addMouseListener (this, true);
h->addChangeListener (this);
h->yPosition = i * 30;
concertinaPanel.setCustomPanelHeader (p, h, false);
concertinaPanel.setPanelHeaderSize (p, 30);
}
addAndMakeVisible (concertinaPanel);
}
void mouseDown (const MouseEvent& e) override
{
for (auto i = concertinaPanel.getNumPanels() - 1; i >= 0; --i)
{
auto* p = concertinaPanel.getPanel (i);
if (! (p->isParentOf (e.eventComponent)))
{
auto* base = dynamic_cast<TreePanelBase*> (p);
if (base == nullptr)
base = dynamic_cast<ConcertinaTreeComponent*> (p)->getTree();
if (base != nullptr)
base->tree.clearSelectedItems();
}
}
}
//==============================================================================
ConcertinaPanel concertinaPanel;
std::vector<std::unique_ptr<ConcertinaHeader>> headers;
Project* project = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Sidebar)
};