Add first pass at example files dialog

pull/319/head
James H Ball 2025-08-18 22:14:54 +01:00
rodzic ccfb8391be
commit 517534782d
9 zmienionych plików z 208 dodań i 169 usunięć

Wyświetl plik

@ -12,6 +12,12 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
addAndMakeVisible(fileButton);
fileButton.setButtonText("Choose File(s)");
// Show Examples panel
addAndMakeVisible(showExamplesButton);
showExamplesButton.onClick = [this] {
pluginEditor.settings.showExamples(true);
};
fileButton.onClick = [this] {
juce::String fileFormats;
for (auto& ext : audioProcessor.FILE_EXTENSIONS) {
@ -198,6 +204,8 @@ void MainComponent::resized() {
auto row = bounds.removeFromTop(buttonHeight);
fileButton.setBounds(row.removeFromLeft(buttonWidth));
row.removeFromLeft(rowPadding);
showExamplesButton.setBounds(row.removeFromLeft(buttonWidth));
row.removeFromLeft(rowPadding);
inputEnabled.setBounds(row.removeFromLeft(20));
row.removeFromLeft(rowPadding);
if (audioProcessor.getCurrentFileIndex() != -1) {

Wyświetl plik

@ -38,6 +38,7 @@ private:
juce::TextEditor fileName;
juce::ComboBox fileType;
juce::TextButton createFile{"Create File"};
juce::TextButton showExamplesButton{"Examples"};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
};

Wyświetl plik

@ -11,6 +11,15 @@ SettingsComponent::SettingsComponent(OscirenderAudioProcessor& p, OscirenderAudi
addAndMakeVisible(midi);
addChildComponent(txt);
addChildComponent(frame);
addChildComponent(examples);
examples.onClosed = [this]() {
showExamples(false);
};
examples.onExampleOpened = [this](const juce::String& fileName, bool shouldOpenEditor) {
pluginEditor.addCodeEditor(audioProcessor.getCurrentFileIndex());
pluginEditor.fileUpdated(fileName, shouldOpenEditor);
};
double midiLayoutPreferredSize = std::any_cast<double>(audioProcessor.getProperty("midiLayoutPreferredSize", pluginEditor.CLOSED_PREF_SIZE));
double mainLayoutPreferredSize = std::any_cast<double>(audioProcessor.getProperty("mainLayoutPreferredSize", -0.5));
@ -63,10 +72,22 @@ void SettingsComponent::resized() {
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
}
perspective.setBounds(dummyBounds.removeFromBottom(120));
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
effects.setBounds(dummyBounds);
if (examplesVisible) {
// Hide other panels while examples are visible
perspective.setVisible(false);
effects.setVisible(false);
txt.setVisible(false);
frame.setVisible(false);
examples.setVisible(true);
examples.setBounds(dummyBounds);
} else {
examples.setVisible(false);
perspective.setVisible(true);
effects.setVisible(true);
perspective.setBounds(dummyBounds.removeFromBottom(120));
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
effects.setBounds(dummyBounds);
}
if (isVisible() && getWidth() > 0 && getHeight() > 0) {
audioProcessor.setProperty("midiLayoutPreferredSize", midiLayout.getItemCurrentRelativeSize(2));
@ -128,6 +149,11 @@ void SettingsComponent::mouseMove(const juce::MouseEvent& event) {
setMouseCursor(juce::MouseCursor::NormalCursor);
}
void SettingsComponent::showExamples(bool shouldShow) {
examplesVisible = shouldShow;
resized();
}
void SettingsComponent::mouseDown(const juce::MouseEvent& event) {
for (int i = 0; i < 1; i++) {
if (toggleComponents[i]->getBounds().removeFromTop(pluginEditor.CLOSED_PREF_SIZE).contains(event.getPosition())) {

Wyświetl plik

@ -10,6 +10,7 @@
#include "PerspectiveComponent.h"
#include "PluginProcessor.h"
#include "TxtComponent.h"
#include "components/ExampleFilesGridComponent.h"
class OscirenderAudioProcessorEditor;
class SettingsComponent : public juce::Component {
@ -21,6 +22,8 @@ public:
void update();
void mouseMove(const juce::MouseEvent& event) override;
void mouseDown(const juce::MouseEvent& event) override;
// Show or hide the example files grid panel on the right-hand side
void showExamples(bool shouldShow);
private:
OscirenderAudioProcessor& audioProcessor;
@ -32,6 +35,9 @@ private:
FrameSettingsComponent frame{audioProcessor, pluginEditor};
EffectsComponent effects{audioProcessor, pluginEditor};
MidiComponent midi{audioProcessor, pluginEditor};
ExampleFilesGridComponent examples{audioProcessor};
bool examplesVisible = false;
juce::StretchableLayoutManager midiLayout;
juce::StretchableLayoutResizerBar midiResizerBar{&midiLayout, 1, false};

Wyświetl plik

@ -1,123 +0,0 @@
#include "EffectTypeItemComponent.h"
EffectTypeItemComponent::EffectTypeItemComponent(const juce::String& name, const juce::String& icon, const juce::String& id)
: effectName(name), effectId(id)
{
juce::String iconSvg = icon;
if (icon.isEmpty()) {
// Default icon if none is provided
iconSvg = juce::String::createStringFromData(BinaryData::rotate_svg, BinaryData::rotate_svgSize);
}
iconButton = std::make_unique<SvgButton>(
"effectIcon",
iconSvg,
juce::Colours::white.withAlpha(0.7f)
);
// Make the icon non-interactive since this is just a visual element
iconButton->setInterceptsMouseClicks(false, false);
addAndMakeVisible(*iconButton);
}
EffectTypeItemComponent::~EffectTypeItemComponent() = default;
void EffectTypeItemComponent::paint(juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat().reduced(10);
// Get animation progress from inherited HoverAnimationMixin (disabled => no hover)
auto animationProgress = isEnabled() ? getAnimationProgress() : 0.0f;
// Apply upward shift based on animation progress
auto yOffset = -animationProgress * HOVER_LIFT_AMOUNT;
bounds = bounds.translated(0, yOffset);
// Draw drop shadow
if (animationProgress > 0.01f) {
juce::DropShadow shadow;
shadow.colour = juce::Colours::lime.withAlpha(animationProgress * 0.2f);
shadow.radius = 15 * animationProgress;
shadow.offset = juce::Point<int>(0, 4);
if (shadow.radius > 0) {
juce::Path shadowPath;
shadowPath.addRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
shadow.drawForPath(g, shadowPath);
}
}
// Draw background with rounded corners - interpolate between normal and hover colors
juce::Colour normalBgColour = Colours::veryDark;
juce::Colour hoverBgColour = normalBgColour.brighter(0.05f);
juce::Colour bgColour = normalBgColour.interpolatedWith(hoverBgColour, animationProgress);
g.setColour(bgColour);
g.fillRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
// Draw colored outline
juce::Colour outlineColour = juce::Colour::fromRGB(160, 160, 160);
g.setColour(outlineColour.withAlpha(0.9f));
g.drawRoundedRectangle(bounds.toFloat(), CORNER_RADIUS, 1.0f);
// Create text area - now accounting for icon space on the left
auto textArea = bounds.reduced(8, 4);
textArea.removeFromLeft(28); // Remove space for icon (24px + 4px gap)
g.setColour(juce::Colours::white);
g.setFont(juce::FontOptions(16.0f, juce::Font::plain));
g.drawText(effectName, textArea, juce::Justification::centred, true);
// If disabled, draw a dark transparent overlay over the rounded rect to simplify visuals
if (! isEnabled()) {
g.setColour(juce::Colours::black.withAlpha(0.35f));
g.fillRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
}
}
void EffectTypeItemComponent::resized()
{
auto bounds = getLocalBounds().reduced(10);
// Reserve space for the icon on the left
auto iconArea = bounds.removeFromLeft(60); // 24px for icon
iconArea = iconArea.withSizeKeepingCentre(40, 40); // Make icon 20x20px
iconButton->setBounds(iconArea);
// Get animation progress and calculate Y offset
auto animationProgress = isEnabled() ? getAnimationProgress() : 0.0f;
auto yOffset = -animationProgress * HOVER_LIFT_AMOUNT;
iconButton->setTransform(juce::AffineTransform::translation(0, yOffset));
}
void EffectTypeItemComponent::mouseDown(const juce::MouseEvent& event)
{
if (! isEnabled()) return;
// Extend base behavior to keep hover press animation
HoverAnimationMixin::mouseDown(event);
// Ensure any hover preview is cleared before permanently selecting/enabling the effect
if (onHoverEnd) onHoverEnd();
if (onEffectSelected) {
onEffectSelected(effectId);
}
}
void EffectTypeItemComponent::mouseMove(const juce::MouseEvent& event) {
setMouseCursor(isEnabled() ? juce::MouseCursor::PointingHandCursor : juce::MouseCursor::NormalCursor);
juce::Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
}
void EffectTypeItemComponent::mouseEnter(const juce::MouseEvent& event)
{
HoverAnimationMixin::mouseEnter(event);
if (isEnabled() && onHoverStart)
onHoverStart(effectId);
}
void EffectTypeItemComponent::mouseExit(const juce::MouseEvent& event)
{
HoverAnimationMixin::mouseExit(event);
if (onHoverEnd)
onHoverEnd();
}

Wyświetl plik

@ -1,38 +0,0 @@
#pragma once
#include <JuceHeader.h>
#include "../LookAndFeel.h"
#include "HoverAnimationMixin.h"
#include "SvgButton.h"
class EffectTypeItemComponent : public HoverAnimationMixin
{
public:
EffectTypeItemComponent(const juce::String& name, const juce::String& icon, const juce::String& id);
~EffectTypeItemComponent() override;
void paint(juce::Graphics& g) override;
void resized() override;
void mouseDown(const juce::MouseEvent& event) override;
void mouseMove(const juce::MouseEvent& event) override;
void mouseEnter(const juce::MouseEvent& event) override;
void mouseExit(const juce::MouseEvent& event) override;
const juce::String& getEffectId() const { return effectId; }
const juce::String& getEffectName() const { return effectName; }
std::function<void(const juce::String& effectId)> onEffectSelected;
std::function<void(const juce::String& effectId)> onHoverStart;
std::function<void()> onHoverEnd;
private:
juce::String effectName;
juce::String effectId;
// Icon for the effect
std::unique_ptr<SvgButton> iconButton;
static constexpr int CORNER_RADIUS = 8;
static constexpr float HOVER_LIFT_AMOUNT = 2.0f;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectTypeItemComponent)
};

Wyświetl plik

@ -0,0 +1,108 @@
#include "ExampleFilesGridComponent.h"
#include "GridItemComponent.h"
#include "../JuceLibraryCode/BinaryData.h"
ExampleFilesGridComponent::ExampleFilesGridComponent(OscirenderAudioProcessor& processor)
: audioProcessor(processor)
{
// Top bar
addAndMakeVisible(title);
addAndMakeVisible(closeButton);
styleHeading(title);
closeButton.onClick = [this]() { if (onClosed) onClosed(); };
// Add categories to component
auto addCat = [this](CategoryViews& cat) {
styleHeading(cat.heading);
addAndMakeVisible(cat.heading);
addAndMakeVisible(cat.grid);
};
addCat(audioCat);
addCat(textCat);
addCat(imagesCat);
addCat(luaCat);
addCat(modelsCat);
addCat(svgsCat);
populate();
}
void ExampleFilesGridComponent::styleHeading(juce::Label& l)
{
l.setInterceptsMouseClicks(false, false);
l.setJustificationType(juce::Justification::left);
l.setFont(juce::FontOptions(16.0f, juce::Font::bold));
}
void ExampleFilesGridComponent::paint(juce::Graphics& g)
{
// transparent background
}
void ExampleFilesGridComponent::resized()
{
auto bounds = getLocalBounds();
auto top = bounds.removeFromTop(30);
title.setBounds(top.removeFromLeft(200).reduced(4));
closeButton.setBounds(top.removeFromRight(80).reduced(4));
auto layCat = [&](CategoryViews& cat) {
auto header = bounds.removeFromTop(24);
cat.heading.setBounds(header.reduced(2));
auto h = cat.grid.calculateRequiredHeight(bounds.getWidth());
cat.grid.setBounds(bounds.removeFromTop(h));
bounds.removeFromTop(8); // gap
};
layCat(audioCat);
layCat(textCat);
layCat(imagesCat);
layCat(luaCat);
layCat(modelsCat);
layCat(svgsCat);
}
void ExampleFilesGridComponent::addExample(CategoryViews& cat, const juce::String& fileName, const char* data, int size)
{
// Use placeholder icon for now
auto* item = new GridItemComponent(fileName, juce::String::createStringFromData(BinaryData::random_svg, BinaryData::random_svgSize), fileName);
item->onItemSelected = [this, fileName, data, size](const juce::String&) {
juce::SpinLock::ScopedLockType parsersLock(audioProcessor.parsersLock);
audioProcessor.addFile(fileName, data, size);
// Signal to UI layer that a new example was added so it can open editors, etc.
const bool openEditor = fileName.endsWithIgnoreCase(".lua") || fileName.endsWithIgnoreCase(".txt");
if (onExampleOpened) onExampleOpened(fileName, openEditor);
};
cat.grid.addItem(item);
}
void ExampleFilesGridComponent::populate()
{
// Audio examples
addExample(audioCat, "sosci.flac", BinaryData::sosci_flac, BinaryData::sosci_flacSize);
// Text examples
addExample(textCat, "helloworld.txt", BinaryData::helloworld_txt, BinaryData::helloworld_txtSize);
addExample(textCat, "greek.txt", BinaryData::greek_txt, BinaryData::greek_txtSize);
// Image examples (will open as images via frame settings path)
addExample(imagesCat, "empty.jpg", BinaryData::empty_jpg, BinaryData::empty_jpgSize);
addExample(imagesCat, "no_reflection.jpg", BinaryData::no_reflection_jpg, BinaryData::no_reflection_jpgSize);
addExample(imagesCat, "noise.jpg", BinaryData::noise_jpg, BinaryData::noise_jpgSize);
addExample(imagesCat, "real.png", BinaryData::real_png, BinaryData::real_pngSize);
addExample(imagesCat, "real_reflection.png", BinaryData::real_reflection_png, BinaryData::real_reflection_pngSize);
addExample(imagesCat, "vector_display.png", BinaryData::vector_display_png, BinaryData::vector_display_pngSize);
addExample(imagesCat, "vector_display_reflection.png", BinaryData::vector_display_reflection_png, BinaryData::vector_display_reflection_pngSize);
// Lua examples
addExample(luaCat, "demo.lua", BinaryData::demo_lua, BinaryData::demo_luaSize);
// 3D model examples
addExample(modelsCat, "cube.obj", BinaryData::cube_obj, BinaryData::cube_objSize);
// SVG examples (just a subset for brevity, can add more)
addExample(svgsCat, "demo.svg", BinaryData::demo_svg, BinaryData::demo_svgSize);
addExample(svgsCat, "lua.svg", BinaryData::lua_svg, BinaryData::lua_svgSize);
addExample(svgsCat, "trace.svg", BinaryData::trace_svg, BinaryData::trace_svgSize);
addExample(svgsCat, "wobble.svg", BinaryData::wobble_svg, BinaryData::wobble_svgSize);
}

Wyświetl plik

@ -0,0 +1,48 @@
#pragma once
#include <JuceHeader.h>
#include "../PluginProcessor.h"
#include "GridComponent.h"
// A grid-based browser for example files grouped by category
class ExampleFilesGridComponent : public juce::Component
{
public:
ExampleFilesGridComponent(OscirenderAudioProcessor& processor);
~ExampleFilesGridComponent() override = default;
void paint(juce::Graphics& g) override;
void resized() override;
// Called when the user closes the view
std::function<void()> onClosed;
// Called after the file has been added to the processor; consumer may open editors, etc.
std::function<void(const juce::String& fileName, bool shouldOpenEditor)> onExampleOpened;
private:
OscirenderAudioProcessor& audioProcessor;
// Top bar
juce::Label title { {}, "Examples" };
juce::TextButton closeButton { "Close" };
// Categories
struct CategoryViews {
juce::Label heading;
GridComponent grid;
};
CategoryViews audioCat { juce::Label({}, "Audio"), GridComponent{} };
CategoryViews textCat { juce::Label({}, "Text"), GridComponent{} };
CategoryViews imagesCat { juce::Label({}, "Images"), GridComponent{} };
CategoryViews luaCat { juce::Label({}, "Lua"), GridComponent{} };
CategoryViews modelsCat { juce::Label({}, "3D models"), GridComponent{} };
CategoryViews svgsCat { juce::Label({}, "SVGs"), GridComponent{} };
// Helpers
void addExample(CategoryViews& cat, const juce::String& fileName, const char* data, int size);
void populate();
void styleHeading(juce::Label& l);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExampleFilesGridComponent)
};

Wyświetl plik

@ -9,6 +9,9 @@
pluginAUMainType="'aumf'">
<MAINGROUP id="j5Ge2T" name="osci-render">
<GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources">
<GROUP id="{17F5509B-A3D9-D161-3F5C-4DC2E1C335E7}" name="audio">
<FILE id="BEhvxK" name="sosci.flac" compile="0" resource="1" file="Resources/audio/sosci.flac"/>
</GROUP>
<GROUP id="{8930EC48-30FD-646B-9DC5-0861171F8B2E}" name="fonts">
<FILE id="GXGPCT" name="FiraSans-Bold.ttf" compile="0" resource="1"
file="Resources/fonts/FiraSans-Bold.ttf"/>
@ -191,12 +194,12 @@
file="Source/components/EffectTypeGridComponent.cpp"/>
<FILE id="Fb2Qci" name="EffectTypeGridComponent.h" compile="0" resource="0"
file="Source/components/EffectTypeGridComponent.h"/>
<FILE id="PPK1iQ" name="EffectTypeItemComponent.cpp" compile="1" resource="0"
file="Source/components/EffectTypeItemComponent.cpp"/>
<FILE id="kGAfsx" name="EffectTypeItemComponent.h" compile="0" resource="0"
file="Source/components/EffectTypeItemComponent.h"/>
<FILE id="aEprcE" name="ErrorCodeEditorComponent.h" compile="0" resource="0"
file="Source/components/ErrorCodeEditorComponent.h"/>
<FILE id="EB1SYX" name="ExampleFilesGridComponent.cpp" compile="1"
resource="0" file="Source/components/ExampleFilesGridComponent.cpp"/>
<FILE id="K9EITe" name="ExampleFilesGridComponent.h" compile="0" resource="0"
file="Source/components/ExampleFilesGridComponent.h"/>
<FILE id="sqD2Zy" name="GridComponent.cpp" compile="1" resource="0"
file="Source/components/GridComponent.cpp"/>
<FILE id="QRwdXD" name="GridComponent.h" compile="0" resource="0" file="Source/components/GridComponent.h"/>