From 3e17bb571fb9e9c9ee2e13de95affdee464fd01a Mon Sep 17 00:00:00 2001 From: Matthias Hoste <42743095+lifecoder-phoenix@users.noreply.github.com> Date: Sat, 24 Aug 2019 22:06:00 +0200 Subject: [PATCH 01/48] Fix for #2544 --- src/Shared/PlatformSupport/AssetLoader.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 9d921acde6..06f0b114e8 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Web; using System.Linq; using System.Reflection; using Avalonia.Platform; @@ -242,6 +243,7 @@ namespace Avalonia.Shared.PlatformSupport throw new InvalidOperationException( $"Assembly {name} needs to be referenced and explicitly loaded before loading resources"); #else + name = HttpUtility.UrlDecode(name); AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name)); #endif } From 7a65748b040d32a4167cd59f85af1be8453279d4 Mon Sep 17 00:00:00 2001 From: LifeCoder Date: Sat, 24 Aug 2019 22:36:59 +0200 Subject: [PATCH 02/48] Update to my fix --- src/Shared/PlatformSupport/AssetLoader.cs | 1 - src/Shared/PlatformSupport/HttpUtility.cs | 693 ++++++++++++++++++ .../PlatformSupport/PlatformSupport.projitems | 1 + 3 files changed, 694 insertions(+), 1 deletion(-) create mode 100644 src/Shared/PlatformSupport/HttpUtility.cs diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 06f0b114e8..1daec490b9 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Web; using System.Linq; using System.Reflection; using Avalonia.Platform; diff --git a/src/Shared/PlatformSupport/HttpUtility.cs b/src/Shared/PlatformSupport/HttpUtility.cs new file mode 100644 index 0000000000..8a56481a19 --- /dev/null +++ b/src/Shared/PlatformSupport/HttpUtility.cs @@ -0,0 +1,693 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Security.Permissions; +using System.Text; +using System.Web.Util; + +namespace Avalonia.Shared.PlatformSupport +{ + public sealed class HttpUtility + { + sealed class HttpQSCollection : NameValueCollection + { + public override string ToString() + { + int count = Count; + if (count == 0) + return ""; + StringBuilder sb = new StringBuilder(); + string[] keys = AllKeys; + for (int i = 0; i < count; i++) + { + sb.AppendFormat("{0}={1}&", keys[i], UrlEncode(this[keys[i]])); + } + if (sb.Length > 0) + sb.Length--; + return sb.ToString(); + } + } + + #region Constructors + + public HttpUtility() + { + } + + #endregion // Constructors + + #region Methods + + public static void HtmlAttributeEncode(string s, TextWriter output) + { + if (output == null) + { + throw new ArgumentNullException("output"); + } + HttpEncoder.Current.HtmlAttributeEncode(s, output); + } + + public static string HtmlAttributeEncode(string s) + { + if (s == null) + return null; + + using (var sw = new StringWriter()) + { + HttpEncoder.Current.HtmlAttributeEncode(s, sw); + return sw.ToString(); + } + } + + public static string UrlDecode(string str) + { + return UrlDecode(str, Encoding.UTF8); + } + + static char[] GetChars(MemoryStream b, Encoding e) + { + return e.GetChars(b.GetBuffer(), 0, (int)b.Length); + } + + static void WriteCharBytes(IList buf, char ch, Encoding e) + { + if (ch > 255) + { + foreach (byte b in e.GetBytes(new char[] { ch })) + buf.Add(b); + } + else + buf.Add((byte)ch); + } + + public static string UrlDecode(string str, Encoding e) + { + if (null == str) + return null; + + if (str.IndexOf('%') == -1 && str.IndexOf('+') == -1) + return str; + + if (e == null) + e = Encoding.UTF8; + + long len = str.Length; + var bytes = new List(); + int xchar; + char ch; + + for (int i = 0; i < len; i++) + { + ch = str[i]; + if (ch == '%' && i + 2 < len && str[i + 1] != '%') + { + if (str[i + 1] == 'u' && i + 5 < len) + { + // unicode hex sequence + xchar = GetChar(str, i + 2, 4); + if (xchar != -1) + { + WriteCharBytes(bytes, (char)xchar, e); + i += 5; + } + else + WriteCharBytes(bytes, '%', e); + } + else if ((xchar = GetChar(str, i + 1, 2)) != -1) + { + WriteCharBytes(bytes, (char)xchar, e); + i += 2; + } + else + { + WriteCharBytes(bytes, '%', e); + } + continue; + } + + if (ch == '+') + WriteCharBytes(bytes, ' ', e); + else + WriteCharBytes(bytes, ch, e); + } + + byte[] buf = bytes.ToArray(); + bytes = null; + return e.GetString(buf); + + } + + public static string UrlDecode(byte[] bytes, Encoding e) + { + if (bytes == null) + return null; + + return UrlDecode(bytes, 0, bytes.Length, e); + } + + static int GetInt(byte b) + { + char c = (char)b; + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; + } + + static int GetChar(byte[] bytes, int offset, int length) + { + int value = 0; + int end = length + offset; + for (int i = offset; i < end; i++) + { + int current = GetInt(bytes[i]); + if (current == -1) + return -1; + value = (value << 4) + current; + } + + return value; + } + + static int GetChar(string str, int offset, int length) + { + int val = 0; + int end = length + offset; + for (int i = offset; i < end; i++) + { + char c = str[i]; + if (c > 127) + return -1; + + int current = GetInt((byte)c); + if (current == -1) + return -1; + val = (val << 4) + current; + } + + return val; + } + + public static string UrlDecode(byte[] bytes, int offset, int count, Encoding e) + { + if (bytes == null) + return null; + if (count == 0) + return String.Empty; + + if (bytes == null) + throw new ArgumentNullException("bytes"); + + if (offset < 0 || offset > bytes.Length) + throw new ArgumentOutOfRangeException("offset"); + + if (count < 0 || offset + count > bytes.Length) + throw new ArgumentOutOfRangeException("count"); + + StringBuilder output = new StringBuilder(); + MemoryStream acc = new MemoryStream(); + + int end = count + offset; + int xchar; + for (int i = offset; i < end; i++) + { + if (bytes[i] == '%' && i + 2 < count && bytes[i + 1] != '%') + { + if (bytes[i + 1] == (byte)'u' && i + 5 < end) + { + if (acc.Length > 0) + { + output.Append(GetChars(acc, e)); + acc.SetLength(0); + } + xchar = GetChar(bytes, i + 2, 4); + if (xchar != -1) + { + output.Append((char)xchar); + i += 5; + continue; + } + } + else if ((xchar = GetChar(bytes, i + 1, 2)) != -1) + { + acc.WriteByte((byte)xchar); + i += 2; + continue; + } + } + + if (acc.Length > 0) + { + output.Append(GetChars(acc, e)); + acc.SetLength(0); + } + + if (bytes[i] == '+') + { + output.Append(' '); + } + else + { + output.Append((char)bytes[i]); + } + } + + if (acc.Length > 0) + { + output.Append(GetChars(acc, e)); + } + + acc = null; + return output.ToString(); + } + + public static byte[] UrlDecodeToBytes(byte[] bytes) + { + if (bytes == null) + return null; + + return UrlDecodeToBytes(bytes, 0, bytes.Length); + } + + public static byte[] UrlDecodeToBytes(string str) + { + return UrlDecodeToBytes(str, Encoding.UTF8); + } + + public static byte[] UrlDecodeToBytes(string str, Encoding e) + { + if (str == null) + return null; + + if (e == null) + throw new ArgumentNullException("e"); + + return UrlDecodeToBytes(e.GetBytes(str)); + } + + public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count) + { + if (bytes == null) + return null; + if (count == 0) + return new byte[0]; + + int len = bytes.Length; + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException("offset"); + + if (count < 0 || offset > len - count) + throw new ArgumentOutOfRangeException("count"); + + MemoryStream result = new MemoryStream(); + int end = offset + count; + for (int i = offset; i < end; i++) + { + char c = (char)bytes[i]; + if (c == '+') + { + c = ' '; + } + else if (c == '%' && i < end - 2) + { + int xchar = GetChar(bytes, i + 1, 2); + if (xchar != -1) + { + c = (char)xchar; + i += 2; + } + } + result.WriteByte((byte)c); + } + + return result.ToArray(); + } + + public static string UrlEncode(string str) + { + return UrlEncode(str, Encoding.UTF8); + } + + public static string UrlEncode(string str, Encoding e) + { + if (str == null) + return null; + + if (str == String.Empty) + return String.Empty; + + bool needEncode = false; + int len = str.Length; + for (int i = 0; i < len; i++) + { + char c = str[i]; + if ((c < '0') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a') || (c > 'z')) + { + if (HttpEncoder.NotEncoded(c)) + continue; + + needEncode = true; + break; + } + } + + if (!needEncode) + return str; + + // avoided GetByteCount call + byte[] bytes = new byte[e.GetMaxByteCount(str.Length)]; + int realLen = e.GetBytes(str, 0, str.Length, bytes, 0); + return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, 0, realLen)); + } + + public static string UrlEncode(byte[] bytes) + { + if (bytes == null) + return null; + + if (bytes.Length == 0) + return String.Empty; + + return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, 0, bytes.Length)); + } + + public static string UrlEncode(byte[] bytes, int offset, int count) + { + if (bytes == null) + return null; + + if (bytes.Length == 0) + return String.Empty; + + return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, offset, count)); + } + + public static byte[] UrlEncodeToBytes(string str) + { + return UrlEncodeToBytes(str, Encoding.UTF8); + } + + public static byte[] UrlEncodeToBytes(string str, Encoding e) + { + if (str == null) + return null; + + if (str.Length == 0) + return new byte[0]; + + byte[] bytes = e.GetBytes(str); + return UrlEncodeToBytes(bytes, 0, bytes.Length); + } + + public static byte[] UrlEncodeToBytes(byte[] bytes) + { + if (bytes == null) + return null; + + if (bytes.Length == 0) + return new byte[0]; + + return UrlEncodeToBytes(bytes, 0, bytes.Length); + } + + public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count) + { + if (bytes == null) + return null; + return HttpEncoder.Current.UrlEncode(bytes, offset, count); + } + + public static string UrlEncodeUnicode(string str) + { + if (str == null) + return null; + + return Encoding.ASCII.GetString(UrlEncodeUnicodeToBytes(str)); + } + + public static byte[] UrlEncodeUnicodeToBytes(string str) + { + if (str == null) + return null; + + if (str.Length == 0) + return new byte[0]; + + MemoryStream result = new MemoryStream(str.Length); + foreach (char c in str) + { + HttpEncoder.UrlEncodeChar(c, result, true); + } + return result.ToArray(); + } + + /// + /// Decodes an HTML-encoded string and returns the decoded string. + /// + /// The HTML string to decode. + /// The decoded text. + public static string HtmlDecode(string s) + { + if (s == null) + return null; + + using (var sw = new StringWriter()) + { + HttpEncoder.Current.HtmlDecode(s, sw); + return sw.ToString(); + } + } + + /// + /// Decodes an HTML-encoded string and sends the resulting output to a TextWriter output stream. + /// + /// The HTML string to decode + /// The TextWriter output stream containing the decoded string. + public static void HtmlDecode(string s, TextWriter output) + { + if (output == null) + { + throw new ArgumentNullException("output"); + } + + if (!String.IsNullOrEmpty(s)) + { + HttpEncoder.Current.HtmlDecode(s, output); + } + } + + public static string HtmlEncode(string s) + { + if (s == null) + return null; + + using (var sw = new StringWriter()) + { + HttpEncoder.Current.HtmlEncode(s, sw); + return sw.ToString(); + } + } + + /// + /// HTML-encodes a string and sends the resulting output to a TextWriter output stream. + /// + /// The string to encode. + /// The TextWriter output stream containing the encoded string. + public static void HtmlEncode(string s, TextWriter output) + { + if (output == null) + { + throw new ArgumentNullException("output"); + } + + if (!String.IsNullOrEmpty(s)) + { + HttpEncoder.Current.HtmlEncode(s, output); + } + } + public static string HtmlEncode(object value) + { + if (value == null) + return null; + +#if !(MOBILE || NO_SYSTEM_WEB_DEPENDENCY) + IHtmlString htmlString = value as IHtmlString; + if (htmlString != null) + return htmlString.ToHtmlString(); +#endif + + return HtmlEncode(value.ToString()); + } + + public static string JavaScriptStringEncode(string value) + { + return JavaScriptStringEncode(value, false); + } + + public static string JavaScriptStringEncode(string value, bool addDoubleQuotes) + { + if (String.IsNullOrEmpty(value)) + return addDoubleQuotes ? "\"\"" : String.Empty; + + int len = value.Length; + bool needEncode = false; + char c; + for (int i = 0; i < len; i++) + { + c = value[i]; + + if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92) + { + needEncode = true; + break; + } + } + + if (!needEncode) + return addDoubleQuotes ? "\"" + value + "\"" : value; + + var sb = new StringBuilder(); + if (addDoubleQuotes) + sb.Append('"'); + + for (int i = 0; i < len; i++) + { + c = value[i]; + if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62) + sb.AppendFormat("\\u{0:x4}", (int)c); + else switch ((int)c) + { + case 8: + sb.Append("\\b"); + break; + + case 9: + sb.Append("\\t"); + break; + + case 10: + sb.Append("\\n"); + break; + + case 12: + sb.Append("\\f"); + break; + + case 13: + sb.Append("\\r"); + break; + + case 34: + sb.Append("\\\""); + break; + + case 92: + sb.Append("\\\\"); + break; + + default: + sb.Append(c); + break; + } + } + + if (addDoubleQuotes) + sb.Append('"'); + + return sb.ToString(); + } + public static string UrlPathEncode(string str) + { + return HttpEncoder.Current.UrlPathEncode(str); + } + + public static NameValueCollection ParseQueryString(string query) + { + return ParseQueryString(query, Encoding.UTF8); + } + + public static NameValueCollection ParseQueryString(string query, Encoding encoding) + { + if (query == null) + throw new ArgumentNullException("query"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (query.Length == 0 || (query.Length == 1 && query[0] == '?')) + return new HttpQSCollection(); + if (query[0] == '?') + query = query.Substring(1); + + NameValueCollection result = new HttpQSCollection(); + ParseQueryString(query, encoding, result); + return result; + } + + internal static void ParseQueryString(string query, Encoding encoding, NameValueCollection result) + { + if (query.Length == 0) + return; + + string decoded = HtmlDecode(query); + int decodedLength = decoded.Length; + int namePos = 0; + bool first = true; + while (namePos <= decodedLength) + { + int valuePos = -1, valueEnd = -1; + for (int q = namePos; q < decodedLength; q++) + { + if (valuePos == -1 && decoded[q] == '=') + { + valuePos = q + 1; + } + else if (decoded[q] == '&') + { + valueEnd = q; + break; + } + } + + if (first) + { + first = false; + if (decoded[namePos] == '?') + namePos++; + } + + string name, value; + if (valuePos == -1) + { + name = null; + valuePos = namePos; + } + else + { + name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding); + } + if (valueEnd < 0) + { + namePos = -1; + valueEnd = decoded.Length; + } + else + { + namePos = valueEnd + 1; + } + value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding); + + result.Add(name, value); + if (namePos == -1) + break; + } + } + #endregion // Methods + } +} diff --git a/src/Shared/PlatformSupport/PlatformSupport.projitems b/src/Shared/PlatformSupport/PlatformSupport.projitems index 34515a0912..cec13ebffe 100644 --- a/src/Shared/PlatformSupport/PlatformSupport.projitems +++ b/src/Shared/PlatformSupport/PlatformSupport.projitems @@ -11,6 +11,7 @@ + From d37b1f3359d19cf1a7212647362f47926a8344ab Mon Sep 17 00:00:00 2001 From: LifeCoder Date: Sat, 24 Aug 2019 22:46:17 +0200 Subject: [PATCH 03/48] Only add functions that we need --- src/Shared/PlatformSupport/HttpUtility.cs | 399 ---------------------- 1 file changed, 399 deletions(-) diff --git a/src/Shared/PlatformSupport/HttpUtility.cs b/src/Shared/PlatformSupport/HttpUtility.cs index 8a56481a19..59a23e733f 100644 --- a/src/Shared/PlatformSupport/HttpUtility.cs +++ b/src/Shared/PlatformSupport/HttpUtility.cs @@ -5,31 +5,11 @@ using System.Globalization; using System.IO; using System.Security.Permissions; using System.Text; -using System.Web.Util; namespace Avalonia.Shared.PlatformSupport { public sealed class HttpUtility { - sealed class HttpQSCollection : NameValueCollection - { - public override string ToString() - { - int count = Count; - if (count == 0) - return ""; - StringBuilder sb = new StringBuilder(); - string[] keys = AllKeys; - for (int i = 0; i < count; i++) - { - sb.AppendFormat("{0}={1}&", keys[i], UrlEncode(this[keys[i]])); - } - if (sb.Length > 0) - sb.Length--; - return sb.ToString(); - } - } - #region Constructors public HttpUtility() @@ -40,27 +20,6 @@ namespace Avalonia.Shared.PlatformSupport #region Methods - public static void HtmlAttributeEncode(string s, TextWriter output) - { - if (output == null) - { - throw new ArgumentNullException("output"); - } - HttpEncoder.Current.HtmlAttributeEncode(s, output); - } - - public static string HtmlAttributeEncode(string s) - { - if (s == null) - return null; - - using (var sw = new StringWriter()) - { - HttpEncoder.Current.HtmlAttributeEncode(s, sw); - return sw.ToString(); - } - } - public static string UrlDecode(string str) { return UrlDecode(str, Encoding.UTF8); @@ -330,364 +289,6 @@ namespace Avalonia.Shared.PlatformSupport return result.ToArray(); } - - public static string UrlEncode(string str) - { - return UrlEncode(str, Encoding.UTF8); - } - - public static string UrlEncode(string str, Encoding e) - { - if (str == null) - return null; - - if (str == String.Empty) - return String.Empty; - - bool needEncode = false; - int len = str.Length; - for (int i = 0; i < len; i++) - { - char c = str[i]; - if ((c < '0') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a') || (c > 'z')) - { - if (HttpEncoder.NotEncoded(c)) - continue; - - needEncode = true; - break; - } - } - - if (!needEncode) - return str; - - // avoided GetByteCount call - byte[] bytes = new byte[e.GetMaxByteCount(str.Length)]; - int realLen = e.GetBytes(str, 0, str.Length, bytes, 0); - return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, 0, realLen)); - } - - public static string UrlEncode(byte[] bytes) - { - if (bytes == null) - return null; - - if (bytes.Length == 0) - return String.Empty; - - return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, 0, bytes.Length)); - } - - public static string UrlEncode(byte[] bytes, int offset, int count) - { - if (bytes == null) - return null; - - if (bytes.Length == 0) - return String.Empty; - - return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, offset, count)); - } - - public static byte[] UrlEncodeToBytes(string str) - { - return UrlEncodeToBytes(str, Encoding.UTF8); - } - - public static byte[] UrlEncodeToBytes(string str, Encoding e) - { - if (str == null) - return null; - - if (str.Length == 0) - return new byte[0]; - - byte[] bytes = e.GetBytes(str); - return UrlEncodeToBytes(bytes, 0, bytes.Length); - } - - public static byte[] UrlEncodeToBytes(byte[] bytes) - { - if (bytes == null) - return null; - - if (bytes.Length == 0) - return new byte[0]; - - return UrlEncodeToBytes(bytes, 0, bytes.Length); - } - - public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count) - { - if (bytes == null) - return null; - return HttpEncoder.Current.UrlEncode(bytes, offset, count); - } - - public static string UrlEncodeUnicode(string str) - { - if (str == null) - return null; - - return Encoding.ASCII.GetString(UrlEncodeUnicodeToBytes(str)); - } - - public static byte[] UrlEncodeUnicodeToBytes(string str) - { - if (str == null) - return null; - - if (str.Length == 0) - return new byte[0]; - - MemoryStream result = new MemoryStream(str.Length); - foreach (char c in str) - { - HttpEncoder.UrlEncodeChar(c, result, true); - } - return result.ToArray(); - } - - /// - /// Decodes an HTML-encoded string and returns the decoded string. - /// - /// The HTML string to decode. - /// The decoded text. - public static string HtmlDecode(string s) - { - if (s == null) - return null; - - using (var sw = new StringWriter()) - { - HttpEncoder.Current.HtmlDecode(s, sw); - return sw.ToString(); - } - } - - /// - /// Decodes an HTML-encoded string and sends the resulting output to a TextWriter output stream. - /// - /// The HTML string to decode - /// The TextWriter output stream containing the decoded string. - public static void HtmlDecode(string s, TextWriter output) - { - if (output == null) - { - throw new ArgumentNullException("output"); - } - - if (!String.IsNullOrEmpty(s)) - { - HttpEncoder.Current.HtmlDecode(s, output); - } - } - - public static string HtmlEncode(string s) - { - if (s == null) - return null; - - using (var sw = new StringWriter()) - { - HttpEncoder.Current.HtmlEncode(s, sw); - return sw.ToString(); - } - } - - /// - /// HTML-encodes a string and sends the resulting output to a TextWriter output stream. - /// - /// The string to encode. - /// The TextWriter output stream containing the encoded string. - public static void HtmlEncode(string s, TextWriter output) - { - if (output == null) - { - throw new ArgumentNullException("output"); - } - - if (!String.IsNullOrEmpty(s)) - { - HttpEncoder.Current.HtmlEncode(s, output); - } - } - public static string HtmlEncode(object value) - { - if (value == null) - return null; - -#if !(MOBILE || NO_SYSTEM_WEB_DEPENDENCY) - IHtmlString htmlString = value as IHtmlString; - if (htmlString != null) - return htmlString.ToHtmlString(); -#endif - - return HtmlEncode(value.ToString()); - } - - public static string JavaScriptStringEncode(string value) - { - return JavaScriptStringEncode(value, false); - } - - public static string JavaScriptStringEncode(string value, bool addDoubleQuotes) - { - if (String.IsNullOrEmpty(value)) - return addDoubleQuotes ? "\"\"" : String.Empty; - - int len = value.Length; - bool needEncode = false; - char c; - for (int i = 0; i < len; i++) - { - c = value[i]; - - if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92) - { - needEncode = true; - break; - } - } - - if (!needEncode) - return addDoubleQuotes ? "\"" + value + "\"" : value; - - var sb = new StringBuilder(); - if (addDoubleQuotes) - sb.Append('"'); - - for (int i = 0; i < len; i++) - { - c = value[i]; - if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62) - sb.AppendFormat("\\u{0:x4}", (int)c); - else switch ((int)c) - { - case 8: - sb.Append("\\b"); - break; - - case 9: - sb.Append("\\t"); - break; - - case 10: - sb.Append("\\n"); - break; - - case 12: - sb.Append("\\f"); - break; - - case 13: - sb.Append("\\r"); - break; - - case 34: - sb.Append("\\\""); - break; - - case 92: - sb.Append("\\\\"); - break; - - default: - sb.Append(c); - break; - } - } - - if (addDoubleQuotes) - sb.Append('"'); - - return sb.ToString(); - } - public static string UrlPathEncode(string str) - { - return HttpEncoder.Current.UrlPathEncode(str); - } - - public static NameValueCollection ParseQueryString(string query) - { - return ParseQueryString(query, Encoding.UTF8); - } - - public static NameValueCollection ParseQueryString(string query, Encoding encoding) - { - if (query == null) - throw new ArgumentNullException("query"); - if (encoding == null) - throw new ArgumentNullException("encoding"); - if (query.Length == 0 || (query.Length == 1 && query[0] == '?')) - return new HttpQSCollection(); - if (query[0] == '?') - query = query.Substring(1); - - NameValueCollection result = new HttpQSCollection(); - ParseQueryString(query, encoding, result); - return result; - } - - internal static void ParseQueryString(string query, Encoding encoding, NameValueCollection result) - { - if (query.Length == 0) - return; - - string decoded = HtmlDecode(query); - int decodedLength = decoded.Length; - int namePos = 0; - bool first = true; - while (namePos <= decodedLength) - { - int valuePos = -1, valueEnd = -1; - for (int q = namePos; q < decodedLength; q++) - { - if (valuePos == -1 && decoded[q] == '=') - { - valuePos = q + 1; - } - else if (decoded[q] == '&') - { - valueEnd = q; - break; - } - } - - if (first) - { - first = false; - if (decoded[namePos] == '?') - namePos++; - } - - string name, value; - if (valuePos == -1) - { - name = null; - valuePos = namePos; - } - else - { - name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding); - } - if (valueEnd < 0) - { - namePos = -1; - valueEnd = decoded.Length; - } - else - { - namePos = valueEnd + 1; - } - value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding); - - result.Add(name, value); - if (namePos == -1) - break; - } - } #endregion // Methods } } From 83abab7d509308eb7e8d1dc63e0f82aa0c6a1213 Mon Sep 17 00:00:00 2001 From: LifeCoder Date: Sun, 25 Aug 2019 14:22:58 +0200 Subject: [PATCH 04/48] Fix it and remove the extra code --- src/Shared/PlatformSupport/AssetLoader.cs | 2 +- src/Shared/PlatformSupport/HttpUtility.cs | 294 ------------------ .../PlatformSupport/PlatformSupport.projitems | 1 - 3 files changed, 1 insertion(+), 296 deletions(-) delete mode 100644 src/Shared/PlatformSupport/HttpUtility.cs diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 1daec490b9..dd72934560 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -242,7 +242,7 @@ namespace Avalonia.Shared.PlatformSupport throw new InvalidOperationException( $"Assembly {name} needs to be referenced and explicitly loaded before loading resources"); #else - name = HttpUtility.UrlDecode(name); + name = Uri.UnescapeDataString(name); AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name)); #endif } diff --git a/src/Shared/PlatformSupport/HttpUtility.cs b/src/Shared/PlatformSupport/HttpUtility.cs deleted file mode 100644 index 59a23e733f..0000000000 --- a/src/Shared/PlatformSupport/HttpUtility.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Globalization; -using System.IO; -using System.Security.Permissions; -using System.Text; - -namespace Avalonia.Shared.PlatformSupport -{ - public sealed class HttpUtility - { - #region Constructors - - public HttpUtility() - { - } - - #endregion // Constructors - - #region Methods - - public static string UrlDecode(string str) - { - return UrlDecode(str, Encoding.UTF8); - } - - static char[] GetChars(MemoryStream b, Encoding e) - { - return e.GetChars(b.GetBuffer(), 0, (int)b.Length); - } - - static void WriteCharBytes(IList buf, char ch, Encoding e) - { - if (ch > 255) - { - foreach (byte b in e.GetBytes(new char[] { ch })) - buf.Add(b); - } - else - buf.Add((byte)ch); - } - - public static string UrlDecode(string str, Encoding e) - { - if (null == str) - return null; - - if (str.IndexOf('%') == -1 && str.IndexOf('+') == -1) - return str; - - if (e == null) - e = Encoding.UTF8; - - long len = str.Length; - var bytes = new List(); - int xchar; - char ch; - - for (int i = 0; i < len; i++) - { - ch = str[i]; - if (ch == '%' && i + 2 < len && str[i + 1] != '%') - { - if (str[i + 1] == 'u' && i + 5 < len) - { - // unicode hex sequence - xchar = GetChar(str, i + 2, 4); - if (xchar != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 5; - } - else - WriteCharBytes(bytes, '%', e); - } - else if ((xchar = GetChar(str, i + 1, 2)) != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 2; - } - else - { - WriteCharBytes(bytes, '%', e); - } - continue; - } - - if (ch == '+') - WriteCharBytes(bytes, ' ', e); - else - WriteCharBytes(bytes, ch, e); - } - - byte[] buf = bytes.ToArray(); - bytes = null; - return e.GetString(buf); - - } - - public static string UrlDecode(byte[] bytes, Encoding e) - { - if (bytes == null) - return null; - - return UrlDecode(bytes, 0, bytes.Length, e); - } - - static int GetInt(byte b) - { - char c = (char)b; - if (c >= '0' && c <= '9') - return c - '0'; - - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return -1; - } - - static int GetChar(byte[] bytes, int offset, int length) - { - int value = 0; - int end = length + offset; - for (int i = offset; i < end; i++) - { - int current = GetInt(bytes[i]); - if (current == -1) - return -1; - value = (value << 4) + current; - } - - return value; - } - - static int GetChar(string str, int offset, int length) - { - int val = 0; - int end = length + offset; - for (int i = offset; i < end; i++) - { - char c = str[i]; - if (c > 127) - return -1; - - int current = GetInt((byte)c); - if (current == -1) - return -1; - val = (val << 4) + current; - } - - return val; - } - - public static string UrlDecode(byte[] bytes, int offset, int count, Encoding e) - { - if (bytes == null) - return null; - if (count == 0) - return String.Empty; - - if (bytes == null) - throw new ArgumentNullException("bytes"); - - if (offset < 0 || offset > bytes.Length) - throw new ArgumentOutOfRangeException("offset"); - - if (count < 0 || offset + count > bytes.Length) - throw new ArgumentOutOfRangeException("count"); - - StringBuilder output = new StringBuilder(); - MemoryStream acc = new MemoryStream(); - - int end = count + offset; - int xchar; - for (int i = offset; i < end; i++) - { - if (bytes[i] == '%' && i + 2 < count && bytes[i + 1] != '%') - { - if (bytes[i + 1] == (byte)'u' && i + 5 < end) - { - if (acc.Length > 0) - { - output.Append(GetChars(acc, e)); - acc.SetLength(0); - } - xchar = GetChar(bytes, i + 2, 4); - if (xchar != -1) - { - output.Append((char)xchar); - i += 5; - continue; - } - } - else if ((xchar = GetChar(bytes, i + 1, 2)) != -1) - { - acc.WriteByte((byte)xchar); - i += 2; - continue; - } - } - - if (acc.Length > 0) - { - output.Append(GetChars(acc, e)); - acc.SetLength(0); - } - - if (bytes[i] == '+') - { - output.Append(' '); - } - else - { - output.Append((char)bytes[i]); - } - } - - if (acc.Length > 0) - { - output.Append(GetChars(acc, e)); - } - - acc = null; - return output.ToString(); - } - - public static byte[] UrlDecodeToBytes(byte[] bytes) - { - if (bytes == null) - return null; - - return UrlDecodeToBytes(bytes, 0, bytes.Length); - } - - public static byte[] UrlDecodeToBytes(string str) - { - return UrlDecodeToBytes(str, Encoding.UTF8); - } - - public static byte[] UrlDecodeToBytes(string str, Encoding e) - { - if (str == null) - return null; - - if (e == null) - throw new ArgumentNullException("e"); - - return UrlDecodeToBytes(e.GetBytes(str)); - } - - public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count) - { - if (bytes == null) - return null; - if (count == 0) - return new byte[0]; - - int len = bytes.Length; - if (offset < 0 || offset >= len) - throw new ArgumentOutOfRangeException("offset"); - - if (count < 0 || offset > len - count) - throw new ArgumentOutOfRangeException("count"); - - MemoryStream result = new MemoryStream(); - int end = offset + count; - for (int i = offset; i < end; i++) - { - char c = (char)bytes[i]; - if (c == '+') - { - c = ' '; - } - else if (c == '%' && i < end - 2) - { - int xchar = GetChar(bytes, i + 1, 2); - if (xchar != -1) - { - c = (char)xchar; - i += 2; - } - } - result.WriteByte((byte)c); - } - - return result.ToArray(); - } - #endregion // Methods - } -} diff --git a/src/Shared/PlatformSupport/PlatformSupport.projitems b/src/Shared/PlatformSupport/PlatformSupport.projitems index cec13ebffe..34515a0912 100644 --- a/src/Shared/PlatformSupport/PlatformSupport.projitems +++ b/src/Shared/PlatformSupport/PlatformSupport.projitems @@ -11,7 +11,6 @@ - From 671361c80567de107a2069d4ac450d570e662573 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 10 Sep 2019 00:02:05 +0200 Subject: [PATCH 05/48] Remove extra lambda allocations from render loop. --- .../Rendering/DeferredRenderer.cs | 52 +++++++++++-------- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 14 ++++- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index bf1799bbdc..64df472e93 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -246,22 +246,7 @@ namespace Avalonia.Rendering { try { - IDrawingContextImpl GetContext() - { - if (context != null) - return context; - if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) - { - RenderTarget.Dispose(); - RenderTarget = null; - } - if (RenderTarget == null) - RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); - return context = RenderTarget.CreateDrawingContext(this); - - } - - var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext); + var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context); using (scene) { @@ -271,9 +256,9 @@ namespace Avalonia.Rendering if (DrawDirtyRects) _dirtyRectsDisplay.Tick(); if (overlay) - RenderOverlay(scene.Item, GetContext()); + RenderOverlay(scene.Item, ref context); if (updated || forceComposite || overlay) - RenderComposite(scene.Item, GetContext()); + RenderComposite(scene.Item, ref context); } } } @@ -291,7 +276,7 @@ namespace Avalonia.Rendering } } - private (IRef scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(Func contextFactory, + private (IRef scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(ref IDrawingContextImpl context, bool recursiveCall = false) { IRef sceneRef; @@ -304,7 +289,8 @@ namespace Avalonia.Rendering var scene = sceneRef.Item; if (scene.Generation != _lastSceneId) { - var context = contextFactory(); + context = context ?? CreateDrawingContext(); + Layers.Update(scene, context); RenderToLayers(scene); @@ -325,7 +311,7 @@ namespace Avalonia.Rendering if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate) { UpdateScene(); - var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true); + var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true); return (rs, true); } @@ -432,8 +418,10 @@ namespace Avalonia.Rendering } - private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent) + private void RenderOverlay(Scene scene, ref IDrawingContextImpl parentContent) { + parentContent = parentContent ?? CreateDrawingContext(); + if (DrawDirtyRects) { var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling); @@ -460,8 +448,10 @@ namespace Avalonia.Rendering } } - private void RenderComposite(Scene scene, IDrawingContextImpl context) + private void RenderComposite(Scene scene, ref IDrawingContextImpl context) { + context = context ?? CreateDrawingContext(); + context.Clear(Colors.Transparent); var clientRect = new Rect(scene.Size); @@ -503,6 +493,22 @@ namespace Avalonia.Rendering } } + private IDrawingContextImpl CreateDrawingContext() + { + if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) + { + RenderTarget.Dispose(); + RenderTarget = null; + } + + if (RenderTarget == null) + { + RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + } + + return RenderTarget.CreateDrawingContext(this); + } + private void UpdateScene() { Dispatcher.UIThread.VerifyAccess(); diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 64a8427290..140688f8bc 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -91,7 +91,19 @@ namespace Avalonia.Rendering { try { - if (_items.Any(item => item.NeedsUpdate) && + bool needsUpdate = false; + + foreach (IRenderLoopTask item in _items) + { + if (item.NeedsUpdate) + { + needsUpdate = true; + + break; + } + } + + if (needsUpdate && Interlocked.CompareExchange(ref _inUpdate, 1, 0) == 0) { _dispatcher.Post(() => From c755334ddece0d094435d50e3c8942cf9572190b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 10 Sep 2019 22:12:38 +0200 Subject: [PATCH 06/48] Review feedback. --- .../Rendering/DeferredRenderer.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 64df472e93..efcc555159 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -289,7 +289,7 @@ namespace Avalonia.Rendering var scene = sceneRef.Item; if (scene.Generation != _lastSceneId) { - context = context ?? CreateDrawingContext(); + EnsureDrawingContext(ref context); Layers.Update(scene, context); @@ -420,7 +420,7 @@ namespace Avalonia.Rendering private void RenderOverlay(Scene scene, ref IDrawingContextImpl parentContent) { - parentContent = parentContent ?? CreateDrawingContext(); + EnsureDrawingContext(ref parentContent); if (DrawDirtyRects) { @@ -450,7 +450,7 @@ namespace Avalonia.Rendering private void RenderComposite(Scene scene, ref IDrawingContextImpl context) { - context = context ?? CreateDrawingContext(); + EnsureDrawingContext(ref context); context.Clear(Colors.Transparent); @@ -493,8 +493,13 @@ namespace Avalonia.Rendering } } - private IDrawingContextImpl CreateDrawingContext() + private void EnsureDrawingContext(ref IDrawingContextImpl context) { + if (context != null) + { + return; + } + if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) { RenderTarget.Dispose(); @@ -506,7 +511,7 @@ namespace Avalonia.Rendering RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); } - return RenderTarget.CreateDrawingContext(this); + context = RenderTarget.CreateDrawingContext(this); } private void UpdateScene() From 49f54b5275a87e6a06b21d214ee02562d5db0c17 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Sep 2019 13:34:43 +0200 Subject: [PATCH 07/48] Added failing test for #2912. --- .../Data/BindingTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 0ba06980af..1cc007033b 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -196,6 +196,29 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal("baz", target.Text); } + [Fact] + public void OneWayToSource_Binding_Should_Not_StackOverflow_With_Null_Value() + { + var target = new TextBlock { Text = null }; + var binding = new Binding + { + Path = "Foo", + Mode = BindingMode.OneWayToSource, + }; + + target.Bind(TextBox.TextProperty, binding); + + var source = new Source { Foo = "foo" }; + target.DataContext = source; + + Assert.Null(source.Foo); + + // When running tests under NCrunch, NCrunch replaces the standard StackOverflowException + // with its own, which will be caught by our code. Detect the stackoverflow anyway, by + // making sure the target property was only set once. + Assert.Equal(1, source.FooSetCount); + } + [Fact] public void Default_BindingMode_Should_Be_Used() { @@ -630,10 +653,13 @@ namespace Avalonia.Markup.UnitTests.Data set { _foo = value; + ++FooSetCount; RaisePropertyChanged(); } } + public int FooSetCount { get; private set; } + public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged([CallerMemberName] string prop = "") From 9acf82d47b60799136bf6835e6332a7bf8da0c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Thu, 12 Sep 2019 16:38:35 +0200 Subject: [PATCH 08/48] !B Fix expression nodes not knowing if last value is actually null or dead. --- src/Avalonia.Base/Data/Core/ExpressionNode.cs | 8 ++++++-- src/Avalonia.Base/Data/Core/SettableNode.cs | 5 +++++ tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index ce40b3e517..ca7d980bcf 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -8,9 +8,13 @@ namespace Avalonia.Data.Core public abstract class ExpressionNode { private static readonly object CacheInvalid = new object(); + protected static readonly WeakReference UnsetReference = new WeakReference(AvaloniaProperty.UnsetValue); + protected static readonly WeakReference NullReference = + new WeakReference(null); + private WeakReference _target = UnsetReference; private Action _subscriber; private bool _listening; @@ -98,7 +102,7 @@ namespace Avalonia.Data.Core if (notification == null) { - LastValue = new WeakReference(value); + LastValue = value != null ? new WeakReference(value) : NullReference; if (Next != null) { @@ -111,7 +115,7 @@ namespace Avalonia.Data.Core } else { - LastValue = new WeakReference(notification.Value); + LastValue = notification.Value != null ? new WeakReference(notification.Value) : NullReference; if (Next != null) { diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs index eb98b9e8d6..d0a918dc88 100644 --- a/src/Avalonia.Base/Data/Core/SettableNode.cs +++ b/src/Avalonia.Base/Data/Core/SettableNode.cs @@ -29,6 +29,11 @@ namespace Avalonia.Data.Core if (!isLastValueAlive) { + if (value == null && LastValue == NullReference) + { + return true; + } + return false; } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 1cc007033b..cbb6abbf7f 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -216,7 +216,7 @@ namespace Avalonia.Markup.UnitTests.Data // When running tests under NCrunch, NCrunch replaces the standard StackOverflowException // with its own, which will be caught by our code. Detect the stackoverflow anyway, by // making sure the target property was only set once. - Assert.Equal(1, source.FooSetCount); + Assert.Equal(2, source.FooSetCount); } [Fact] From 661254c9cf142a2ade9664618e516cf3807ff80c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Sep 2019 16:45:37 +0200 Subject: [PATCH 09/48] Add issue number to test. --- tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index cbb6abbf7f..a0a285ee4e 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -199,6 +199,7 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void OneWayToSource_Binding_Should_Not_StackOverflow_With_Null_Value() { + // Issue #2912 var target = new TextBlock { Text = null }; var binding = new Binding { From 65e457a38b9b8d6b1f32ec52721980b248222576 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Sep 2019 16:51:41 +0200 Subject: [PATCH 10/48] Added OneWayToSource binding to BindingDemo. Now crashing with an NRE. --- samples/BindingDemo/MainWindow.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml index a232a06383..4cb7cc012e 100644 --- a/samples/BindingDemo/MainWindow.xaml +++ b/samples/BindingDemo/MainWindow.xaml @@ -24,6 +24,7 @@ + From 15e388da7521cc9a59bf625cade6886ab55570bb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Sep 2019 18:32:07 +0200 Subject: [PATCH 11/48] Adding failing binding tests. - `OneTime` bindings are failing to release their subscription if the `DataContext` is unset when the binding is made. - Combining `OneTime` and `OneWayToSource` bindings are causing an NRE. The NRE is being caught but it too results in a subscription being leaked. --- .../Data/BindingTests.cs | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index a0a285ee4e..7e053392c7 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -154,6 +154,18 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal("bar", source.Foo); } + [Fact] + public void OneTime_Binding_Releases_Subscription_If_DataContext_Set_Later() + { + var target = new TextBlock(); + var source = new Source { Foo = "foo" }; + + target.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime)); + target.DataContext = source; + + Assert.Equal(0, source.SubscriberCount); + } + [Fact] public void OneWayToSource_Binding_Should_Be_Set_Up() { @@ -567,6 +579,23 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(expected, child.DoubleValue); } + [Fact] + public void Combined_OneTime_And_OneWayToSource_Bindings_Should_Release_Subscriptions() + { + var target1 = new TextBlock(); + var target2 = new TextBlock(); + var root = new Panel { Children = { target1, target2 } }; + var source = new Source { Foo = "foo" }; + + using (target1.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime))) + using (target2.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneWayToSource))) + { + root.DataContext = source; + } + + Assert.Equal(0, source.SubscriberCount); + } + private class StyledPropertyClass : AvaloniaObject { public static readonly StyledProperty DoubleValueProperty = @@ -646,6 +675,7 @@ namespace Avalonia.Markup.UnitTests.Data public class Source : INotifyPropertyChanged { + private PropertyChangedEventHandler _propertyChanged; private string _foo; public string Foo @@ -661,11 +691,18 @@ namespace Avalonia.Markup.UnitTests.Data public int FooSetCount { get; private set; } - public event PropertyChangedEventHandler PropertyChanged; + + public int SubscriberCount { get; private set; } + + public event PropertyChangedEventHandler PropertyChanged + { + add { _propertyChanged += value; ++SubscriberCount; } + remove { _propertyChanged += value; --SubscriberCount; } + } private void RaisePropertyChanged([CallerMemberName] string prop = "") { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); } } From d3f8d08a47ce1a65c3f975d9d614f749792e6b88 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Sep 2019 18:39:52 +0200 Subject: [PATCH 12/48] Make sure OneTime bindings unsubscribe. `OneTime` bindings unsubscribe when the first value is pushed, meaning that they unsubscribe _during_ `ExpressionNode.StartListeningCore`. Make sure we handle this by: - Setting `_listening = true` before calling `StartListeningCore` - Subscribe to current value in `InpcPropertyAccessorPlugin` before sending current value, because we're unsubscribed when `SendCurrentValue` exits --- src/Avalonia.Base/Data/Core/ExpressionNode.cs | 2 +- .../Data/Core/Plugins/InpcPropertyAccessorPlugin.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index ca7d980bcf..c2e5c8e4f3 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -140,8 +140,8 @@ namespace Avalonia.Data.Core } else if (target != AvaloniaProperty.UnsetValue) { - StartListeningCore(_target); _listening = true; + StartListeningCore(_target); } else { diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 4716b45340..cbceb58204 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -103,8 +103,8 @@ namespace Avalonia.Data.Core.Plugins protected override void SubscribeCore() { - SendCurrentValue(); SubscribeToChanges(); + SendCurrentValue(); } protected override void UnsubscribeCore() From de4e69b0ba5f5ddbc67c2479076e3c1d1bfe85b4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Sep 2019 19:07:24 +0200 Subject: [PATCH 13/48] Remove OneWayToSource binding from BindingDemo. Until #2983 is fixed. --- samples/BindingDemo/MainWindow.xaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml index 4cb7cc012e..b57a9a0a9e 100644 --- a/samples/BindingDemo/MainWindow.xaml +++ b/samples/BindingDemo/MainWindow.xaml @@ -24,7 +24,9 @@ - + From 3902730301a994821223802a05350d231ccc2f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 13 Sep 2019 15:14:28 +0100 Subject: [PATCH 14/48] Use Microsoft.NETFramework.ReferenceAssemblies. --- build/NetFX.props | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/build/NetFX.props b/build/NetFX.props index 4d2841714b..ed5cb6dd69 100644 --- a/build/NetFX.props +++ b/build/NetFX.props @@ -1,11 +1,7 @@ - - - /usr/lib/mono/4.6.1-api - /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.6.1-api - - - /usr/lib/mono/4.7-api/ - /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.7-api - + + + + + From 2362b5a847749decf630369b91bf04a8682d75ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Fri, 13 Sep 2019 17:07:32 +0200 Subject: [PATCH 15/48] Cleanup KeyGesture code. Add constructor taking non-deprecated arguments. --- src/Avalonia.Input/KeyGesture.cs | 131 ++++++++++-------- .../KeyGestureTests.cs | 20 +-- 2 files changed, 81 insertions(+), 70 deletions(-) diff --git a/src/Avalonia.Input/KeyGesture.cs b/src/Avalonia.Input/KeyGesture.cs index 2377edf640..0928b87264 100644 --- a/src/Avalonia.Input/KeyGesture.cs +++ b/src/Avalonia.Input/KeyGesture.cs @@ -1,41 +1,60 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using System; using System.Collections.Generic; using System.Linq; namespace Avalonia.Input { + /// + /// Defines a keyboard input combination. + /// public sealed class KeyGesture : IEquatable { + private static readonly Dictionary s_keySynonyms = new Dictionary + { + { "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod } + }; + public KeyGesture() { - } - public KeyGesture(Key key, InputModifiers modifiers = InputModifiers.None) + [Obsolete("Use constructor taking KeyModifiers")] + public KeyGesture(Key key, InputModifiers modifiers) + { + Key = key; + KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xf); + } + + public KeyGesture(Key key, KeyModifiers modifiers = KeyModifiers.None) { Key = key; - Modifiers = modifiers; + KeyModifiers = modifiers; } - + public bool Equals(KeyGesture other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Key == other.Key && Modifiers == other.Modifiers; + + return Key == other.Key && KeyModifiers == other.KeyModifiers; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - return obj is KeyGesture && Equals((KeyGesture) obj); + + return obj is KeyGesture && Equals((KeyGesture)obj); } public override int GetHashCode() { unchecked { - return ((int) Key*397) ^ (int) Modifiers; + return ((int)Key * 397) ^ (int)KeyModifiers; } } @@ -49,85 +68,85 @@ namespace Avalonia.Input return !Equals(left, right); } - public Key Key { get; set; } + public Key Key { get; } [Obsolete("Use KeyModifiers")] - public InputModifiers Modifiers - { - get => (InputModifiers)KeyModifiers; - set => KeyModifiers = (KeyModifiers)(((int)value) & 0xf); - } - - public KeyModifiers KeyModifiers { get; set; } + public InputModifiers Modifiers => (InputModifiers)KeyModifiers; - - static readonly Dictionary KeySynonyms = new Dictionary - { - {"+", Key.OemPlus }, - {"-", Key.OemMinus}, - {".", Key.OemPeriod } - }; - - //TODO: Move that to external key parser - static Key ParseKey(string key) - { - Key rv; - if (KeySynonyms.TryGetValue(key.ToLower(), out rv)) - return rv; - return (Key)Enum.Parse(typeof (Key), key, true); - } - - static InputModifiers ParseModifier(string modifier) - { - if (modifier.Equals("ctrl", StringComparison.OrdinalIgnoreCase)) - return InputModifiers.Control; - return (InputModifiers) Enum.Parse(typeof (InputModifiers), modifier, true); - } + public KeyModifiers KeyModifiers { get; } public static KeyGesture Parse(string gesture) { - //string.Split can't be used here because "Ctrl++" is a perfectly valid key gesture + // string.Split can't be used here because "Ctrl++" is a perfectly valid key gesture - var parts = new List(); + var key = Key.None; + var keyModifiers = KeyModifiers.None; var cstart = 0; + for (var c = 0; c <= gesture.Length; c++) { var ch = c == gesture.Length ? '\0' : gesture[c]; - if (c == gesture.Length || (ch == '+' && cstart != c)) + bool isLast = c == gesture.Length; + + if (isLast || (ch == '+' && cstart != c)) { - parts.Add(gesture.Substring(cstart, c - cstart)); + var partSpan = gesture.AsSpan(cstart, c - cstart).Trim(); + + if (isLast) + { + key = ParseKey(partSpan.ToString()); + } + else + { + keyModifiers |= ParseModifier(partSpan); + } + cstart = c + 1; } } - for (var c = 0; c < parts.Count; c++) - parts[c] = parts[c].Trim(); - var rv = new KeyGesture(); - for (var c = 0; c < parts.Count; c++) - { - if (c == parts.Count - 1) - rv.Key = ParseKey(parts[c]); - else - rv.Modifiers |= ParseModifier(parts[c]); - } - return rv; + return new KeyGesture(key, keyModifiers); } public override string ToString() { var parts = new List(); - foreach (var flag in Enum.GetValues(typeof (InputModifiers)).Cast()) + + foreach (var flag in Enum.GetValues(typeof(KeyModifiers)).Cast()) { - if (Modifiers.HasFlag(flag) && flag != InputModifiers.None) + if (KeyModifiers.HasFlag(flag) && flag != KeyModifiers.None) + { parts.Add(flag.ToString()); + } } + parts.Add(Key.ToString()); + return string.Join(" + ", parts); } - public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.Modifiers == Modifiers; + public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.KeyModifiers == KeyModifiers; + + // TODO: Move that to external key parser + private static Key ParseKey(string key) + { + if (s_keySynonyms.TryGetValue(key.ToLower(), out Key rv)) + return rv; + + return (Key)Enum.Parse(typeof(Key), key, true); + } + + private static KeyModifiers ParseModifier(ReadOnlySpan modifier) + { + if (modifier.Equals("ctrl".AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + return KeyModifiers.Control; + } + + return (KeyModifiers)Enum.Parse(typeof(KeyModifiers), modifier.ToString(), true); + } private Key ResolveNumPadOperationKey(Key key) { diff --git a/tests/Avalonia.Input.UnitTests/KeyGestureTests.cs b/tests/Avalonia.Input.UnitTests/KeyGestureTests.cs index 006ed1140e..95eaab30de 100644 --- a/tests/Avalonia.Input.UnitTests/KeyGestureTests.cs +++ b/tests/Avalonia.Input.UnitTests/KeyGestureTests.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Avalonia.Input.UnitTests @@ -11,13 +7,11 @@ namespace Avalonia.Input.UnitTests { public static readonly IEnumerable SampleData = new object[][] { - new object[]{"Ctrl+A", new KeyGesture {Key = Key.A, Modifiers = InputModifiers.Control}}, - new object[]{" \tShift\t+Alt +B", new KeyGesture {Key = Key.B, Modifiers = InputModifiers.Shift|InputModifiers.Alt} }, - new object[]{"Control++", new KeyGesture {Key = Key.OemPlus, Modifiers = InputModifiers.Control} } + new object[]{"Ctrl+A", new KeyGesture(Key.A, InputModifiers.Control)}, + new object[]{" \tShift\t+Alt +B", new KeyGesture(Key.B, InputModifiers.Shift | InputModifiers.Alt) }, + new object[]{"Control++", new KeyGesture(Key.OemPlus, InputModifiers.Control) } }; - - - + [Theory] [MemberData(nameof(SampleData))] public void Key_Gesture_Is_Able_To_Parse_Sample_Data(string text, KeyGesture gesture) @@ -31,10 +25,8 @@ namespace Avalonia.Input.UnitTests [InlineData(Key.OemPeriod, Key.Decimal)] public void Key_Gesture_Matches_NumPad_To_Regular_Digit(Key gestureKey, Key pressedKey) { - var keyGesture = new KeyGesture - { - Key = gestureKey - }; + var keyGesture = new KeyGesture(gestureKey); + Assert.True(keyGesture.Matches(new KeyEventArgs { Key = pressedKey From d409090b90af0021e7183a661a433fe4a7d715b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Fri, 13 Sep 2019 17:10:56 +0200 Subject: [PATCH 16/48] Fix unit tests. Remove default ctor. --- src/Avalonia.Input/KeyGesture.cs | 4 ---- tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Input/KeyGesture.cs b/src/Avalonia.Input/KeyGesture.cs index 0928b87264..5eaee4833c 100644 --- a/src/Avalonia.Input/KeyGesture.cs +++ b/src/Avalonia.Input/KeyGesture.cs @@ -17,10 +17,6 @@ namespace Avalonia.Input { "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod } }; - public KeyGesture() - { - } - [Obsolete("Use constructor taking KeyModifiers")] public KeyGesture(Key key, InputModifiers modifiers) { diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index df522397ee..dd3b113d5d 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -24,8 +24,8 @@ namespace Avalonia.Controls.UnitTests.Utils .Bind().ToConstant(new WindowingPlatformMock()) .Bind().ToConstant(styler.Object); - var gesture1 = new KeyGesture {Key = Key.A, Modifiers = InputModifiers.Control}; - var gesture2 = new KeyGesture {Key = Key.B, Modifiers = InputModifiers.Control}; + var gesture1 = new KeyGesture(Key.A, InputModifiers.Control); + var gesture2 = new KeyGesture(Key.B, InputModifiers.Control); var tl = new Window(); var button = new Button(); From 9de04b3297b326f62eb086e86d492acd2d9e3dac Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 14 Sep 2019 19:23:33 +0800 Subject: [PATCH 17/48] Fix drive enumeration crash on Windows's Managed File Dialog. --- src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs index a17e6b8b51..6fc4851dc9 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -32,6 +32,7 @@ namespace Avalonia.Win32 var allDrives = DriveInfo.GetDrives(); var mountVolInfos = allDrives + .Where(p => p.IsReady) .Select(p => new MountedVolumeInfo() { VolumeLabel = p.VolumeLabel, From f4100fd8a4ff743d6ce60d93cb47949c69d8ccf8 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 14 Sep 2019 19:37:19 +0800 Subject: [PATCH 18/48] Set Path as name if there's no volume label. --- src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs index 6fc4851dc9..c1c17c6507 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -35,7 +35,8 @@ namespace Avalonia.Win32 .Where(p => p.IsReady) .Select(p => new MountedVolumeInfo() { - VolumeLabel = p.VolumeLabel, + VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName + : p.VolumeLabel, VolumePath = p.RootDirectory.FullName, VolumeSizeBytes = (ulong)p.TotalSize }) From 2afa74e62862f7b53b58160b0c1ac1580107ff61 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 14 Sep 2019 19:05:20 +0100 Subject: [PATCH 19/48] include drive letter on windows. --- src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs index c1c17c6507..db4c916052 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -36,7 +36,7 @@ namespace Avalonia.Win32 .Select(p => new MountedVolumeInfo() { VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName - : p.VolumeLabel, + : $"{p.VolumeLabel} ({p.Name})", VolumePath = p.RootDirectory.FullName, VolumeSizeBytes = (ulong)p.TotalSize }) From 06a4a1af5ba992355441c4440af75b3e30b72160 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 15 Sep 2019 01:29:17 +0200 Subject: [PATCH 20/48] Add failing unit test. --- .../WeakEventHandlerManagerTests.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs b/tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs index 9ed6590821..81b882308d 100644 --- a/tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs +++ b/tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void EventShoudBePassedToSubscriber() + public void EventShouldBePassedToSubscriber() { bool handled = false; var subscriber = new Subscriber(() => handled = true); @@ -47,7 +47,23 @@ namespace Avalonia.Base.UnitTests Assert.True(handled); } - + [Fact] + public void EventShouldNotBeRaisedAfterUnsubscribe() + { + bool handled = false; + var subscriber = new Subscriber(() => handled = true); + var source = new EventSource(); + WeakEventHandlerManager.Subscribe(source, "Event", + subscriber.OnEvent); + + WeakEventHandlerManager.Unsubscribe(source, "Event", + subscriber.OnEvent); + + source.Fire(); + + Assert.False(handled); + } + [Fact] public void EventHandlerShouldNotBeKeptAlive() { From 3030427c07d3d38c869a42aab0ad44e64b7dcc18 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 15 Sep 2019 01:33:32 +0200 Subject: [PATCH 21/48] Fix unsubscribe not doing anything due to wrong check. --- src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs index b59ed166bc..607e2a147b 100644 --- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -161,9 +161,8 @@ namespace Avalonia.Utilities for (int c = 0; c < _count; ++c) { var reference = _data[c].Subscriber; - TSubscriber instance; - if (reference != null && reference.TryGetTarget(out instance) && instance == s) + if (reference != null && reference.TryGetTarget(out TSubscriber instance) && instance == (TSubscriber)s.Target) { _data[c] = default; removed = true; From 213600f6c23b247d561727963face70b23d7fec3 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 15 Sep 2019 01:47:04 +0200 Subject: [PATCH 22/48] Use Equals. --- src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs index 607e2a147b..f4cec98628 100644 --- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -162,7 +162,7 @@ namespace Avalonia.Utilities { var reference = _data[c].Subscriber; - if (reference != null && reference.TryGetTarget(out TSubscriber instance) && instance == (TSubscriber)s.Target) + if (reference != null && reference.TryGetTarget(out TSubscriber instance) && Equals(instance, s.Target)) { _data[c] = default; removed = true; From 16a8ff862b32dbdb3113dcd5f84bc21aaf13ee59 Mon Sep 17 00:00:00 2001 From: Yatao Li Date: Mon, 16 Sep 2019 00:49:27 +0800 Subject: [PATCH 23/48] X11: map super to win key --- src/Avalonia.X11/X11KeyTransform.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.X11/X11KeyTransform.cs b/src/Avalonia.X11/X11KeyTransform.cs index 87a4174c06..59f1da564c 100644 --- a/src/Avalonia.X11/X11KeyTransform.cs +++ b/src/Avalonia.X11/X11KeyTransform.cs @@ -104,8 +104,8 @@ namespace Avalonia.X11 {X11Key.x, Key.X}, {X11Key.y, Key.Y}, {X11Key.z, Key.Z}, - {X11Key.Meta_L, Key.LWin }, - {X11Key.Meta_R, Key.RWin }, + {X11Key.Super_L, Key.LWin }, + {X11Key.Super_R, Key.RWin }, {X11Key.Menu, Key.Apps}, //{ X11Key.?, Key.Sleep } {X11Key.KP_0, Key.NumPad0}, From c1c7f57ee6c13971216ac5b9f49ce42ca43abbcb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 16 Sep 2019 10:04:37 +0200 Subject: [PATCH 24/48] Disable vertical scrollbar in ItemsRepeater page. --- samples/ControlCatalog/MainView.xaml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index c35f8a3c0c..c99a6b117b 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -24,7 +24,6 @@ - @@ -34,12 +33,15 @@ - + + + - + @@ -50,12 +52,12 @@ - + Light Dark - + From 249e9940da2aaf70e5fdda553dd670a9a6030fd7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 17 Sep 2019 15:44:35 +0200 Subject: [PATCH 25/48] Added failing tests for #2982. Crash in renderer when reparenting a control. --- .../Rendering/DeferredRendererTests.cs | 50 ++++++++++++++++ .../Rendering/SceneGraph/SceneBuilderTests.cs | 58 +++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 4c302a24a2..568ccb81d8 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -270,6 +270,56 @@ namespace Avalonia.Visuals.UnitTests.Rendering Assert.Same(stackNode.Children[1].Visual, canvas1); } + [Fact] + public void Should_Update_VisualNodes_When_Child_Moved_To_New_Parent() + { + var dispatcher = new ImmediateDispatcher(); + var loop = new Mock(); + + Decorator moveFrom; + Decorator moveTo; + Canvas moveMe; + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + (moveFrom = new Decorator + { + Child = moveMe = new Canvas(), + }), + (moveTo = new Decorator()), + } + } + }; + + var sceneBuilder = new SceneBuilder(); + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder, + dispatcher: dispatcher); + + root.Renderer = target; + target.Start(); + RunFrame(target); + + moveFrom.Child = null; + moveTo.Child = moveMe; + + RunFrame(target); + + var scene = target.UnitTestScene(); + var moveFromNode = (VisualNode)scene.FindNode(moveFrom); + var moveToNode = (VisualNode)scene.FindNode(moveTo); + + Assert.Empty(moveFromNode.Children); + Assert.Equal(1, moveToNode.Children.Count); + Assert.Same(moveMe, moveToNode.Children[0].Visual); + + } + [Fact] public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity() { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index 3d084b81e1..13bcd27240 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -475,6 +475,64 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph } } + [Fact] + public void Should_Update_When_Control_Moved() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Decorator moveFrom; + Decorator moveTo; + Canvas moveMe; + var tree = new TestRoot + { + Width = 100, + Height = 100, + Child = new StackPanel + { + Children = + { + (moveFrom = new Decorator + { + Child = moveMe = new Canvas(), + }), + (moveTo = new Decorator()), + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var moveFromNode = (VisualNode)scene.FindNode(moveFrom); + var moveToNode = (VisualNode)scene.FindNode(moveTo); + + Assert.Equal(1, moveFromNode.Children.Count); + Assert.Same(moveMe, moveFromNode.Children[0].Visual); + Assert.Empty(moveToNode.Children); + + moveFrom.Child = null; + moveTo.Child = moveMe; + + scene = scene.CloneScene(); + moveFromNode = (VisualNode)scene.FindNode(moveFrom); + moveToNode = (VisualNode)scene.FindNode(moveTo); + + moveFromNode.SortChildren(scene); + moveToNode.SortChildren(scene); + sceneBuilder.Update(scene, moveFrom); + sceneBuilder.Update(scene, moveTo); + sceneBuilder.Update(scene, moveMe); + + Assert.Empty(moveFromNode.Children); + Assert.Equal(1, moveToNode.Children.Count); + Assert.Same(moveMe, moveToNode.Children[0].Visual); + } + } + [Fact] public void Should_Update_When_Control_Made_Invisible() { From db8751d71164f866d4b3acb59cd3e13900096239 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 17 Sep 2019 15:47:41 +0200 Subject: [PATCH 26/48] Handle reparenting controls in SceneBuilder. Renamed `VisualNode.SortChildren` -> `UpdateChildren` and make it remove nodes for controls that are no longer children. --- .../Rendering/DeferredRenderer.cs | 2 +- .../Rendering/SceneGraph/VisualNode.cs | 15 ++++++++++++--- .../Rendering/SceneGraph/SceneBuilderTests.cs | 4 ++-- .../Rendering/SceneGraph/VisualNodeTests.cs | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index efcc555159..0ff0285a04 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -540,7 +540,7 @@ namespace Avalonia.Rendering foreach (var visual in _recalculateChildren) { var node = scene.FindNode(visual); - ((VisualNode)node)?.SortChildren(scene); + ((VisualNode)node)?.UpdateChildren(scene); } _recalculateChildren.Clear(); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index f579bf0a62..f079023c6d 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -174,12 +174,12 @@ namespace Avalonia.Rendering.SceneGraph /// /// Sorts the collection according to the order of the visual's - /// children and their z-index. + /// children and their z-index and removes controls that are no longer children. /// /// The scene that the node is a part of. - public void SortChildren(Scene scene) + public void UpdateChildren(Scene scene) { - if (_children == null || _children.Count <= 1) + if (_children == null || _children.Count == 0) { return; } @@ -193,9 +193,12 @@ namespace Avalonia.Rendering.SceneGraph keys.Add(((long)zIndex << 32) + i); } + var toRemove = _children.ToList(); + keys.Sort(); _children.Clear(); + foreach (var i in keys) { var child = Visual.VisualChildren[(int)(i & 0xffffffff)]; @@ -204,8 +207,14 @@ namespace Avalonia.Rendering.SceneGraph if (node != null) { _children.Add(node); + toRemove.Remove(node); } } + + foreach (var node in toRemove) + { + scene.Remove(node); + } } /// diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index 13bcd27240..dcf23e94e2 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -521,8 +521,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph moveFromNode = (VisualNode)scene.FindNode(moveFrom); moveToNode = (VisualNode)scene.FindNode(moveTo); - moveFromNode.SortChildren(scene); - moveToNode.SortChildren(scene); + moveFromNode.UpdateChildren(scene); + moveToNode.UpdateChildren(scene); sceneBuilder.Update(scene, moveFrom); sceneBuilder.Update(scene, moveTo); sceneBuilder.Update(scene, moveMe); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs index 24ba2d1c48..d4f7a6a142 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs @@ -99,7 +99,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var node = new VisualNode(Mock.Of(), null); var scene = new Scene(Mock.Of()); - node.SortChildren(scene); + node.UpdateChildren(scene); } } } From fcddf46cd7a62507f0f0910e8dfb95287abddf4c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 17 Sep 2019 20:05:35 +0100 Subject: [PATCH 27/48] [OSX] dont use cursor rects, to prevent crashing. --- native/Avalonia.Native/src/OSX/window.mm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index bfa33eb259..340a04c02d 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -369,12 +369,9 @@ public: virtual void UpdateCursor() { - [View resetCursorRects]; if (cursor != nil) { - auto rect = [Window frame]; - [View addCursorRect:rect cursor:cursor]; - [cursor set]; + [cursor set]; } } @@ -425,6 +422,7 @@ private: { WindowEvents = events; [Window setCanBecomeKeyAndMain]; + [Window disableCursorRects]; } virtual HRESULT Show () override From 3ede24cf74bb565f8a899af84113b9ad8f6eb450 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 18 Sep 2019 21:40:22 +0800 Subject: [PATCH 28/48] Set a ratio of client size as default window size on xorg. --- src/Avalonia.X11/X11Window.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 975b3d11d7..9c9e421a6c 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -98,14 +98,23 @@ namespace Avalonia.X11 valueMask |= SetWindowValuemask.ColorMap; } - _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0, + int defaultWidth = 300, defaultHeight = 200; + + if (!_popup) + { + // Emulate Window 7+'s default window size behavior. + defaultWidth = (int)(MaxClientSize.Width * 0.75d); + defaultHeight = (int)(MaxClientSize.Height * 0.7d); + } + + _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, defaultWidth, defaultHeight, 0, depth, (int)CreateWindowArgs.InputOutput, visual, new UIntPtr((uint)valueMask), ref attr); if (_useRenderWindow) - _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, depth, + _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, defaultWidth, defaultHeight, 0, depth, (int)CreateWindowArgs.InputOutput, visual, new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | From 43ed3e2964bdd9d4636cebcf90412e04a98b49a2 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 18 Sep 2019 21:53:04 +0800 Subject: [PATCH 29/48] Fix transitions since the last PR. --- src/Avalonia.Animation/Animatable.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 2c321b8b28..ca45fb8c4d 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -55,6 +55,11 @@ namespace Avalonia.Animation } set { + if (value is null) + return; + + if (_previousTransitions is null) + _previousTransitions = new Dictionary(); SetAndRaise(TransitionsProperty, ref _transitions, value); } @@ -70,7 +75,7 @@ namespace Avalonia.Animation if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return; // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations). - foreach (var transition in Transitions) + foreach (var transition in _transitions) { if (transition.Property == e.Property) { From 76f948c45a5dae329c59e673ae45a92ca75c7fe9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 16:17:41 +0100 Subject: [PATCH 30/48] osx add the pixeldensity to screen info. --- native/Avalonia.Native/inc/avalonia-native.h | 1 + native/Avalonia.Native/src/OSX/Screens.mm | 2 ++ 2 files changed, 3 insertions(+) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 36e16c24d1..e000c88836 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -52,6 +52,7 @@ struct AvnScreen { AvnRect Bounds; AvnRect WorkingArea; + float PixelDenisty; bool Primary; }; diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 9d436b98c5..1c9f78873b 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -38,6 +38,8 @@ class Screens : public ComSingleObject ret->WorkingArea.Height = [screen visibleFrame].size.height; ret->WorkingArea.Width = [screen visibleFrame].size.width; + ret->PixelDenisty = [screen backingScaleFactor]; + ret->Primary = index == 0; return S_OK; From 2282c99c163adeedfdea105d12370c326955e512 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 16:35:03 +0100 Subject: [PATCH 31/48] add dpi information to screens api. --- native/Avalonia.Native/inc/avalonia-native.h | 2 +- src/Avalonia.Controls/Platform/Screen.cs | 5 ++++- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Native/ScreenImpl.cs | 1 + src/Avalonia.X11/X11Screens.cs | 2 +- src/Windows/Avalonia.Win32/ScreenImpl.cs | 4 +++- src/Windows/Avalonia.Win32/WinScreen.cs | 2 +- tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs | 2 +- 8 files changed, 13 insertions(+), 7 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index e000c88836..c7a7256f9a 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -52,7 +52,7 @@ struct AvnScreen { AvnRect Bounds; AvnRect WorkingArea; - float PixelDenisty; + float PixelDensity; bool Primary; }; diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index e7c811235c..d272904f99 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -2,14 +2,17 @@ { public class Screen { + public double PixelDenisty { get; } + public PixelRect Bounds { get; } public PixelRect WorkingArea { get; } public bool Primary { get; } - public Screen(PixelRect bounds, PixelRect workingArea, bool primary) + public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary) { + this.PixelDenisty = pixelDensity; this.Bounds = bounds; this.WorkingArea = workingArea; this.Primary = primary; diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 4ce0da60a2..16d434b614 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -178,6 +178,6 @@ namespace Avalonia.DesignerSupport.Remote public int ScreenCount => 1; public IReadOnlyList AllScreens { get; } = - new Screen[] { new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) }; + new Screen[] { new Screen(1, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) }; } } diff --git a/src/Avalonia.Native/ScreenImpl.cs b/src/Avalonia.Native/ScreenImpl.cs index 2afa71753e..3ec3be20d3 100644 --- a/src/Avalonia.Native/ScreenImpl.cs +++ b/src/Avalonia.Native/ScreenImpl.cs @@ -31,6 +31,7 @@ namespace Avalonia.Native var screen = _native.GetScreen(i); result[i] = new Screen( + screen.PixelDensity, screen.Bounds.ToAvaloniaPixelRect(), screen.WorkingArea.ToAvaloniaPixelRect(), screen.Primary); diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index 6f860145d3..e247a4241a 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -157,7 +157,7 @@ namespace Avalonia.X11 public int ScreenCount => _impl.Screens.Length; public IReadOnlyList AllScreens => - _impl.Screens.Select(s => new Screen(s.Bounds, s.WorkingArea, s.Primary)).ToArray(); + _impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.Primary)).ToArray(); } interface IX11Screens diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 1833e21e23..e77aa07bcd 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -30,6 +30,8 @@ namespace Avalonia.Win32 MONITORINFO monitorInfo = MONITORINFO.Create(); if (GetMonitorInfo(monitor,ref monitorInfo)) { + GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var x, out _); + RECT bounds = monitorInfo.rcMonitor; RECT workingArea = monitorInfo.rcWork; PixelRect avaloniaBounds = new PixelRect(bounds.left, bounds.top, bounds.right - bounds.left, @@ -38,7 +40,7 @@ namespace Avalonia.Win32 new PixelRect(workingArea.left, workingArea.top, workingArea.right - workingArea.left, workingArea.bottom - workingArea.top); screens[index] = - new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1, + new WinScreen((double)x / 96.0d, avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1, monitor); index++; } diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs index e849800e62..0cf9fe31db 100644 --- a/src/Windows/Avalonia.Win32/WinScreen.cs +++ b/src/Windows/Avalonia.Win32/WinScreen.cs @@ -7,7 +7,7 @@ namespace Avalonia.Win32 { private readonly IntPtr _hMonitor; - public WinScreen(PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary) + public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary) { this._hMonitor = hMonitor; } diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index ac80fc6c7a..f44f89e91f 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -198,7 +198,7 @@ namespace Avalonia.Controls.UnitTests var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100)); var screenImpl = new Mock(); screenImpl.Setup(x => x.ScreenCount).Returns(1); - screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(screen, screen, true) }); + screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) }); popupImpl = MockWindowingPlatform.CreatePopupMock(); popupImpl.SetupGet(x => x.Scaling).Returns(1); From 7ee0bb804ac000a112742aafb7b34f7f04fa1441 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 16:37:28 +0100 Subject: [PATCH 32/48] display screen dpi info on screenspage --- samples/ControlCatalog/Pages/ScreenPage.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 13c1667ed2..79d1289111 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -57,12 +57,15 @@ namespace ControlCatalog.Pages text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"; context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text); + + text.Text = $"Scaling: {screen.PixelDenisty * 100}%"; + context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text); text.Text = $"Primary: {screen.Primary}"; - context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text); + context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text); text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"; - context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text); + context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text); } context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10)); From 54dd8a4002cb15dc02841b6593a57785674642e4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 16:43:42 +0100 Subject: [PATCH 33/48] add implementation for osx. --- src/Avalonia.Native/WindowImplBase.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 217fb4b078..05068a275f 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; @@ -48,6 +49,11 @@ namespace Avalonia.Native Screen = new ScreenImpl(screens); _savedLogicalSize = ClientSize; _savedScaling = Scaling; + + var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) + .FirstOrDefault(m => m.Bounds.Contains(Position)); + + Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d); } public Size ClientSize From 1a5ffa945842529816651eaa10a13bfe02093756 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 16:43:48 +0100 Subject: [PATCH 34/48] fix typo --- src/Avalonia.Controls/Platform/Screen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index d272904f99..976faed3fd 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -2,7 +2,7 @@ { public class Screen { - public double PixelDenisty { get; } + public double PixelDensity { get; } public PixelRect Bounds { get; } @@ -12,7 +12,7 @@ public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary) { - this.PixelDenisty = pixelDensity; + this.PixelDensity = pixelDensity; this.Bounds = bounds; this.WorkingArea = workingArea; this.Primary = primary; From 31cf785927f31ce6380a38ddb81dee19701ef26c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 16:43:58 +0100 Subject: [PATCH 35/48] fix x11 impl --- src/Avalonia.X11/X11Window.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 9c9e421a6c..00761dfce8 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -102,9 +102,12 @@ namespace Avalonia.X11 if (!_popup) { + var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) + .FirstOrDefault(m => m.Bounds.Contains(Position)); + // Emulate Window 7+'s default window size behavior. - defaultWidth = (int)(MaxClientSize.Width * 0.75d); - defaultHeight = (int)(MaxClientSize.Height * 0.7d); + defaultWidth = (int)(monitor.WorkingArea.Width * 0.75d); + defaultHeight = (int)(monitor.WorkingArea.Height * 0.7d); } _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, defaultWidth, defaultHeight, 0, From 65caadd7cdab71b2aeafce489ad7d898eda33808 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 16:48:35 +0100 Subject: [PATCH 36/48] fix missing bracket. --- src/Avalonia.Native/WindowImplBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 05068a275f..74ee76bd38 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -53,7 +53,7 @@ namespace Avalonia.Native var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) .FirstOrDefault(m => m.Bounds.Contains(Position)); - Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d); + Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d)); } public Size ClientSize From 439f7af6632acf2c304d4ac3983ec6214398cda6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 16:49:14 +0100 Subject: [PATCH 37/48] fix typo --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 79d1289111..d775eb9635 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -58,7 +58,7 @@ namespace ControlCatalog.Pages text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"; context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text); - text.Text = $"Scaling: {screen.PixelDenisty * 100}%"; + text.Text = $"Scaling: {screen.PixelDensity * 100}%"; context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text); text.Text = $"Primary: {screen.Primary}"; From e530ab468cd9062cb01850ee9b6468ebd17aad63 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Sep 2019 17:03:40 +0100 Subject: [PATCH 38/48] fix implementation of maxclientsize osx. --- native/Avalonia.Native/inc/avalonia-native.h | 1 - native/Avalonia.Native/src/OSX/Screens.mm | 2 +- native/Avalonia.Native/src/OSX/window.mm | 24 ++------------------ src/Avalonia.Native/WindowImplBase.cs | 3 ++- 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index c7a7256f9a..e54f3fa6a7 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -188,7 +188,6 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown virtual HRESULT Close() = 0; virtual HRESULT Activate () = 0; virtual HRESULT GetClientSize(AvnSize*ret) = 0; - virtual HRESULT GetMaxClientSize(AvnSize* ret) = 0; virtual HRESULT GetScaling(double*ret)=0; virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0; virtual HRESULT Resize(double width, double height) = 0; diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 1c9f78873b..e7f009787a 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -38,7 +38,7 @@ class Screens : public ComSingleObject ret->WorkingArea.Height = [screen visibleFrame].size.height; ret->WorkingArea.Width = [screen visibleFrame].size.width; - ret->PixelDenisty = [screen backingScaleFactor]; + ret->PixelDensity = [screen backingScaleFactor]; ret->Primary = index == 0; diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index bfa33eb259..c515f84b94 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -54,7 +54,6 @@ public: FORWARD_IUNKNOWN() virtual ~WindowBaseImpl() { - NSDebugLog(@"~WindowBaseImpl()"); View = NULL; Window = NULL; } @@ -161,22 +160,6 @@ public: } } - virtual HRESULT GetMaxClientSize(AvnSize* ret) override - { - @autoreleasepool - { - if(ret == nullptr) - return E_POINTER; - - auto size = [NSScreen.screens objectAtIndex:0].frame.size; - - ret->Height = size.height; - ret->Width = size.width; - - return S_OK; - } - } - virtual HRESULT GetScaling (double* ret) override { @autoreleasepool @@ -416,8 +399,8 @@ private: INHERIT_INTERFACE_MAP(WindowBaseImpl) INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow) END_INTERFACE_MAP() - virtual ~WindowImpl(){ - NSDebugLog(@"~WindowImpl"); + virtual ~WindowImpl() + { } ComPtr WindowEvents; @@ -664,10 +647,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (void)dealloc { - NSDebugLog(@"AvnView dealloc"); } - - (void)onClosed { _parent = NULL; @@ -1067,7 +1048,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (void)dealloc { - NSDebugLog(@"AvnWindow dealloc"); } - (void)pollModalSession:(nonnull NSModalSession)session diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 74ee76bd38..d8ff370c45 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -306,7 +306,8 @@ namespace Avalonia.Native _native.BeginMoveDrag(); } - public Size MaxClientSize => _native.GetMaxClientSize().ToAvaloniaSize(); + public Size MaxClientSize => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity)) + .OrderByDescending(x => x.Width + x.Height).FirstOrDefault(); public void SetTopmost(bool value) { From b7a42d96d2b6802ffbc6659884a99902b82b63bf Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 19 Sep 2019 02:33:49 +0800 Subject: [PATCH 39/48] Do it the hackey way --- src/Avalonia.X11/X11Window.cs | 51 ++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 00761dfce8..707910b9a5 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -42,6 +42,7 @@ namespace Avalonia.X11 private X11Window _transientParent; private double? _scalingOverride; public object SyncRoot { get; } = new object(); + private int defaultWidth = 300, defaultHeight = 200; class InputEventContainer { @@ -98,17 +99,6 @@ namespace Avalonia.X11 valueMask |= SetWindowValuemask.ColorMap; } - int defaultWidth = 300, defaultHeight = 200; - - if (!_popup) - { - var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) - .FirstOrDefault(m => m.Bounds.Contains(Position)); - - // Emulate Window 7+'s default window size behavior. - defaultWidth = (int)(monitor.WorkingArea.Width * 0.75d); - defaultHeight = (int)(monitor.WorkingArea.Height * 0.7d); - } _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, defaultWidth, defaultHeight, 0, depth, @@ -126,7 +116,7 @@ namespace Avalonia.X11 _renderHandle = _handle; Handle = new PlatformHandle(_handle, "XID"); - _realSize = new PixelSize(300, 200); + _realSize = new PixelSize(defaultWidth, defaultHeight); platform.Windows[_handle] = OnEvent; XEventMask ignoredMask = XEventMask.SubstructureRedirectMask | XEventMask.ResizeRedirectMask @@ -167,8 +157,21 @@ namespace Avalonia.X11 XFlush(_x11.Display); if(_popup) PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize)); + else + sizingState = DefaultSizingState.Start; + + } + + DefaultSizingState sizingState; + + enum DefaultSizingState + { + None, + Start, + End } + class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { private readonly X11Window _window; @@ -381,7 +384,23 @@ namespace Avalonia.X11 XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow, 0, 0, out var tx, out var ty, out _); + _configurePoint = new PixelPoint(tx, ty); + + if (sizingState == DefaultSizingState.Start) + { + var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) + .FirstOrDefault(m => m.Bounds.Contains(new PixelPoint(tx, ty))); + + // Emulate Window 7+'s default window size behavior. + var defW = (int)(monitor.WorkingArea.Width * 0.75d); + var defH = (int)(monitor.WorkingArea.Height * 0.7d); + + var defSize = new Size(defW, defH); + Resize(defSize); + + sizingState = DefaultSizingState.End; + } } if (needEnqueue) Dispatcher.UIThread.Post(() => @@ -765,6 +784,12 @@ namespace Avalonia.X11 void Resize(Size clientSize, bool force) { + // It's a hack but ohwell... + if(sizingState == DefaultSizingState.Start + & clientSize.Width != defaultWidth + & clientSize.Height != defaultHeight) + sizingState = DefaultSizingState.End; + if (!force && clientSize == ClientSize) return; @@ -782,6 +807,8 @@ namespace Avalonia.X11 _realSize = pixelSize; Resized?.Invoke(ClientSize); } + + } public void CanResize(bool value) From 7601da1160eac4d93a9dd06af01c6de816e15808 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 20 Sep 2019 00:28:41 +0200 Subject: [PATCH 40/48] Reduce closure allocations for priority level and entry. --- src/Avalonia.Base/PriorityBindingEntry.cs | 21 +++++----- src/Avalonia.Base/PriorityLevel.cs | 49 ++++++++++++++++------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Base/PriorityBindingEntry.cs b/src/Avalonia.Base/PriorityBindingEntry.cs index 95add0dfac..312018d8ee 100644 --- a/src/Avalonia.Base/PriorityBindingEntry.cs +++ b/src/Avalonia.Base/PriorityBindingEntry.cs @@ -99,37 +99,40 @@ namespace Avalonia void IObserver.OnNext(object value) { - void Signal() + void Signal(PriorityBindingEntry instance, object newValue) { - var notification = value as BindingNotification; + var notification = newValue as BindingNotification; if (notification != null) { if (notification.HasValue || notification.ErrorType == BindingErrorType.Error) { - Value = notification.Value; - _owner.Changed(this); + instance.Value = notification.Value; + instance._owner.Changed(instance); } if (notification.ErrorType != BindingErrorType.None) { - _owner.Error(this, notification); + instance._owner.Error(instance, notification); } } else { - Value = value; - _owner.Changed(this); + instance.Value = newValue; + instance._owner.Changed(instance); } } if (Dispatcher.UIThread.CheckAccess()) { - Signal(); + Signal(this, value); } else { - Dispatcher.UIThread.Post(Signal); + var instance = this; + var newValue = value; + + Dispatcher.UIThread.Post(() => Signal(instance, newValue)); } } diff --git a/src/Avalonia.Base/PriorityLevel.cs b/src/Avalonia.Base/PriorityLevel.cs index 909558b0ce..6366911e77 100644 --- a/src/Avalonia.Base/PriorityLevel.cs +++ b/src/Avalonia.Base/PriorityLevel.cs @@ -110,20 +110,7 @@ namespace Avalonia entry.Start(binding); - return Disposable.Create(() => - { - if (!entry.HasCompleted) - { - Bindings.Remove(node); - - entry.Dispose(); - - if (entry.Index >= ActiveBindingIndex) - { - ActivateFirstBinding(); - } - } - }); + return new RemoveBindingDisposable(node, Bindings, this); } /// @@ -191,5 +178,39 @@ namespace Avalonia ActiveBindingIndex = -1; Owner.LevelValueChanged(this); } + + private sealed class RemoveBindingDisposable : IDisposable + { + private readonly LinkedListNode _binding; + private readonly LinkedList _bindings; + private readonly PriorityLevel _priorityLevel; + + public RemoveBindingDisposable( + LinkedListNode binding, + LinkedList bindings, + PriorityLevel priorityLevel) + { + _binding = binding; + _bindings = bindings; + _priorityLevel = priorityLevel; + } + + public void Dispose() + { + PriorityBindingEntry entry = _binding.Value; + + if (!entry.HasCompleted) + { + _bindings.Remove(_binding); + + entry.Dispose(); + + if (entry.Index >= _priorityLevel.ActiveBindingIndex) + { + _priorityLevel.ActivateFirstBinding(); + } + } + } + } } } From a2302a02ab766633a8df730782011c24eb29efcf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 20 Sep 2019 13:12:28 +0200 Subject: [PATCH 41/48] Fix focusedChild null in MoveFocusFromClearedIndex. --- src/Avalonia.Controls/Repeater/ViewManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index 833e708e9e..3e09a5b3ee 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -128,8 +128,7 @@ namespace Avalonia.Controls private void MoveFocusFromClearedIndex(int clearedIndex) { - IControl focusedChild = null; - var focusCandidate = FindFocusCandidate(clearedIndex, focusedChild); + var focusCandidate = FindFocusCandidate(clearedIndex, out var focusedChild); if (focusCandidate != null) { focusCandidate.Focus(); @@ -145,7 +144,7 @@ namespace Avalonia.Controls } } - IControl FindFocusCandidate(int clearedIndex, IControl focusedChild) + IControl FindFocusCandidate(int clearedIndex, out IControl focusedChild) { // Walk through all the children and find elements with index before and after the cleared index. // Note that during a delete the next element would now have the same index. @@ -183,7 +182,7 @@ namespace Avalonia.Controls // TODO: Find the next element if one exists, if not use the previous element. // If the container itself is not focusable, find a descendent that is. - + focusedChild = nextElement; return nextElement; } From c58e4a51b3764527113b86f913bea257407d0254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Fri, 20 Sep 2019 15:24:33 +0200 Subject: [PATCH 42/48] Add comments. --- src/Avalonia.Base/PriorityBindingEntry.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Base/PriorityBindingEntry.cs b/src/Avalonia.Base/PriorityBindingEntry.cs index 312018d8ee..7f5415c2d8 100644 --- a/src/Avalonia.Base/PriorityBindingEntry.cs +++ b/src/Avalonia.Base/PriorityBindingEntry.cs @@ -129,6 +129,8 @@ namespace Avalonia } else { + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. var instance = this; var newValue = value; From 17c4c10404ca8d0d0c4edafa9394acd8fa824853 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 20 Sep 2019 15:57:40 +0100 Subject: [PATCH 43/48] fix unit test for window center location. --- tests/Avalonia.Controls.UnitTests/WindowTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 75239f014f..d87014f646 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -271,8 +271,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { - var screen1 = new Mock(new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); - var screen2 = new Mock(new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false); + var screen1 = new Mock(1.0, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); + var screen2 = new Mock(1.0, new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false); var screens = new Mock(); screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object }); From 290410b2962756f9fac6bc2695c9046c799e6fc8 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 20 Sep 2019 23:18:22 +0800 Subject: [PATCH 44/48] Revert "Do it the hackey way" This reverts commit b7a42d96d2b6802ffbc6659884a99902b82b63bf. --- src/Avalonia.X11/X11Window.cs | 51 +++++++++-------------------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 707910b9a5..00761dfce8 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -42,7 +42,6 @@ namespace Avalonia.X11 private X11Window _transientParent; private double? _scalingOverride; public object SyncRoot { get; } = new object(); - private int defaultWidth = 300, defaultHeight = 200; class InputEventContainer { @@ -99,6 +98,17 @@ namespace Avalonia.X11 valueMask |= SetWindowValuemask.ColorMap; } + int defaultWidth = 300, defaultHeight = 200; + + if (!_popup) + { + var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) + .FirstOrDefault(m => m.Bounds.Contains(Position)); + + // Emulate Window 7+'s default window size behavior. + defaultWidth = (int)(monitor.WorkingArea.Width * 0.75d); + defaultHeight = (int)(monitor.WorkingArea.Height * 0.7d); + } _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, defaultWidth, defaultHeight, 0, depth, @@ -116,7 +126,7 @@ namespace Avalonia.X11 _renderHandle = _handle; Handle = new PlatformHandle(_handle, "XID"); - _realSize = new PixelSize(defaultWidth, defaultHeight); + _realSize = new PixelSize(300, 200); platform.Windows[_handle] = OnEvent; XEventMask ignoredMask = XEventMask.SubstructureRedirectMask | XEventMask.ResizeRedirectMask @@ -157,21 +167,8 @@ namespace Avalonia.X11 XFlush(_x11.Display); if(_popup) PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize)); - else - sizingState = DefaultSizingState.Start; - - } - - DefaultSizingState sizingState; - - enum DefaultSizingState - { - None, - Start, - End } - class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { private readonly X11Window _window; @@ -384,23 +381,7 @@ namespace Avalonia.X11 XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow, 0, 0, out var tx, out var ty, out _); - _configurePoint = new PixelPoint(tx, ty); - - if (sizingState == DefaultSizingState.Start) - { - var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) - .FirstOrDefault(m => m.Bounds.Contains(new PixelPoint(tx, ty))); - - // Emulate Window 7+'s default window size behavior. - var defW = (int)(monitor.WorkingArea.Width * 0.75d); - var defH = (int)(monitor.WorkingArea.Height * 0.7d); - - var defSize = new Size(defW, defH); - Resize(defSize); - - sizingState = DefaultSizingState.End; - } } if (needEnqueue) Dispatcher.UIThread.Post(() => @@ -784,12 +765,6 @@ namespace Avalonia.X11 void Resize(Size clientSize, bool force) { - // It's a hack but ohwell... - if(sizingState == DefaultSizingState.Start - & clientSize.Width != defaultWidth - & clientSize.Height != defaultHeight) - sizingState = DefaultSizingState.End; - if (!force && clientSize == ClientSize) return; @@ -807,8 +782,6 @@ namespace Avalonia.X11 _realSize = pixelSize; Resized?.Invoke(ClientSize); } - - } public void CanResize(bool value) From 7ac9475d33231dd05af170cc1f943c3b528e2509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josua=20J=C3=A4ger?= Date: Fri, 20 Sep 2019 17:23:30 +0200 Subject: [PATCH 45/48] Update ProgressBar.xaml --- src/Avalonia.Themes.Default/ProgressBar.xaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Themes.Default/ProgressBar.xaml b/src/Avalonia.Themes.Default/ProgressBar.xaml index a77a2f3ad5..72271e785a 100644 --- a/src/Avalonia.Themes.Default/ProgressBar.xaml +++ b/src/Avalonia.Themes.Default/ProgressBar.xaml @@ -7,9 +7,7 @@ - + From 08c7b70497a55a59541019df1d10305fc63f7ca8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 20 Sep 2019 17:48:45 +0100 Subject: [PATCH 46/48] allow TranslateSize and Point to be overriden. --- .../PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs index bb701da651..0647020b12 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs @@ -43,8 +43,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling); } - public Point TranslatePoint(Point pt) => pt * _parent.Scaling; + public virtual Point TranslatePoint(Point pt) => pt * _parent.Scaling; - public Size TranslateSize(Size size) => size * _parent.Scaling; + public virtual Size TranslateSize(Size size) => size * _parent.Scaling; } } From af88b38bef15ee98bd9a8f3408932f4e37564375 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 20 Sep 2019 17:54:34 +0100 Subject: [PATCH 47/48] fix osx popup positioning. --- src/Avalonia.Native/AvaloniaNativePlatform.cs | 1 - ...OsxManagedPopupPositionerPopupImplHelper.cs | 18 ++++++++++++++++++ src/Avalonia.Native/PopupImpl.cs | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 0da97b915c..6d48ab3829 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -1,7 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using Avalonia.Controls.Platform; using Avalonia.Input; diff --git a/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs new file mode 100644 index 0000000000..6c98e3c0cc --- /dev/null +++ b/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs @@ -0,0 +1,18 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Platform; + +namespace Avalonia.Native +{ + class OsxManagedPopupPositionerPopupImplHelper : ManagedPopupPositionerPopupImplHelper + { + public OsxManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) : base(parent, moveResize) + { + + } + public override Point TranslatePoint(Point pt) => pt; + + public override Size TranslateSize(Size size) => size; + } +} diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index f776ee0132..d7fa1052ff 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -22,7 +22,7 @@ namespace Avalonia.Native { Init(factory.CreatePopup(e), factory.CreateScreens()); } - PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); + PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } private void MoveResize(PixelPoint position, Size size, double scaling) From e0f3651cba281e5b42ea09b6575c60e2d3df1fd5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 20 Sep 2019 18:14:10 +0100 Subject: [PATCH 48/48] fix bottom right corner on osx. --- .../PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs index 0647020b12..8e7e429a73 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs @@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning { // Popup positioner operates with abstract coordinates, but in our case they are pixel ones var point = _parent.PointToScreen(default); - var size = PixelSize.FromSize(_parent.ClientSize, _parent.Scaling); + var size = TranslateSize(_parent.ClientSize); return new Rect(point.X, point.Y, size.Width, size.Height); }