diff --git a/Xwt/Xwt.Drawing/Image.cs b/Xwt/Xwt.Drawing/Image.cs index 75427f0a7..b7472ce5b 100644 --- a/Xwt/Xwt.Drawing/Image.cs +++ b/Xwt/Xwt.Drawing/Image.cs @@ -31,6 +31,7 @@ using System.Reflection; using System.IO; using System.Collections.Generic; +using System.Diagnostics; namespace Xwt.Drawing { @@ -268,7 +269,7 @@ static bool ParseImageHints (string baseName, string fileName, string ext, out i return false; } else i2 = fileName.Length; - tags = new ImageTagSet (fileName.Substring (i, i2 - i)); + tags = ImageTagSet.Parse (fileName.Substring (i, i2 - i)); return true; } else { @@ -288,7 +289,7 @@ static bool ParseImageHints (string baseName, string fileName, string ext, out i return false; } if (i2 + 2 < fileName.Length) - tags = new ImageTagSet (fileName.Substring (i2 + 2)); + tags = ImageTagSet.Parse (fileName.Substring (i2 + 2)); return true; } } @@ -307,18 +308,23 @@ public static Image CreateMultiSizeIcon (IEnumerable images) // If one of the images is themed, then the whole resulting image will be themed. // To create the new image, we group images with the same theme but different size, and we create a multi-size icon for those. // The resulting image is the combination of those multi-size icons. - var allThemes = allImages.OfType ().SelectMany (i => i.Images).Select (i => new ImageTagSet (i.Item2)).Distinct ().ToArray (); + var allThemes = allImages + .OfType () + .SelectMany (i => i.Images) + .Select(i => i.Item2) + .Distinct (TagSetEqualityComparer.Instance) + .ToArray (); List> newImages = new List> (); foreach (var ts in allThemes) { List multiSizeImages = new List (); foreach (var i in allImages) { if (i is ThemedImage) - multiSizeImages.Add (((ThemedImage)i).GetImage (ts.AsArray)); + multiSizeImages.Add (((ThemedImage)i).GetImage (ts)); else multiSizeImages.Add (i); } var img = CreateMultiSizeIcon (multiSizeImages); - newImages.Add (new Tuple (img, ts.AsArray)); + newImages.Add (new Tuple (img, ts)); } return new ThemedImage (newImages); } else { @@ -1013,65 +1019,74 @@ 2 active "~contrast~dark~disabled", }; - readonly string[][] knownTagArrays = new[] { - new[] { "dark", }, - new[] { "contrast", }, - new[] { "contrast", "dark", }, - new[] { "sel", }, - new[] { "dark", "sel", }, - new[] { "disabled", }, - new[] { "dark", "disabled", }, - new[] { "contrast", "disabled", }, - new[] { "contrast", "dark", "disabled", }, + readonly ImageTagSet[] knownTagArrays = new[] { + new ImageTagSet(new[] { "dark", }), + new ImageTagSet(new[] { "contrast", }), + new ImageTagSet(new[] { "contrast", "dark", }), + new ImageTagSet(new[] { "sel", }), + new ImageTagSet(new[] { "dark", "sel", }), + new ImageTagSet(new[] { "disabled", }), + new ImageTagSet(new[] { "dark", "disabled", }), + new ImageTagSet(new[] { "contrast", "disabled", }), + new ImageTagSet(new[] { "contrast", "dark", "disabled", }), }; - static readonly char[] tagSeparators = { '~' }; - public bool GetTagArray(string tags, out string[] tagArray) + public ImageTagSet TryGetTagSet(string tags) { var index = Array.IndexOf(knownTags, tags); - tagArray = index >= 0 ? knownTagArrays[index] : SplitTags(tags); - return index >= 0; + return index >= 0 ? knownTagArrays[index] : null; } + } - static string[] SplitTags(string tags) - { - var array = tags.Split(tagSeparators, StringSplitOptions.RemoveEmptyEntries); - Array.Sort(array); + // As much as I don't like the duplication, it's simpler than accessing a static instance every time. + class TagSetEqualityComparer : IEqualityComparer + { + public static TagSetEqualityComparer Instance { get; } = new TagSetEqualityComparer(); + + public bool Equals(string[] x, string[] y) => x.SequenceEqual(y); - return array; + public int GetHashCode(string[] obj) + { + unchecked + { + int c = 0; + foreach (var s in obj) + c %= s.GetHashCode(); + return c; + } } } + [DebuggerDisplay("{DebuggerDisplay,nq}")] sealed class ImageTagSet { - string tags; string[] tagsArray; public static readonly ImageTagSet Empty = new ImageTagSet (new string[0]); static readonly ImageTagCache imageTagCache; + static readonly char[] tagSeparators = { '~' }; - public ImageTagSet (string [] tagsArray) + public static ImageTagSet Parse(string tags) { - this.tagsArray = tagsArray; - Array.Sort (tagsArray); + return imageTagCache.TryGetTagSet(tags) ?? Create(tags); } - public bool IsEmpty { - get { - return tagsArray.Length == 0; - } + static ImageTagSet Create(string tags) + { + var tagArray = tags.Split(tagSeparators, StringSplitOptions.RemoveEmptyEntries); + Array.Sort(tagArray); + + return new ImageTagSet(tagArray); } - public ImageTagSet (string tags) + public ImageTagSet (string [] tagsArray) { - imageTagCache.GetTagArray(tags, out tagsArray); + this.tagsArray = tagsArray; } - public string AsString { + public bool IsEmpty { get { - if (tags == null) - tags = string.Join ("~", tagsArray); - return tags; + return tagsArray.Length == 0; } } @@ -1096,6 +1111,8 @@ public override int GetHashCode () return c; } } + + string DebuggerDisplay => string.Join("~", tagsArray); } abstract class ImageLoader