Skip to content

Commit

Permalink
Add support for menus
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Dec 4, 2022
1 parent baeae22 commit a28fcb0
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 55 deletions.
71 changes: 71 additions & 0 deletions src/NPlug/AudioContextMenu.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

using System;
using System.Diagnostics.CodeAnalysis;
using NPlug.Backend;
using NPlug.Vst3;

namespace NPlug;

public readonly struct AudioContextMenu
{
private readonly IAudioContextMenuBackend _backend;
internal readonly IntPtr NativeContext;

public AudioContextMenu(IAudioContextMenuBackend backend, IntPtr nativeContext)
{
_backend = backend;
NativeContext = nativeContext;
}

/// <summary>
/// Gets the number of menu items.
/// </summary>
public int ItemCount => _backend?.GetItemCount(this) ?? 0;

/// <summary>
/// Gets a menu item and its target (target could be not assigned).
/// </summary>
public void GetItem(int index, out AudioContextMenuItem item, out AudioContextMenuAction? target)
{
GetSafeBackend().GetItem(this, index, out item, out target);
}

/// <summary>
/// Adds a menu item and its target.
/// </summary>
public void AddItem(in AudioContextMenuItem item, AudioContextMenuAction target)
{
GetSafeBackend().AddItem(this, item, target);
}

/// <summary>
/// Removes a menu item.
/// </summary>
public void RemoveItem(in AudioContextMenuItem item, AudioContextMenuAction target)
{
GetSafeBackend().RemoveItem(this, item, target);
}

/// <summary>
/// Pop-ups the menu. Coordinates are relative to the top-left position of the plug-ins view.
/// </summary>
public void Popup(int x, int y)
{
GetSafeBackend().Popup(this, x, y);
}

private IAudioContextMenuBackend GetSafeBackend()
{
if (_backend is null) ThrowNotInitialized();
return _backend;
}

[DoesNotReturn]
private static void ThrowNotInitialized()
{
throw new InvalidOperationException("This context menu is not initialized");
}
}
11 changes: 11 additions & 0 deletions src/NPlug/AudioContextMenuAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

namespace NPlug;

/// <summary>
/// Delegate used by <see cref="AudioContextMenu"/>.
/// </summary>
/// <param name="tag">The associated tag to the triggered menu item (<see cref="AudioContextMenuItem.Tag"/>) </param>
public delegate void AudioContextMenuAction(uint tag);
13 changes: 13 additions & 0 deletions src/NPlug/AudioContextMenuItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

namespace NPlug;

/// <summary>
/// A context menu item.
/// </summary>
/// <param name="Name">Name of the item.</param>
/// <param name="Tag">Identifier tag of the item.</param>
/// <param name="Flags">Flags of the item</param>
public readonly record struct AudioContextMenuItem(string Name, int Tag = 0, AudioContextMenuItemFlags Flags = AudioContextMenuItemFlags.None);
38 changes: 38 additions & 0 deletions src/NPlug/AudioContextMenuItemFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

namespace NPlug;

public enum AudioContextMenuItemFlags
{
/// <summary>
/// No options.
/// </summary>
None = 0,

/// <summary>
/// Item is a separator
/// </summary>
IsSeparator = 1 << 0,

/// <summary>
/// Item is disabled
/// </summary>
IsDisabled = 1 << 1,

/// <summary>
/// Item is checked
/// </summary>
IsChecked = 1 << 2,

/// <summary>
/// Item is a group start (like sub folder)
/// </summary>
IsGroupStart = 1 << 3 | IsDisabled,

/// <summary>
/// Item is a group end
/// </summary>
IsGroupEnd = 1 << 4 | IsSeparator,
}
8 changes: 1 addition & 7 deletions src/NPlug/AudioControllerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,4 @@ public enum AudioProgressType : uint
UIBackgroundTask,
}

public record struct AudioProgramListId(int Value);


public struct AudioContextMenu
{
// TODO: implement
}
public record struct AudioProgramListId(int Value);
33 changes: 33 additions & 0 deletions src/NPlug/Backend/IAudioContextMenuBackend.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

namespace NPlug.Backend;

public interface IAudioContextMenuBackend
{
/// <summary>
/// Gets the number of menu items.
/// </summary>
int GetItemCount(in AudioContextMenu contextMenu);

/// <summary>
/// Gets a menu item and its target (target could be not assigned).
/// </summary>
void GetItem(in AudioContextMenu contextMenu, int index, out AudioContextMenuItem item, out AudioContextMenuAction? target);

/// <summary>
/// Adds a menu item and its target.
/// </summary>
void AddItem(in AudioContextMenu contextMenu, in AudioContextMenuItem item, AudioContextMenuAction target);

/// <summary>
/// Removes a menu item.
/// </summary>
void RemoveItem(in AudioContextMenu contextMenu, in AudioContextMenuItem item, AudioContextMenuAction target);

/// <summary>
/// Pop-ups the menu. Coordinates are relative to the top-left position of the plug-ins view.
/// </summary>
void Popup(in AudioContextMenu contextMenu, int x, int y);
}
63 changes: 28 additions & 35 deletions src/NPlug/Vst3/ComObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public uint ReleaseRef()
{
disposable.Dispose();
Reset();
Release();
Manager.Return(this);
}
}
return _refCount;
Expand Down Expand Up @@ -125,7 +125,6 @@ public void Reset()
_interfaceCount = 0;
_refCount = 1;
_lock = default;
Target = null;
}

private static class VtblInitializer<T> where T: INativeVtbl
Expand All @@ -144,12 +143,6 @@ public void Dispose()
NativeMemory.Free(_handles);
_thisHandle.Free();
}

public void Release()
{
// Pool the object back to the manager
Manager.ReleaseObject(this);
}
}

public sealed class ComObjectManager : IDisposable
Expand All @@ -158,7 +151,8 @@ public sealed class ComObjectManager : IDisposable

private const int DefaultComObjectCacheCount = 16;
private readonly Stack<ComObject> _comObjectCache;
private SpinLock _lock;
private readonly Dictionary<object, ComObject> _mapTargetToComObject;
private readonly object _thisLock;

public ComObjectManager()
{
Expand All @@ -167,53 +161,52 @@ public ComObjectManager()
{
_comObjectCache.Push(new ComObject(this));
}
_mapTargetToComObject = new Dictionary<object, ComObject>(ReferenceEqualityComparer.Instance);
_thisLock = new object();
}

public ComObject GetOrCreateComObject(object target)
{
ComObject comObject;
bool taken = false;
_lock.Enter(ref taken);
if (_comObjectCache.Count > 0)
lock (_thisLock)
{
comObject = _comObjectCache.Pop();
if (taken) _lock.Exit();
}
else
{
if (taken) _lock.Exit();
comObject = new ComObject(this);
}
if (_mapTargetToComObject.TryGetValue(target, out var comObject))
{
return comObject;
}

comObject.Target = target;
return comObject;
comObject = _comObjectCache.Count > 0 ? _comObjectCache.Pop() : new ComObject(this);
_mapTargetToComObject.Add(target, comObject);
comObject.Target = target;
return comObject;
}
}

public void ReleaseObject(ComObject comObject)
public void Return(ComObject comObject)
{
bool taken = false;
try
var target = comObject.Target!;
comObject.Target = null;
lock (_thisLock)
{
_lock.Enter(ref taken);
_mapTargetToComObject.Remove(target);
_comObjectCache.Push(comObject);
}
finally
{
if (taken) _lock.Exit();
}
}

public void Dispose()
{
foreach (var comObject in _comObjectCache)
lock (_thisLock)
{
comObject.Dispose();
foreach (var comObject in _comObjectCache)
{
comObject.Dispose();
}

_comObjectCache.Clear();
_mapTargetToComObject.Clear();
}
_comObjectCache.Clear();
}
}


public unsafe interface INativeGuid
{
public static abstract Guid* NativeGuid { get; }
Expand Down
Loading

0 comments on commit a28fcb0

Please sign in to comment.