Processor editor and processor in seperate files. slider paramters now can be added by simply adding them in the processor, everything else is dynamic
This commit is contained in:
michalcourson
2025-11-04 20:04:07 -05:00
parent 3468c1f389
commit f5245eb557
11 changed files with 2771 additions and 2232 deletions

185
Source/PluginEditor.cpp Normal file
View File

@ -0,0 +1,185 @@
/*
==============================================================================
PluginEditor.cpp
Created: 4 Nov 2025 6:20:46pm
Author: mickl
==============================================================================
*/
#include "PluginEditor.h"
#include "DemoUtilities.h"
static ZipFile* getZipFile()
{
static auto stream = createAssetInputStream("webviewplugin-gui_1.0.0.zip", AssertAssetExists::no);
if (stream == nullptr)
return nullptr;
static ZipFile f{ stream.get(), false };
return &f;
}
static const char* getMimeForExtension(const String& extension)
{
static const std::unordered_map<String, const char*> mimeMap =
{
{ { "htm" }, "text/html" },
{ { "html" }, "text/html" },
{ { "txt" }, "text/plain" },
{ { "jpg" }, "image/jpeg" },
{ { "jpeg" }, "image/jpeg" },
{ { "svg" }, "image/svg+xml" },
{ { "ico" }, "image/vnd.microsoft.icon" },
{ { "json" }, "application/json" },
{ { "png" }, "image/png" },
{ { "css" }, "text/css" },
{ { "map" }, "application/json" },
{ { "js" }, "text/javascript" },
{ { "woff2" }, "font/woff2" }
};
if (const auto it = mimeMap.find(extension.toLowerCase()); it != mimeMap.end())
return it->second;
jassertfalse;
return "";
}
static String getExtension(String filename)
{
return filename.fromLastOccurrenceOf(".", false, false);
}
static auto streamToVector(InputStream& stream)
{
std::vector<std::byte> result((size_t)stream.getTotalLength());
stream.setPosition(0);
[[maybe_unused]] const auto bytesRead = stream.read(result.data(), result.size());
jassert(bytesRead == (ssize_t)result.size());
return result;
}
std::optional<WebBrowserComponent::Resource> WebViewPluginAudioProcessorEditor::getResource(const String& url)
{
const auto urlToRetrive = url == "/" ? String{ "index.html" }
: url.fromFirstOccurrenceOf("/", false, false);
if (auto* archive = getZipFile())
{
if (auto* entry = archive->getEntry(urlToRetrive))
{
auto stream = rawToUniquePtr(archive->createStreamForEntry(*entry));
auto v = streamToVector(*stream);
auto mime = getMimeForExtension(getExtension(entry->filename).toLowerCase());
return WebBrowserComponent::Resource{ std::move(v),
std::move(mime) };
}
}
if (urlToRetrive == "index.html")
{
auto fallbackIndexHtml = createAssetInputStream("webviewplugin-gui-fallback.html");
return WebBrowserComponent::Resource{ streamToVector(*fallbackIndexHtml),
String { "text/html" } };
}
if (urlToRetrive == "data.txt")
{
WebBrowserComponent::Resource resource;
static constexpr char testData[] = "testdata";
MemoryInputStream stream{ testData, numElementsInArray(testData) - 1, false };
return WebBrowserComponent::Resource{ streamToVector(stream), String { "text/html" } };
}
if (urlToRetrive == "midNoteData.json")
{
juce::Array<var> notes;
int voice_num = 0;
for (auto& voice : processorRef.shifter.voices) {
if (voice.onoff_) {
auto obj = new DynamicObject();
obj->setProperty("voice", voice_num);
obj->setProperty("midi", voice.GetMidiNote());
notes.add(var(obj));
}
voice_num++;
}
DynamicObject::Ptr d(new DynamicObject());
d->setProperty("notes", notes);
const auto s = JSON::toString(d.get());
MemoryInputStream stream{ s.getCharPointer(), s.getNumBytesAsUTF8(), false };
return WebBrowserComponent::Resource{ streamToVector(stream), String { "application/json" } };
}
return std::nullopt;
}
#if JUCE_ANDROID
// The localhost is available on this address to the emulator
const String localDevServerAddress = "http://10.0.2.2:3000/";
#else
const String localDevServerAddress = "http://localhost:3000/";
#endif
bool SinglePageBrowser::pageAboutToLoad(const String& newURL)
{
return newURL == localDevServerAddress || newURL == getResourceProviderRoot();
}
WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPluginAudioProcessor& p)
: AudioProcessorEditor(&p), processorRef(p)
{
auto options = WebBrowserComponent::Options{}
.withBackend(WebBrowserComponent::Options::Backend::webview2)
.withWinWebView2Options(WebBrowserComponent::Options::WinWebView2{}
.withUserDataFolder(File::getSpecialLocation(File::SpecialLocationType::tempDirectory)))
.withNativeIntegrationEnabled()
.withOptionsFrom(controlParameterIndexReceiver)
.withResourceProvider([this](const auto& url)
{
return getResource(url);
},
URL{ localDevServerAddress }.getOrigin());
for (auto& sliderId : p.parameters.sliderIds) {
slider_relays.push_back(new WebSliderRelay{ sliderId });
slider_attatchments.push_back(new
WebSliderParameterAttachment(
*processorRef.state.getParameter(sliderId),
*slider_relays.back(),
processorRef.state.undoManager));
options = options.withOptionsFrom(*slider_relays.back());
}
webComponent = new SinglePageBrowser(options);
addAndMakeVisible(*webComponent);
webComponent->goToURL(localDevServerAddress);
//webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());
setSize(500, 500);
startTimerHz(60);
}
void WebViewPluginAudioProcessorEditor::paint(Graphics& g)
{
// (Our component is opaque, so we must completely fill the background with a solid colour)
g.fillAll(getLookAndFeel().findColour(ResizableWindow::backgroundColourId));
}
void WebViewPluginAudioProcessorEditor::resized()
{
if (webComponent == nullptr) return;
webComponent->setBounds(getLocalBounds());
}