diff --git a/plugin/components/ide_view.cpp b/plugin/components/ide_view.cpp index 09df8f4..5c6a382 100644 --- a/plugin/components/ide_view.cpp +++ b/plugin/components/ide_view.cpp @@ -42,6 +42,7 @@ struct YsfxIDEView::Impl { std::unique_ptr m_relayoutTimer; std::unique_ptr m_fileCheckTimer; std::unique_ptr m_fileChooser; + std::unique_ptr m_tabPopup; std::unique_ptr m_tabs; @@ -66,6 +67,7 @@ struct YsfxIDEView::Impl { std::shared_ptr addEditor(); void openDocument(juce::File file); void setCurrentEditor(int idx); + void tabPopup(int idx); std::shared_ptr getCurrentEditor(); //========================================================================== @@ -301,7 +303,7 @@ void YsfxIDEView::Impl::setCurrentEditor(int editorIndex) { if (editorIndex >= m_editors.size()) return; - m_editors[m_currentEditorIndex]->setVisible(false); + if (m_currentEditorIndex < m_editors.size()) m_editors[m_currentEditorIndex]->setVisible(false); m_currentEditorIndex = editorIndex; m_editors[m_currentEditorIndex]->setVisible(true); @@ -439,10 +441,47 @@ void YsfxIDEView::Impl::createUI() m_self->addAndMakeVisible(*m_searchEditor); m_self->addAndMakeVisible(*m_lblStatus); m_searchEditor->setVisible(false); - m_tabs.reset(new YSFXTabbedButtonBar(juce::TabbedButtonBar::Orientation::TabsAtBottom, [this](int newCurrentTabIndex) { setCurrentEditor(newCurrentTabIndex); })); + m_tabs.reset( + new YSFXTabbedButtonBar( + juce::TabbedButtonBar::Orientation::TabsAtBottom, + [this](int newCurrentTabIndex) { setCurrentEditor(newCurrentTabIndex); }, + [this](int tabIndex) { tabPopup(tabIndex); } + ) + ); m_self->addAndMakeVisible(*m_tabs); } +void YsfxIDEView::Impl::tabPopup(int tabIndex) +{ + auto tab = m_tabs->getTabButton(tabIndex); + + juce::PopupMenu::Options popupOptions = juce::PopupMenu::Options{} + .withTargetComponent(*tab); + + m_tabPopup.reset(new juce::PopupMenu); + + // The base tab may not be removed. + if (tabIndex != 0) { + m_tabPopup->addItem(1, TRANS("Close tab"), true, false); + m_tabPopup->addSeparator(); + } + + m_tabPopup->showMenuAsync( + popupOptions, [this, tabIndex](int index) { + if (index != 0) { + if (m_editors.size() > tabIndex) { + m_editors.erase(m_editors.begin() + tabIndex); + if (m_currentEditorIndex == tabIndex) { + setCurrentEditor(tabIndex - 1); + } else { + relayoutUILater(); + } + } + } + } + ); +} + void YsfxIDEView::Impl::connectUI() { m_btnSave->onClick = [this]() { saveCurrentFile(); }; @@ -469,6 +508,9 @@ void YsfxIDEView::Impl::relayoutUI() ++idx; } m_tabs->setCurrentTabIndex(m_currentEditorIndex, false); + m_tabs->setVisible(true); + } else { + m_tabs->setVisible(false); } const juce::Rectangle statusArea = temp.removeFromBottom(20); diff --git a/plugin/components/ysfx_document.h b/plugin/components/ysfx_document.h index 589ccf2..84109e9 100644 --- a/plugin/components/ysfx_document.h +++ b/plugin/components/ysfx_document.h @@ -194,7 +194,9 @@ class YSFXCodeEditor : public juce::CodeDocument::Listener class YSFXTabbedButtonBar : public juce::TabbedButtonBar { public: - YSFXTabbedButtonBar(TabbedButtonBar::Orientation orientation, std::function changeCallback): TabbedButtonBar(orientation), m_changeCallback(changeCallback) {}; + YSFXTabbedButtonBar( + TabbedButtonBar::Orientation orientation, std::function changeCallback, std::function popupCallback + ): TabbedButtonBar(orientation), m_changeCallback(changeCallback), m_popupCallback(popupCallback) {}; private: void currentTabChanged(int newCurrentTabIndex, const juce::String &newCurrentTabName) override @@ -203,7 +205,14 @@ class YSFXTabbedButtonBar : public juce::TabbedButtonBar if (m_emitChange) m_changeCallback(newCurrentTabIndex); } + void popupMenuClickOnTab(int tabIndex, const juce::String& tabName) override + { + (void) tabName; + if (m_popupCallback) m_popupCallback(tabIndex); + } + std::function m_changeCallback = [](int idx){ (void) idx; }; + std::function m_popupCallback = [](int idx){ (void) idx; }; bool m_emitChange{true}; friend class ScopedUpdateBlocker;