JD2022-TU1/main/tools/framework/JD.Serialization/Timeline/Clips/PictogramModel.cs

421 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using JD.Core.Collections;
using JD.Core.Helpers;
using JetBrains.Annotations;
namespace JD.Serialization
{
public class PictogramModel : INotifyPropertyChanged
{
#region Variables
private string _name;
[NotNull]
private readonly Tape _tape;
private Path _pictoPath = new Path("");
private int _instancesCount;
private float _beatDuration;
private readonly NullingExpandoObject _extensions = new NullingExpandoObject();
private static readonly char[] _validModelNameChars = Enumerable.Range('a', 26)
.Union(Enumerable.Range('A', 26))
.Union(Enumerable.Range('0', 10))
.Union(new int[] { '_' })
.Select(x => (char)x).ToArray();
#endregion
#region Constructor
public PictogramModel(Tape tape, Path pictoPath)
{
if (tape == null)
{
throw new ArgumentNullException(nameof(tape));
}
_tape = tape;
PictoPath = pictoPath;
_name = GetPictogramNameFromPictogramPath(PictoPath);
InstancesCount = Instances.Count();
}
#endregion
#region Properties
[Browsable(false)]
public IEnumerable<PictogramClip> Instances
{
get
{
return _tape.Clips.OfType<PictogramClip>().Where(clip => clip.PictoPath.Equals(PictoPath));
}
}
[Browsable(true)]
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
string sanitizedValue = SanitizePictoModelName(value);
string uniqueNewMoveName = sanitizedValue.MakeUniqueName(Tape.PictogramModels().Where(x => x != this).Select(x => x.Name).ToList());
if (uniqueNewMoveName != sanitizedValue)
{
PictogramNameMadeUnique?.Invoke(uniqueNewMoveName);
}
_name = uniqueNewMoveName != sanitizedValue ? uniqueNewMoveName : sanitizedValue;
OnPropertyChanged();
PictoPath = new Path(GetPictogramPathFromPictogramName(Tape, _name));
}
}
}
[Browsable(true)]
[ReadOnly(true)]
public Path PictoPath
{
get { return _pictoPath; }
set
{
if (!_pictoPath.Equals(value))
{
PictoPathChanged?.Invoke(this, _pictoPath.getAbsolute(), value.getAbsolute());
_pictoPath = value;
_name = GetPictogramNameFromPictogramPath(PictoPath);
OnPropertyChanged();
}
}
}
[DisplayName("Duration (in beats)")]
[Description("Duration in beats")]
[Browsable(false)]
public float BeatDuration
{
get { return _beatDuration; }
set
{
if (!_beatDuration.AlmostEquals(value))
{
_beatDuration = value;
OnPropertyChanged();
}
}
}
[Browsable(true)]
[ReadOnly(true)]
public int InstancesCount
{
get { return _instancesCount; }
set
{
if (_instancesCount != value)
{
InstancesCountChanged?.Invoke(this, _instancesCount, value);
_instancesCount = value;
OnPropertyChanged();
}
}
}
public Tape Tape
{
get { return _tape; }
}
public PictogramFlag Flags
{
get
{
return GetFlagsFromName(Name);
}
set
{
if (GetFlagsFromName(Name) != value)
{
Name = GetNameFromFlags(Name, value);
OnPropertyChanged();
}
}
}
/// <summary>
/// Allows to add additional values to the object since the classes are generated.
/// Typically, an extension method can reuse this to get and set value.
/// </summary>
public dynamic Extensions => _extensions;
#endregion
#region UIProperties
public string PictogramTypeFriendlyName
{
get { return InstancesCount > 0 ? "Pictogram" : "Orphan pictogram"; }
}
#endregion
#region Methods
public override string ToString()
{
return Name;
}
/// <summary>
/// Get the name of the pictogram from its path
/// </summary>
internal static string GetPictogramNameFromPictogramPath(Path pictoPath)
{
return System.IO.Path.GetFileNameWithoutExtension(pictoPath.getAbsolute());
}
/// <summary>
/// Get the path of the pictogram from its name
/// </summary>
internal static string GetPictogramPathFromPictogramName(Tape tape, string pictoName)
{
string classifierFileName = pictoName + ".png";
string classifierPath = System.IO.Path.Combine(tape.MapInfo?.PictosFolderRelative, classifierFileName);
classifierPath = classifierPath.Replace("\\", "/");
return classifierPath;
}
/// <summary>
/// Gets the Pictogram informations by parsing its name
/// </summary>
private static PictogramFlag GetFlagsFromName(string name)
{
var flags = PictogramFlag.None;
var elements = name.Split('_');
foreach (PictogramFlag flag in Enum.GetValues(typeof(PictogramFlag)))
{
foreach (string nomenclature in flag.GetNomenclatures())
{
if (elements.Contains(nomenclature, StringComparer.CurrentCultureIgnoreCase))
{
flags |= flag;
}
}
}
return flags;
}
/// <summary>
/// Gets the name of a pictogram from a current name and its informations
/// </summary>
private static string GetNameFromFlags(string name, PictogramFlag flags)
{
var elements = name.Split('_').ToList();
var pictoFlags = Enum.GetValues(typeof(PictogramFlag)).Cast<PictogramFlag>().Where(flag => flag != PictogramFlag.None).ToArray();
var pictoFlagsNames = pictoFlags.SelectMany(flag => flag.GetNomenclatures()).ToArray();
int countSuffix;
Func<string, bool> isCount = s => s == elements.LastOrDefault() && int.TryParse(s, out countSuffix);
var names = elements.Where(part => !pictoFlagsNames.Contains(part, StringComparer.CurrentCultureIgnoreCase) && !isCount(part));
var newFlags = pictoFlags.Where(flag => flags.HasFlag(flag)).Select(flag => flag.GetNomenclatures()[0]);
var count = elements.Where(isCount);
return string.Join("_", names.Concat(newFlags).Concat(count));
}
private static string SanitizePictoModelName(string modelName)
{
string sanitizedValue = modelName;
var invalidChars = modelName.ToCharArray().Distinct().Where(x => !_validModelNameChars.Contains(x)).ToList();
foreach (var invalidChar in invalidChars)
{
sanitizedValue = sanitizedValue.Replace(invalidChar.ToString(), string.Empty);
}
return sanitizedValue;
}
public static bool IsNameValid(string modelName)
{
return modelName.ToCharArray().All(@char => _validModelNameChars.Contains(@char));
}
#endregion
#region Events
public delegate void PictogramNameMadeUniqueFunc(string newMoveName);
public static event PictogramNameMadeUniqueFunc PictogramNameMadeUnique;
public delegate void PictoPathChangedFunc(PictogramModel model, string oldPath, string newPath);
public static event PictoPathChangedFunc PictoPathChanged;
public delegate void InstancesCountChangedFunc(PictogramModel model, int oldValue, int newValue);
public static event InstancesCountChangedFunc InstancesCountChanged;
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
OnPropertyChanged(propertyName);
}
//[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class PictogramModelCollection : UAFList<PictogramModel>
{
}
#region Manager
public static class PictogramModelManager
{
private static void CreatePictogramModelCollection(this Tape tape)
{
tape.Extensions.PictogramModels = new PictogramModelCollection();
tape.Clips.CollectionChanged += tape.ClipsOnCollectionChanged;
tape.Clips.ItemChanged += tape.ClipsOnItemChanged;
tape.PropertyChanged += tape.tape_PropertyChanged;
}
static void tape_PropertyChanged(this Tape tape, object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsLoading" && !tape.IsLoading)
{
RefreshInstancesCount(tape);
}
}
private static void ClipsOnItemChanged(this Tape tape, object sender, ItemChangedEventArgs<Clip> args)
{
if (args.PropertyName == "Model" && !tape.IsLoading)
{
RefreshInstancesCount(tape);
}
}
static void ClipsOnCollectionChanged(this Tape tape, object sender, NotifyCollectionChangedEventArgs e)
{
if (!tape.IsLoading)
{
if (e.NewItems != null)
{
foreach (var model in e.NewItems.OfType<PictogramClip>().Select(x => x.Model).Distinct())
{
RefreshInstancesCount(tape, model);
}
}
if (e.OldItems != null)
{
foreach (var model in e.OldItems.OfType<PictogramClip>().Select(x => x.Model).Distinct())
{
RefreshInstancesCount(tape, model);
}
}
}
}
private static void RefreshInstancesCount(this Tape tape)
{
foreach (var model in tape.PictogramModels().ToList())
{
RefreshInstancesCount(tape, model);
}
}
private static void RefreshInstancesCount(this Tape tape, PictogramModel model)
{
model.InstancesCount = tape.Clips.OfType<PictogramClip>().Count(x => x.Model == model);
if (model.InstancesCount == 0)
{
tape.PictogramModels().Remove(model);
}
}
public static PictogramModel GetOrCreatePictogramModel(this Tape tape, Path pictogramPath)
{
var models = tape.PictogramModels();
PictogramModel model = models.FirstOrDefault(x => x.PictoPath.Equals(pictogramPath));
if (model == null)
{
model = new PictogramModel(tape, pictogramPath);
models.Add(model);
}
return model;
}
public static PictogramModel GetOrCreatePictogramModel(this Tape tape, string modelName)
{
return GetOrCreatePictogramModel(tape, new Path(PictogramModel.GetPictogramPathFromPictogramName(tape, modelName)));
}
public static PictogramModelCollection PictogramModels(this Tape tape)
{
if (tape.Extensions.PictogramModels == null)
{
tape.CreatePictogramModelCollection();
}
return tape.Extensions.PictogramModels;
}
public static void ClearPictogramModels(this Tape tape)
{
PictogramModelCollection models = tape.Extensions.PictogramModels;
models?.Clear();
}
}
#endregion
#region PictogramFlag
[Flags]
public enum PictogramFlag
{
None = 0,
Pose = 2,
Move = 4,
Arrow = 8,
Ghost = 16,
Shake = 32,
Clap = 64,
Punch = 128,
Object = 256,
Jump = 512,
Gold = 1024
}
public static class PictogramFlagExtensions
{
public static string[] GetNomenclatures(this PictogramFlag flag)
{
switch (flag)
{
case PictogramFlag.Pose: return new []{"po", "pose", "P"};
case PictogramFlag.Arrow: return new[] { "ar", "arrow", "F" };
case PictogramFlag.Ghost: return new[] { "gh", "ghost", "G" };
case PictogramFlag.Gold: return new[] { "gold" };
}
return new[] { flag.ToString().ToLower().Substring(0, 2), flag.ToString().ToLower() };
}
}
#endregion
}