mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-28 16:50:57 +02:00
improve UX
This commit is contained in:
@@ -16,6 +16,7 @@ class LUFSMeterEngine : public ScopeEngine
|
||||
public:
|
||||
const char* scopeId() const override { return "lufsmeter"; }
|
||||
juce::Identifier frameEventId() const override { return frameId; }
|
||||
PreferredSize preferredSize() const override { return { 360, 340, 240, 220 }; }
|
||||
|
||||
void setSampleRate(double sampleRate) override
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ class OscilloscopeEngine : public ScopeEngine
|
||||
public:
|
||||
const char* scopeId() const override { return "oscilloscope"; }
|
||||
juce::Identifier frameEventId() const override { return frameId; }
|
||||
PreferredSize preferredSize() const override { return { 760, 300, 320, 160 }; }
|
||||
|
||||
void setSampleRate(double sampleRate) override
|
||||
{
|
||||
|
||||
@@ -84,7 +84,8 @@ namespace
|
||||
.withInitialisationData("prismScope", juce::String(scopeId))
|
||||
.withEventListener("prismConfig", [&editor](juce::var v) { editor.onPrismConfig(std::move(v)); })
|
||||
.withEventListener("prismReady", [&editor](juce::var) { editor.onPrismReady(); })
|
||||
.withEventListener("prismSpectrogramConfig", [&editor](juce::var v) { editor.onScopeNativeConfig(std::move(v)); });
|
||||
.withEventListener("prismSpectrogramConfig", [&editor](juce::var v) { editor.onScopeNativeConfig(std::move(v)); })
|
||||
.withEventListener("prismSettingsPanel", [&editor](juce::var v) { editor.onSettingsPanel(std::move(v)); });
|
||||
#if ! PRISM_USE_DEV_SERVER
|
||||
options = options.withResourceProvider([](const auto& url) { return provideResource(url); });
|
||||
#endif
|
||||
@@ -109,9 +110,11 @@ PrismSpectrumEditor::PrismSpectrumEditor(PrismSpectrumProcessor& p)
|
||||
webView.goToURL(juce::WebBrowserComponent::getResourceProviderRoot());
|
||||
#endif
|
||||
|
||||
// Per-scope sizing: open at the scope's preferred default, with a per-scope min.
|
||||
const auto pref = engine->preferredSize();
|
||||
setResizable(true, true);
|
||||
setResizeLimits(360, 200, 4096, 4096);
|
||||
setSize(900, 480);
|
||||
setResizeLimits(pref.minWidth, pref.minHeight, 4096, 4096);
|
||||
setSize(pref.defaultWidth, pref.defaultHeight);
|
||||
|
||||
// Drive frames at the display's refresh rate (adapts to 60/120/144 Hz).
|
||||
vblank = juce::VBlankAttachment(this, [this] { renderFrame(); });
|
||||
@@ -122,6 +125,20 @@ void PrismSpectrumEditor::resized()
|
||||
webView.setBounds(getLocalBounds());
|
||||
}
|
||||
|
||||
void PrismSpectrumEditor::onSettingsPanel(juce::var payload)
|
||||
{
|
||||
const int height = juce::jmax(0, (int) payload.getProperty("height", 0));
|
||||
const int delta = height - settingsPanelHeight;
|
||||
if (delta == 0)
|
||||
return;
|
||||
|
||||
settingsPanelHeight = height;
|
||||
// Grow/shrink the window by exactly the panel height so the scope area is
|
||||
// unchanged. Deliberately NOT touching resize limits — doing so makes JUCE's
|
||||
// constrainer snap the window and double the applied size.
|
||||
setSize(getWidth(), getHeight() + delta);
|
||||
}
|
||||
|
||||
void PrismSpectrumEditor::onPrismConfig(juce::var payload)
|
||||
{
|
||||
const auto settings = payload.getProperty("settings", juce::var());
|
||||
|
||||
@@ -30,6 +30,11 @@ public:
|
||||
// Scope-specific native config (e.g. the spectrogram's canvas-derived rowCount).
|
||||
void onScopeNativeConfig(juce::var payload);
|
||||
|
||||
// The UI's settings panel opened/closed along the bottom. Grow/shrink the editor
|
||||
// height by exactly the panel height so the scope area is unchanged (the window
|
||||
// accommodates the panel, like the app). 0 = closed.
|
||||
void onSettingsPanel(juce::var payload);
|
||||
|
||||
// Push the processor's saved settings to the UI (used on ready + on host
|
||||
// state restore, to cover either ordering).
|
||||
void pushRestoreSettings();
|
||||
@@ -47,6 +52,9 @@ private:
|
||||
std::vector<float> drainLeft, drainRight;
|
||||
double lastSampleRate = 0.0;
|
||||
|
||||
// Height (px) the editor is currently grown by for the open settings panel.
|
||||
int settingsPanelHeight = 0;
|
||||
|
||||
// One-time attempt to lift WKWebView's private 60fps cap (macOS).
|
||||
bool frameRateUncapped = false;
|
||||
int uncapAttempts = 0;
|
||||
|
||||
@@ -62,6 +62,8 @@ private:
|
||||
|
||||
juce::CriticalSection settingsLock;
|
||||
juce::String settingsJson;
|
||||
std::atomic<int> editorWidth { 0 };
|
||||
std::atomic<int> editorHeight { 0 };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrismSpectrumProcessor)
|
||||
};
|
||||
|
||||
@@ -13,6 +13,16 @@ class ScopeEngine
|
||||
public:
|
||||
virtual ~ScopeEngine() = default;
|
||||
|
||||
/** Editor window sizing (logical px). Each scope picks a shape that fits it. */
|
||||
struct PreferredSize
|
||||
{
|
||||
int defaultWidth = 720;
|
||||
int defaultHeight = 320;
|
||||
int minWidth = 320;
|
||||
int minHeight = 160;
|
||||
};
|
||||
virtual PreferredSize preferredSize() const { return {}; }
|
||||
|
||||
/** Stable id sent to the webview (via initialisation data) to pick the UI scope. */
|
||||
virtual const char* scopeId() const = 0;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class SpectrogramEngine : public ScopeEngine
|
||||
public:
|
||||
const char* scopeId() const override { return "spectrogram"; }
|
||||
juce::Identifier frameEventId() const override { return frameId; }
|
||||
PreferredSize preferredSize() const override { return { 700, 340, 360, 200 }; }
|
||||
|
||||
void setSampleRate(double sampleRate) override
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ class SpectrumEngine : public ScopeEngine
|
||||
public:
|
||||
const char* scopeId() const override { return "spectrum"; }
|
||||
juce::Identifier frameEventId() const override { return frameId; }
|
||||
PreferredSize preferredSize() const override { return { 820, 320, 360, 180 }; }
|
||||
|
||||
void setSampleRate(double sampleRate) override
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ class VUMeterEngine : public ScopeEngine
|
||||
public:
|
||||
const char* scopeId() const override { return "vumeter"; }
|
||||
juce::Identifier frameEventId() const override { return frameId; }
|
||||
PreferredSize preferredSize() const override { return { 480, 300, 280, 180 }; }
|
||||
|
||||
void setSampleRate(double sampleRate) override
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ class VectorscopeEngine : public ScopeEngine
|
||||
public:
|
||||
const char* scopeId() const override { return "vectorscope"; }
|
||||
juce::Identifier frameEventId() const override { return frameId; }
|
||||
PreferredSize preferredSize() const override { return { 440, 440, 240, 200 }; }
|
||||
|
||||
void setSampleRate(double sampleRate) override
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
|
||||
const char* scopeId() const override { return "waveform"; }
|
||||
juce::Identifier frameEventId() const override { return frameId; }
|
||||
PreferredSize preferredSize() const override { return { 720, 260, 360, 140 }; }
|
||||
|
||||
void setSampleRate(double sr) override
|
||||
{
|
||||
|
||||
+27
-14
@@ -1,10 +1,16 @@
|
||||
import { useState, type JSX, type ReactNode } from 'react'
|
||||
import { useEffect, useState, type JSX, type ReactNode } from 'react'
|
||||
import type { ScopeKind } from '../types/scope'
|
||||
import type { ScopeSettings } from '../types/settings'
|
||||
import type { PrismResolvedTheme } from '../types/theme'
|
||||
import ScopeSettingsSection from '../renderer/components/ScopeSettingsSection'
|
||||
import GearIcon from './GearIcon'
|
||||
import { useScopeHostSync } from './useScopeHostSync'
|
||||
import { emitToHost } from './juceBridge'
|
||||
|
||||
// Height (CSS px) of the bottom settings panel. The C++ editor grows its window by
|
||||
// exactly this when settings open (and shrinks back on close) so the scope area is
|
||||
// unchanged — like the desktop app. Must match `.spectrum-app__panel` in styles.css.
|
||||
const PANEL_HEIGHT = 280
|
||||
|
||||
interface ScopeAppProps<K extends ScopeKind> {
|
||||
kind: K
|
||||
@@ -13,29 +19,36 @@ interface ScopeAppProps<K extends ScopeKind> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic plugin shell for any scope: hosts the canvas + a gear-toggled settings
|
||||
* drawer (the reused ScopeSettingsSection), wired to host sync via useScopeHostSync.
|
||||
* Generic plugin shell for any scope: the scope fills the viewport, and the gear
|
||||
* toggles a settings panel that opens along the bottom (like the desktop app). The
|
||||
* window grows by the panel height to accommodate it, so the scope area never resizes.
|
||||
*/
|
||||
export default function ScopeApp<K extends ScopeKind>({ kind, renderScope }: ScopeAppProps<K>): JSX.Element {
|
||||
const { settings, resolvedTheme, handleUpdate } = useScopeHostSync(kind)
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
emitToHost('prismSettingsPanel', { height: settingsOpen ? PANEL_HEIGHT : 0 })
|
||||
}, [settingsOpen])
|
||||
|
||||
return (
|
||||
<div className="spectrum-app">
|
||||
{renderScope(settings, resolvedTheme)}
|
||||
<div className="spectrum-app__viewport">
|
||||
{renderScope(settings, resolvedTheme)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`spectrum-app__gear ${settingsOpen ? 'is-active' : ''}`.trim()}
|
||||
onClick={() => setSettingsOpen((open) => !open)}
|
||||
aria-label="Settings"
|
||||
title="Settings"
|
||||
>
|
||||
<GearIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`spectrum-app__gear ${settingsOpen ? 'is-active' : ''}`.trim()}
|
||||
onClick={() => setSettingsOpen((open) => !open)}
|
||||
aria-label="Settings"
|
||||
title="Settings"
|
||||
>
|
||||
<GearIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{settingsOpen && (
|
||||
<div className="spectrum-app__settings">
|
||||
<div className="spectrum-app__panel">
|
||||
<ScopeSettingsSection
|
||||
kind={kind}
|
||||
settings={settings}
|
||||
|
||||
+17
-13
@@ -22,11 +22,19 @@ body {
|
||||
}
|
||||
|
||||
.spectrum-app {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* The scope fills everything above the (optional) bottom settings panel. */
|
||||
.spectrum-app__viewport {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.spectrum-scope {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -76,21 +84,17 @@ body {
|
||||
border-color: var(--control-border-active, rgba(56, 189, 248, 0.4));
|
||||
}
|
||||
|
||||
.spectrum-app__settings {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
right: 8px;
|
||||
z-index: 5;
|
||||
width: 320px;
|
||||
max-width: calc(100% - 16px);
|
||||
max-height: calc(100% - 56px);
|
||||
/* Settings panel along the bottom (like the app). The editor window grows by exactly
|
||||
this height when it opens, so the scope viewport above keeps its size. The fixed
|
||||
basis MUST match PANEL_HEIGHT in ScopeApp.tsx (the grow delta), or the scope would
|
||||
shift. Content taller than this scrolls. */
|
||||
.spectrum-app__panel {
|
||||
flex: 0 0 280px;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
background: var(--settings-bg-top, rgba(8, 10, 14, 0.96));
|
||||
border: 1px solid var(--panel-outline, rgba(255, 255, 255, 0.12));
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--panel-shadow, 0 14px 34px rgba(0, 0, 0, 0.4));
|
||||
backdrop-filter: blur(12px);
|
||||
border-top: 1px solid var(--panel-outline, rgba(255, 255, 255, 0.12));
|
||||
}
|
||||
|
||||
.spectrum-scope__peak {
|
||||
|
||||
Reference in New Issue
Block a user