Skip to content

Commit

Permalink
Hides state pin, adds invisible pin to persist window bounds
Browse files Browse the repository at this point in the history
  • Loading branch information
azeno committed Nov 4, 2024
1 parent 7b38f33 commit 561b451
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 22 deletions.
57 changes: 50 additions & 7 deletions src/EffectHost.UI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using VST3.Hosting;
using VST3;
using System.Runtime.InteropServices.Marshalling;
using System.Reactive.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;

namespace VL.Audio.VST;
partial class EffectHost
Expand All @@ -21,7 +24,7 @@ private void ShowEditor()
if (view is null || view.isPlatformTypeSupported("HWND") != 0)
return;

window = new Window(view);
window = new Window(this, view);

// High DPI support
var scaleSupport = view as IPlugViewContentScaleSupport;
Expand All @@ -30,14 +33,23 @@ private void ShowEditor()
scaleSupport?.setContentScaleFactor(e.DeviceDpiNew / 96f);
};

try
bounds = boundsPin.Value?.Value ?? default;
if (!bounds.IsEmpty)
{
var plugViewSize = view.getSize();
window.ClientSize = new Size(plugViewSize.Width, plugViewSize.Height);
window.StartPosition = FormStartPosition.Manual;
SetWindowBounds(bounds);
}
catch (Exception e)
else
{
logger.LogError(e, "Failed to get initial view size");
try
{
var plugViewSize = view.getSize();
window.ClientSize = new Size(plugViewSize.Width, plugViewSize.Height);
}
catch (Exception e)
{
logger.LogError(e, "Failed to get initial view size");
}
}

try
Expand All @@ -55,6 +67,26 @@ private void ShowEditor()
}

window.Visible = true;
window.Activate();
}

void SaveCurrentWindowBounds()
{
if (window is null || window.IsDisposed)
return;

var position = window.DesktopLocation;
var bounds = new Stride.Core.Mathematics.RectangleF(position.X, position.Y, window.ClientSize.Width, window.ClientSize.Height);
SaveToChannelOrPin(boundsPin.Value, BoundsInputPinName, bounds);
}

private void SetWindowBounds(Stride.Core.Mathematics.RectangleF bounds)
{
if (window is null || window.IsDisposed)
return;

window.Location = new Point((int)bounds.X, (int)bounds.Y);
window.ClientSize = new Size((int)bounds.Width, (int)bounds.Height);
}

private void HideEditor()
Expand All @@ -67,11 +99,14 @@ private void HideEditor()
[GeneratedComClass]
sealed partial class Window : Form, IPlugFrame
{
private readonly EffectHost effectHost;
private readonly IPlugView view;
private readonly IPlugViewContentScaleSupport? scaleSupport;
private readonly SingleAssignmentDisposable boundsSubscription = new();

public Window(IPlugView view)
public Window(EffectHost effectHost, IPlugView view)
{
this.effectHost = effectHost;
this.view = view;
this.scaleSupport = view as IPlugViewContentScaleSupport;
}
Expand All @@ -82,11 +117,19 @@ protected override void OnHandleCreated(EventArgs e)
view.attached(Handle, "HWND");
scaleSupport?.setContentScaleFactor(DeviceDpi / 96f);

boundsSubscription.Disposable = Observable.Merge(
Observable.FromEventPattern(this, nameof(ClientSizeChanged)),
Observable.FromEventPattern(this, nameof(LocationChanged)))
.Throttle(TimeSpan.FromSeconds(0.25))
.ObserveOn(SynchronizationContext.Current!)
.Subscribe(_ => effectHost.SaveCurrentWindowBounds());

base.OnHandleCreated(e);
}

protected override void OnHandleDestroyed(EventArgs e)
{
boundsSubscription.Dispose();
view.removed();
view.ReleaseComObject();

Expand Down
27 changes: 21 additions & 6 deletions src/EffectHost.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sanford.Multimedia.Midi;
using Stride.Core.Mathematics;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -27,6 +28,7 @@ namespace VL.Audio.VST;
internal partial class EffectHost : FactoryBasedVLNode, IVLNode, IComponentHandler, IComponentHandler2, IDisposable
{
public const string StateInputPinName = "State";
public const string BoundsInputPinName = "Bounds";

private static readonly ParameterChanges s_noChanges = new();

Expand All @@ -50,12 +52,14 @@ internal partial class EffectHost : FactoryBasedVLNode, IVLNode, IComponentHandl
private readonly BlockingCollection<Event> inputEventQueue = new(boundedCapacity: 1024);
private readonly Subject<Event> outputEvents = new();

private readonly Pin<IChannel<RectangleF>> boundsPin;
private readonly Pin<IObservable<IMidiMessage>> midiInputPin, midiOutputPin;
private readonly Pin<string> channelPrefixPin;
private readonly Pin<bool> showUiPin;
private readonly Pin<bool> applyPin;

private PluginState? state;
private RectangleF bounds;
private AudioPin audioInputPin, audioOutputPin;
private IObservable<IMidiMessage>? midiInput;
private string? channelPrefix;
Expand Down Expand Up @@ -129,7 +133,7 @@ public EffectHost(NodeContext nodeContext, IVLNodeDescription nodeDescription, s

Inputs[i] = new StatePin();
i++;

Inputs[i++] = boundsPin = new Pin<IChannel<RectangleF>>();
Inputs[i++] = audioInputPin = new AudioPin();
Inputs[i++] = midiInputPin = new Pin<IObservable<IMidiMessage>>();
Inputs[i++] = new ParametersInput(this);
Expand Down Expand Up @@ -183,10 +187,7 @@ public void Dispose()
var state = PluginState.From(plugProvider.ClassInfo.ID, component, controller);
var statePin = (StatePin)Inputs[0];
var channel = statePin.Value;
if (channel is null || channel.IsSystemGenerated())
SaveToPin(StateInputPinName, state);
else
channel.Value = state;
SaveToChannelOrPin(channel, StateInputPinName, state);

processor.SetProcessing_IgnoreNotImplementedException(false);
component.setActive(false);
Expand Down Expand Up @@ -248,6 +249,12 @@ public void Update()
HideEditor();
}

if (Acknowledge(ref bounds, boundsPin.Value?.Value ?? RectangleF.Empty))
{
if (!bounds.IsEmpty)
SetWindowBounds(bounds);
}

if (Acknowledge(ref apply, applyPin.Value))
{
if (byPassParameter.HasValue)
Expand Down Expand Up @@ -530,11 +537,19 @@ void IComponentHandler2.finishGroupEdit()
logger.LogTrace("Finishing group edit");
}

private void SaveToChannelOrPin<T>(IChannel<T>? channel, string pinName, T value)
{
if (channel is null || channel.IsSystemGenerated())
SaveToPin(pinName, value);
else
channel.Value = value;
}

private void SaveToPin<T>(string pinName, T value)
{
var solution = IDevSession.Current?.CurrentSolution
.SetPinValue(nodeContext.Path.Stack, pinName, value);
solution?.Confirm();
solution?.Confirm(Model.SolutionUpdateKind.DontCompile | Model.SolutionUpdateKind.TweakLast);
}

sealed class ParametersInput : IVLPin<IReadOnlyDictionary<string, float>>
Expand Down
42 changes: 33 additions & 9 deletions src/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Sanford.Multimedia.Midi;
using Stride.Core.Mathematics;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
Expand Down Expand Up @@ -93,18 +94,19 @@ IVLNodeDescription GetNodeDescription(IVLNodeDescriptionFactory nodeDescriptionF
{
var inputs = new List<IVLPinDescription>()
{
ctx.Pin(EffectHost.StateInputPinName, typeof(IChannel<PluginState>)),
ctx.Pin("Input", typeof(IEnumerable<AudioSignal>)),
ctx.Pin("Midi In", typeof(IObservable<IMidiMessage>)),
ctx.Pin("Parameters", typeof(IReadOnlyDictionary<string, float>)),
ctx.Pin("Channel Prefix", typeof(string), null),
ctx.Pin("Show Editor", typeof(bool)),
ctx.Pin("Apply", typeof(bool), defaultValue: true)
new PinDescription(EffectHost.StateInputPinName, typeof(IChannel<PluginState>)) { IsVisible = false },
new PinDescription(EffectHost.BoundsInputPinName, typeof(IChannel<RectangleF>)) { IsVisible = false },
new PinDescription("Input", typeof(IEnumerable<AudioSignal>)),
new PinDescription("Midi In", typeof(IObservable<IMidiMessage>)),
new PinDescription("Parameters", typeof(IReadOnlyDictionary<string, float>)),
new PinDescription("Channel Prefix", typeof(string), null),
new PinDescription("Show Editor", typeof(bool)),
new PinDescription("Apply", typeof(bool), defaultValue: true)
};
var outputs = new List<IVLPinDescription>()
{
ctx.Pin("Output", typeof(IReadOnlyList<AudioSignal>)),
ctx.Pin("Midi Out", typeof(IObservable<IMidiMessage>)),
new PinDescription("Output", typeof(IReadOnlyList<AudioSignal>)),
new PinDescription("Midi Out", typeof(IObservable<IMidiMessage>)),
//ctx.Pin("Parameters", typeof(IReadOnlyDictionary<string, float>))
};

Expand All @@ -114,4 +116,26 @@ IVLNodeDescription GetNodeDescription(IVLNodeDescriptionFactory nodeDescriptionF
});
});
}

sealed class PinDescription : IVLPinDescription, IInfo, IVLPinDescriptionWithVisibility
{
public PinDescription(string name, Type type, object? defaultValue = null)
{
Name = name;
Type = type;
DefaultValue = defaultValue;
}

public string Name { get; init; }

public Type Type { get; init; }

public object? DefaultValue { get; init; }

public bool IsVisible { get; init; } = true;

public string? Summary { get; init; }

public string? Remarks { get; init; }
}
}

0 comments on commit 561b451

Please sign in to comment.