From 7f6610ed6d92b6fa248092ec1ab1123b2b937c83 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Jan 2026 16:04:57 +0100 Subject: [PATCH 1/2] Add tab animations when creating/deleting tabs --- Source/TabComponent.cpp | 33 ++++++++++++++++++++++++--------- Source/TabComponent.h | 1 + 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index cc21e7f55..812519be1 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -240,11 +240,11 @@ class TabComponent::TabBarButtonComponent final : public Component { SafePointer cnv; TabComponent* parent; ScaledImage tabImage; - bool isDragging = false; ComponentDragger dragger; TabDragConstrainer tabDragConstrainer; CloseTabButton closeButton = CloseTabButton(Icons::Clear); + bool isDragging : 1 = false; }; TabComponent::TabComponent(PluginEditor* editor) @@ -383,7 +383,8 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch, bool const warnIfA } auto* cnv = canvases.add(new Canvas(editor, existingPatch)); - + newCanvases.insert(cnv); + auto const patchTitle = existingPatch->getTitle(); // Open help files and references in Locked Mode if (patchTitle.contains("-help") || patchTitle.equalsIgnoreCase("reference")) @@ -662,7 +663,16 @@ void TabComponent::handleAsyncUpdate() for (auto* cnv : getCanvases()) { cnv->saveViewportState(); } - + + UnorderedMap> oldTabBounds; + for(auto& tabbar : tabbars) + { + for(auto* tab : tabbar) + { + oldTabBounds.insert({tab->cnv, tab->getBounds()}); + } + } + tabbars[0].clear(); tabbars[1].clear(); @@ -713,12 +723,22 @@ void TabComponent::handleAsyncUpdate() if (!cnv) { cnv = canvases.add(new Canvas(editor, patch)); + newCanvases.insert(cnv); resized(); cnv->restoreViewportState(); } // Create tab buttons auto* newTabButton = new TabBarButtonComponent(cnv, this); + if(newCanvases.contains(cnv)) + { + newTabButton->setBounds(getWidth(), 0, 0, 30); + newCanvases.erase(cnv); + } + else if(oldTabBounds.contains(cnv)) { + newTabButton->setBounds(oldTabBounds[cnv]); + } + tabbars[patch->splitViewIndex == 1].add(newTabButton); addAndMakeVisible(newTabButton); } @@ -957,7 +977,6 @@ void TabComponent::resized() } bool wasOverflown = false; - for (auto* tabButton : tabButtons) { if (tabWidth > splitBounds.getWidth()) { wasOverflown = true; @@ -977,11 +996,7 @@ void TabComponent::resized() continue; // We reserve space for it, but don't set the bounds to create a ghost tab } - if (draggingOverTabbar) { - animator.animateComponent(tabButton, targetBounds, 1.0f, 200, false, 3.0, 0.0); - } else { - tabButton->setBounds(targetBounds); - } + animator.animateComponent(tabButton, targetBounds, 1.0f, 200, false, 3.0, 0.0); } tabOverflowButtons[i].setVisible(wasOverflown); diff --git a/Source/TabComponent.h b/Source/TabComponent.h index 463fe4a4a..abff70b32 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -89,6 +89,7 @@ class TabComponent final : public Component StackArray, 12>, 2> lastShownTabs; StackArray lastSplitPatches { nullptr, nullptr }; + UnorderedSet newCanvases; t_glist* lastActiveCanvas = nullptr; bool draggingOverTabbar = false; From 22f6059a115011dce9ad6026c3f2d19803d170aa Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 27 Jan 2026 17:05:08 +0100 Subject: [PATCH 2/2] Clean up, don't animate when window size changes --- Source/TabComponent.cpp | 19 +++++++++---------- Source/TabComponent.h | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index efcb623c5..42beb5116 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -383,7 +383,6 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch, bool const warnIfA } auto* cnv = canvases.add(new Canvas(editor, existingPatch)); - newCanvases.insert(cnv); auto const patchTitle = existingPatch->getTitle(); // Open help files and references in Locked Mode @@ -723,21 +722,18 @@ void TabComponent::handleAsyncUpdate() if (!cnv) { cnv = canvases.add(new Canvas(editor, patch)); - newCanvases.insert(cnv); resized(); cnv->restoreViewportState(); } // Create tab buttons auto* newTabButton = new TabBarButtonComponent(cnv, this); - if(newCanvases.contains(cnv)) - { - newTabButton->setBounds(getWidth(), 0, 0, 30); - newCanvases.erase(cnv); - } - else if(oldTabBounds.contains(cnv)) { + if(oldTabBounds.contains(cnv)) { newTabButton->setBounds(oldTabBounds[cnv]); } + else { + newTabButton->setBounds(getWidth(), 0, 0, 30); + } tabbars[patch->splitViewIndex == 1].add(newTabButton); addAndMakeVisible(newTabButton); @@ -955,6 +951,9 @@ void TabComponent::resized() { auto const isSplit = splits[1] != nullptr; auto bounds = getLocalBounds(); + bool boundsChanged = lastBounds != bounds; + lastBounds = bounds; + auto tabbarBounds = bounds.removeFromTop(30); auto& animator = Desktop::getInstance().getAnimator(); @@ -984,7 +983,7 @@ void TabComponent::resized() continue; } tabButton->setVisible(true); - + auto targetBounds = splitBounds.removeFromLeft(tabWidth); if (tabButton->isDragging) { tabButton->setSize(tabWidth, 30); @@ -994,7 +993,7 @@ void TabComponent::resized() continue; // We reserve space for it, but don't set the bounds to create a ghost tab } - animator.animateComponent(tabButton, targetBounds, 1.0f, 200, false, 3.0, 0.0); + animator.animateComponent(tabButton, targetBounds, 1.0f, boundsChanged ? 0 : 200, false, 4.0, 0.5); } tabOverflowButtons[i].setVisible(wasOverflown); diff --git a/Source/TabComponent.h b/Source/TabComponent.h index abff70b32..d53cb45d5 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -89,12 +89,12 @@ class TabComponent final : public Component StackArray, 12>, 2> lastShownTabs; StackArray lastSplitPatches { nullptr, nullptr }; - UnorderedSet newCanvases; t_glist* lastActiveCanvas = nullptr; bool draggingOverTabbar = false; bool draggingSplitResizer = false; Rectangle splitDropBounds; + Rectangle lastBounds; float splitProportion = 2; int splitSize = 0;