Fix UI bugs with spout/syphon

visualiser-refactor
James H Ball 2025-05-03 12:35:51 +01:00
rodzic 67650b682e
commit 93d82fa4da
9 zmienionych plików z 84 dodań i 49 usunięć

Wyświetl plik

@ -101,6 +101,8 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr
}
OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() {
audioProcessor.syphonInputActive = false;
// Clear the file removal callback
audioProcessor.setFileRemovedCallback(nullptr);
@ -516,7 +518,6 @@ void OscirenderAudioProcessorEditor::openSyphonInputDialog() {
sharedTextureManager,
[this](const juce::String& server, const juce::String& app) { connectSyphonInput(server, app); },
[this]() { disconnectSyphonInput(); },
syphonFrameGrabber && syphonFrameGrabber->isActive(),
getSyphonSourceName());
}
juce::DialogWindow::LaunchOptions options;
@ -535,6 +536,8 @@ void OscirenderAudioProcessorEditor::connectSyphonInput(const juce::String& serv
if (!syphonFrameGrabber) {
syphonFrameGrabber = std::make_unique<SyphonFrameGrabber>(sharedTextureManager, server, app, audioProcessor.syphonImageParser);
audioProcessor.syphonInputActive = true;
model.resetMenuItems();
model.menuItemsChanged();
{
juce::MessageManagerLock lock;
audioProcessor.fileChangeBroadcaster.sendChangeMessage();
@ -549,6 +552,8 @@ void OscirenderAudioProcessorEditor::disconnectSyphonInput() {
}
audioProcessor.syphonInputActive = false;
syphonFrameGrabber.reset();
model.resetMenuItems();
model.menuItemsChanged();
{
juce::MessageManagerLock lock;
audioProcessor.fileChangeBroadcaster.sendChangeMessage();

Wyświetl plik

@ -21,7 +21,7 @@ juce::StringArray MainMenuBarModel::getMenuBarNames() {
juce::PopupMenu MainMenuBarModel::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) {
juce::PopupMenu menu;
if (customMenuLogic) {
customMenuLogic(menu, topLevelMenuIndex);
}
@ -41,3 +41,8 @@ void MainMenuBarModel::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
}
void MainMenuBarModel::menuBarActivated(bool isActive) {}
void MainMenuBarModel::resetMenuItems() {
topLevelMenuNames.clear();
menuItems.clear();
}

Wyświetl plik

@ -8,7 +8,8 @@ public:
void addTopLevelMenu(const juce::String& name);
void addMenuItem(int topLevelMenuIndex, const juce::String& name, std::function<void()> action);
void resetMenuItems();
std::function<void(juce::PopupMenu&, int)> customMenuLogic;
std::function<bool(int, int)> customMenuSelectedLogic;

Wyświetl plik

@ -1,11 +1,18 @@
#include "OsciMainMenuBarModel.h"
#include "../PluginEditor.h"
#include "../PluginProcessor.h"
OsciMainMenuBarModel::OsciMainMenuBarModel(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& e) : audioProcessor(p), editor(e) {
resetMenuItems();
}
void OsciMainMenuBarModel::resetMenuItems() {
MainMenuBarModel::resetMenuItems();
addTopLevelMenu("File");
addTopLevelMenu("About");
addTopLevelMenu("Recording");
addTopLevelMenu("Video");
if (editor.processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone) {
addTopLevelMenu("Audio");
}
@ -20,19 +27,22 @@ OsciMainMenuBarModel::OsciMainMenuBarModel(OscirenderAudioProcessor& p, Oscirend
addMenuItem(1, "About osci-render", [this] {
juce::DialogWindow::LaunchOptions options;
AboutComponent* about = new AboutComponent(BinaryData::logo_png, BinaryData::logo_pngSize,
juce::String(ProjectInfo::projectName) + " by " + ProjectInfo::companyName + "\n"
juce::String(ProjectInfo::projectName) + " by " + ProjectInfo::companyName +
"\n"
#if OSCI_PREMIUM
"Thank you for purchasing osci-render premium!\n"
"Thank you for purchasing osci-render premium!\n"
#else
"Free version\n"
#endif
"Version " + ProjectInfo::versionString + "\n\n"
"A huge thank you to:\n"
"DJ_Level_3, for contributing several features to osci-render\n"
"BUS ERROR Collective, for providing the source code for the Hilligoss encoder\n"
"Jean Perbet (@jeanprbt) for the osci-render macOS icon\n"
"All the community, for suggesting features and reporting issues!",
std::any_cast<int>(audioProcessor.getProperty("objectServerPort")));
"Version " +
ProjectInfo::versionString +
"\n\n"
"A huge thank you to:\n"
"DJ_Level_3, for contributing several features to osci-render\n"
"BUS ERROR Collective, for providing the source code for the Hilligoss encoder\n"
"Jean Perbet (@jeanprbt) for the osci-render macOS icon\n"
"All the community, for suggesting features and reporting issues!",
std::any_cast<int>(audioProcessor.getProperty("objectServerPort")));
options.content.setOwned(about);
options.content->setSize(500, 270);
options.dialogTitle = "About";

Wyświetl plik

@ -1,9 +1,9 @@
#pragma once
#include <JuceHeader.h>
#include "AboutComponent.h"
#include "MainMenuBarModel.h"
class OscirenderAudioProcessorEditor;
class OscirenderAudioProcessor;
class OsciMainMenuBarModel : public MainMenuBarModel {
@ -11,6 +11,7 @@ public:
OsciMainMenuBarModel(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor);
void openSyphonInputDialog();
void disconnectSyphonInput();
void resetMenuItems();
private:
OscirenderAudioProcessor& audioProcessor;

Wyświetl plik

@ -1,34 +1,35 @@
#include "SosciMainMenuBarModel.h"
#include "../SosciPluginEditor.h"
#include "../SosciPluginProcessor.h"
SosciMainMenuBarModel::SosciMainMenuBarModel(SosciPluginEditor& e, SosciAudioProcessor& p) : editor(e), processor(p) {
addTopLevelMenu("File");
addTopLevelMenu("About");
addTopLevelMenu("Recording");
addTopLevelMenu("Video");
addTopLevelMenu("Audio");
std::vector<std::tuple<juce::String, const void *, int>> examples = {
std::vector<std::tuple<juce::String, const void*, int>> examples = {
{"default.sosci", BinaryData::default_sosci, BinaryData::default_sosciSize},
{"clean.sosci", BinaryData::clean_sosci, BinaryData::clean_sosciSize},
{"vector_display.sosci", BinaryData::vector_display_sosci, BinaryData::vector_display_sosciSize},
{"real_oscilloscope.sosci", BinaryData::real_oscilloscope_sosci, BinaryData::real_oscilloscope_sosciSize},
{"rainbow.sosci", BinaryData::rainbow_sosci, BinaryData::rainbow_sosciSize},
};
// This is a hack - ideally I would improve the MainMenuBarModel class to allow for submenus
customMenuLogic = [this, examples](juce::PopupMenu& menu, int topLevelMenuIndex) {
if (topLevelMenuIndex == 0) {
juce::PopupMenu submenu;
for (int i = 0; i < examples.size(); i++) {
submenu.addItem(SUBMENU_ID + i, std::get<0>(examples[i]));
}
menu.addSubMenu("Examples", submenu);
}
};
customMenuSelectedLogic = [this, examples](int menuItemID, int topLevelMenuIndex) {
if (topLevelMenuIndex == 0) {
if (menuItemID >= SUBMENU_ID) {
@ -61,19 +62,21 @@ SosciMainMenuBarModel::SosciMainMenuBarModel(SosciPluginEditor& e, SosciAudioPro
addMenuItem(1, "About sosci", [&]() {
juce::DialogWindow::LaunchOptions options;
AboutComponent* about = new AboutComponent(BinaryData::sosci_logo_png, BinaryData::sosci_logo_pngSize,
juce::String(ProjectInfo::projectName) + " by " + ProjectInfo::companyName + "\n"
juce::String(ProjectInfo::projectName) + " by " + ProjectInfo::companyName +
"\n"
#if OSCI_PREMIUM
"Thank you for purchasing sosci!\n"
"Thank you for purchasing sosci!\n"
#else
"Free version\n"
#endif
"Version " + ProjectInfo::versionString + "\n\n"
"A huge thank you to:\n"
"Neil Thapen, for allowing me to adapt the brilliant dood.al/oscilloscope\n"
"Kevin Kripper, for guiding much of the features and development of sosci\n"
"DJ_Level_3, for testing throughout and helping add features\n"
"All the community, for suggesting features and reporting issues!"
);
"Version " +
ProjectInfo::versionString +
"\n\n"
"A huge thank you to:\n"
"Neil Thapen, for allowing me to adapt the brilliant dood.al/oscilloscope\n"
"Kevin Kripper, for guiding much of the features and development of sosci\n"
"DJ_Level_3, for testing throughout and helping add features\n"
"All the community, for suggesting features and reporting issues!");
options.content.setOwned(about);
options.content->setSize(500, 270);
options.dialogTitle = "About";
@ -89,7 +92,7 @@ SosciMainMenuBarModel::SosciMainMenuBarModel(SosciPluginEditor& e, SosciAudioPro
juce::DialogWindow* dw = options.launchAsync();
});
addMenuItem(2, "Settings...", [this] {
editor.openRecordingSettings();
});

Wyświetl plik

@ -1,12 +1,13 @@
#pragma once
#include <JuceHeader.h>
#include "../modules/juce_sharedtexture/SharedTexture.h"
class SyphonInputSelectorComponent : public juce::Component, private juce::Button::Listener {
public:
SyphonInputSelectorComponent(SharedTextureManager& manager, std::function<void(const juce::String&, const juce::String&)> onConnect, std::function<void()> onDisconnect, bool isConnected, juce::String currentSource)
: sharedTextureManager(manager), onConnectCallback(onConnect), onDisconnectCallback(onDisconnect), connected(isConnected), currentSourceName(currentSource) {
SyphonInputSelectorComponent(SharedTextureManager& manager, std::function<void(const juce::String&, const juce::String&)> onConnect, std::function<void()> onDisconnect, juce::String currentSource = "")
: sharedTextureManager(manager), onConnectCallback(onConnect), onDisconnectCallback(onDisconnect), currentSourceName(currentSource) {
addAndMakeVisible(sourceLabel);
sourceLabel.setText("Syphon/Spout Source:", juce::dontSendNotification);
@ -16,12 +17,16 @@ public:
};
addAndMakeVisible(connectButton);
connectButton.setButtonText(connected ? "Disconnect" : "Connect");
connectButton.setButtonText("Connect");
connectButton.addListener(this);
refreshSources();
// Auto-select first item if no current source specified
if (!currentSourceName.isEmpty()) {
sourceDropdown.setText(currentSourceName, juce::dontSendNotification);
} else if (sourceDropdown.getNumItems() > 0) {
sourceDropdown.setSelectedItemIndex(0, juce::sendNotificationSync);
}
}
@ -36,21 +41,23 @@ public:
auto area = getLocalBounds().reduced(10);
sourceLabel.setBounds(area.removeFromTop(24));
sourceDropdown.setBounds(area.removeFromTop(28));
connectButton.setBounds(area.removeFromTop(28).reduced(0, 8));
// Make the button not take up the full width
auto buttonArea = area.removeFromTop(40).reduced(0, 8);
connectButton.setBounds(buttonArea.withSizeKeepingCentre(120, buttonArea.getHeight()));
}
void buttonClicked(juce::Button* b) override {
if (connected) {
onDisconnectCallback();
} else {
auto selected = sourceDropdown.getText();
if (selected.isNotEmpty()) {
// Syphon: "ServerName - AppName"
auto parts = juce::StringArray::fromTokens(selected, "-", "");
juce::String server = parts[0].trim();
juce::String app = parts.size() > 1 ? parts[1].trim() : juce::String();
onConnectCallback(server, app);
}
auto selected = sourceDropdown.getText();
if (selected.isNotEmpty()) {
// Syphon: "ServerName - AppName"
auto parts = juce::StringArray::fromTokens(selected, "-", "");
juce::String server = parts[0].trim();
juce::String app = parts.size() > 1 ? parts[1].trim() : juce::String();
onConnectCallback(server, app);
if (auto* window = findParentComponentOfClass<juce::DialogWindow>())
window->exitModalState(0);
}
}
@ -58,7 +65,6 @@ private:
SharedTextureManager& sharedTextureManager;
std::function<void(const juce::String&, const juce::String&)> onConnectCallback;
std::function<void()> onDisconnectCallback;
bool connected;
juce::String currentSourceName;
juce::String selectedSource;

Wyświetl plik

@ -70,7 +70,11 @@ public:
juce::String getSourceName() const {
if (receiver) {
return receiver->sharingName + " (" + receiver->sharingAppName + ")";
auto name = receiver->sharingName;
if (receiver->sharingAppName.isNotEmpty()) {
name += " (" + receiver->sharingAppName + ")";
}
return name;
}
return "";
}

Wyświetl plik

@ -4,7 +4,7 @@
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" pluginCharacteristicsValue="pluginWantsMidiIn"
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
cppLanguageStandard="20" projectLineFeed="&#10;" headerPath="./include"
version="2.5.0.1" companyName="James H Ball" companyWebsite="https://osci-render.com"
version="2.5.0.2" companyName="James H Ball" companyWebsite="https://osci-render.com"
companyEmail="james@ball.sh" defines="NOMINMAX=1&#10;INTERNET_FLAG_NO_AUTO_REDIRECT=0&#10;OSCI_PREMIUM=1&#10;JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1&#10;JUCE_MODAL_LOOPS_PERMITTED=1"
pluginAUMainType="'aumf'">
<MAINGROUP id="j5Ge2T" name="osci-render">