using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using VRC.SDK3.Avatars.ScriptableObjects;
// Internal runtime API for the Virtual Menu system.
//
// IMPORTANT: This API is currently considered unstable. Due to C# protection rules, we are required to make classes
// here public, but be aware that they may change without warning in the future.
namespace nadena.dev.modular_avatar.core.menu
{
///
/// A MenuNode represents a single VRCExpressionsMenu, prior to overflow splitting. MenuNodes form a directed graph,
/// which may contain cycles, and may include contributions from multiple MenuInstallers, or from the base avatar
/// menu.
///
public class VirtualMenuNode
{
public List Controls = new List();
///
/// The primary (serialized) object that contributed to this menu; if we want to add more items to it, we look
/// here. This can currently be either a VRCExpressionsMenu, a MAMenuItem, or a RootMenu.
///
public readonly object NodeKey;
internal VirtualMenuNode(object nodeKey)
{
NodeKey = nodeKey;
}
}
/**
* A single control on a MenuNode. The main difference between this and a true VRCExpressionsMenu.Control is that
* we use a MenuNode instead of a VRCExpressionsMenu for submenus.
*/
public class VirtualControl : VRCExpressionsMenu.Control
{
///
/// VirtualControls do not reference real VRCExpressionsMenu objects, but rather virtual MenuNodes.
///
public VirtualMenuNode SubmenuNode;
internal VirtualControl(VRCExpressionsMenu.Control control)
{
this.name = control.name;
this.type = control.type;
this.parameter = new Parameter() {name = control?.parameter?.name};
this.value = control.value;
this.icon = control.icon;
this.style = control.style;
this.subMenu = null;
this.subParameters = control.subParameters?.Select(p => new VRCExpressionsMenu.Control.Parameter()
{
name = p.name
})?.ToArray();
this.labels = control.labels?.ToArray();
}
}
///
/// Helper MenuSource which includes all children of a given GameObject containing MenuSourceComponents as menu
/// items. Implements equality based on the GameObject in question.
///
internal class MenuNodesUnder : MenuSource
{
internal readonly GameObject root;
public MenuNodesUnder(GameObject root)
{
this.root = root;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return root == ((MenuNodesUnder) obj).root;
}
public override int GetHashCode()
{
return (root != null ? root.GetHashCode() : 0);
}
public void Visit(NodeContext context)
{
foreach (Transform t in root.transform)
{
var source = t.GetComponent();
if (source != null) context.PushNode(source);
}
}
}
///
/// The NodeContext provides callbacks for MenuSource visitors to append controls and/or other node types to a menu
/// node.
///
public interface NodeContext
{
///
/// Pushes the contents of this expressions menu asset onto the current menu node, handling loops and menu
/// installer invocations.
///
///
void PushNode(VRCExpressionsMenu expMenu);
///
/// Pushes the contents of this menu source onto the current menu node.
///
///
void PushNode(MenuSource source);
///
/// Pushes this menu installer onto this node
///
///
void PushNode(ModularAvatarMenuInstaller installer);
///
/// Pushes a single expressions menu control onto the current menu node. Converts submenus into menu nodes
/// automatically.
///
///
void PushControl(VRCExpressionsMenu.Control control);
///
/// Pushes a single expressions menu control onto the current menu node.
///
///
void PushControl(VirtualControl control);
///
/// Returns the menu node for a given VRCExpressionsMenu asset. This node may not be populated at the time this
/// node returns.
///
///
///
VirtualMenuNode NodeFor(VRCExpressionsMenu menu);
///
/// Returns the menu node for a given menu source asset. The contents of the node may not yet be populated.
///
///
///
VirtualMenuNode NodeFor(MenuSource menu);
}
///
/// An object which can contribute controls to a menu.
///
public interface MenuSource
{
void Visit(NodeContext context);
}
///
/// A component which can be used to generate menu items.
///
[DisallowMultipleComponent]
public abstract class MenuSourceComponent : AvatarTagComponent, MenuSource
{
protected override void OnValidate()
{
base.OnValidate();
RuntimeUtil.InvalidateMenu();
}
public abstract void Visit(NodeContext context);
}
}