421 lines
14 KiB
C#
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
|
|
|
|
|
|
}
|