From a9a22bcc4b8c4a21c02f94558064cf7c6eb9c8a5 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 3 Apr 2026 14:44:55 -0400 Subject: [PATCH 01/14] Base component block created, toggle functionality enabled, placed at bottom of main component for now. --- src/Application.cpp | 15 ++++++- src/MainComponent.cpp | 69 +++++++++++++++++++++++++++++++++ src/MainComponent.h | 5 +++ src/widgets/PreviewPaneWidget.h | 26 +++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/widgets/PreviewPaneWidget.h diff --git a/src/Application.cpp b/src/Application.cpp index 058e4fbc..f50123f7 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -17,6 +17,7 @@ enum CommandIDs // View viewStatusArea = 0x2000, viewMediaClipboard = 0x2001, + viewPreviewPane = 0x2002, // Help about = 0x3000, @@ -71,6 +72,7 @@ PopupMenu MainComponent::getMenuForIndex([[maybe_unused]] int menuIndex, const S { menu.addCommandItem(&commandManager, CommandIDs::viewStatusArea); menu.addCommandItem(&commandManager, CommandIDs::viewMediaClipboard); + menu.addCommandItem(&commandManager, CommandIDs::viewPreviewPane); } else if (menuName == "Help") { @@ -135,7 +137,7 @@ void MainComponent::getAllCommands(Array& commands) commands.addArray(editIDs, numElementsInArray(editIDs)); - const CommandID viewIDs[] = { CommandIDs::viewStatusArea, CommandIDs::viewMediaClipboard }; + const CommandID viewIDs[] = { CommandIDs::viewStatusArea, CommandIDs::viewMediaClipboard, CommandIDs::viewPreviewPane }; commands.addArray(viewIDs, numElementsInArray(viewIDs)); @@ -207,6 +209,12 @@ void MainComponent::getCommandInfo(CommandID commandID, ApplicationCommandInfo& break; + case CommandIDs::viewPreviewPane: + result.setInfo("Preview Pane", "Toggles display of track preview pane", "View", 0); + result.setTicked(showPreviewPane); + + break; + /* --Help-- */ case CommandIDs::about: result.setInfo( @@ -277,6 +285,11 @@ bool MainComponent::perform(const InvocationInfo& info) break; + case CommandIDs::viewPreviewPane: + DBG_AND_LOG("MainComponent:: perform: \"ViewPreviewPane\" command invoked."); + viewPreviewPaneCallback(); + break; + /* --Help-- */ case CommandIDs::about: DBG_AND_LOG("MainComponent::perform: \"about\" command invoked."); diff --git a/src/MainComponent.cpp b/src/MainComponent.cpp index a31a32df..429f6382 100644 --- a/src/MainComponent.cpp +++ b/src/MainComponent.cpp @@ -15,9 +15,11 @@ MainComponent::MainComponent() addAndMakeVisible(mainModelTab); addAndMakeVisible(statusAreaWidget); addAndMakeVisible(mediaClipboardWidget); + addAndMakeVisible(previewPaneWidget); showStatusArea = Settings::getBoolValue("view.showStatusArea", true); showMediaClipboard = Settings::getBoolValue("view.showMediaClipboard", false); + showPreviewPane = Settings::getBoolValue("view.showPreviewPane", true); requiredWindowWidth = minimumWindowWidth; requiredWindowHeight = minimumWindowHeight; @@ -109,6 +111,15 @@ void MainComponent::resized() statusAreaWidget.setBounds(0, 0, 0, 0); } + if (showPreviewPane) + { + mainPanel.items.add(FlexItem(previewPaneWidget).withHeight(previewPaneHeight)); + } + else + { + previewPaneWidget.setBounds(0, 0, 0, 0); + } + fullWindow.items.add(FlexItem(mainPanel).withFlex(1.0)); if (showMediaClipboard) @@ -336,6 +347,64 @@ void MainComponent::viewMediaClipboardCallback() updateWindowConstraints(); } +void MainComponent::viewPreviewPaneCallback() +{ + // Toggle preview pane visibility state + showPreviewPane = ! showPreviewPane; + + // Find top-level window for resizing + if (auto* window = findParentComponentOfClass()) + { + // Determine which display contains HARP + auto* currentDisplay = + Desktop::getInstance().getDisplays().getDisplayForRect(window->getScreenBounds()); + + // Get current bounds of top-level window + Rectangle windowBounds = window->getBounds(); + + // Default display height to height of current window + int currentDisplayHeight = windowBounds.getHeight(); + + if (currentDisplay != nullptr) + { + if (window->isFullScreen()) + { + currentDisplayHeight = currentDisplay->totalArea.getHeight(); + } + else + { + currentDisplayHeight = currentDisplay->userArea.getHeight(); + } + } + + if (showPreviewPane) + { + // Scale bounds to extend window by height of status area + windowBounds.setHeight( + jmin(currentDisplayHeight, windowBounds.getHeight() + previewPaneHeight)); + } + else + { + if (! window->isFullScreen()) + { + // Scale bounds to reduce window to main height + windowBounds.setHeight(windowBounds.getHeight() - previewPaneHeight); + } + } + + // Set extended or reduced bounds + window->setBounds(windowBounds); + } + + // Add view preference to persistent settings + Settings::setValue("view.showPreviewPane", showPreviewPane ? "1" : "0", true); + + // Send status message to add check to file menu + commandManager.commandStatusChanged(); + + updateWindowConstraints(); +} + /* --Help-- */ void MainComponent::openAboutWindow() diff --git a/src/MainComponent.h b/src/MainComponent.h index b229d7a4..40bcca60 100644 --- a/src/MainComponent.h +++ b/src/MainComponent.h @@ -14,6 +14,7 @@ #include "widgets/MediaClipboardWidget.h" #include "widgets/StatusAreaWidget.h" +#include "widgets/PreviewPaneWidget.h" #include "windows/AboutWindow.h" #include "windows/settings/SettingsWindow.h" @@ -67,6 +68,7 @@ class MainComponent : public Component, // View void viewStatusAreaCallback(); void viewMediaClipboardCallback(); + void viewPreviewPaneCallback(); // Help void openAboutWindow(); @@ -131,6 +133,7 @@ class MainComponent : public Component, /* Interface */ const int statusAreaHeight = 100; + const int previewPaneHeight = 200; const float mediaClipboardFlex = 0.4f; const float mediaClipboardScale = 1.4f; @@ -149,10 +152,12 @@ class MainComponent : public Component, bool showStatusArea; bool showMediaClipboard; + bool showPreviewPane; ModelTab mainModelTab; StatusAreaWidget statusAreaWidget; MediaClipboardWidget mediaClipboardWidget; + PreviewPaneWidget previewPaneWidget; bool isTutorialActive = false; Rectangle tutorialHighlightRect; diff --git a/src/widgets/PreviewPaneWidget.h b/src/widgets/PreviewPaneWidget.h new file mode 100644 index 00000000..79dcf7ce --- /dev/null +++ b/src/widgets/PreviewPaneWidget.h @@ -0,0 +1,26 @@ +/** + * @file PreviewPaneWidget.h + * @brief Component that allows for the previewing of MediaComponents. + * @author NatalieElizabeth + */ + +#pragma once + +#include + +using namespace juce; + +class PreviewPaneWidget : public Component +{ +public: + PreviewPaneWidget() {} + + void paint(Graphics& g) override + { + g.fillAll(Colour(0x1a, 0x1a, 0x2e)); + g.setColour(Colours::white); + g.drawText("Preview Pane (TODO)", getLocalBounds(), Justification::centred); + } + + void resized() override {} +}; \ No newline at end of file From 4e0649abe8f2ac6f57d4d3956bd8b6a5db1b9e01 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 10 Apr 2026 13:25:50 -0400 Subject: [PATCH 02/14] Enhance MediaClipboardWidget and PreviewPaneWidget functionality - Added setPreviewPane method to MediaClipboardWidget for managing preview pane interactions. - Implemented showTrack and clearTrack methods in PreviewPaneWidget to handle track display and clearing. - Updated MediaDisplayComponent to support preview track functionality. - Improved track name handling in TrackAreaWidget by removing escape characters. Issues: - waveform does not appear on startup, and there are artifacts from trying to fix this problem that remain and will need to be cleaned up later. - preview mode is not in its final form - no resize and no title bar yet --- src/MainComponent.cpp | 2 + src/media/MediaDisplayComponent.cpp | 12 +++- src/media/MediaDisplayComponent.h | 11 ++- src/widgets/MediaClipboardWidget.h | 19 +++++ src/widgets/PreviewPaneWidget.h | 104 ++++++++++++++++++++++++++-- src/widgets/TrackAreaWidget.h | 4 +- 6 files changed, 141 insertions(+), 11 deletions(-) diff --git a/src/MainComponent.cpp b/src/MainComponent.cpp index 429f6382..e924def1 100644 --- a/src/MainComponent.cpp +++ b/src/MainComponent.cpp @@ -17,6 +17,8 @@ MainComponent::MainComponent() addAndMakeVisible(mediaClipboardWidget); addAndMakeVisible(previewPaneWidget); + mediaClipboardWidget.setPreviewPane(&previewPaneWidget); + showStatusArea = Settings::getBoolValue("view.showStatusArea", true); showMediaClipboard = Settings::getBoolValue("view.showMediaClipboard", false); showPreviewPane = Settings::getBoolValue("view.showPreviewPane", true); diff --git a/src/media/MediaDisplayComponent.cpp b/src/media/MediaDisplayComponent.cpp index e7d57142..87a40ab0 100644 --- a/src/media/MediaDisplayComponent.cpp +++ b/src/media/MediaDisplayComponent.cpp @@ -321,6 +321,14 @@ void MediaDisplayComponent::resized() .withHeight(trackNameLabel.getFont().getHeight()) .withMargin(1)); } + else if (isPreviewTrack()) + { + // Place header over media + mainFlexBox.flexDirection = FlexBox::Direction::column; + mainFlexBox.items.add(FlexItem(headerComponent) + .withHeight(trackNameLabel.getFont().getHeight()) + .withMargin(1)); + } else { // Place header beside media @@ -340,7 +348,7 @@ void MediaDisplayComponent::resized() // Add track label to header flex headerFlexBox.items.add(FlexItem(trackNameLabel).withFlex(1).withMargin({ 0, 2, 0, 0 })); - if (! isThumbnailTrack()) + if (! isThumbnailTrack() && ! isPreviewTrack()) { // Add buttons to header flex headerFlexBox.items.add(FlexItem(buttonsComponent).withFlex(2).withMargin({ 0, 0, 0, 1 })); @@ -353,7 +361,7 @@ void MediaDisplayComponent::resized() float trackNameLabelWidth = labelBounds.getWidth(); float trackNameLabelHeight = labelBounds.getHeight(); - if (! isThumbnailTrack()) + if (! isThumbnailTrack() && ! isPreviewTrack()) { Point labelCenter = labelBounds.getCentre(); // Rotate track name label 90 degrees diff --git a/src/media/MediaDisplayComponent.h b/src/media/MediaDisplayComponent.h index 154b698f..018565d4 100644 --- a/src/media/MediaDisplayComponent.h +++ b/src/media/MediaDisplayComponent.h @@ -24,7 +24,8 @@ enum class DisplayMode Input, Output, Hybrid, // All functionality - Thumbnail // Reduced functionality + Thumbnail, // Reduced functionality + Preview // For preview pane }; class ColorablePanel : public Component @@ -95,6 +96,7 @@ class MediaDisplayComponent : public Component, bool isOutputTrack() { return (displayMode == DisplayMode::Output) || isHybridTrack(); } bool isHybridTrack() { return displayMode == DisplayMode::Hybrid; } bool isThumbnailTrack() { return displayMode == DisplayMode::Thumbnail; } + bool isPreviewTrack() { return displayMode == DisplayMode::Preview; } void setMediaInstructions(String instructions) { mediaInstructions = instructions; } @@ -187,7 +189,12 @@ class MediaDisplayComponent : public Component, void timerCallback() override; virtual void visibleRangeCallback() { repaint(); } - virtual void changeListenerCallback(ChangeBroadcaster*) override { repaint(); } + virtual void changeListenerCallback(ChangeBroadcaster*) override + { + repaint(); + DBG_AND_LOG("MediaDisplayComponent::changeListenerCallback fired, scheduling sendChangeMessage"); + MessageManager::callAsync([this]() { sendChangeMessage(); }); + } virtual void resetMedia() = 0; void resetPaths(); diff --git a/src/widgets/MediaClipboardWidget.h b/src/widgets/MediaClipboardWidget.h index 3753cbfd..086e034f 100644 --- a/src/widgets/MediaClipboardWidget.h +++ b/src/widgets/MediaClipboardWidget.h @@ -14,6 +14,8 @@ #include "../utils/Logging.h" +#include "PreviewPaneWidget.h" + using namespace juce; class MediaClipboardWidget : public Component, public ChangeListener @@ -322,6 +324,11 @@ class MediaClipboardWidget : public Component, public ChangeListener }); } + void setPreviewPane(PreviewPaneWidget* pane) + { + previewPane = pane; + } + private: void initializeButtons() { @@ -482,12 +489,22 @@ class MediaClipboardWidget : public Component, public ChangeListener selectTrack(mediaDisplay); // Handle track area resizing after adding a track resized(); // TODO - decouple from track selection? + + // Tell the preview pane about the selection + if (previewPane != nullptr) + previewPane->showTrack(mediaDisplay); } } else { // Handle deselect events resetState(); + + // Clear the preview pane when nothing is selected + if (previewPane != nullptr) + { + previewPane->clearTrack(); + } } } @@ -630,5 +647,7 @@ class MediaClipboardWidget : public Component, public ChangeListener std::unique_ptr chooseFileBrowser; + PreviewPaneWidget* previewPane = nullptr; + MediaDisplayComponent* currentlySelectedDisplay; }; diff --git a/src/widgets/PreviewPaneWidget.h b/src/widgets/PreviewPaneWidget.h index 79dcf7ce..415f9440 100644 --- a/src/widgets/PreviewPaneWidget.h +++ b/src/widgets/PreviewPaneWidget.h @@ -6,21 +6,115 @@ #pragma once +#include "../media/AudioDisplayComponent.h" +#include "../media/MediaDisplayComponent.h" +#include "../media/MidiDisplayComponent.h" #include using namespace juce; -class PreviewPaneWidget : public Component +class PreviewPaneWidget : public Component, public ChangeListener { public: PreviewPaneWidget() {} + void showTrack(MediaDisplayComponent* source) + { + // Remove previosuly displayed track + if (currentDisplay != nullptr) + { + currentDisplay->removeChangeListener(this); + removeChildComponent(currentDisplay.get()); + currentDisplay.reset(); + } + + if (source == nullptr) + { + repaint(); + return; + } + + URL filePath = source->getOriginalFilePath(); + + if (dynamic_cast(source) != nullptr) + { + auto* newDisplay = + new AudioDisplayComponent(source->getTrackName(), false, false, DisplayMode::Preview); + currentDisplay.reset(newDisplay); + + addAndMakeVisible(newDisplay); + currentDisplay->addChangeListener(this); + + if (filePath.isLocalFile()) + { + newDisplay->initializeDisplay(filePath); + MessageManager::callAsync([this]() { + repaint(); + }); + } + } + else + { + auto* newDisplay = + new MidiDisplayComponent(source->getTrackName(), false, false, DisplayMode::Preview); + currentDisplay.reset(newDisplay); + + addAndMakeVisible(newDisplay); + currentDisplay->addChangeListener(this); + + if (filePath.isLocalFile()) + { + newDisplay->initializeDisplay(filePath); + } + } + + resized(); + repaint(); + MessageManager::callAsync([this]() { + resized(); + repaint(); + }); + } + + void clearTrack() + { + if (currentDisplay != nullptr) + { + currentDisplay->removeChangeListener(this); + removeChildComponent(currentDisplay.get()); + currentDisplay.reset(); + } + repaint(); + } + + void changeListenerCallback(ChangeBroadcaster* source) override + { + DBG_AND_LOG("PreviewPaneWidget::changeListenerCallback fired"); + if (source == currentDisplay.get()) + { + DBG_AND_LOG("PreviewPaneWidget: source matches currentDisplay, calling resized/repaint"); + resized(); + repaint(); + } + } + void paint(Graphics& g) override { - g.fillAll(Colour(0x1a, 0x1a, 0x2e)); - g.setColour(Colours::white); - g.drawText("Preview Pane (TODO)", getLocalBounds(), Justification::centred); + g.fillAll(Colour(Colours::darkgrey)); + + if (currentDisplay == nullptr) + { + g.setColour(Colours::grey); + g.drawText("No track selected.", getLocalBounds(), Justification::centred); + } + } + + void resized() override + { + if (currentDisplay != nullptr) + currentDisplay->setBounds(getLocalBounds().reduced(8,0)); } - void resized() override {} +private: + std::unique_ptr currentDisplay; }; \ No newline at end of file diff --git a/src/widgets/TrackAreaWidget.h b/src/widgets/TrackAreaWidget.h index f81f7765..28ee150a 100644 --- a/src/widgets/TrackAreaWidget.h +++ b/src/widgets/TrackAreaWidget.h @@ -282,7 +282,7 @@ class TrackAreaWidget : public Component, } String ext = f.getFileExtension(); - String label = filePath.getFileName(); + String label = URL::removeEscapeChars(filePath.getFileName()); bool validExt = true; @@ -318,7 +318,7 @@ class TrackAreaWidget : public Component, { addTrackFromComponentInfo(trackInfo.get(), fromDAW); mediaDisplays.back()->initializeDisplay(filePath); - mediaDisplays.back()->setTrackName(filePath.getFileName()); + mediaDisplays.back()->setTrackName(URL::removeEscapeChars(filePath.getFileName())); } } From 32676edd8e8e7d2c9a6e8698eae51a77abb51f19 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Thu, 16 Apr 2026 16:09:53 -0400 Subject: [PATCH 03/14] Add minimize and close functionality to PreviewPaneWidget, add drag functionality --- src/MainComponent.cpp | 12 ++- src/MainComponent.h | 2 +- src/widgets/PreviewPaneWidget.h | 161 +++++++++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 5 deletions(-) diff --git a/src/MainComponent.cpp b/src/MainComponent.cpp index e924def1..95a456eb 100644 --- a/src/MainComponent.cpp +++ b/src/MainComponent.cpp @@ -18,6 +18,15 @@ MainComponent::MainComponent() addAndMakeVisible(previewPaneWidget); mediaClipboardWidget.setPreviewPane(&previewPaneWidget); + // Wire minimize and close buttons to toggle the preview pane off + previewPaneWidget.onClose = [this] { viewPreviewPaneCallback(); }; + + // Wire the resize drag to update previewPaneHeight and re-layout + previewPaneWidget.onResize = [this](int newHeight) + { + previewPaneHeight = newHeight; + resized(); + }; showStatusArea = Settings::getBoolValue("view.showStatusArea", true); showMediaClipboard = Settings::getBoolValue("view.showMediaClipboard", false); @@ -381,7 +390,8 @@ void MainComponent::viewPreviewPaneCallback() if (showPreviewPane) { - // Scale bounds to extend window by height of status area + previewPaneWidget.resetMinimizeState(); + previewPaneHeight = previewPaneWidget.getExpandedHeight(); windowBounds.setHeight( jmin(currentDisplayHeight, windowBounds.getHeight() + previewPaneHeight)); } diff --git a/src/MainComponent.h b/src/MainComponent.h index 40bcca60..5c7c7df8 100644 --- a/src/MainComponent.h +++ b/src/MainComponent.h @@ -133,9 +133,9 @@ class MainComponent : public Component, /* Interface */ const int statusAreaHeight = 100; - const int previewPaneHeight = 200; const float mediaClipboardFlex = 0.4f; const float mediaClipboardScale = 1.4f; + int previewPaneHeight = 200; // Minimum size to ensure all controls remain visible and functional: // - WelcomeWindow popup is 480x500, needs padding diff --git a/src/widgets/PreviewPaneWidget.h b/src/widgets/PreviewPaneWidget.h index 415f9440..7abb9cec 100644 --- a/src/widgets/PreviewPaneWidget.h +++ b/src/widgets/PreviewPaneWidget.h @@ -16,7 +16,49 @@ using namespace juce; class PreviewPaneWidget : public Component, public ChangeListener { public: - PreviewPaneWidget() {} + PreviewPaneWidget() + { + minimizeButton.setButtonText("-"); + minimizeButton.setTooltip("Minimize preview pane"); + minimizeButton.onClick = [this] + { + if (isMinimized) + { + isMinimized = false; + minimizeButton.setButtonText("-"); + if (onResize) + { + onResize(expandedHeight); + } + } + else + { + expandedHeight = getHeight(); + isMinimized = true; + + minimizeButton.setButtonText(CharPointer_UTF8("\xe2\x96\xb2")); + if (onResize) + { + onResize(titleBarHeight); + } + } + }; + addAndMakeVisible(minimizeButton); + + closeButton.setButtonText(CharPointer_UTF8("\xc3\x97")); + closeButton.setTooltip("Close preview pane"); + closeButton.onClick = [this] + { + isMinimized = false; + minimizeButton.setButtonText("-"); + + if (onClose) + { + onClose(); + } + }; + addAndMakeVisible(closeButton); + } void showTrack(MediaDisplayComponent* source) { @@ -98,10 +140,41 @@ class PreviewPaneWidget : public Component, public ChangeListener } } + void resetMinimizeState() + { + isMinimized = false; + minimizeButton.setButtonText("-"); + } + + int getExpandedHeight() const { return expandedHeight; } + + // Callbacks + + std::function onClose; + + std::function onResize; + void paint(Graphics& g) override { + // Widget background g.fillAll(Colour(Colours::darkgrey)); + // Title bar background + Rectangle titleArea(0, 0, getWidth(), titleBarHeight); + g.setColour(getUIColourIfAvailable(LookAndFeel_V4::ColourScheme::UIColour::windowBackground)); + g.fillRect(titleArea); + + // Title bar bottom border + g.setColour(Colours::black.withAlpha(0.4f)); + g.drawLine(0, titleBarHeight, getWidth(), titleBarHeight, 1.0f); + + // Draw "Preview" label + g.setColour(Colours::lightgrey); + g.setFont(Font(12.0f).boldened()); + Rectangle labelArea(8, 0, getWidth() - 8 - 2 * buttonWidth - 3 * buttonMargin, titleBarHeight); + g.drawText("Preview", labelArea, Justification::centredLeft); + + // No track loaded placeholder message if (currentDisplay == nullptr) { g.setColour(Colours::grey); @@ -111,10 +184,92 @@ class PreviewPaneWidget : public Component, public ChangeListener void resized() override { - if (currentDisplay != nullptr) - currentDisplay->setBounds(getLocalBounds().reduced(8,0)); + // Position the close button flush against the right edge of the title bar + int closeX = getWidth() - buttonMargin - buttonWidth; + closeButton.setBounds(closeX, (titleBarHeight - buttonWidth) / 2, buttonWidth, buttonWidth); + + // Position the minimize button immediately to the left of the close button + int minimizeX = closeX - buttonMargin - buttonWidth; + minimizeButton.setBounds(minimizeX, (titleBarHeight - buttonWidth) / 2, buttonWidth, buttonWidth); + + if (!isMinimized && currentDisplay != nullptr) + currentDisplay->setBounds(0, titleBarHeight, getWidth(), getHeight() - titleBarHeight); + } + + // Mouse handling for top-edge resize + + // Called once when the user first presses the mouse button down. + // We record the starting Y position and the starting height so we can + // compute deltas during the drag. + void mouseDown(const MouseEvent& e) override + { + if (isInResizeZone(e.y) && !isMinimized) + { + dragStartY = e.getScreenY(); // absolute screen Y when drag begins + dragStartHeight = getHeight(); // height of this component at drag start + } + } + + // Called repeatedly as the mouse moves while a button is held down. + // We compute how far the mouse has moved upward and ask MainComponent + // to change the pane height accordingly. + void mouseDrag(const MouseEvent& e) override + { + if (dragStartY < 0 && !isMinimized) // drag wasn't initiated in the resize zone + return; + + // A positive delta means the mouse moved down (pane would shrink), + // a negative delta means the mouse moved up (pane would grow). + int delta = e.getScreenY() - dragStartY; + int newHeight = jmax(minimumPaneHeight, dragStartHeight - delta); + + if (onResize) + onResize(newHeight); + } + + // Called whenever the mouse moves (without a button held). + // We use this to switch the cursor to a resize cursor when hovering + // near the top edge, and back to the default otherwise. + void mouseMove(const MouseEvent& e) override + { + if (isInResizeZone(e.y) && !isMinimized) + setMouseCursor(MouseCursor::UpDownResizeCursor); + else + setMouseCursor(MouseCursor::NormalCursor); + } + + // Called when the mouse button is released — reset drag tracking state. + void mouseUp(const MouseEvent& e) override + { + dragStartY = -1; + dragStartHeight = -1; + + if (dragStartY >= 0) + { + expandedHeight = getHeight(); + } } private: + static constexpr int titleBarHeight = 22; // height of the title bar strip + static constexpr int buttonWidth = 16; // width and height of each title button + static constexpr int buttonMargin = 4; // space between/around title buttons + static constexpr int resizeZoneHeight = 6; // px from top edge that trigger resize cursor + static constexpr int minimumPaneHeight = 60; // smallest the pane can be dragged to + + TextButton minimizeButton; + TextButton closeButton; std::unique_ptr currentDisplay; + + // Negative sentinel values mean no drag is in progress + int dragStartY = -1; + int dragStartHeight = -1; + + bool isMinimized = false; + int expandedHeight = 150; + + bool isInResizeZone(int localY) const + { + return localY < resizeZoneHeight; + } }; \ No newline at end of file From 24ce8de759228d441cb85b55ecebefc7f44577e4 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 17 Apr 2026 16:18:16 -0400 Subject: [PATCH 04/14] No longer breaks when closed while minimized --- src/Application.cpp | 4 +- src/MainComponent.cpp | 81 ------------------------------ src/MainComponent.h | 5 -- src/widgets/MediaClipboardWidget.h | 50 ++++++++++++++---- 4 files changed, 42 insertions(+), 98 deletions(-) diff --git a/src/Application.cpp b/src/Application.cpp index f50123f7..46c23426 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -211,7 +211,7 @@ void MainComponent::getCommandInfo(CommandID commandID, ApplicationCommandInfo& case CommandIDs::viewPreviewPane: result.setInfo("Preview Pane", "Toggles display of track preview pane", "View", 0); - result.setTicked(showPreviewPane); + result.setTicked(mediaClipboardWidget.isPreviewPaneVisible()); break; @@ -287,7 +287,7 @@ bool MainComponent::perform(const InvocationInfo& info) case CommandIDs::viewPreviewPane: DBG_AND_LOG("MainComponent:: perform: \"ViewPreviewPane\" command invoked."); - viewPreviewPaneCallback(); + mediaClipboardWidget.togglePreviewPane(); break; /* --Help-- */ diff --git a/src/MainComponent.cpp b/src/MainComponent.cpp index 95a456eb..a31a32df 100644 --- a/src/MainComponent.cpp +++ b/src/MainComponent.cpp @@ -15,22 +15,9 @@ MainComponent::MainComponent() addAndMakeVisible(mainModelTab); addAndMakeVisible(statusAreaWidget); addAndMakeVisible(mediaClipboardWidget); - addAndMakeVisible(previewPaneWidget); - - mediaClipboardWidget.setPreviewPane(&previewPaneWidget); - // Wire minimize and close buttons to toggle the preview pane off - previewPaneWidget.onClose = [this] { viewPreviewPaneCallback(); }; - - // Wire the resize drag to update previewPaneHeight and re-layout - previewPaneWidget.onResize = [this](int newHeight) - { - previewPaneHeight = newHeight; - resized(); - }; showStatusArea = Settings::getBoolValue("view.showStatusArea", true); showMediaClipboard = Settings::getBoolValue("view.showMediaClipboard", false); - showPreviewPane = Settings::getBoolValue("view.showPreviewPane", true); requiredWindowWidth = minimumWindowWidth; requiredWindowHeight = minimumWindowHeight; @@ -122,15 +109,6 @@ void MainComponent::resized() statusAreaWidget.setBounds(0, 0, 0, 0); } - if (showPreviewPane) - { - mainPanel.items.add(FlexItem(previewPaneWidget).withHeight(previewPaneHeight)); - } - else - { - previewPaneWidget.setBounds(0, 0, 0, 0); - } - fullWindow.items.add(FlexItem(mainPanel).withFlex(1.0)); if (showMediaClipboard) @@ -358,65 +336,6 @@ void MainComponent::viewMediaClipboardCallback() updateWindowConstraints(); } -void MainComponent::viewPreviewPaneCallback() -{ - // Toggle preview pane visibility state - showPreviewPane = ! showPreviewPane; - - // Find top-level window for resizing - if (auto* window = findParentComponentOfClass()) - { - // Determine which display contains HARP - auto* currentDisplay = - Desktop::getInstance().getDisplays().getDisplayForRect(window->getScreenBounds()); - - // Get current bounds of top-level window - Rectangle windowBounds = window->getBounds(); - - // Default display height to height of current window - int currentDisplayHeight = windowBounds.getHeight(); - - if (currentDisplay != nullptr) - { - if (window->isFullScreen()) - { - currentDisplayHeight = currentDisplay->totalArea.getHeight(); - } - else - { - currentDisplayHeight = currentDisplay->userArea.getHeight(); - } - } - - if (showPreviewPane) - { - previewPaneWidget.resetMinimizeState(); - previewPaneHeight = previewPaneWidget.getExpandedHeight(); - windowBounds.setHeight( - jmin(currentDisplayHeight, windowBounds.getHeight() + previewPaneHeight)); - } - else - { - if (! window->isFullScreen()) - { - // Scale bounds to reduce window to main height - windowBounds.setHeight(windowBounds.getHeight() - previewPaneHeight); - } - } - - // Set extended or reduced bounds - window->setBounds(windowBounds); - } - - // Add view preference to persistent settings - Settings::setValue("view.showPreviewPane", showPreviewPane ? "1" : "0", true); - - // Send status message to add check to file menu - commandManager.commandStatusChanged(); - - updateWindowConstraints(); -} - /* --Help-- */ void MainComponent::openAboutWindow() diff --git a/src/MainComponent.h b/src/MainComponent.h index 5c7c7df8..b229d7a4 100644 --- a/src/MainComponent.h +++ b/src/MainComponent.h @@ -14,7 +14,6 @@ #include "widgets/MediaClipboardWidget.h" #include "widgets/StatusAreaWidget.h" -#include "widgets/PreviewPaneWidget.h" #include "windows/AboutWindow.h" #include "windows/settings/SettingsWindow.h" @@ -68,7 +67,6 @@ class MainComponent : public Component, // View void viewStatusAreaCallback(); void viewMediaClipboardCallback(); - void viewPreviewPaneCallback(); // Help void openAboutWindow(); @@ -135,7 +133,6 @@ class MainComponent : public Component, const int statusAreaHeight = 100; const float mediaClipboardFlex = 0.4f; const float mediaClipboardScale = 1.4f; - int previewPaneHeight = 200; // Minimum size to ensure all controls remain visible and functional: // - WelcomeWindow popup is 480x500, needs padding @@ -152,12 +149,10 @@ class MainComponent : public Component, bool showStatusArea; bool showMediaClipboard; - bool showPreviewPane; ModelTab mainModelTab; StatusAreaWidget statusAreaWidget; MediaClipboardWidget mediaClipboardWidget; - PreviewPaneWidget previewPaneWidget; bool isTutorialActive = false; Rectangle tutorialHighlightRect; diff --git a/src/widgets/MediaClipboardWidget.h b/src/widgets/MediaClipboardWidget.h index 086e034f..b215d671 100644 --- a/src/widgets/MediaClipboardWidget.h +++ b/src/widgets/MediaClipboardWidget.h @@ -34,6 +34,18 @@ class MediaClipboardWidget : public Component, public ChangeListener trackAreaWidget.addChangeListener(this); trackArea.setViewedComponent(&trackAreaWidget, false); addAndMakeVisible(trackArea); + + addAndMakeVisible(previewPaneWidget); + previewPaneWidget.onClose = [this] + { + showPreviewPane = false; + resized(); + }; + previewPaneWidget.onResize = [this](int newHeight) + { + previewPaneHeight = newHeight; + resized(); + }; } ~MediaClipboardWidget() { trackAreaWidget.removeChangeListener(this); } @@ -55,6 +67,15 @@ class MediaClipboardWidget : public Component, public ChangeListener .withMargin(marginSize)); //jmax(30, trackNameLabel.getFont().getHeight())) mainFlexBox.items.add( FlexItem(trackArea).withFlex(10).withMargin({ 0, marginSize, marginSize, marginSize })); + if (showPreviewPane) + { + mainFlexBox.items.add( + FlexItem(previewPaneWidget).withHeight(previewPaneHeight).withMargin({ 0, marginSize, marginSize, marginSize })); + } + else + { + previewPaneWidget.setBounds(0, 0, 0, 0); + } mainFlexBox.performLayout(totalBounds); @@ -167,6 +188,11 @@ class MediaClipboardWidget : public Component, public ChangeListener return getLocalArea(&buttonsComponent, sendToDAWButton.getBounds()).expanded(2); } + bool isPreviewPaneVisible() const + { + return showPreviewPane; + } + void addFileCallback() { StringArray validExtensions = MediaDisplayComponent::getSupportedExtensions(); @@ -324,9 +350,15 @@ class MediaClipboardWidget : public Component, public ChangeListener }); } - void setPreviewPane(PreviewPaneWidget* pane) + void togglePreviewPane() { - previewPane = pane; + showPreviewPane = !showPreviewPane; + if (showPreviewPane) + { + previewPaneWidget.resetMinimizeState(); + previewPaneHeight = previewPaneWidget.getExpandedHeight(); + } + resized(); } private: @@ -491,8 +523,7 @@ class MediaClipboardWidget : public Component, public ChangeListener resized(); // TODO - decouple from track selection? // Tell the preview pane about the selection - if (previewPane != nullptr) - previewPane->showTrack(mediaDisplay); + previewPaneWidget.showTrack(mediaDisplay); } } else @@ -501,10 +532,7 @@ class MediaClipboardWidget : public Component, public ChangeListener resetState(); // Clear the preview pane when nothing is selected - if (previewPane != nullptr) - { - previewPane->clearTrack(); - } + previewPaneWidget.clearTrack(); } } @@ -645,9 +673,11 @@ class MediaClipboardWidget : public Component, public ChangeListener Viewport trackArea; TrackAreaWidget trackAreaWidget { DisplayMode::Thumbnail, 75 }; - std::unique_ptr chooseFileBrowser; + PreviewPaneWidget previewPaneWidget; + int previewPaneHeight = 200; + bool showPreviewPane = true; - PreviewPaneWidget* previewPane = nullptr; + std::unique_ptr chooseFileBrowser; MediaDisplayComponent* currentlySelectedDisplay; }; From 05a5a522f4e9ca53c2adeca5196c42a2b873dac5 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 01:01:39 -0400 Subject: [PATCH 05/14] Merged develop in for updates, removed some artifact debug code in preparation to fix asynchronized waveform loading bug. --- src/media/MediaDisplayComponent.h | 1 - src/widgets/PreviewPaneWidget.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/media/MediaDisplayComponent.h b/src/media/MediaDisplayComponent.h index b857cab1..0498e273 100644 --- a/src/media/MediaDisplayComponent.h +++ b/src/media/MediaDisplayComponent.h @@ -193,7 +193,6 @@ class MediaDisplayComponent : public Component, virtual void changeListenerCallback(ChangeBroadcaster*) override { repaint(); - DBG_AND_LOG("MediaDisplayComponent::changeListenerCallback fired, scheduling sendChangeMessage"); MessageManager::callAsync([this]() { sendChangeMessage(); }); } diff --git a/src/widgets/PreviewPaneWidget.h b/src/widgets/PreviewPaneWidget.h index 7abb9cec..92b932e4 100644 --- a/src/widgets/PreviewPaneWidget.h +++ b/src/widgets/PreviewPaneWidget.h @@ -131,10 +131,8 @@ class PreviewPaneWidget : public Component, public ChangeListener void changeListenerCallback(ChangeBroadcaster* source) override { - DBG_AND_LOG("PreviewPaneWidget::changeListenerCallback fired"); if (source == currentDisplay.get()) { - DBG_AND_LOG("PreviewPaneWidget: source matches currentDisplay, calling resized/repaint"); resized(); repaint(); } From 51843e53df208c41720d1d72fad33e6c5fd03e06 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 01:11:53 -0400 Subject: [PATCH 06/14] removed a few artifacts from old debugging, quick interim commit because I need to switch branches --- src/widgets/PreviewPaneWidget.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/widgets/PreviewPaneWidget.h b/src/widgets/PreviewPaneWidget.h index 92b932e4..d0e76173 100644 --- a/src/widgets/PreviewPaneWidget.h +++ b/src/widgets/PreviewPaneWidget.h @@ -90,9 +90,6 @@ class PreviewPaneWidget : public Component, public ChangeListener if (filePath.isLocalFile()) { newDisplay->initializeDisplay(filePath); - MessageManager::callAsync([this]() { - repaint(); - }); } } else @@ -112,10 +109,6 @@ class PreviewPaneWidget : public Component, public ChangeListener resized(); repaint(); - MessageManager::callAsync([this]() { - resized(); - repaint(); - }); } void clearTrack() From 01757c679daf7c305a58a6b268e361313526c141 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 01:18:49 -0400 Subject: [PATCH 07/14] Restored MediaDisplayComponent.h to liberate it from the changelistener async mess --- src/media/MediaDisplayComponent.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/media/MediaDisplayComponent.h b/src/media/MediaDisplayComponent.h index 0498e273..1c2e3526 100644 --- a/src/media/MediaDisplayComponent.h +++ b/src/media/MediaDisplayComponent.h @@ -190,11 +190,7 @@ class MediaDisplayComponent : public Component, void timerCallback() override; virtual void visibleRangeCallback() { repaint(); } - virtual void changeListenerCallback(ChangeBroadcaster*) override - { - repaint(); - MessageManager::callAsync([this]() { sendChangeMessage(); }); - } + virtual void changeListenerCallback(ChangeBroadcaster*) override { repaint(); } virtual void resetMedia() = 0; void resetPaths(); From 5b487914f37b76aed0f9d48567b19583abcfc10d Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 02:10:19 -0400 Subject: [PATCH 08/14] Fixed waveform not loading immediately in preview pane. --- JUCE | 2 +- src/media/MediaDisplayComponent.h | 10 +++++++++- src/widgets/MediaClipboardWidget.h | 11 ++++++----- src/widgets/PreviewPaneWidget.h | 8 ++++++++ 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/JUCE b/JUCE index 501c0767..9f643254 160000 --- a/JUCE +++ b/JUCE @@ -1 +1 @@ -Subproject commit 501c07674e1ad693085a7e7c398f205c2677f5da +Subproject commit 9f64325446ec0baf11f0f5f99c8484a15bbd1ab0 diff --git a/src/media/MediaDisplayComponent.h b/src/media/MediaDisplayComponent.h index 1c2e3526..3652c916 100644 --- a/src/media/MediaDisplayComponent.h +++ b/src/media/MediaDisplayComponent.h @@ -190,7 +190,15 @@ class MediaDisplayComponent : public Component, void timerCallback() override; virtual void visibleRangeCallback() { repaint(); } - virtual void changeListenerCallback(ChangeBroadcaster*) override { repaint(); } + virtual void changeListenerCallback(ChangeBroadcaster*) override + { + repaint(); + Component::SafePointer safeThis(this); + MessageManager::callAsync([safeThis]() mutable { + if (safeThis != nullptr) + safeThis->sendChangeMessage(); + }); + } virtual void resetMedia() = 0; void resetPaths(); diff --git a/src/widgets/MediaClipboardWidget.h b/src/widgets/MediaClipboardWidget.h index 992c8d2b..b8653825 100644 --- a/src/widgets/MediaClipboardWidget.h +++ b/src/widgets/MediaClipboardWidget.h @@ -559,12 +559,13 @@ class MediaClipboardWidget : public Component, public ChangeListener { if (mediaDisplay != currentlySelectedDisplay) { - // Handle select events if selected display changes selectTrack(mediaDisplay); - // Handle track area resizing after adding a track - resized(); // TODO - decouple from track selection? - - // Tell the preview pane about the selection + resized(); + previewPaneWidget.showTrack(mediaDisplay); + } + else if (!previewPaneWidget.isShowingTrack() && mediaDisplay->isFileLoaded()) + { + // File finished loading after initial showTrack was called too early previewPaneWidget.showTrack(mediaDisplay); } } diff --git a/src/widgets/PreviewPaneWidget.h b/src/widgets/PreviewPaneWidget.h index d0e76173..e1bc1353 100644 --- a/src/widgets/PreviewPaneWidget.h +++ b/src/widgets/PreviewPaneWidget.h @@ -78,6 +78,12 @@ class PreviewPaneWidget : public Component, public ChangeListener URL filePath = source->getOriginalFilePath(); + if (!filePath.isLocalFile()) + { + repaint(); + return; + } + if (dynamic_cast(source) != nullptr) { auto* newDisplay = @@ -139,6 +145,8 @@ class PreviewPaneWidget : public Component, public ChangeListener int getExpandedHeight() const { return expandedHeight; } + bool isShowingTrack() const { return currentDisplay != nullptr && currentDisplay->isFileLoaded(); } + // Callbacks std::function onClose; From 8e20c0fdc899263e62e7b70d145052c89e9caa29 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 02:55:57 -0400 Subject: [PATCH 09/14] Added controls to preview pane (horizontal bottom orientation). --- src/media/MediaDisplayComponent.cpp | 86 +++++++++++++++++++++++++++++ src/media/MediaDisplayComponent.h | 12 ++++ 2 files changed, 98 insertions(+) diff --git a/src/media/MediaDisplayComponent.cpp b/src/media/MediaDisplayComponent.cpp index c07d7f86..bdc54c35 100644 --- a/src/media/MediaDisplayComponent.cpp +++ b/src/media/MediaDisplayComponent.cpp @@ -172,6 +172,14 @@ MediaDisplayComponent::MediaDisplayComponent(String name, bool req, bool fromDAW mediaAreaContainer.addAndMakeVisible(horizontalScrollBar); addAndMakeVisible(mediaAreaContainer); + if (isPreviewTrack()) + { + previewControlsFlexBox.flexDirection = FlexBox::Direction::row; + previewControlsFlexBox.alignItems = FlexBox::AlignItems::center; + previewControlsFlexBox.justifyContent = FlexBox::JustifyContent::center; + addAndMakeVisible(previewControlsComponent); + } + mediaAreaFlexBox.flexDirection = FlexBox::Direction::column; currentPositionCursor.setFill(cursorColor); @@ -253,6 +261,42 @@ void MediaDisplayComponent::initializeButtons() copyFileButton.addMode(copyFileButtonInactiveInfo); headerComponent.addAndMakeVisible(copyFileButton); + // Modes for the preview pane + if (isPreviewTrack()) + { + previewPlayButtonInfo = MultiButton::Mode { + "Preview-Play", + "Click to start playback.", + [this] { start(); }, + MultiButton::DrawingMode::IconOnly, + Colours::limegreen, + fontaudio::Play + }; + previewPauseButtonInfo = MultiButton::Mode { + "Preview-Pause", + "Click to pause playback.", + [this] { pause(); }, + MultiButton::DrawingMode::IconOnly, + Colours::yellow, + fontaudio::Pause + }; + previewStopButtonInfo = MultiButton::Mode { + "Preview-Stop", + "Click to stop playback.", + [this] { stop(); }, + MultiButton::DrawingMode::IconOnly, + Colours::orangered, + fontaudio::Stop + }; + previewPlayPauseButton.addMode(previewPlayButtonInfo); + previewPlayPauseButton.addMode(previewPauseButtonInfo); + previewStopButton.addMode(previewStopButtonInfo); + previewStopButton.setMode(previewStopButtonInfo.displayLabel); + + previewControlsComponent.addAndMakeVisible(previewPlayPauseButton); + previewControlsComponent.addAndMakeVisible(previewStopButton); + } + resetButtonState(); } @@ -350,6 +394,12 @@ void MediaDisplayComponent::resized() // Media area takes remaining space mainFlexBox.items.add(FlexItem(mediaAreaContainer).withFlex(8)); + if (isPreviewTrack()) + { + mainFlexBox.items.add( + FlexItem(previewControlsComponent).withHeight(36).withMargin({ 4, 0, 4, 0 })); + } + mainFlexBox.performLayout(totalBounds); // Remove existing items in header flex @@ -462,6 +512,18 @@ void MediaDisplayComponent::resized() // Perform layout in media area mediaAreaFlexBox.performLayout(mediaAreaContainer.getLocalBounds()); + // Performs layout in preview pane + if (isPreviewTrack()) + { + previewControlsFlexBox.items.clear(); + previewControlsFlexBox.items.add( + FlexItem(previewPlayPauseButton).withWidth(28).withHeight(28).withMargin({ 0, 4, 0, 4 })); + previewControlsFlexBox.items.add( + FlexItem(previewStopButton).withWidth(28).withHeight(28).withMargin({ 0, 4, 0, 4 })); + previewControlsFlexBox.performLayout(previewControlsComponent.getLocalBounds()); + } + + if (! isLabelRepositioningScheduled) { isLabelRepositioningScheduled = true; @@ -613,6 +675,10 @@ void MediaDisplayComponent::resetButtonState() chooseFileButton.setMode(chooseFileButtonActiveInfo.displayLabel); saveFileButton.setMode(saveFileButtonInactiveInfo.displayLabel); copyFileButton.setMode(copyFileButtonInactiveInfo.displayLabel); + if (isPreviewTrack()) + { + previewPlayPauseButton.setMode(previewPlayButtonInfo.displayLabel); + } } void MediaDisplayComponent::initializeDisplay(const URL& filePath) @@ -1199,6 +1265,11 @@ void MediaDisplayComponent::start() startTimerHz(40); playStopButton.setMode(stopButtonInfo.displayLabel); + + if (isPreviewTrack()) + { + previewPlayPauseButton.setMode(previewPauseButtonInfo.displayLabel); + } } void MediaDisplayComponent::stop() @@ -1212,9 +1283,24 @@ void MediaDisplayComponent::stop() playStopButton.setMode(playButtonActiveInfo.displayLabel); + if (isPreviewTrack()) + { + previewPlayPauseButton.setMode(previewPlayButtonInfo.displayLabel); + } + sendChangeMessage(); } +void MediaDisplayComponent::pause() +{ + stopPlaying(); + + stopTimer(); + + //currentPositionCursor.setVisible(true); + previewPlayPauseButton.setMode(previewPlayButtonInfo.displayLabel); +} + void MediaDisplayComponent::updateCursorPosition() { float mediaWidth = getMediaWidth(); diff --git a/src/media/MediaDisplayComponent.h b/src/media/MediaDisplayComponent.h index 3652c916..3733853d 100644 --- a/src/media/MediaDisplayComponent.h +++ b/src/media/MediaDisplayComponent.h @@ -142,6 +142,7 @@ class MediaDisplayComponent : public Component, void start(); void stop(); + void pause(); virtual bool isPlaying() { return transportSource.isPlaying(); } @@ -265,6 +266,8 @@ class MediaDisplayComponent : public Component, Component buttonsComponent; // Media + overhead panel (if any) Component mediaAreaContainer; + // Preview pane controls + Component previewControlsComponent; // Header sub-components Label trackNameLabel; @@ -282,6 +285,13 @@ class MediaDisplayComponent : public Component, MultiButton::Mode copyFileButtonActiveInfo; MultiButton::Mode copyFileButtonInactiveInfo; + // Preview mode controls + MultiButton previewPlayPauseButton; + MultiButton::Mode previewPlayButtonInfo; + MultiButton::Mode previewPauseButtonInfo; + MultiButton previewStopButton; + MultiButton::Mode previewStopButtonInfo; + // Panel displaying overhead labels ColorablePanel overheadPanel { overheadPanelColor }; @@ -293,6 +303,8 @@ class MediaDisplayComponent : public Component, FlexBox buttonsFlexBox; // Flex for media / overhead panel (if any) FlexBox mediaAreaFlexBox; + // Flex for preview pane buttons + FlexBox previewControlsFlexBox; Uuid trackID; String trackName; From 6fda9911acb275490ca80b9b92f8dce516d948f5 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 12:36:12 -0400 Subject: [PATCH 10/14] Playback now stops upon minimize and close. --- src/media/MediaDisplayComponent.cpp | 2 +- src/widgets/PreviewPaneWidget.h | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/media/MediaDisplayComponent.cpp b/src/media/MediaDisplayComponent.cpp index bdc54c35..7400c77f 100644 --- a/src/media/MediaDisplayComponent.cpp +++ b/src/media/MediaDisplayComponent.cpp @@ -397,7 +397,7 @@ void MediaDisplayComponent::resized() if (isPreviewTrack()) { mainFlexBox.items.add( - FlexItem(previewControlsComponent).withHeight(36).withMargin({ 4, 0, 4, 0 })); + FlexItem(previewControlsComponent).withHeight(36).withMargin({ 2, 0, 2, 0 })); } mainFlexBox.performLayout(totalBounds); diff --git a/src/widgets/PreviewPaneWidget.h b/src/widgets/PreviewPaneWidget.h index e1bc1353..9218110f 100644 --- a/src/widgets/PreviewPaneWidget.h +++ b/src/widgets/PreviewPaneWidget.h @@ -22,6 +22,10 @@ class PreviewPaneWidget : public Component, public ChangeListener minimizeButton.setTooltip("Minimize preview pane"); minimizeButton.onClick = [this] { + if (currentDisplay != nullptr && currentDisplay->isPlaying()) + { + currentDisplay->stop(); + } if (isMinimized) { isMinimized = false; @@ -49,6 +53,11 @@ class PreviewPaneWidget : public Component, public ChangeListener closeButton.setTooltip("Close preview pane"); closeButton.onClick = [this] { + if (currentDisplay != nullptr && currentDisplay->isPlaying()) + { + currentDisplay->stop(); + } + isMinimized = false; minimizeButton.setButtonText("-"); From 5bd6c4ced9bfb2b6d938e54a2fe584c3764a2780 Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 13:12:22 -0400 Subject: [PATCH 11/14] Implemented vertial button placement and fixed preview pane height to be a bit shorter. --- src/media/MediaDisplayComponent.cpp | 40 +++++++++++++++++++++-------- src/media/MediaDisplayComponent.h | 4 +++ src/widgets/MediaClipboardWidget.h | 2 +- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/media/MediaDisplayComponent.cpp b/src/media/MediaDisplayComponent.cpp index 7400c77f..06b34a58 100644 --- a/src/media/MediaDisplayComponent.cpp +++ b/src/media/MediaDisplayComponent.cpp @@ -170,14 +170,22 @@ MediaDisplayComponent::MediaDisplayComponent(String name, bool req, bool fromDAW mediaAreaContainer.addAndMakeVisible(contentComponent); mediaAreaContainer.addAndMakeVisible(*timeAxisStrip); mediaAreaContainer.addAndMakeVisible(horizontalScrollBar); - addAndMakeVisible(mediaAreaContainer); - if (isPreviewTrack()) { - previewControlsFlexBox.flexDirection = FlexBox::Direction::row; + mediaRowComponent.addAndMakeVisible(mediaAreaContainer); + mediaRowComponent.addAndMakeVisible(previewControlsComponent); + addAndMakeVisible(mediaRowComponent); + + mediaRowFlexBox.flexDirection = FlexBox::Direction::row; + mediaRowFlexBox.alignItems = FlexBox::AlignItems::stretch; + + previewControlsFlexBox.flexDirection = FlexBox::Direction::column; previewControlsFlexBox.alignItems = FlexBox::AlignItems::center; previewControlsFlexBox.justifyContent = FlexBox::JustifyContent::center; - addAndMakeVisible(previewControlsComponent); + } + else + { + addAndMakeVisible(mediaAreaContainer); } mediaAreaFlexBox.flexDirection = FlexBox::Direction::column; @@ -391,17 +399,27 @@ void MediaDisplayComponent::resized() mainFlexBox.items.add(FlexItem(headerComponent).withFlex(1).withMaxWidth(40).withMargin(4)); } - // Media area takes remaining space - mainFlexBox.items.add(FlexItem(mediaAreaContainer).withFlex(8)); - + // Media area takes remaining space unless preview pane is open if (isPreviewTrack()) { - mainFlexBox.items.add( - FlexItem(previewControlsComponent).withHeight(36).withMargin({ 2, 0, 2, 0 })); + mainFlexBox.items.add(FlexItem(mediaRowComponent).withFlex(8)); + } + else + { + mainFlexBox.items.add(FlexItem(mediaAreaContainer).withFlex(8)); } mainFlexBox.performLayout(totalBounds); + if (isPreviewTrack()) + { + mediaRowFlexBox.items.clear(); + mediaRowFlexBox.items.add(FlexItem(mediaAreaContainer).withFlex(1)); + mediaRowFlexBox.items.add( + FlexItem(previewControlsComponent).withWidth(25).withMargin({ 0, 2, 0, 2 })); + mediaRowFlexBox.performLayout(mediaRowComponent.getLocalBounds()); + } + // Remove existing items in header flex headerFlexBox.items.clear(); @@ -517,9 +535,9 @@ void MediaDisplayComponent::resized() { previewControlsFlexBox.items.clear(); previewControlsFlexBox.items.add( - FlexItem(previewPlayPauseButton).withWidth(28).withHeight(28).withMargin({ 0, 4, 0, 4 })); + FlexItem(previewPlayPauseButton).withWidth(25).withHeight(25).withMargin({ 2, 0, 2, 0 })); previewControlsFlexBox.items.add( - FlexItem(previewStopButton).withWidth(28).withHeight(28).withMargin({ 0, 4, 0, 4 })); + FlexItem(previewStopButton).withWidth(25).withHeight(25).withMargin({ 2, 0, 2, 0 })); previewControlsFlexBox.performLayout(previewControlsComponent.getLocalBounds()); } diff --git a/src/media/MediaDisplayComponent.h b/src/media/MediaDisplayComponent.h index 3733853d..37879718 100644 --- a/src/media/MediaDisplayComponent.h +++ b/src/media/MediaDisplayComponent.h @@ -268,6 +268,8 @@ class MediaDisplayComponent : public Component, Component mediaAreaContainer; // Preview pane controls Component previewControlsComponent; + // Preview pane waveform and control row + Component mediaRowComponent; // Header sub-components Label trackNameLabel; @@ -305,6 +307,8 @@ class MediaDisplayComponent : public Component, FlexBox mediaAreaFlexBox; // Flex for preview pane buttons FlexBox previewControlsFlexBox; + // Flex for preview pane waveform + control row + FlexBox mediaRowFlexBox; Uuid trackID; String trackName; diff --git a/src/widgets/MediaClipboardWidget.h b/src/widgets/MediaClipboardWidget.h index b8653825..7766f8d5 100644 --- a/src/widgets/MediaClipboardWidget.h +++ b/src/widgets/MediaClipboardWidget.h @@ -794,7 +794,7 @@ class MediaClipboardWidget : public Component, public ChangeListener TrackAreaWidget trackAreaWidget { DisplayMode::Thumbnail, 75 }; PreviewPaneWidget previewPaneWidget; - int previewPaneHeight = 200; + int previewPaneHeight = 150; bool showPreviewPane = true; std::unique_ptr chooseFileBrowser; From 9f42626e6841040e8dcaa9dfcd562fa538490e4e Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 13:21:31 -0400 Subject: [PATCH 12/14] Fixed playback cursor positioning. --- src/media/MediaDisplayComponent.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/media/MediaDisplayComponent.cpp b/src/media/MediaDisplayComponent.cpp index 06b34a58..8ea41d41 100644 --- a/src/media/MediaDisplayComponent.cpp +++ b/src/media/MediaDisplayComponent.cpp @@ -1346,14 +1346,11 @@ void MediaDisplayComponent::updateCursorPosition() cursorPositionX = jmin(maxCursorXPos, jmax(minCursorXPos, cursorPositionX)); - Rectangle mediaAreaBounds = mediaAreaContainer.getBounds(); Rectangle mediaBounds = contentComponent.getBounds(); - - // Include offset(s) for track header - cursorPositionX += static_cast(mediaAreaBounds.getX()); - cursorPositionY += static_cast(mediaAreaBounds.getY()); - // Include offset for label overlay header - cursorPositionY += static_cast(mediaBounds.getY()); + // Get position of contentComponent in the correct coordinate space, accounting for nested parents + Point mediaOrigin = getLocalPoint(&contentComponent, Point(0, 0)); + cursorPositionX += static_cast(mediaOrigin.getX()); + cursorPositionY += static_cast(mediaOrigin.getY()); // Offset by half of width cursorPositionX -= cursorWidth / 2.0f; From 68a77d89617652b77e0185aaf909dd4a74ac0d2a Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 13:33:52 -0400 Subject: [PATCH 13/14] Removed play button from media clipboard toolbar. --- src/MainComponent.cpp | 10 ---- src/widgets/MediaClipboardWidget.h | 82 ++---------------------------- 2 files changed, 3 insertions(+), 89 deletions(-) diff --git a/src/MainComponent.cpp b/src/MainComponent.cpp index 46bf43b9..96e8c0ff 100644 --- a/src/MainComponent.cpp +++ b/src/MainComponent.cpp @@ -536,16 +536,6 @@ Rectangle MainComponent::getClipboardRemoveButtonBounds() return {}; } -Rectangle MainComponent::getClipboardPlayButtonBounds() -{ - if (showMediaClipboard && mediaClipboardWidget.isVisible()) - { - auto bounds = mediaClipboardWidget.getPlayButtonBounds(); - return getLocalArea(&mediaClipboardWidget, bounds); - } - return {}; -} - Rectangle MainComponent::getClipboardSendToDAWButtonBounds() { if (showMediaClipboard && mediaClipboardWidget.isVisible()) diff --git a/src/widgets/MediaClipboardWidget.h b/src/widgets/MediaClipboardWidget.h index 7766f8d5..d011d4a9 100644 --- a/src/widgets/MediaClipboardWidget.h +++ b/src/widgets/MediaClipboardWidget.h @@ -89,7 +89,7 @@ class MediaClipboardWidget : public Component, public ChangeListener // Add control elements to control flex controlsFlexBox.items.add(FlexItem(selectionTextBox).withFlex(1)); controlsFlexBox.items.add( - FlexItem(buttonsComponent).withWidth(5 * (buttonWidth + marginSize))); + FlexItem(buttonsComponent).withWidth(4 * (buttonWidth + marginSize))); controlsFlexBox.performLayout(controlsComponent.getLocalBounds()); @@ -112,10 +112,6 @@ class MediaClipboardWidget : public Component, public ChangeListener .withWidth(buttonWidth) .withHeight(buttonWidth) .withMargin({ 0, 0, 0, marginSize })); - buttonsFlexBox.items.add(FlexItem(playStopButton) - .withWidth(buttonWidth) - .withHeight(buttonWidth) - .withMargin({ 0, 0, 0, marginSize })); buttonsFlexBox.items.add(FlexItem(saveFileButton) .withWidth(buttonWidth) .withHeight(buttonWidth) @@ -183,11 +179,6 @@ class MediaClipboardWidget : public Component, public ChangeListener return getLocalArea(&buttonsComponent, removeSelectionButton.getBounds()).expanded(2); } - Rectangle getPlayButtonBounds() const - { - return getLocalArea(&buttonsComponent, playStopButton.getBounds()).expanded(2); - } - Rectangle getSendToDAWButtonBounds() const { return getLocalArea(&buttonsComponent, sendToDAWButton.getBounds()).expanded(2); @@ -457,30 +448,6 @@ class MediaClipboardWidget : public Component, public ChangeListener removeSelectionButton.addMode(removeSelectionButtonInactiveInfo); buttonsComponent.addAndMakeVisible(removeSelectionButton); - // Mode when a playable track is selected (play enabled) - playButtonActiveInfo = MultiButton::Mode { "Play-Active", - "Click to start playback.", - [this] { playCallback(); }, - MultiButton::DrawingMode::IconOnly, - Colours::limegreen, - fontaudio::Play }; - // Mode when there is no track selected (play disabled) - playButtonInactiveInfo = - MultiButton::Mode { "Play-Inactive", "Nothing to play.", - [this] {}, MultiButton::DrawingMode::IconOnly, - Colours::lightgrey, fontaudio::Play }; - // Mode during playback (stop enabled) - stopButtonInfo = MultiButton::Mode { "Stop", - "Click to stop playback.", - [this] { stopCallback(); }, - MultiButton::DrawingMode::IconOnly, - Colours::orangered, - fontaudio::Stop }; - playStopButton.addMode(playButtonActiveInfo); - playStopButton.addMode(playButtonInactiveInfo); - playStopButton.addMode(stopButtonInfo); - buttonsComponent.addAndMakeVisible(playStopButton); - // Mode when a track is selected (save file enabled) saveFileButtonActiveInfo = MultiButton::Mode { "Save-Active", @@ -541,18 +508,9 @@ class MediaClipboardWidget : public Component, public ChangeListener { MediaDisplayComponent* mediaDisplay = trackAreaWidget.getCurrentlySelectedDisplay(); - if (currentlySelectedDisplay) + if (currentlySelectedDisplay && currentlySelectedDisplay->isPlaying()) { - if (currentlySelectedDisplay->isPlaying()) - { - // Cancel playback and reset play/stop button state for select and stop events - stopCallback(currentlySelectedDisplay); - } - else - { - // Reset play/stop button state for select and stop events (avoid infinite messages) - playStopButton.setMode(playButtonActiveInfo.displayLabel); - } + currentlySelectedDisplay->stop(); } if (mediaDisplay) @@ -605,33 +563,6 @@ class MediaClipboardWidget : public Component, public ChangeListener } } - void playCallback() - { - MediaDisplayComponent* mediaDisplay = trackAreaWidget.getCurrentlySelectedDisplay(); - - if (mediaDisplay) - { - mediaDisplay->start(); - - playStopButton.setMode(stopButtonInfo.displayLabel); - } - } - - void stopCallback(MediaDisplayComponent* mediaDisplay = nullptr) - { - if (! mediaDisplay) - { - mediaDisplay = trackAreaWidget.getCurrentlySelectedDisplay(); - } - - if (mediaDisplay) - { - mediaDisplay->stop(); - - playStopButton.setMode(playButtonActiveInfo.displayLabel); - } - } - // TODO - move to utils/? bool convertAudioFile(const File& source, const File& target) { @@ -715,7 +646,6 @@ class MediaClipboardWidget : public Component, public ChangeListener //renameSelectionButton.setMode(renameSelectionButtonInactiveInfo.label); addFileButton.setMode(addFileButtonInfo.displayLabel); removeSelectionButton.setMode(removeSelectionButtonInactiveInfo.displayLabel); - playStopButton.setMode(playButtonInactiveInfo.displayLabel); saveFileButton.setMode(saveFileButtonInactiveInfo.displayLabel); sendToDAWButton.setMode(sendToDAWButtonInactiveInfo1.displayLabel); @@ -729,7 +659,6 @@ class MediaClipboardWidget : public Component, public ChangeListener //renameSelectionButton.setMode(renameSelectionButtonActiveInfo.label); removeSelectionButton.setMode(removeSelectionButtonActiveInfo.displayLabel); - playStopButton.setMode(playButtonActiveInfo.displayLabel); saveFileButton.setMode(saveFileButtonActiveInfo.displayLabel); int nOtherDAWLinkedTracks = @@ -773,11 +702,6 @@ class MediaClipboardWidget : public Component, public ChangeListener MultiButton::Mode removeSelectionButtonActiveInfo; MultiButton::Mode removeSelectionButtonInactiveInfo; - MultiButton playStopButton; - MultiButton::Mode playButtonActiveInfo; - MultiButton::Mode playButtonInactiveInfo; - MultiButton::Mode stopButtonInfo; - MultiButton saveFileButton; MultiButton::Mode saveFileButtonActiveInfo; MultiButton::Mode saveFileButtonInactiveInfo; From 301ad399e99a73417473cc2f0951155bb6dd9eea Mon Sep 17 00:00:00 2001 From: Natalie Smith Date: Fri, 1 May 2026 14:12:35 -0400 Subject: [PATCH 14/14] Fixed a minor logic error in mouseUp(). --- src/widgets/PreviewPaneWidget.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/widgets/PreviewPaneWidget.h b/src/widgets/PreviewPaneWidget.h index 9218110f..acdc3c80 100644 --- a/src/widgets/PreviewPaneWidget.h +++ b/src/widgets/PreviewPaneWidget.h @@ -251,11 +251,6 @@ class PreviewPaneWidget : public Component, public ChangeListener { dragStartY = -1; dragStartHeight = -1; - - if (dragStartY >= 0) - { - expandedHeight = getHeight(); - } } private: