diff --git a/AnnoDesigner.Core/DataStructures/QuadTree.cs b/AnnoDesigner.Core/DataStructures/QuadTree.cs index 0c6d10cb..493a7e3b 100644 --- a/AnnoDesigner.Core/DataStructures/QuadTree.cs +++ b/AnnoDesigner.Core/DataStructures/QuadTree.cs @@ -401,6 +401,15 @@ public QuadTree(Rect extent) root = new Quadrant(extent); } + /// + /// Create a + /// + /// The bounds of the + public QuadTree(Rect extent, IEnumerable enumerable) : this(extent) + { + AddRange(enumerable); + } + /// /// Insert a item into the QuadTree. /// diff --git a/AnnoDesigner.Core/Extensions/IEnumerableExtensions.cs b/AnnoDesigner.Core/Extensions/IEnumerableExtensions.cs index f2023daf..45ea9b0d 100644 --- a/AnnoDesigner.Core/Extensions/IEnumerableExtensions.cs +++ b/AnnoDesigner.Core/Extensions/IEnumerableExtensions.cs @@ -115,5 +115,33 @@ public static bool IsIgnoredObject(this AnnoObject annoObject) { return string.Equals(annoObject.Template, "Blocker", StringComparison.OrdinalIgnoreCase); } + + public static T MinOrDefault(this IEnumerable enumerable, T @default = default) + { + if (!enumerable.Any()) return @default; + + return enumerable.Min(); + } + + public static TResult MinOrDefault(this IEnumerable enumerable, Func selector, TResult @default = default) + { + if (!enumerable.Any()) return @default; + + return enumerable.Min(selector); + } + + public static T MaxOrDefault(this IEnumerable enumerable, T @default = default) + { + if (!enumerable.Any()) return @default; + + return enumerable.Max(); + } + + public static TResult MaxOrDefault(this IEnumerable enumerable, Func selector, TResult @default = default) + { + if (!enumerable.Any()) return @default; + + return enumerable.Max(selector); + } } } diff --git a/AnnoDesigner.Core/Layout/LayoutLoader.cs b/AnnoDesigner.Core/Layout/LayoutLoader.cs index 79e80cee..a54e7bc8 100644 --- a/AnnoDesigner.Core/Layout/LayoutLoader.cs +++ b/AnnoDesigner.Core/Layout/LayoutLoader.cs @@ -64,6 +64,17 @@ public LayoutFile LoadLayout(Stream streamWithLayout, bool forceLoad = false) return Load(jsonString, forceLoad); } + public async Task 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 }; @@ -88,5 +99,10 @@ private LayoutFile Load(string jsonString, bool forceLoad) _ => throw new NotImplementedException() }; } + + private Task LoadAsync(string jsonString, bool forceLoad) + { + return Task.Run(() => Load(jsonString, forceLoad)); + } } } diff --git a/AnnoDesigner.Core/Layout/Models/LayoutFile.cs b/AnnoDesigner.Core/Layout/Models/LayoutFile.cs index e3b7fd02..475a163b 100644 --- a/AnnoDesigner.Core/Layout/Models/LayoutFile.cs +++ b/AnnoDesigner.Core/Layout/Models/LayoutFile.cs @@ -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; @@ -21,5 +22,13 @@ public LayoutFile(IEnumerable 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(); + } } } \ No newline at end of file diff --git a/AnnoDesigner.Core/Layout/Presets/IPresetLayout.cs b/AnnoDesigner.Core/Layout/Presets/IPresetLayout.cs new file mode 100644 index 00000000..790c7cc6 --- /dev/null +++ b/AnnoDesigner.Core/Layout/Presets/IPresetLayout.cs @@ -0,0 +1,7 @@ +namespace AnnoDesigner.Core.Layout.Presets +{ + public interface IPresetLayout + { + public MultilangInfo Name { get; } + } +} diff --git a/AnnoDesigner.Core/Layout/Presets/LayoutPresetInfo.cs b/AnnoDesigner.Core/Layout/Presets/LayoutPresetInfo.cs new file mode 100644 index 00000000..2c4c73e4 --- /dev/null +++ b/AnnoDesigner.Core/Layout/Presets/LayoutPresetInfo.cs @@ -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; + } + } +} diff --git a/AnnoDesigner.Core/Layout/Presets/MultilangInfo.cs b/AnnoDesigner.Core/Layout/Presets/MultilangInfo.cs new file mode 100644 index 00000000..53a5390c --- /dev/null +++ b/AnnoDesigner.Core/Layout/Presets/MultilangInfo.cs @@ -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 + { + 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); + } + } +} diff --git a/AnnoDesigner.Core/Layout/Presets/PresetLayout.cs b/AnnoDesigner.Core/Layout/Presets/PresetLayout.cs new file mode 100644 index 00000000..7cd0da93 --- /dev/null +++ b/AnnoDesigner.Core/Layout/Presets/PresetLayout.cs @@ -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 images; + private IFileSystem fileSystem; + private Func> 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 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 OpenAsync(string zipFile, Func> 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(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> 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 + { + 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; + } + } + } +} diff --git a/AnnoDesigner.Core/Layout/Presets/PresetLayoutDirectory.cs b/AnnoDesigner.Core/Layout/Presets/PresetLayoutDirectory.cs new file mode 100644 index 00000000..ac990162 --- /dev/null +++ b/AnnoDesigner.Core/Layout/Presets/PresetLayoutDirectory.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace AnnoDesigner.Core.Layout.Presets +{ + public class PresetLayoutDirectory : IPresetLayout + { + public MultilangInfo Name { get; set; } + + public List Presets { get; set; } + } +} diff --git a/AnnoDesigner.Core/Layout/Presets/PresetLayoutLoader.cs b/AnnoDesigner.Core/Layout/Presets/PresetLayoutLoader.cs new file mode 100644 index 00000000..99c3ef49 --- /dev/null +++ b/AnnoDesigner.Core/Layout/Presets/PresetLayoutLoader.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO.Abstractions; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Media; +using AnnoDesigner.Core.Layout.Models; +using NLog; + +namespace AnnoDesigner.Core.Layout.Presets +{ + public class PresetLayoutLoader + { + private readonly IFileSystem _fileSystem; + private static readonly Logger Logger = LogManager.GetLogger(nameof(PresetLayoutLoader)); + + public Func> RenderLayoutToImage { get; set; } + public PresetLayoutLoader(Func> renderLayoutToImage, IFileSystem fileSystem = null) + { + RenderLayoutToImage = renderLayoutToImage; + _fileSystem = fileSystem ?? new FileSystem(); + } + + public async Task> LoadAsync(string rootDirectory) + { + var data = await LoadDirectoryAsync(rootDirectory).ConfigureAwait(false); + + return data.Presets; + } + + private async Task LoadDirectoryAsync(string directory) + { + var subdirectories = await Task.WhenAll(_fileSystem.Directory.GetDirectories(directory).Select(LoadDirectoryAsync)).ConfigureAwait(false); + var layouts = await Task.WhenAll(_fileSystem.Directory.GetFiles(directory, "*.zip").Select(LoadLayoutAsync)).ConfigureAwait(false); + + return new PresetLayoutDirectory() + { + Name = _fileSystem.Path.GetFileName(directory), + Presets = subdirectories.Cast().Concat(layouts.Where(f => f != null)).ToList() + }; + } + + private async Task LoadLayoutAsync(string file) + { + try + { + return await PresetLayout.OpenAsync(file, RenderLayoutToImage, _fileSystem).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.Warn(e, $"Failed to parse loadout preset {file}"); + } + + return null; + } + } +} diff --git a/AnnoDesigner.Core/Models/IAppSettings.cs b/AnnoDesigner.Core/Models/IAppSettings.cs index 005e1db3..4a7b3b25 100644 --- a/AnnoDesigner.Core/Models/IAppSettings.cs +++ b/AnnoDesigner.Core/Models/IAppSettings.cs @@ -56,5 +56,6 @@ public interface IAppSettings bool InvertScrollingDirection { get; set; } bool ShowScrollbars { get; set; } bool IncludeRoadsInStatisticCalculation { get; set; } + string PresetLayoutLocation { get; set; } } } diff --git a/AnnoDesigner/AnnoCanvas.xaml.cs b/AnnoDesigner/AnnoCanvas.xaml.cs index a1105a6e..701a36ce 100644 --- a/AnnoDesigner/AnnoCanvas.xaml.cs +++ b/AnnoDesigner/AnnoCanvas.xaml.cs @@ -755,6 +755,14 @@ public AnnoCanvas(BuildingPresets presetsToUse, StatisticsUpdated?.Invoke(this, UpdateStatisticsEventArgs.All); } + public void Uninitialize() + { + _appSettings.SettingsChanged -= AppSettings_SettingsChanged; + + SelectedObjects.Clear(); + PlacedObjects.Clear(); + } + #endregion private bool _showScrollBars; diff --git a/AnnoDesigner/AnnoDesigner.csproj b/AnnoDesigner/AnnoDesigner.csproj index c8685b16..2139ea25 100644 --- a/AnnoDesigner/AnnoDesigner.csproj +++ b/AnnoDesigner/AnnoDesigner.csproj @@ -56,6 +56,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + Always + + + Always + SettingsSingleFileGenerator Settings.Designer.cs @@ -83,6 +95,7 @@ + diff --git a/AnnoDesigner/Constants.cs b/AnnoDesigner/Constants.cs index fc65bb50..ebdf8469 100644 --- a/AnnoDesigner/Constants.cs +++ b/AnnoDesigner/Constants.cs @@ -108,5 +108,10 @@ public static class Constants /// The default number of recent files to show. /// public const int MaxRecentFiles = 10; + + /// + /// Default location of preset layout folder. + /// + public const string DefaultPresetLayoutLocation = ".\\Layouts"; } } diff --git a/AnnoDesigner/Converters/ReferenceToBooleanConverter.cs b/AnnoDesigner/Converters/ReferenceToBooleanConverter.cs new file mode 100644 index 00000000..5f711fa1 --- /dev/null +++ b/AnnoDesigner/Converters/ReferenceToBooleanConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace AnnoDesigner.Converters +{ + [ValueConversion(typeof(object), typeof(object))] + public class ReferenceToValueConverter : IValueConverter + { + public object NullValue { get; set; } + + public object NotNullValue { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value == null ? NullValue : NotNullValue; + } + + 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/ImageWindow.xaml b/AnnoDesigner/ImageWindow.xaml new file mode 100644 index 00000000..c5633525 --- /dev/null +++ b/AnnoDesigner/ImageWindow.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/AnnoDesigner/ImageWindow.xaml.cs b/AnnoDesigner/ImageWindow.xaml.cs new file mode 100644 index 00000000..03521afd --- /dev/null +++ b/AnnoDesigner/ImageWindow.xaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace AnnoDesigner +{ + /// + /// Interaction logic for ImageWindow.xaml + /// + public partial class ImageWindow : Window + { + public ImageWindow() + { + InitializeComponent(); + } + } +} diff --git a/AnnoDesigner/Layouts/Arctic/Medium Islands/colony03_a01_01.zip b/AnnoDesigner/Layouts/Arctic/Medium Islands/colony03_a01_01.zip new file mode 100644 index 00000000..babeb22a Binary files /dev/null and b/AnnoDesigner/Layouts/Arctic/Medium Islands/colony03_a01_01.zip differ diff --git a/AnnoDesigner/Layouts/Arctic/Plateaus/colony03_a02_01.zip b/AnnoDesigner/Layouts/Arctic/Plateaus/colony03_a02_01.zip new file mode 100644 index 00000000..d18d2014 Binary files /dev/null and b/AnnoDesigner/Layouts/Arctic/Plateaus/colony03_a02_01.zip differ diff --git a/AnnoDesigner/Layouts/LayoutInfoSchema.json b/AnnoDesigner/Layouts/LayoutInfoSchema.json new file mode 100644 index 00000000..068488f2 --- /dev/null +++ b/AnnoDesigner/Layouts/LayoutInfoSchema.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema", + "type": "object", + "definitions": { + "multilang": { + "oneOf": [ + { + "type": "string", + "description": "Single value used for all languages" + }, + { + "type": "object", + "properties": { + "eng": { + "type": "string" + }, + "ger": { + "type": "string" + }, + "fra": { + "type": "string" + }, + "esp": { + "type": "string" + }, + "pol": { + "type": "string" + }, + "rus": { + "type": "string" + } + } + } + ] + } + }, + "properties": { + "Name": { + "#ref": "/definitions/multilang" + }, + "Description": { + "#ref": "/definitions/multilang" + }, + "Author": { + "type": "string" + }, + "AuthorContact": { + "type": "string" + } + }, + "required": [ + "Name" + ] +} \ No newline at end of file diff --git a/AnnoDesigner/Layouts/Readme.md b/AnnoDesigner/Layouts/Readme.md new file mode 100644 index 00000000..bde70c90 --- /dev/null +++ b/AnnoDesigner/Layouts/Readme.md @@ -0,0 +1,14 @@ +Each ZIP file in this folder and all subfolders should contain information about layout preset + +Layout preset must have: +- layout file named layout.ad + +Layout preset can have: +- info file names info.json + - JSON schema of this file is defined in LayoutInfoSchema.json in this folder + - if not provided the layout preset's name will be the ZIP file name +- any number of image files which should be associated with that layout + - supported file types are "png", "jpg" and "jpeg" (case insensitive) + +ZIP files which fail to load as layout preset are ignored and not shown in AnnoDesigner +Info about ZIP file failing to load is writen to the application log diff --git a/AnnoDesigner/Localization/Localization.cs b/AnnoDesigner/Localization/Localization.cs index 60cf4386..40b8ffee 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]; @@ -1283,6 +1283,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..9ca89da7 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.Presets; namespace AnnoDesigner.Localization { @@ -92,4 +94,61 @@ 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[1] is null) + { + return null; + } + + 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 6849678e..bcdb3c8e 100644 --- a/AnnoDesigner/MainWindow.xaml +++ b/AnnoDesigner/MainWindow.xaml @@ -199,6 +199,8 @@ Command="{Binding CanvasResetZoomCommand}" /> + diff --git a/AnnoDesigner/Models/AppSettings.cs b/AnnoDesigner/Models/AppSettings.cs index af57f83e..c524f19b 100644 --- a/AnnoDesigner/Models/AppSettings.cs +++ b/AnnoDesigner/Models/AppSettings.cs @@ -293,6 +293,12 @@ public bool IncludeRoadsInStatisticCalculation get => Settings.Default.IncludeRoadsInStatisticCalculation; set => Settings.Default.IncludeRoadsInStatisticCalculation = value; } + + public string PresetLayoutLocation + { + get => Settings.Default.PresetLayoutLocation; + set => Settings.Default.PresetLayoutLocation = value; + } } } diff --git a/AnnoDesigner/Models/CanvasRenderSetting.cs b/AnnoDesigner/Models/CanvasRenderSetting.cs new file mode 100644 index 00000000..af28fabd --- /dev/null +++ b/AnnoDesigner/Models/CanvasRenderSetting.cs @@ -0,0 +1,17 @@ +namespace AnnoDesigner.Models +{ + public class CanvasRenderSetting + { + public int? GridSize { get; set; } + + public bool RenderStatistics { get; set; } + public bool RenderVersion { get; set; } + public bool RenderGrid { get; set; } + public bool RenderIcon { get; set; } + public bool RenderLabel { get; set; } + public bool RenderHarborBlockedArea { get; set; } + public bool RenderPanorama { get; set; } + public bool RenderTrueInfluenceRange { get; set; } + public bool RenderInfluences { get; set; } + } +} diff --git a/AnnoDesigner/PreferencesPages/GeneralSettingsPage.xaml b/AnnoDesigner/PreferencesPages/GeneralSettingsPage.xaml index f5d79630..1715ab4d 100644 --- a/AnnoDesigner/PreferencesPages/GeneralSettingsPage.xaml +++ b/AnnoDesigner/PreferencesPages/GeneralSettingsPage.xaml @@ -22,7 +22,8 @@ + Margin="10,5,0,0" + Grid.IsSharedSizeScope="True"> @@ -33,7 +34,8 @@ + Width="Auto" + SharedSizeGroup="TitleColumn"/> @@ -74,12 +76,11 @@ Visibility="{Binding IsGridLineColorPickerVisible, Converter={StaticResource converterBoolToVisibilityCollapsed}}" /> - + + SharedSizeGroup="TitleColumn" /> @@ -120,12 +121,11 @@ Visibility="{Binding IsObjectBorderLineColorPickerVisible, Converter={StaticResource converterBoolToVisibilityCollapsed}}" /> - + + SharedSizeGroup="TitleColumn" /> @@ -190,7 +190,6 @@ - @@ -204,7 +203,8 @@ + Width="Auto" + SharedSizeGroup="TitleColumn"/> @@ -241,5 +241,44 @@ + + + + + + + + + + + + + + + + + + + + +