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); } }