Files

227 lines
6.1 KiB
C#

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 { }
/// <summary>
/// Can be dispatched up the component tree to notify parent about something
/// add pass some event data without creating a hard link
/// </summary>
/// <param name="Name"></param>
public record CUICommand(string Name, object Data = null);
/// <summary>
/// Can be dispatched down the component tree to pass some data to the children
/// without creating a hard link
/// </summary>
public record CUIData(string Name, object Data = null);
public partial class CUIComponent
{
private void SetupCommands()
{
// This is actually expensive
//AddCommands();
OnTreeChanged += UpdateDataTargets;
}
/// <summary>
/// This command will be dispatched up when some component specific event happens
/// </summary>
[CUISerializable] public string Command { get; set; }
/// <summary>
/// this will be executed on any command
/// </summary>
public event Action<CUICommand> OnAnyCommand;
/// <summary>
/// Will be executed when receiving any data
/// </summary>
public event Action<CUIData> OnAnyData;
/// <summary>
/// Happens when appropriate data is received
/// </summary>
public event Action<Object> OnConsume;
/// <summary>
/// Will consume data with this name
/// </summary>
[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);
};
}
}
/// <summary>
/// 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
/// </summary>
//[CUISerializable]
public ObservableCollection<string> Emits
{
get => emits;
set
{
emits = value;
emits.CollectionChanged += (o, e) => UpdateDataTargets();
UpdateDataTargets();
}
}
private ObservableCollection<string> 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);
}
});
}
}
/// <summary>
/// Consumers of emmited data, updates on tree change
/// </summary>
public Dictionary<string, List<CUIComponent>> DataTargets = new();
/// <summary>
/// All commands
/// </summary>
public Dictionary<string, Action<object>> Commands { get; set; } = new();
/// <summary>
/// Manually adds command
/// </summary>
/// <param name="name"></param>
/// <param name="action"></param>
public void AddCommand(string name, Action<object> action) => Commands.Add(name, action);
public void RemoveCommand(string name) => Commands.Remove(name);
/// <summary>
/// Executed autpmatically on component creation
/// Methods ending in "Command" will be added as commands
/// </summary>
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<Action<object>>(this));
}
catch (Exception e)
{
Info($"{e.Message}\nMethod: {this.GetType()}.{mi.Name}");
}
}
}
}
/// <summary>
/// Dispathes command up the component tree until someone consumes it
/// </summary>
/// <param name="command"></param>
public void DispatchUp(CUICommand command)
{
if (OnAnyCommand != null) OnAnyCommand?.Invoke(command);
else if (Commands.ContainsKey(command.Name)) Execute(command);
else Parent?.DispatchUp(command);
}
/// <summary>
/// Dispathes command down the component tree until someone consumes it
/// </summary>
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);
}
}
}
/// <summary>
/// Will execute action corresponding to this command
/// </summary>
/// <param name="commandName"></param>
public void Execute(CUICommand command)
{
Commands.GetValueOrDefault(command.Name)?.Invoke(command.Data);
}
}
}