From 20e50cf90fd70da16a224ab8ea22a7c9ff3071ea Mon Sep 17 00:00:00 2001
From: alcomposer <alex.w.mitchell@gmail.com>
Date: Tue, 12 Sep 2023 02:17:04 +0930
Subject: [PATCH 1/5] small cleanup splitview - fix off-by-1 error in
 splitview, make focus-outline it's own component

---
 Source/CanvasViewport.h             |  6 +++
 Source/PluginEditor.cpp             |  2 +-
 Source/ResizableTabbedComponent.cpp | 38 ++++++--------
 Source/ResizableTabbedComponent.h   |  2 -
 Source/SplitView.cpp                | 81 ++++++++++++-----------------
 Source/SplitView.h                  | 12 ++---
 Source/Tabbar.cpp                   |  9 ++++
 Source/Tabbar.h                     |  2 +
 8 files changed, 71 insertions(+), 81 deletions(-)

diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h
index 0a5f9bdcb..95aff5c68 100644
--- a/Source/CanvasViewport.h
+++ b/Source/CanvasViewport.h
@@ -46,6 +46,9 @@ class CanvasViewport : public Viewport {
             e.originalComponent->setMouseCursor(MouseCursor::DraggingHandCursor);
             downPosition = viewport->getViewPosition();
             downCanvasOrigin = viewport->cnv->canvasOrigin;
+
+            for (auto* object : viewport->cnv->objects)
+                object->setBufferedToImage(true);
         }
 
         void mouseDrag(MouseEvent const& e) override
@@ -59,6 +62,9 @@ class CanvasViewport : public Viewport {
         void mouseUp(MouseEvent const& e) override
         {
             e.originalComponent->setMouseCursor(MouseCursor::NormalCursor);
+            for (auto* object : viewport->cnv->objects){
+                object->setBufferedToImage(false);
+            }
         }
 
     private:
diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp
index 9570dba18..be835b67c 100644
--- a/Source/PluginEditor.cpp
+++ b/Source/PluginEditor.cpp
@@ -383,7 +383,7 @@ void PluginEditor::resized()
 
     palettes->setBounds(0, toolbarHeight, palettes->getWidth(), getHeight() - toolbarHeight - (statusbar->getHeight()));
 
-    splitView.setBounds(paletteWidth, toolbarHeight, (getWidth() - sidebar->getWidth() - paletteWidth) + 1, getHeight() - toolbarHeight - Statusbar::statusbarHeight);
+    splitView.setBounds(paletteWidth, toolbarHeight, (getWidth() - sidebar->getWidth() - paletteWidth), getHeight() - toolbarHeight - Statusbar::statusbarHeight);
     sidebar->setBounds(getWidth() - sidebar->getWidth(), toolbarHeight, sidebar->getWidth(), getHeight() - toolbarHeight - Statusbar::statusbarHeight);
     statusbar->setBounds(0, getHeight() - Statusbar::statusbarHeight, getWidth(), statusbar->getHeight());
 
diff --git a/Source/ResizableTabbedComponent.cpp b/Source/ResizableTabbedComponent.cpp
index 0f0d81ae6..0a8f2102b 100644
--- a/Source/ResizableTabbedComponent.cpp
+++ b/Source/ResizableTabbedComponent.cpp
@@ -15,7 +15,6 @@
 
 #include "Utility/ObjectDragAndDrop.h"
 
-
 #define ENABLE_SPLITS_DROPZONE_DEBUGGING 0
 
 ResizableTabbedComponent::ResizableTabbedComponent(PluginEditor* editor, TabComponent* mainTabComponent)
@@ -266,19 +265,11 @@ int ResizableTabbedComponent::findZoneFromSource(SourceDetails const& dragSource
     return -1;
 }
 
-void ResizableTabbedComponent::mouseDrag(MouseEvent const& e)
-{
-}
-
 void ResizableTabbedComponent::mouseDown(MouseEvent const& e)
 {
     editor->splitView.setFocus(this);
 }
 
-void ResizableTabbedComponent::mouseMove(MouseEvent const& e)
-{
-}
-
 void ResizableTabbedComponent::setBoundsWithFactors(Rectangle<int> bounds)
 {
     if (resizerLeft)
@@ -373,6 +364,7 @@ void ResizableTabbedComponent::paintOverChildren(Graphics& g)
 
 void ResizableTabbedComponent::itemDragEnter(SourceDetails const& dragSourceDetails)
 {
+    editor->splitView.setFocus(this);
     // if we are dragging a tabbar, update the highlight split
     if(dynamic_cast<TabBarButtonComponent*>(dragSourceDetails.sourceComponent.get())) {
         isDragAndDropOver = true;
@@ -383,27 +375,21 @@ void ResizableTabbedComponent::itemDragEnter(SourceDetails const& dragSourceDeta
 
 void ResizableTabbedComponent::itemDragExit(SourceDetails const& dragSourceDetails)
 {
-    isDragAndDropOver = false;
-    repaint();
+    if(dynamic_cast<TabBarButtonComponent*>(dragSourceDetails.sourceComponent.get())) {
+        isDragAndDropOver = false;
+        repaint();
+    }
 }
 
 void ResizableTabbedComponent::itemDragMove(SourceDetails const& dragSourceDetails)
 {
-    activeZone = DropZones::None;
-    // if we are dragging from a palette or automation item, highlight the dragged over split
-    if (dynamic_cast<ObjectDragAndDrop*>(dragSourceDetails.sourceComponent.get())) {
-        isDragAndDropOver = false;
-        editor->splitView.setFocus(this);
-    }
     // if we are dragging a tabbed window or from the document browser
-    else if (auto sourceTabButton = static_cast<TabBarButtonComponent*>(dragSourceDetails.sourceComponent.get())) {
+    if (auto sourceTabButton = dynamic_cast<TabBarButtonComponent*>(dragSourceDetails.sourceComponent.get())) {
         auto sourceTabContent = sourceTabButton->getTabComponent();
         int sourceNumTabs = sourceTabContent->getNumTabs();
 
         auto zone = findZoneFromSource(dragSourceDetails);
 
-        editor->splitView.setFocus(this);
-
         if (editor->splitView.canSplit() && sourceNumTabs > 1) {
             if (activeZone != zone) {
                 activeZone = zone;
@@ -411,8 +397,16 @@ void ResizableTabbedComponent::itemDragMove(SourceDetails const& dragSourceDetai
                 // std::cout << "dragging over: " << getZoneName(zone) << std::endl;
             }
         } else if (sourceTabButton->getTabComponent() != tabComponent.get()) {
-            activeZone = zone == DropZones::TabBar ? DropZones::None : DropZones::Centre;
-            repaint();
+            auto foundZone = zone == DropZones::TabBar ? DropZones::None : DropZones::Centre;
+            if (activeZone != foundZone){
+                activeZone = foundZone;
+                repaint();
+            }
+        } else if (sourceTabButton->getTabComponent() == tabComponent.get()) {
+            if (activeZone != DropZones::None) {
+                activeZone = DropZones::None;
+                repaint();
+            }
         }
     }
 }
diff --git a/Source/ResizableTabbedComponent.h b/Source/ResizableTabbedComponent.h
index 7a4bac2d3..9cb6d2376 100644
--- a/Source/ResizableTabbedComponent.h
+++ b/Source/ResizableTabbedComponent.h
@@ -20,9 +20,7 @@ class ResizableTabbedComponent : public Component
 
     ~ResizableTabbedComponent();
 
-    void mouseDrag(MouseEvent const& e) override;
     void mouseDown(MouseEvent const& e) override;
-    void mouseMove(MouseEvent const& e) override;
 
     void resized() override;
     void paintOverChildren(Graphics& g) override;
diff --git a/Source/SplitView.cpp b/Source/SplitView.cpp
index a90052270..d1f8b4d67 100644
--- a/Source/SplitView.cpp
+++ b/Source/SplitView.cpp
@@ -14,63 +14,45 @@
 #include "PluginProcessor.h"
 #include "Sidebar/Sidebar.h"
 
-class FadeAnimation : private Timer {
+class SplitViewFocusOutline : public Component
+    , public ComponentListener {
 public:
-    explicit FadeAnimation(SplitView* splitView)
-        : splitView(splitView)
+    SplitViewFocusOutline()
     {
+        setInterceptsMouseClicks(false, false);
     }
 
-    float fadeIn()
+    void setActive(ResizableTabbedComponent* tabComponent)
     {
-        targetAlpha = 0.3f;
-        if (!isTimerRunning() && currentAlpha < targetAlpha)
-            startTimerHz(60);
+        if (tabbedComponent != tabComponent) {
+            if (tabbedComponent)
+                tabbedComponent->removeComponentListener(this);
 
-        return currentAlpha;
+            tabComponent->addComponentListener(this);
+            setBounds(tabComponent->getBounds());
+            tabbedComponent = tabComponent;
+        }
     }
 
-    float fadeOut()
+    void componentMovedOrResized(Component& component, bool moved, bool resized) override
     {
-        targetAlpha = 0.0f;
-        if (!isTimerRunning() && currentAlpha > targetAlpha)
-            startTimerHz(60);
-
-        return currentAlpha;
+        if (&component == tabbedComponent) {
+            setBounds(component.getBounds());
+        }
     }
 
-private:
-    void timerCallback() override
+    void paint(Graphics& g)
     {
-        float const stepSize = 0.025f;
-        if (targetAlpha > currentAlpha) {
-            currentAlpha += stepSize;
-            if (currentAlpha >= targetAlpha) {
-                currentAlpha = targetAlpha;
-                stopTimer();
-            }
-        } else if (targetAlpha < currentAlpha) {
-            currentAlpha -= stepSize;
-            if (currentAlpha <= targetAlpha) {
-                currentAlpha = targetAlpha;
-                stopTimer();
-            }
-        }
-        if (splitView != nullptr)
-            splitView->repaint();
+        g.setColour(findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f));
+        g.drawRect(getLocalBounds(), 2.5f);
     }
 
 private:
-    SplitView* splitView;
-    float currentAlpha = 0.0f;
-    float targetAlpha = 0.0f;
+    SafePointer<ResizableTabbedComponent> tabbedComponent;
 };
 
 SplitView::SplitView(PluginEditor* parent)
     : editor(parent)
-    , fadeAnimation(new FadeAnimation(this))
-    , fadeAnimationLeft(new FadeAnimation(this))
-    , fadeAnimationRight(new FadeAnimation(this))
 {
     rootComponent = new ResizableTabbedComponent(editor);
     splits.add(rootComponent);
@@ -80,6 +62,10 @@ SplitView::SplitView(PluginEditor* parent)
     // either we check if the tabcomponent is welcome mode, or we check if it's nullptr down the line
     activeTabComponent = rootComponent;
 
+    focusOutline = std::make_unique<SplitViewFocusOutline>();
+    addChildComponent(focusOutline.get());
+    focusOutline->setAlwaysOnTop(true);
+
     addMouseListener(this, true);
 }
 
@@ -117,6 +103,9 @@ void SplitView::removeSplit(TabComponent* toRemove)
         }
     }
     delete toBeRemoved;
+
+    if (splits.size() == 1)
+        focusOutline->setVisible(false);
 }
 
 void SplitView::addSplit(ResizableTabbedComponent* split)
@@ -166,7 +155,11 @@ void SplitView::setFocus(ResizableTabbedComponent* selectedTabComponent)
 {
     if (activeTabComponent != selectedTabComponent) {
         activeTabComponent = selectedTabComponent;
-        repaint();
+        if (splits.size() > 1) {
+            focusOutline->setActive(activeTabComponent);
+            focusOutline->setVisible(true);
+        } else
+            focusOutline->setVisible(false);
     }
 }
 
@@ -197,16 +190,6 @@ void SplitView::closeEmptySplits()
     }
 }
 
-void SplitView::paintOverChildren(Graphics& g)
-{
-    if (splits.size() > 1) {
-        g.setColour(findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f));
-        auto screenBounds = activeTabComponent->getScreenBounds();
-        auto b = getLocalArea(nullptr, screenBounds);
-        g.drawRect(b, 2.5f);
-    }
-}
-
 ResizableTabbedComponent* SplitView::getSplitAtScreenPosition(Point<int> position)
 {
     for (auto* split : splits) {
diff --git a/Source/SplitView.h b/Source/SplitView.h
index b79b11ab6..5207a6a5b 100644
--- a/Source/SplitView.h
+++ b/Source/SplitView.h
@@ -6,12 +6,14 @@
 
 #pragma once
 
+#include <JuceHeader.h>
+
 #include "SplitViewResizer.h"
 #include "ResizableTabbedComponent.h"
 
 class PluginEditor;
 class Canvas;
-class FadeAnimation;
+class SplitViewFocusOutline;
 class SplitView : public Component {
 public:
     explicit SplitView(PluginEditor* parent);
@@ -31,8 +33,6 @@ class SplitView : public Component {
 
     void closeEmptySplits();
 
-    void paintOverChildren(Graphics& g) override;
-
     int getTabComponentSplitIndex(TabComponent* tabComponent);
 
     ResizableTabbedComponent* getSplitAtScreenPosition(Point<int> position);
@@ -52,12 +52,10 @@ class SplitView : public Component {
     SafePointer<ResizableTabbedComponent> activeTabComponent = nullptr;
     ResizableTabbedComponent* rootComponent;
 
-    std::unique_ptr<FadeAnimation> fadeAnimation;
-    std::unique_ptr<FadeAnimation> fadeAnimationLeft;
-    std::unique_ptr<FadeAnimation> fadeAnimationRight;
-
     bool splitviewIndicator = false;
 
+    std::unique_ptr<SplitViewFocusOutline> focusOutline;
+
     PluginEditor* editor;
 
     std::unique_ptr<Component> splitViewResizer;
diff --git a/Source/Tabbar.cpp b/Source/Tabbar.cpp
index f6a16f982..47730e9b8 100644
--- a/Source/Tabbar.cpp
+++ b/Source/Tabbar.cpp
@@ -165,6 +165,7 @@ void ButtonBar::itemDragEnter(SourceDetails const& dragSourceDetails)
 {
     if (auto* tab = dynamic_cast<TabBarButtonComponent*>(dragSourceDetails.sourceComponent.get())) {
         ghostTabAnimator.cancelAllAnimations(false);
+        owner.setFocused();
         // if this tabbar is DnD on itself, we don't need to add a new tab
         // we move the existing tab
         if (tab->getTabComponent() == &owner) {
@@ -293,6 +294,14 @@ TabComponent::~TabComponent()
     tabs.reset();
 }
 
+void TabComponent::setFocused()
+{
+    for (auto * split : editor->splitView.splits){
+        if (split->getTabComponent() == this)
+            editor->splitView.setFocus(split);
+    }
+}
+
 int TabComponent::getCurrentTabIndex()
 {
     return tabs->getCurrentTabIndex();
diff --git a/Source/Tabbar.h b/Source/Tabbar.h
index 5f7697fd9..13e3a9b46 100644
--- a/Source/Tabbar.h
+++ b/Source/Tabbar.h
@@ -284,6 +284,8 @@ class TabComponent : public Component
 
     Canvas* getCurrentCanvas();
 
+    void setFocused();
+
     PluginEditor* getEditor();
 
     Image tabSnapshot;

From 1745fe3a832a146a5a7981e35cdf1834ea5d05e3 Mon Sep 17 00:00:00 2001
From: alcomposer <alex.w.mitchell@gmail.com>
Date: Tue, 12 Sep 2023 07:54:12 +0930
Subject: [PATCH 2/5] adding tab's add's - not subtracts

---
 Source/SplitView.cpp | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/Source/SplitView.cpp b/Source/SplitView.cpp
index d1f8b4d67..bd01f8204 100644
--- a/Source/SplitView.cpp
+++ b/Source/SplitView.cpp
@@ -24,6 +24,8 @@ class SplitViewFocusOutline : public Component
 
     void setActive(ResizableTabbedComponent* tabComponent)
     {
+        setVisible(true);
+
         if (tabbedComponent != tabComponent) {
             if (tabbedComponent)
                 tabbedComponent->removeComponentListener(this);
@@ -155,11 +157,7 @@ void SplitView::setFocus(ResizableTabbedComponent* selectedTabComponent)
 {
     if (activeTabComponent != selectedTabComponent) {
         activeTabComponent = selectedTabComponent;
-        if (splits.size() > 1) {
-            focusOutline->setActive(activeTabComponent);
-            focusOutline->setVisible(true);
-        } else
-            focusOutline->setVisible(false);
+        focusOutline->setActive(activeTabComponent);
     }
 }
 

From 787479d732b42a0b613a46eae16562c15e829d76 Mon Sep 17 00:00:00 2001
From: alcomposer <alex.w.mitchell@gmail.com>
Date: Tue, 12 Sep 2023 16:57:57 +0930
Subject: [PATCH 3/5] fix [hack] for crash when dragging a tab to the right
 edge of a tabbar from another tabbar. We need to have our own TabButtonBar,
 that is able to work correctly with DnD & Overflow

This fix forces all tabs dragged into the right edge of a tabbar to be added before the last. Which stops the index being incorrect if the the tabbar goes into overflow.
---
 Source/Tabbar.cpp | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/Source/Tabbar.cpp b/Source/Tabbar.cpp
index 47730e9b8..634255e19 100644
--- a/Source/Tabbar.cpp
+++ b/Source/Tabbar.cpp
@@ -25,8 +25,12 @@ class ButtonBar::GhostTab : public Component {
     void setTabButtonToGhost(TabBarButton* tabButton)
     {
         tab = tabButton;
-        setBounds(tab->getBounds());
-        repaint();
+        // this should never happen, if it does then the index for the tab is wrong ( getTab(idx) will return nullptr )
+        // which can happen if the tabbar goes into overflow, because we don't know exactly when that will happen
+        if (tab){
+            setBounds(tab->getBounds());
+            repaint();
+        }
     }
 
     int getIndex()
@@ -181,10 +185,14 @@ void ButtonBar::itemDragEnter(SourceDetails const& dragSourceDetails)
             // WARNING: because we are using the overflow (show extra items menu)
             // we need to find out how many tabs are visible, not how many there are all together
             auto targetTabPos = getWidth() / (getNumVisibleTabs() + 1);
-            auto tabPos = dragSourceDetails.localPosition.getX() / targetTabPos;
+            // FIXME: This is a hack. When tab is added to tabbar right edge, and it goes into overflow
+            //        we don't know when that will happen.
+            //        So we force the right most tab to think it's always one less.
+            auto tabPos = jmin(dragSourceDetails.localPosition.getX() / targetTabPos, getNumVisibleTabs() - 1);
             inOtherSplit = true;
             auto unusedComponent = std::make_unique<Component>();
             owner.addTab(tab->getButtonText(), unusedComponent.get(), tabPos);
+
             auto* fakeTab = getTabButton(tabPos);
             tab->getProperties().set("dragged", var(true));
             ghostTab->setTabButtonToGhost(fakeTab);

From 439d27b14ad21a1eeedd2d58fb8c89348ef89c29 Mon Sep 17 00:00:00 2001
From: alcomposer <alex.w.mitchell@gmail.com>
Date: Tue, 12 Sep 2023 17:12:30 +0930
Subject: [PATCH 4/5] overflow menu can reduce the 4 tabs to 2 in some geometry
 situations, not 3, so take that into account

---
 Source/Tabbar.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Source/Tabbar.cpp b/Source/Tabbar.cpp
index 634255e19..34d1c28bb 100644
--- a/Source/Tabbar.cpp
+++ b/Source/Tabbar.cpp
@@ -188,7 +188,7 @@ void ButtonBar::itemDragEnter(SourceDetails const& dragSourceDetails)
             // FIXME: This is a hack. When tab is added to tabbar right edge, and it goes into overflow
             //        we don't know when that will happen.
             //        So we force the right most tab to think it's always one less.
-            auto tabPos = jmin(dragSourceDetails.localPosition.getX() / targetTabPos, getNumVisibleTabs() - 1);
+            auto tabPos = jmin(dragSourceDetails.localPosition.getX() / targetTabPos, getNumVisibleTabs() - 2);
             inOtherSplit = true;
             auto unusedComponent = std::make_unique<Component>();
             owner.addTab(tab->getButtonText(), unusedComponent.get(), tabPos);

From 7247b8cf4ce2d9803b8f38ce1d8b6f25ee634717 Mon Sep 17 00:00:00 2001
From: alcomposer <alex.w.mitchell@gmail.com>
Date: Wed, 13 Sep 2023 00:12:21 +0930
Subject: [PATCH 5/5] we also need to make sure the tabPos doens't go below 0

---
 Source/Tabbar.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Source/Tabbar.cpp b/Source/Tabbar.cpp
index 34d1c28bb..01a6383d5 100644
--- a/Source/Tabbar.cpp
+++ b/Source/Tabbar.cpp
@@ -187,8 +187,10 @@ void ButtonBar::itemDragEnter(SourceDetails const& dragSourceDetails)
             auto targetTabPos = getWidth() / (getNumVisibleTabs() + 1);
             // FIXME: This is a hack. When tab is added to tabbar right edge, and it goes into overflow
             //        we don't know when that will happen.
-            //        So we force the right most tab to think it's always one less.
+            //        So we force the right most tab to think it's always two less when it gets added
             auto tabPos = jmin(dragSourceDetails.localPosition.getX() / targetTabPos, getNumVisibleTabs() - 2);
+            tabPos = jmax(0, tabPos);
+
             inOtherSplit = true;
             auto unusedComponent = std::make_unique<Component>();
             owner.addTab(tab->getButtonText(), unusedComponent.get(), tabPos);