diff --git a/.ncrunch/Avalonia.Desktop.v3.ncrunchproject b/.ncrunch/Avalonia.Desktop.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Desktop.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.net461.v3.ncrunchproject b/.ncrunch/Avalonia.net461.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution index 1b5b0c8930..a2208a9a91 100644 --- a/Avalonia.v3.ncrunchsolution +++ b/Avalonia.v3.ncrunchsolution @@ -2,6 +2,7 @@ tests\TestFiles\**.* + src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll True .ncrunch diff --git a/samples/ControlCatalog/Pages/DropDownPage.xaml b/samples/ControlCatalog/Pages/DropDownPage.xaml index 864d2be49c..7673294e46 100644 --- a/samples/ControlCatalog/Pages/DropDownPage.xaml +++ b/samples/ControlCatalog/Pages/DropDownPage.xaml @@ -27,6 +27,15 @@ + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/DropDownPage.xaml.cs b/samples/ControlCatalog/Pages/DropDownPage.xaml.cs index edab5f1ceb..397f9f21e1 100644 --- a/samples/ControlCatalog/Pages/DropDownPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DropDownPage.xaml.cs @@ -13,6 +13,9 @@ namespace ControlCatalog.Pages private void InitializeComponent() { AvaloniaXamlLoader.Load(this); + var fontDropDown = this.Find("fontDropDown"); + fontDropDown.Items = Avalonia.Media.FontFamily.SystemFontFamilies; + fontDropDown.SelectedIndex = 0; } } } diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index bc7e106a2a..6f601a3e13 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -19,6 +19,7 @@ namespace Avalonia.Animation private ulong? _iterationCount; private ulong _currentIteration; private bool _gotFirstKFValue; + private bool _playbackReversed; private FillMode _fillMode; private PlaybackDirection _playbackDirection; private Animator _animator; @@ -160,9 +161,14 @@ namespace Avalonia.Animation _currentIteration = (ulong)(opsTime / iterationTime); - // Stop animation when the current iteration is beyond the iteration count. + // Stop animation when the current iteration is beyond the iteration count + // and snap the last iteration value to exact values. if ((_currentIteration + 1) > _iterationCount) + { + var easedTime = _easeFunc.Ease(_playbackReversed ? 0.0 : 1.0); + _lastInterpValue = _interpolator(easedTime, _neutralValue); DoComplete(); + } if (playbackTime <= iterDuration) { @@ -170,27 +176,26 @@ namespace Avalonia.Animation var normalizedTime = playbackTime / iterDuration; // Check if normalized time needs to be reversed according to PlaybackDirection - - bool playbackReversed; + switch (_playbackDirection) { case PlaybackDirection.Normal: - playbackReversed = false; + _playbackReversed = false; break; case PlaybackDirection.Reverse: - playbackReversed = true; + _playbackReversed = true; break; case PlaybackDirection.Alternate: - playbackReversed = (_currentIteration % 2 == 0) ? false : true; + _playbackReversed = (_currentIteration % 2 == 0) ? false : true; break; case PlaybackDirection.AlternateReverse: - playbackReversed = (_currentIteration % 2 == 0) ? true : false; + _playbackReversed = (_currentIteration % 2 == 0) ? true : false; break; default: throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}"); } - if (playbackReversed) + if (_playbackReversed) normalizedTime = 1 - normalizedTime; // Ease and interpolate diff --git a/src/Avalonia.OpenGL/EglContext.cs b/src/Avalonia.OpenGL/EglContext.cs index 27b1abe411..17caf84179 100644 --- a/src/Avalonia.OpenGL/EglContext.cs +++ b/src/Avalonia.OpenGL/EglContext.cs @@ -31,14 +31,14 @@ namespace Avalonia.OpenGL public void MakeCurrent() { if (!_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, Context)) - throw new OpenGlException("eglMakeCurrent failed"); + throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); } public void MakeCurrent(EglSurface surface) { - var surf = ((EglSurface)surface)?.DangerousGetHandle() ?? OffscreenSurface; + var surf = surface?.DangerousGetHandle() ?? OffscreenSurface; if (!_egl.MakeCurrent(_disp.Handle, surf, surf, Context)) - throw new OpenGlException("eglMakeCurrent failed"); + throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); } } } diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 90a70adcb7..2a75e9458e 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; using System.Runtime.InteropServices; using Avalonia.Platform.Interop; using static Avalonia.OpenGL.EglConsts; @@ -34,11 +33,11 @@ namespace Avalonia.OpenGL if (_display == IntPtr.Zero) _display = _egl.GetDisplay(IntPtr.Zero); - if(_display == IntPtr.Zero) - throw new OpenGlException("eglGetDisplay failed"); - + if (_display == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglGetDisplay", _egl); + if (!_egl.Initialize(_display, out var major, out var minor)) - throw new OpenGlException("eglInitialize failed"); + throw OpenGlException.GetFormattedException("eglInitialize", _egl); foreach (var cfg in new[] { @@ -113,7 +112,7 @@ namespace Avalonia.OpenGL var shareCtx = (EglContext)share; var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes); if (ctx == IntPtr.Zero) - throw new OpenGlException("eglCreateContext failed"); + throw OpenGlException.GetFormattedException("eglCreateContext", _egl); var surf = _egl.CreatePBufferSurface(_display, _config, new[] { EGL_WIDTH, 1, @@ -121,7 +120,7 @@ namespace Avalonia.OpenGL EGL_NONE }); if (surf == IntPtr.Zero) - throw new OpenGlException("eglCreatePbufferSurface failed"); + throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); var rv = new EglContext(this, _egl, ctx, surf); rv.MakeCurrent(null); return rv; @@ -130,14 +129,14 @@ namespace Avalonia.OpenGL public void ClearContext() { if (!_egl.MakeCurrent(_display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)) - throw new OpenGlException("eglMakeCurrent failed"); + throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); } public EglSurface CreateWindowSurface(IntPtr window) { var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE}); if (s == IntPtr.Zero) - throw new OpenGlException("eglCreateWindowSurface failed"); + throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl); return new EglSurface(this, _egl, s); } diff --git a/src/Avalonia.OpenGL/EglErrors.cs b/src/Avalonia.OpenGL/EglErrors.cs new file mode 100644 index 0000000000..bfe46f2b69 --- /dev/null +++ b/src/Avalonia.OpenGL/EglErrors.cs @@ -0,0 +1,21 @@ +namespace Avalonia.OpenGL +{ + public enum EglErrors + { + EGL_SUCCESS = EglConsts.EGL_SUCCESS, + EGL_NOT_INITIALIZED = EglConsts.EGL_NOT_INITIALIZED, + EGL_BAD_ACCESS = EglConsts.EGL_BAD_ACCESS, + EGL_BAD_ALLOC = EglConsts.EGL_BAD_ALLOC, + EGL_BAD_ATTRIBUTE = EglConsts.EGL_BAD_ATTRIBUTE, + EGL_BAD_CONTEXT = EglConsts.EGL_BAD_CONTEXT, + EGL_BAD_CONFIG = EglConsts.EGL_BAD_CONFIG, + EGL_BAD_CURRENT_SURFACE = EglConsts.EGL_BAD_CURRENT_SURFACE, + EGL_BAD_DISPLAY = EglConsts.EGL_BAD_DISPLAY, + EGL_BAD_SURFACE = EglConsts.EGL_BAD_SURFACE, + EGL_BAD_MATCH = EglConsts.EGL_BAD_MATCH, + EGL_BAD_PARAMETER = EglConsts.EGL_BAD_PARAMETER, + EGL_BAD_NATIVE_PIXMAP = EglConsts.EGL_BAD_NATIVE_PIXMAP, + EGL_BAD_NATIVE_WINDOW = EglConsts.EGL_BAD_NATIVE_WINDOW, + EGL_CONTEXT_LOST = EglConsts.EGL_CONTEXT_LOST + } +} diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs index 535f66ee1f..c41a01340c 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -1,7 +1,6 @@ using System; using Avalonia.Platform; using Avalonia.Platform.Interop; -using static Avalonia.OpenGL.EglConsts; namespace Avalonia.OpenGL { @@ -32,8 +31,12 @@ namespace Avalonia.OpenGL var lib = dyn.LoadLibrary(library); return (s, o) => dyn.GetProcAddress(lib, s, o); } - + // ReSharper disable UnassignedGetOnlyAutoProperty + public delegate int EglGetError(); + [EntryPoint("eglGetError")] + public EglGetError GetError { get; } + public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay); [EntryPoint("eglGetDisplay")] public EglGetDisplay GetDisplay { get; } diff --git a/src/Avalonia.OpenGL/GlConsts.cs b/src/Avalonia.OpenGL/GlConsts.cs index 3084a6f958..275f351e3e 100644 --- a/src/Avalonia.OpenGL/GlConsts.cs +++ b/src/Avalonia.OpenGL/GlConsts.cs @@ -456,12 +456,15 @@ namespace Avalonia.OpenGL public const int GL_RENDERER = 0x1F01; public const int GL_VERSION = 0x1F02; public const int GL_EXTENSIONS = 0x1F03; + public const int GL_NO_ERROR = 0; public const int GL_INVALID_ENUM = 0x0500; public const int GL_INVALID_VALUE = 0x0501; public const int GL_INVALID_OPERATION = 0x0502; public const int GL_STACK_OVERFLOW = 0x0503; public const int GL_STACK_UNDERFLOW = 0x0504; public const int GL_OUT_OF_MEMORY = 0x0505; + public const int GL_INVALID_FRAMEBUFFER_OPERATION = 0x0506; + public const int GL_CONTEXT_LOST = 0x0507; public const int GL_CURRENT_BIT = 0x00000001; public const int GL_POINT_BIT = 0x00000002; public const int GL_LINE_BIT = 0x00000004; diff --git a/src/Avalonia.OpenGL/GlErrors.cs b/src/Avalonia.OpenGL/GlErrors.cs new file mode 100644 index 0000000000..ecc7d21c7a --- /dev/null +++ b/src/Avalonia.OpenGL/GlErrors.cs @@ -0,0 +1,15 @@ +namespace Avalonia.OpenGL +{ + public enum GlErrors + { + GL_NO_ERROR = GlConsts.GL_NO_ERROR, + GL_INVALID_ENUM = GlConsts.GL_INVALID_ENUM, + GL_INVALID_VALUE = GlConsts.GL_INVALID_VALUE, + GL_INVALID_OPERATION = GlConsts.GL_INVALID_OPERATION, + GL_INVALID_FRAMEBUFFER_OPERATION = GlConsts.GL_INVALID_FRAMEBUFFER_OPERATION, + GL_STACK_OVERFLOW = GlConsts.GL_STACK_OVERFLOW, + GL_STACK_UNDERFLOW = GlConsts.GL_STACK_UNDERFLOW, + GL_OUT_OF_MEMORY = GlConsts.GL_OUT_OF_MEMORY, + GL_CONTEXT_LOST = GlConsts.GL_CONTEXT_LOST + } +} diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 60dc5381d4..f23e1c5829 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -19,7 +19,10 @@ namespace Avalonia.OpenGL public T GetProcAddress(string proc) => Marshal.GetDelegateForFunctionPointer(GetProcAddress(proc)); // ReSharper disable UnassignedGetOnlyAutoProperty - + public delegate int GlGetError(); + [EntryPoint("glGetError")] + public GlGetError GetError { get; } + public delegate void GlClearStencil(int s); [EntryPoint("glClearStencil")] public GlClearStencil ClearStencil { get; } diff --git a/src/Avalonia.OpenGL/OpenGlException.cs b/src/Avalonia.OpenGL/OpenGlException.cs index 7ba539a2b2..d3cd7d059e 100644 --- a/src/Avalonia.OpenGL/OpenGlException.cs +++ b/src/Avalonia.OpenGL/OpenGlException.cs @@ -4,9 +4,39 @@ namespace Avalonia.OpenGL { public class OpenGlException : Exception { + public int? ErrorCode { get; private set; } + public OpenGlException(string message) : base(message) { + } + + private OpenGlException(string message, int errorCode) : base(message) + { + ErrorCode = errorCode; + } + + public static OpenGlException GetFormattedException(string funcName, EglInterface egl) + { + return GetFormattedException(typeof(EglErrors), funcName, egl.GetError()); + } + + public static OpenGlException GetFormattedException(string funcName, GlInterface gl) + { + return GetFormattedException(typeof(GlErrors), funcName, gl.GetError()); + } + private static OpenGlException GetFormattedException(Type consts, string funcName, int errorCode) + { + try + { + string errorName = Enum.GetName(consts, errorCode); + return new OpenGlException( + $"{funcName} failed with error {errorName} (0x{errorCode.ToString("X")})", errorCode); + } + catch (ArgumentException) + { + return new OpenGlException($"{funcName} failed with error 0x{errorCode.ToString("X")}", errorCode); + } } } } diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs index cd019e6535..362ac86e50 100644 --- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs @@ -116,11 +116,9 @@ namespace Avalonia.Styling } } - if (Name != null) + if (Name != null && control.Name != Name) { - return control.Name == Name ? - SelectorMatch.AlwaysThisInstance : - SelectorMatch.NeverThisInstance; + return SelectorMatch.NeverThisInstance; } if (_classes.IsValueCreated && _classes.Value.Count > 0) @@ -130,17 +128,13 @@ namespace Avalonia.Styling var observable = new ClassObserver(control.Classes, _classes.Value); return new SelectorMatch(observable); } - else + else if (!Matches(control.Classes)) { - return Matches(control.Classes) ? - SelectorMatch.AlwaysThisInstance : - SelectorMatch.NeverThisInstance; + return SelectorMatch.NeverThisInstance; } } - else - { - return SelectorMatch.AlwaysThisType; - } + + return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance; } protected override Selector MovePrevious() => _previous; diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 0ba25e6c0d..5c152cd8a0 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Avalonia.Media.Fonts; +using Avalonia.Platform; namespace Avalonia.Media { @@ -51,6 +52,12 @@ namespace Avalonia.Media /// public static FontFamily Default => new FontFamily(String.Empty); + /// + /// Represents all font families in the system. This can be an expensive call depending on platform implementation. + /// + public static IEnumerable SystemFontFamilies => + AvaloniaLocator.Current.GetService().InstalledFontNames.Select(name => new FontFamily(name)); + /// /// Gets the primary family name of the font family. /// diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index aacdef0538..3a1f79e32a 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -13,6 +13,11 @@ namespace Avalonia.Platform /// public interface IPlatformRenderInterface { + /// + /// Get all installed fonts in the system + /// + IEnumerable InstalledFontNames { get; } + /// /// Creates a formatted text implementation. /// diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 9b10d74c64..8080c27831 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -19,6 +19,8 @@ namespace Avalonia.Skia { private GRContext GrContext { get; } + public IEnumerable InstalledFontNames => SKFontManager.Default.FontFamilies; + public PlatformRenderInterface() { var gl = AvaloniaLocator.Current.GetService(); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 3ec18dac5e..8412a65e23 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -41,6 +41,19 @@ namespace Avalonia.Direct2D1 public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; } + public IEnumerable InstalledFontNames + { + get + { + var cache = Direct2D1FontCollectionCache.s_installedFontCollection; + var length = cache.FontFamilyCount; + for (int i = 0; i < length; i++) + { + var names = cache.GetFontFamily(i).FamilyNames; + yield return names.GetString(0); + } + } + } private static readonly object s_initLock = new object(); private static bool s_initialized = false; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index d60aa15a5e..d93a59d384 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs @@ -7,7 +7,7 @@ namespace Avalonia.Direct2D1.Media internal static class Direct2D1FontCollectionCache { private static readonly ConcurrentDictionary s_cachedCollections; - private static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; + internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; static Direct2D1FontCollectionCache() { diff --git a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs index d89b8469df..f7a8774689 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs @@ -8,6 +8,7 @@ using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.Data; using Xunit; +using Avalonia.Animation.Easings; namespace Avalonia.Animation.UnitTests { @@ -73,5 +74,58 @@ namespace Avalonia.Animation.UnitTests Assert.True(animationRun.Status == TaskStatus.RanToCompletion); Assert.Equal(border.Width, 100d); } + + [Fact] + public void Check_FillModes_Start_and_End_Values_if_Retained() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 0d), + }, + Cue = new Cue(0.0d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 300d), + }, + Cue = new Cue(1.0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(0.05d), + Delay = TimeSpan.FromSeconds(0.05d), + Easing = new SineEaseInOut(), + FillMode = FillMode.Both, + Children = + { + keyframe1, + keyframe2 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 100d, + }; + + var clock = new TestClock(); + var animationRun = animation.RunAsync(border, clock); + + clock.Step(TimeSpan.FromSeconds(0d)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(0.050d)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(0.100d)); + Assert.Equal(border.Width, 300d); + } } } diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs index 04b29376b0..ba9b443d92 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs @@ -85,6 +85,44 @@ namespace Avalonia.Styling.UnitTests Assert.Equal(SelectorMatchResult.NeverThisType, match.Result); } + [Fact] + public void Named_Class_Template_Child_Of_Control() + { + var template = new FuncControlTemplate(parent => + { + return new Border + { + Name = "border", + }; + }); + + var control = new Button + { + Template = template, + }; + + control.ApplyTemplate(); + + var selector = default(Selector) + .OfType