import "./check_native_interop.js"; class PromiseHandler { constructor() { this.lastPromiseId = 0; this.promises = new Map(); window.__JUCE__.backend.addEventListener( "__juce__complete", ({ promiseId, result }) => { if (this.promises.has(promiseId)) { this.promises.get(promiseId).resolve(result); this.promises.delete(promiseId); } } ); } createPromise() { const promiseId = this.lastPromiseId++; const result = new Promise((resolve, reject) => { this.promises.set(promiseId, { resolve: resolve, reject: reject }); }); return [promiseId, result]; } } const promiseHandler = new PromiseHandler(); /** * Returns a function object that calls a function registered on the JUCE backend and forwards all * parameters to it. * * The provided name should be the same as the name argument passed to * WebBrowserComponent::Options.withNativeFunction() on the backend. * * @param {String} name */ function getNativeFunction(name) { if (!window.__JUCE__.initialisationData.__juce__functions.includes(name)) console.warn( `Creating native function binding for '${name}', which is unknown to the backend` ); const f = function () { const [promiseId, result] = promiseHandler.createPromise(); window.__JUCE__.backend.emitEvent("__juce__invoke", { name: name, params: Array.prototype.slice.call(arguments), resultId: promiseId, }); return result; }; return f; } //============================================================================== class ListenerList { constructor() { this.listeners = new Map(); this.listenerId = 0; } addListener(fn) { const newListenerId = this.listenerId++; this.listeners.set(newListenerId, fn); return newListenerId; } removeListener(id) { if (this.listeners.has(id)) { this.listeners.delete(id); } } callListeners(payload) { for (const [, value] of this.listeners) { value(payload); } } } const BasicControl_valueChangedEventId = "valueChanged"; const BasicControl_propertiesChangedId = "propertiesChanged"; class SliderState { constructor(name) { if (!window.__JUCE__.initialisationData.__juce__sliders.includes(name)) console.warn( "Creating SliderState for '" + name + "', which is unknown to the backend" ); this.name = name; this.identifier = "__juce__slider" + this.name; this.scaledValue = 0; this.properties = { start: 0, end: 1, skew: 1, name: "", label: "", numSteps: 100, interval: 0, }; this.valueChangedEvent = new ListenerList(); this.propertiesChangedEvent = new ListenerList(); window.__JUCE__.backend.addEventListener(this.identifier, (event) => this.handleEvent(event) ); window.__JUCE__.backend.emitEvent(this.identifier, { eventType: "requestInitialUpdate", }); } setNormalisedValue(newValue) { this.scaledValue = this.snapToLegalValue( this.normalisedToScaledValue(newValue) ); window.__JUCE__.backend.emitEvent(this.identifier, { eventType: BasicControl_valueChangedEventId, value: this.scaledValue, }); } sliderDragStarted() {} sliderDragEnded() {} handleEvent(event) { if (event.eventType == BasicControl_valueChangedEventId) { this.scaledValue = event.value; this.valueChangedEvent.callListeners(); } if (event.eventType == BasicControl_propertiesChangedId) { // eslint-disable-next-line no-unused-vars let { eventType: _, ...rest } = event; this.properties = rest; this.propertiesChangedEvent.callListeners(); } } getScaledValue() { return this.scaledValue; } getNormalisedValue() { return Math.pow( (this.scaledValue - this.properties.start) / (this.properties.end - this.properties.start), this.properties.skew ); } normalisedToScaledValue(normalisedValue) { return ( Math.pow(normalisedValue, 1 / this.properties.skew) * (this.properties.end - this.properties.start) + this.properties.start ); } snapToLegalValue(value) { const interval = this.properties.interval; if (interval == 0) return value; const start = this.properties.start; const clamp = (val, min = 0, max = 1) => Math.max(min, Math.min(max, val)); return clamp( start + interval * Math.floor((value - start) / interval + 0.5), this.properties.start, this.properties.end ); } } const sliderStates = new Map(); for (const sliderName of window.__JUCE__.initialisationData.__juce__sliders) sliderStates.set(sliderName, new SliderState(sliderName)); /** * Returns a SliderState object that is connected to the backend WebSliderRelay object that was * created with the same name argument. * * To register a WebSliderRelay object create one with the right name and add it to the * WebBrowserComponent::Options struct using withOptionsFrom. * * @param {String} name */ function getSliderState(name) { if (!sliderStates.has(name)) sliderStates.set(name, new SliderState(name)); return sliderStates.get(name); } class ToggleState { constructor(name) { if (!window.__JUCE__.initialisationData.__juce__toggles.includes(name)) console.warn( "Creating ToggleState for '" + name + "', which is unknown to the backend" ); this.name = name; this.identifier = "__juce__toggle" + this.name; this.value = false; this.properties = { name: "", }; this.valueChangedEvent = new ListenerList(); this.propertiesChangedEvent = new ListenerList(); window.__JUCE__.backend.addEventListener(this.identifier, (event) => this.handleEvent(event) ); window.__JUCE__.backend.emitEvent(this.identifier, { eventType: "requestInitialUpdate", }); } getValue() { return this.value; } setValue(newValue) { this.value = newValue; window.__JUCE__.backend.emitEvent(this.identifier, { eventType: BasicControl_valueChangedEventId, value: this.value, }); } handleEvent(event) { if (event.eventType == BasicControl_valueChangedEventId) { this.value = event.value; this.valueChangedEvent.callListeners(); } if (event.eventType == BasicControl_propertiesChangedId) { // eslint-disable-next-line no-unused-vars let { eventType: _, ...rest } = event; this.properties = rest; this.propertiesChangedEvent.callListeners(); } } } const toggleStates = new Map(); for (const name of window.__JUCE__.initialisationData.__juce__toggles) toggleStates.set(name, new ToggleState(name)); /** * Returns a ToggleState object that is connected to the backend WebToggleButtonRelay object that was * created with the same name argument. * * To register a WebToggleButtonRelay object create one with the right name and add it to the * WebBrowserComponent::Options struct using withOptionsFrom. * * @param {String} name */ function getToggleState(name) { if (!toggleStates.has(name)) toggleStates.set(name, new ToggleState(name)); return toggleStates.get(name); } class ComboBoxState { constructor(name) { if (!window.__JUCE__.initialisationData.__juce__comboBoxes.includes(name)) console.warn( "Creating ComboBoxState for '" + name + "', which is unknown to the backend" ); this.name = name; this.identifier = "__juce__comboBox" + this.name; this.value = 0.0; this.properties = { name: "", choices: [], }; this.valueChangedEvent = new ListenerList(); this.propertiesChangedEvent = new ListenerList(); window.__JUCE__.backend.addEventListener(this.identifier, (event) => this.handleEvent(event) ); window.__JUCE__.backend.emitEvent(this.identifier, { eventType: "requestInitialUpdate", }); } getChoiceIndex() { return Math.round(this.value * (this.properties.choices.length - 1)); } setChoiceIndex(index) { const numItems = this.properties.choices.length; this.value = numItems > 1 ? index / (numItems - 1) : 0.0; window.__JUCE__.backend.emitEvent(this.identifier, { eventType: BasicControl_valueChangedEventId, value: this.value, }); } handleEvent(event) { if (event.eventType == BasicControl_valueChangedEventId) { this.value = event.value; this.valueChangedEvent.callListeners(); } if (event.eventType == BasicControl_propertiesChangedId) { // eslint-disable-next-line no-unused-vars let { eventType: _, ...rest } = event; this.properties = rest; this.propertiesChangedEvent.callListeners(); } } } const comboBoxStates = new Map(); for (const name of window.__JUCE__.initialisationData.__juce__comboBoxes) comboBoxStates.set(name, new ComboBoxState(name)); /** * Returns a ComboBoxState object that is connected to the backend WebComboBoxRelay object that was * created with the same name argument. * * To register a WebComboBoxRelay object create one with the right name and add it to the * WebBrowserComponent::Options struct using withOptionsFrom. * * @param {String} name */ function getComboBoxState(name) { if (!comboBoxStates.has(name)) comboBoxStates.set(name, new ComboBoxState(name)); return comboBoxStates.get(name); } /** * Appends a platform-specific prefix to the path to ensure that a request sent to this address will * be received by the backend's ResourceProvider. * @param {String} path */ function getBackendResourceAddress(path) { const platform = window.__JUCE__.initialisationData.__juce__platform.length > 0 ? window.__JUCE__.initialisationData.__juce__platform[0] : ""; if (platform == "windows" || platform == "android") return "https://juce.backend/" + path; if (platform == "macos" || platform == "ios" || platform == "linux") return "juce://juce.backend/" + path; console.warn( "getBackendResourceAddress() called, but no JUCE native backend is detected." ); return path; } export { getNativeFunction, getSliderState, getToggleState, getComboBoxState, getBackendResourceAddress, };