From f2dc472a2055fd3986ce82658bf9e1eeca6fb712 Mon Sep 17 00:00:00 2001 From: Atria1234 Date: Sun, 28 Nov 2021 10:19:45 +0100 Subject: [PATCH] Created layout preset library dialog (#375) --- .../PreparedLayout/PresetLayoutLoader.cs | 211 +++++++++++++ .../Converters/ReferenceToBooleanConverter.cs | 40 +++ ...ataIO.cs => FrameworkElementExtensions.cs} | 29 +- AnnoDesigner/Localization/Localization.cs | 3 +- AnnoDesigner/Localization/LocalizeBinding.cs | 54 ++++ AnnoDesigner/MainWindow.xaml | 2 + AnnoDesigner/PresetLayoutWindow.xaml | 63 ++++ AnnoDesigner/PresetLayoutWindow.xaml.cs | 107 +++++++ AnnoDesigner/ViewModels/MainViewModel.cs | 279 ++++++++++-------- 9 files changed, 661 insertions(+), 127 deletions(-) create mode 100644 AnnoDesigner.Core/Layout/PreparedLayout/PresetLayoutLoader.cs create mode 100644 AnnoDesigner/Converters/ReferenceToBooleanConverter.cs rename AnnoDesigner/{DataIO.cs => FrameworkElementExtensions.cs} (57%) create mode 100644 AnnoDesigner/PresetLayoutWindow.xaml create mode 100644 AnnoDesigner/PresetLayoutWindow.xaml.cs diff --git a/AnnoDesigner.Core/Layout/PreparedLayout/PresetLayoutLoader.cs b/AnnoDesigner.Core/Layout/PreparedLayout/PresetLayoutLoader.cs new file mode 100644 index 00000000..03782fe9 --- /dev/null +++ b/AnnoDesigner.Core/Layout/PreparedLayout/PresetLayoutLoader.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using AnnoDesigner.Core.Layout.Models; +using Newtonsoft.Json; + +namespace AnnoDesigner.Core.Layout.PreparedLayout +{ + public interface IPresetLayoutBase + { + public MultilangInfo Name { get; } + } + + public class PresetLayoutDirectory : IPresetLayoutBase + { + public MultilangInfo Name { get; set; } + + public List Directories { get; set; } = new List(); + + public List Layouts { get; set; } = new List(); + + public IEnumerable Presets => Directories.Cast().Concat(Layouts); + } + + [JsonConverter(typeof(MultilangInfoConverter))] + public class MultilangInfo + { + private class MultilangInfoConverter : JsonConverter + { + 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>(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 Translations { get; set; } + + private string Default { get; set; } + + public string this[string language] + { + set + { + Translations ??= new Dictionary(); + 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 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); + } + } + + public class LayoutPresetInfo + { + public MultilangInfo Name { get; set; } + + public MultilangInfo Description { get; set; } + + public string Author { get; set; } + + public string AuthorContact { get; set; } + } + + public class PresetLayout : IPresetLayoutBase + { + public MultilangInfo Name => Info.Name; + + public LayoutPresetInfo Info { get; set; } + + public LayoutFile Layout { get; set; } + + public List Images { get; set; } + } + + public class PresetLayoutLoader + { + public Func RenderLayoutToImage { get; set; } + + public PresetLayoutLoader(Func renderLayoutToImage) + { + RenderLayoutToImage = renderLayoutToImage; + } + + public List Load(string rootDirectory) + { + var data = LoadDirectory(rootDirectory); + + return data.Directories.Cast().Concat(data.Layouts).ToList(); + } + + private PresetLayoutDirectory LoadDirectory(string directory) + { + try + { + return new PresetLayoutDirectory() + { + Name = Path.GetFileName(directory), + Directories = Directory.GetDirectories(directory).Select(LoadDirectory).Where(d => d != null).ToList(), + Layouts = Directory.GetFiles(directory, "*.zip").Select(LoadLayout).Where(f => f != null).ToList() + }; + } + catch { /* TODO log */ } + + return null; + } + + private PresetLayout LoadLayout(string file) + { + try + { + var images = new List(); + LayoutFile layout = null; + var info = new LayoutPresetInfo() + { + Name = Path.GetFileNameWithoutExtension(file) + }; + + using var zipFile = ZipFile.OpenRead(file); + foreach (var item in zipFile.Entries) + { + using var stream = item.Open(); + switch (Path.GetExtension(item.FullName).ToLowerInvariant()) + { + case ".png": + case ".jpg": + case ".jpeg": + var imageStream = new MemoryStream(); + stream.CopyTo(imageStream); + var image = new BitmapImage(); + image.BeginInit(); + image.StreamSource = imageStream; + image.EndInit(); + image.Freeze(); + images.Add(image); + break; + case ".ad": + layout = new LayoutLoader().LoadLayout(stream, true); + images.Insert(0, RenderLayoutToImage(layout)); + break; + case ".json": + using (var streamReader = new StreamReader(stream)) + { + info = JsonConvert.DeserializeObject(streamReader.ReadToEnd()); + } + break; + } + } + + if (layout != null) + { + return new PresetLayout() + { + Info = info, + Layout = layout, + Images = images + }; + } + } + catch { /* TODO log */ } + + return null; + } + } +} diff --git a/AnnoDesigner/Converters/ReferenceToBooleanConverter.cs b/AnnoDesigner/Converters/ReferenceToBooleanConverter.cs new file mode 100644 index 00000000..8342fccf --- /dev/null +++ b/AnnoDesigner/Converters/ReferenceToBooleanConverter.cs @@ -0,0 +1,40 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace AnnoDesigner.Converters +{ + [ValueConversion(typeof(object), typeof(bool))] + public class ReferenceToBooleanConverter : IValueConverter + { + public bool NullValue { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value == null == NullValue; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + [ValueConversion(typeof(object), typeof(bool))] + public class ReferenceTypeToBooleanConverter : IValueConverter + { + public Type ExpectedType { get; set; } + + public bool ValueOnCorrectType { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (value != null && value.GetType() == ExpectedType) == ValueOnCorrectType; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/AnnoDesigner/DataIO.cs b/AnnoDesigner/FrameworkElementExtensions.cs similarity index 57% rename from AnnoDesigner/DataIO.cs rename to AnnoDesigner/FrameworkElementExtensions.cs index 9478db85..2d170b9b 100644 --- a/AnnoDesigner/DataIO.cs +++ b/AnnoDesigner/FrameworkElementExtensions.cs @@ -3,26 +3,35 @@ using System.Windows.Media; using System.Windows.Media.Imaging; -namespace AnnoDesigner +namespace AnnoDesigner.Core.Extensions { /// - /// Provides I/O methods + /// Provides extension methods for framework elements to render it to multiple targets. /// - public static class DataIO + public static class FrameworkElementExtensions { - #region Render to file - /// - /// Renders the given target to an image file, png encoded. + /// Renders the given target to a bitmap. /// /// target to be rendered - /// output filename - public static void RenderToFile(FrameworkElement target, string filename) + /// Bitmap containing rendered framework element + public static RenderTargetBitmap RenderToBitmap(this FrameworkElement target) { - // render control const int dpi = 96; var rtb = new RenderTargetBitmap((int)target.ActualWidth, (int)target.ActualHeight, dpi, dpi, PixelFormats.Default); rtb.Render(target); + + return rtb; + } + + /// + /// Renders the given target to an image file, png encoded. + /// + /// target to be rendered + /// output filename + public static void RenderToFile(this FrameworkElement target, string filename) + { + var rtb = target.RenderToBitmap(); // put result into bitmap var encoder = Constants.GetExportImageEncoder(); encoder.Frames.Add(BitmapFrame.Create(rtb)); @@ -32,7 +41,5 @@ public static void RenderToFile(FrameworkElement target, string filename) encoder.Save(file); } } - - #endregion } } \ No newline at end of file diff --git a/AnnoDesigner/Localization/Localization.cs b/AnnoDesigner/Localization/Localization.cs index c17e1c3f..c140781f 100644 --- a/AnnoDesigner/Localization/Localization.cs +++ b/AnnoDesigner/Localization/Localization.cs @@ -36,7 +36,7 @@ private Localization() { } private static IDictionary> TranslationsRaw { get; set; } - private string SelectedLanguageCode => _commons.CurrentLanguageCode; + public string SelectedLanguageCode => _commons.CurrentLanguageCode; public static IDictionary Translations => TranslationsRaw[Instance.SelectedLanguageCode]; @@ -1253,6 +1253,7 @@ private void Commons_SelectedLanguageChanged(object sender, EventArgs e) { OnPropertyChanged(nameof(Translations)); OnPropertyChanged(nameof(InstanceTranslations)); + OnPropertyChanged(nameof(SelectedLanguageCode)); } public string GetLocalization(string valueToTranslate) diff --git a/AnnoDesigner/Localization/LocalizeBinding.cs b/AnnoDesigner/Localization/LocalizeBinding.cs index 528fb51c..ae6bba2a 100644 --- a/AnnoDesigner/Localization/LocalizeBinding.cs +++ b/AnnoDesigner/Localization/LocalizeBinding.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Windows; using System.Windows.Data; +using AnnoDesigner.Core.Layout.PreparedLayout; namespace AnnoDesigner.Localization { @@ -92,4 +94,56 @@ public DynamicLocalize(string keyPath) : this() KeyPath = keyPath; } } + + public class Multilang : MultiBinding + { + private class MultilangInfoConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length == 2) + { + if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue) + { + return DependencyProperty.UnsetValue; + } + + if (values[0] is string language && values[1] is MultilangInfo translations) + { + return translations.Translate(language); + } + } + throw new Exception($"Incorrect Multilang parameters. {values[1]}"); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + private static MultilangInfoConverter SingletonConverter { get; } = new MultilangInfoConverter(); + + public string Path + { + set + { + Bindings.Add(new Binding(value)); + } + } + + public Multilang() + { + Bindings.Add(new Binding(nameof(Localization.Instance.SelectedLanguageCode)) + { + Source = Localization.Instance + }); + Converter = SingletonConverter; + } + + public Multilang(string path) : this() + { + Path = path; + } + } } diff --git a/AnnoDesigner/MainWindow.xaml b/AnnoDesigner/MainWindow.xaml index 88e812f0..73c788e5 100644 --- a/AnnoDesigner/MainWindow.xaml +++ b/AnnoDesigner/MainWindow.xaml @@ -198,6 +198,8 @@ Command="{Binding CanvasResetZoomCommand}" /> + diff --git a/AnnoDesigner/PresetLayoutWindow.xaml b/AnnoDesigner/PresetLayoutWindow.xaml new file mode 100644 index 00000000..a68e6f92 --- /dev/null +++ b/AnnoDesigner/PresetLayoutWindow.xaml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +