using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Diagnostics; using System.Runtime.CompilerServices; using System.IO; using System.Collections.ObjectModel; using System.Collections.Specialized; using Barotrauma; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Graphics; using System.Xml; using System.Xml.Linq; namespace QICrabUI { public class CommandAttribute : System.Attribute { } /// /// Can be dispatched up the component tree to notify parent about something /// add pass some event data without creating a hard link /// /// public record CUICommand(string Name, object Data = null); /// /// Can be dispatched down the component tree to pass some data to the children /// without creating a hard link /// public record CUIData(string Name, object Data = null); public partial class CUIComponent { private void SetupCommands() { // This is actually expensive //AddCommands(); OnTreeChanged += UpdateDataTargets; } /// /// This command will be dispatched up when some component specific event happens /// [CUISerializable] public string Command { get; set; } /// /// this will be executed on any command /// public event Action OnAnyCommand; /// /// Will be executed when receiving any data /// public event Action OnAnyData; /// /// Happens when appropriate data is received /// public event Action OnConsume; /// /// Will consume data with this name /// [CUISerializable] public string Consumes { get; set; } private bool reflectCommands; [CUISerializable] public bool ReflectCommands { get => reflectCommands; set { reflectCommands = value; OnAnyCommand += (command) => { foreach (CUIComponent child in Children) { child.DispatchDown(new CUIData(command.Name, command.Data)); } }; } } private bool retranslateCommands; [CUISerializable] public bool RetranslateCommands { get => retranslateCommands; set { retranslateCommands = value; OnAnyCommand += (command) => { Parent?.DispatchUp(command); }; } } /// /// Optimization to data flow /// If not empty component will search for consumers of the data /// and pass it directly to them instead of broadcasting it /// //[CUISerializable] public ObservableCollection Emits { get => emits; set { emits = value; emits.CollectionChanged += (o, e) => UpdateDataTargets(); UpdateDataTargets(); } } private ObservableCollection emits = new(); private void UpdateDataTargets() { if (Emits.Count > 0) { DataTargets.Clear(); RunRecursiveOn(this, (c) => { if (Emits.Contains(c.Consumes)) { if (!DataTargets.ContainsKey(c.Consumes)) DataTargets[c.Consumes] = new(); DataTargets[c.Consumes].Add(c); } }); } } /// /// Consumers of emmited data, updates on tree change /// public Dictionary> DataTargets = new(); /// /// All commands /// public Dictionary> Commands { get; set; } = new(); /// /// Manually adds command /// /// /// public void AddCommand(string name, Action action) => Commands.Add(name, action); public void RemoveCommand(string name) => Commands.Remove(name); /// /// Executed autpmatically on component creation /// Methods ending in "Command" will be added as commands /// private void AddCommands() { foreach (MethodInfo mi in this.GetType().GetMethods()) { if (Attribute.IsDefined(mi, typeof(CommandAttribute))) { try { string name = mi.Name; if (name != "Command" && name.EndsWith("Command")) { name = name.Substring(0, name.Length - "Command".Length); } AddCommand(name, mi.CreateDelegate>(this)); } catch (Exception e) { Info($"{e.Message}\nMethod: {this.GetType()}.{mi.Name}"); } } } } /// /// Dispathes command up the component tree until someone consumes it /// /// public void DispatchUp(CUICommand command) { if (OnAnyCommand != null) OnAnyCommand?.Invoke(command); else if (Commands.ContainsKey(command.Name)) Execute(command); else Parent?.DispatchUp(command); } /// /// Dispathes command down the component tree until someone consumes it /// public void DispatchDown(CUIData data) { if (Emits.Contains(data.Name)) { if (DataTargets.ContainsKey(data.Name)) { foreach (CUIComponent target in DataTargets[data.Name]) { target.OnConsume?.Invoke(data.Data); } } } else { if (Consumes == data.Name) OnConsume?.Invoke(data.Data); else if (OnAnyData != null) OnAnyData.Invoke(data); else { foreach (CUIComponent child in Children) child.DispatchDown(data); } } } /// /// Will execute action corresponding to this command /// /// public void Execute(CUICommand command) { Commands.GetValueOrDefault(command.Name)?.Invoke(command.Data); } } }