Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created layout preset library (#375) #383

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AnnoDesigner.Core/DataStructures/QuadTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,15 @@ public QuadTree(Rect extent)
root = new Quadrant(extent);
}

/// <summary>
/// Create a <see cref="QuadTree{T}"/>
/// </summary>
/// <param name="extent">The bounds of the <see cref="QuadTree{T}"/></param>
public QuadTree(Rect extent, IEnumerable<T> enumerable) : this(extent)
{
AddRange(enumerable);
}

/// <summary>
/// Insert a <typeparamref name="T"/> item into the QuadTree.
/// </summary>
Expand Down
28 changes: 28 additions & 0 deletions AnnoDesigner.Core/Extensions/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,33 @@ public static bool IsIgnoredObject(this AnnoObject annoObject)
{
return string.Equals(annoObject.Template, "Blocker", StringComparison.OrdinalIgnoreCase);
}

public static T MinOrDefault<T>(this IEnumerable<T> enumerable, T @default = default)
{
if (!enumerable.Any()) return @default;

return enumerable.Min();
}

public static TResult MinOrDefault<T, TResult>(this IEnumerable<T> enumerable, Func<T, TResult> selector, TResult @default = default)
{
if (!enumerable.Any()) return @default;

return enumerable.Min(selector);
}

public static T MaxOrDefault<T>(this IEnumerable<T> enumerable, T @default = default)
{
if (!enumerable.Any()) return @default;

return enumerable.Max();
}

public static TResult MaxOrDefault<T, TResult>(this IEnumerable<T> enumerable, Func<T, TResult> selector, TResult @default = default)
{
if (!enumerable.Any()) return @default;

return enumerable.Max(selector);
}
}
}
16 changes: 16 additions & 0 deletions AnnoDesigner.Core/Layout/LayoutLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ public LayoutFile LoadLayout(Stream streamWithLayout, bool forceLoad = false)
return Load(jsonString, forceLoad);
}

public async Task<LayoutFile> LoadLayoutAsync(Stream streamWithLayout, bool forceLoad = false)
{
if (streamWithLayout == null)
{
throw new ArgumentNullException(nameof(streamWithLayout));
}
using var sr = new StreamReader(streamWithLayout);
var jsonString = await sr.ReadToEndAsync();
return await LoadAsync(jsonString, forceLoad);
}

private LayoutFile Load(string jsonString, bool forceLoad)
{
var layoutVersion = new LayoutFileVersionContainer() { FileVersion = 0 };
Expand All @@ -88,5 +99,10 @@ private LayoutFile Load(string jsonString, bool forceLoad)
_ => throw new NotImplementedException()
};
}

private Task<LayoutFile> LoadAsync(string jsonString, bool forceLoad)
{
return Task.Run(() => Load(jsonString, forceLoad));
}
}
}
11 changes: 10 additions & 1 deletion AnnoDesigner.Core/Layout/Models/LayoutFile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using AnnoDesigner.Core.Models;
Expand All @@ -21,5 +22,13 @@ public LayoutFile(IEnumerable<AnnoObject> objects)
FileVersion = CoreConstants.LayoutFileVersion;
Objects = objects.ToList();
}

public LayoutFile(LayoutFile copy)
{
FileVersion = copy.FileVersion;
LayoutVersion = (Version)copy.LayoutVersion.Clone();
Modified = copy.Modified;
Objects = copy.Objects.Select(x => new AnnoObject(x)).ToList();
}
}
}
7 changes: 7 additions & 0 deletions AnnoDesigner.Core/Layout/Presets/IPresetLayout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace AnnoDesigner.Core.Layout.Presets
{
public interface IPresetLayout
{
public MultilangInfo Name { get; }
}
}
20 changes: 20 additions & 0 deletions AnnoDesigner.Core/Layout/Presets/LayoutPresetInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace AnnoDesigner.Core.Layout.Presets
{
public class LayoutPresetInfo
{
public MultilangInfo Name { get; set; }

public MultilangInfo Description { get; set; }

public string Author { get; set; }

public string AuthorContact { get; set; }

public LayoutPresetInfo() { }

public LayoutPresetInfo(string name)
{
Name = name;
}
}
}
78 changes: 78 additions & 0 deletions AnnoDesigner.Core/Layout/Presets/MultilangInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

namespace AnnoDesigner.Core.Layout.Presets
{
[JsonConverter(typeof(MultilangInfoConverter))]
public class MultilangInfo
{
private class MultilangInfoConverter : JsonConverter<MultilangInfo>
{
public override MultilangInfo ReadJson(JsonReader reader, Type objectType, MultilangInfo existingValue, bool hasExistingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.String:
return reader.Value as string;
case JsonToken.StartObject:
return serializer.Deserialize<Dictionary<string, string>>(reader);
default:
throw new JsonSerializationException($"Unexpected token during deserialization of {nameof(MultilangInfo)}");
}
}

public override void WriteJson(JsonWriter writer, MultilangInfo value, JsonSerializer serializer)
{
serializer.Serialize(writer, (object)value.Default ?? value.Translations);
}
}

private Dictionary<string, string> Translations { get; set; }

private string Default { get; set; }

public string this[string language]
{
set
{
Translations ??= new Dictionary<string, string>();
Translations[language] = value;
}
}

public string Translate(string language)
{
return Default ?? (
Translations.TryGetValue(language, out var translation)
? translation
: Translations.Count > 0
? $"{Translations.FirstOrDefault().Value} ({Translations.FirstOrDefault().Key})"
: string.Empty
);
}

public static implicit operator MultilangInfo(string value)
{
return new MultilangInfo()
{
Default = value
};
}

public static implicit operator MultilangInfo(Dictionary<string, string> value)
{
return new MultilangInfo()
{
Translations = value
};
}

public static explicit operator string(MultilangInfo info)
{
var first = info.Translations.FirstOrDefault();
return info.Default ?? (info.Translations.Count > 0 ? $"{first.Value} ({first.Key})" : string.Empty);
}
}
}
142 changes: 142 additions & 0 deletions AnnoDesigner.Core/Layout/Presets/PresetLayout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.IO.Compression;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using AnnoDesigner.Core.Layout.Models;
using AnnoDesigner.Core.Models;
using Newtonsoft.Json;

namespace AnnoDesigner.Core.Layout.Presets
{
public class PresetLayout : Notify, IPresetLayout, IDisposable
{
private bool disposed;
private List<ImageSource> images;
private IFileSystem fileSystem;
private Func<LayoutFile, Task<ImageSource>> renderLayoutToImage;

public MultilangInfo Name => Info.Name;

public LayoutPresetInfo Info { get; set; }

public string Author { get; set; }

public string AuthorContact { get; set; }

public LayoutFile Layout { get; set; }

public List<ImageSource> Images
{
get { return images; }
set { UpdateProperty(ref images, value); }
}

private ZipArchive ZipArchive { get; set; }

[Obsolete($"Constructor should not be used to construct {nameof(PresetLayout)}. Use {nameof(OpenAsync)} instead.")]
public PresetLayout() { }

public static async Task<PresetLayout> OpenAsync(string zipFile, Func<LayoutFile, Task<ImageSource>> renderLayoutToImage = null, IFileSystem fileSystem = null)
{
fileSystem ??= new FileSystem();
var zip = ZipFile.OpenRead(zipFile);

using var layoutFile = zip.GetEntry("layout.ad").Open();
var layout = await new LayoutLoader().LoadLayoutAsync(layoutFile, true).ConfigureAwait(false);
if (layout == null)
{
throw new ArgumentException("Provided ZIP file doesn't contain valid AD layout file with name layout.ad");
}

using var infoFile = zip.GetEntry("info.json")?.Open() ?? Stream.Null;
using var infoStream = new StreamReader(infoFile);
var info = JsonConvert.DeserializeObject<LayoutPresetInfo>(await infoStream.ReadToEndAsync().ConfigureAwait(false))
?? new LayoutPresetInfo(fileSystem.Path.GetFileNameWithoutExtension(zipFile));

return new PresetLayout()
{
Info = info,
Layout = layout,
ZipArchive = zip,
fileSystem = fileSystem,
renderLayoutToImage = renderLayoutToImage
};
}

private bool IsImage(ZipArchiveEntry f)
{
switch (fileSystem.Path.GetExtension(f.FullName).ToLowerInvariant())
{
case ".png":
case ".jpg":
case ".jpeg":
return true;
default:
return false;
}
}

public async Task LoadImages(Func<LayoutFile, Task<ImageSource>> renderLayoutToImage = null)
{
renderLayoutToImage ??= this.renderLayoutToImage;

if (renderLayoutToImage == null)
{
throw new ArgumentNullException(nameof(renderLayoutToImage), $"Argument not provided nor set in {nameof(OpenAsync)}");
}

var images = new List<ImageSource>
{
await renderLayoutToImage(Layout)
};

await Task.WhenAll(ZipArchive.Entries.Where(IsImage).Select(async f =>
{
using var stream = f.Open();
var imageStream = new MemoryStream();
await stream.CopyToAsync(imageStream).ConfigureAwait(true);

var image = new BitmapImage();
image.BeginInit();
image.StreamSource = imageStream;
image.EndInit();
image.Freeze();
images.Add(image);
}));

Images = images;
}

public void UnloadImages()
{
Images = null;
}

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
ZipArchive.Dispose();
ZipArchive = null;
}

disposed = true;
}
}
}
}
11 changes: 11 additions & 0 deletions AnnoDesigner.Core/Layout/Presets/PresetLayoutDirectory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;

namespace AnnoDesigner.Core.Layout.Presets
{
public class PresetLayoutDirectory : IPresetLayout
{
public MultilangInfo Name { get; set; }

public List<IPresetLayout> Presets { get; set; }
}
}
Loading