diff --git a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs index f6e28483fd..2b115aec7e 100644 --- a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs @@ -1,6 +1,7 @@ -using System.Linq; using Avalonia.LogicalTree; +#nullable enable + namespace Avalonia.Controls.Templates { /// @@ -18,21 +19,23 @@ namespace Avalonia.Controls.Templates /// tree are searched. /// /// The data template or null if no matching data template was found. - public static IDataTemplate FindDataTemplate( + public static IDataTemplate? FindDataTemplate( this IControl control, object data, - IDataTemplate primary = null) + IDataTemplate? primary = null) { if (primary?.Match(data) == true) { return primary; } - foreach (var i in control.GetSelfAndLogicalAncestors().OfType()) + var currentTemplateHost = control as ILogical; + + while (currentTemplateHost != null) { - if (i.IsDataTemplatesInitialized) + if (currentTemplateHost is IDataTemplateHost hostCandidate && hostCandidate.IsDataTemplatesInitialized) { - foreach (IDataTemplate dt in i.DataTemplates) + foreach (IDataTemplate dt in hostCandidate.DataTemplates) { if (dt.Match(data)) { @@ -40,20 +43,19 @@ namespace Avalonia.Controls.Templates } } } + + currentTemplateHost = currentTemplateHost.LogicalParent; } IGlobalDataTemplates global = AvaloniaLocator.Current.GetService(); - if (global != null) + if (global != null && global.IsDataTemplatesInitialized) { - if (global.IsDataTemplatesInitialized) + foreach (IDataTemplate dt in global.DataTemplates) { - foreach (IDataTemplate dt in global.DataTemplates) + if (dt.Match(data)) { - if (dt.Match(data)) - { - return dt; - } + return dt; } } } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 15e14935ca..7a1a2cb4a3 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -99,6 +99,11 @@ namespace Avalonia.Rendering /// public string DebugFramesPath { get; set; } + /// + /// Forces the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered + /// + public bool RenderOnlyOnRenderThread { get; set; } = true; + /// public event EventHandler SceneInvalidated; @@ -180,11 +185,38 @@ namespace Avalonia.Rendering /// public void Paint(Rect rect) { - var t = (IRenderLoopTask)this; - if(t.NeedsUpdate) - UpdateScene(); - if(_scene?.Item != null) - Render(true); + if (RenderOnlyOnRenderThread) + { + while (true) + { + Scene scene; + bool? updated; + lock (_sceneLock) + { + updated = UpdateScene(); + scene = _scene?.Item; + } + + // Renderer is in invalid state, skip drawing + if(updated == null) + return; + + // Wait for the scene to be rendered or disposed + scene?.Rendered.Wait(); + + // That was an up-to-date scene, we can return immediately + if (updated == true) + return; + } + } + else + { + var t = (IRenderLoopTask)this; + if (t.NeedsUpdate) + UpdateScene(); + if (_scene?.Item != null) + Render(true); + } } /// @@ -270,13 +302,20 @@ namespace Avalonia.Rendering { if (scene?.Item != null) { - var overlay = DrawDirtyRects || DrawFps; - if (DrawDirtyRects) - _dirtyRectsDisplay.Tick(); - if (overlay) - RenderOverlay(scene.Item, ref context); - if (updated || forceComposite || overlay) - RenderComposite(scene.Item, ref context); + try + { + var overlay = DrawDirtyRects || DrawFps; + if (DrawDirtyRects) + _dirtyRectsDisplay.Tick(); + if (overlay) + RenderOverlay(scene.Item, ref context); + if (updated || forceComposite || overlay) + RenderComposite(scene.Item, ref context); + } + finally + { + scene.Item.MarkAsRendered(); + } } } } @@ -559,15 +598,15 @@ namespace Avalonia.Rendering UpdateScene(); } - private void UpdateScene() + private bool? UpdateScene() { Dispatcher.UIThread.VerifyAccess(); lock (_sceneLock) { if (_disposed) - return; + return null; if (_scene?.Item.Generation > _lastSceneId) - return; + return false; } if (_root.IsVisible) { @@ -619,6 +658,8 @@ namespace Avalonia.Rendering SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); } + + return true; } else { @@ -628,6 +669,8 @@ namespace Avalonia.Rendering _scene = null; oldScene?.Dispose(); } + + return null; } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 4f5c97cdff..6a4c532d4a 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Avalonia.Collections.Pooled; using Avalonia.VisualTree; @@ -13,6 +14,7 @@ namespace Avalonia.Rendering.SceneGraph public class Scene : IDisposable { private readonly Dictionary _index; + private readonly TaskCompletionSource _rendered = new TaskCompletionSource(); /// /// Initializes a new instance of the class. @@ -41,6 +43,8 @@ namespace Avalonia.Rendering.SceneGraph root.LayerRoot = root.Visual; } + public Task Rendered => _rendered.Task; + /// /// Gets a value identifying the scene's generation. This is incremented each time the scene is cloned. /// @@ -97,6 +101,7 @@ namespace Avalonia.Rendering.SceneGraph public void Dispose() { + _rendered.TrySetResult(false); foreach (var node in _index.Values) { node.Dispose(); @@ -340,5 +345,7 @@ namespace Avalonia.Rendering.SceneGraph } } } + + public void MarkAsRendered() => _rendered.TrySetResult(true); } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2cd3b973d8..41c061613d 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -189,6 +189,11 @@ namespace Avalonia.X11 if (platform.Options.UseDBusMenu) NativeMenuExporter = DBusMenuExporter.TryCreate(_handle); NativeControlHost = new X11NativeControlHost(_platform, this); + DispatcherTimer.Run(() => + { + Paint?.Invoke(default); + return _handle != IntPtr.Zero; + }, TimeSpan.FromMilliseconds(100)); } class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo @@ -338,7 +343,10 @@ namespace Avalonia.X11 return customRendererFactory.Create(root, loop); return _platform.Options.UseDeferredRendering ? - new DeferredRenderer(root, loop) : + new DeferredRenderer(root, loop) + { + RenderOnlyOnRenderThread = true + } : (IRenderer)new X11ImmediateRendererProxy(root, loop); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlFontFamilyAstNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlFontFamilyAstNode.cs new file mode 100644 index 0000000000..1f50e661d8 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlFontFamilyAstNode.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes +{ + class AvaloniaXamlIlFontFamilyAstNode: XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode + { + private readonly AvaloniaXamlIlWellKnownTypes _types; + private readonly string _text; + + public IXamlAstTypeReference Type { get; } + + public AvaloniaXamlIlFontFamilyAstNode(AvaloniaXamlIlWellKnownTypes types, + string text, + IXamlLineInfo lineInfo) : base(lineInfo) + { + _types = types; + _text = text; + Type = new XamlAstClrTypeReference(lineInfo, types.FontFamily, false); + } + + public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen + .Ldloc(context.ContextLocal) + .Castclass(context.Configuration.TypeMappings.UriContextProvider) + .EmitCall(context.Configuration.TypeMappings.UriContextProvider.FindMethod( + "get_BaseUri", _types.Uri, false)) + .Ldstr(_text) + .Newobj(_types.FontFamilyConstructorUriName); + return XamlILNodeEmitResult.Type(0, _types.FontFamily); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs index 99ec3744bf..15413689f8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using XamlX; using XamlX.Ast; @@ -166,17 +168,41 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public static bool CustomValueConverter(AstTransformationContext context, IXamlAstValueNode node, IXamlType type, out IXamlAstValueNode result) { - if (type.FullName == "System.TimeSpan" - && node is XamlAstTextNode tn - && !tn.Text.Contains(":")) + if (!(node is XamlAstTextNode textNode)) { - var seconds = double.Parse(tn.Text, CultureInfo.InvariantCulture); - result = new XamlStaticOrTargetedReturnMethodCallNode(tn, - type.FindMethod("FromSeconds", type, false, context.Configuration.WellKnownTypes.Double), - new[] - { - new XamlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds) - }); + result = null; + return false; + } + + var text = textNode.Text; + + var types = context.GetAvaloniaTypes(); + + if (type.FullName == "System.TimeSpan") + { + var tsText = text.Trim(); + + if (!TimeSpan.TryParse(tsText, CultureInfo.InvariantCulture, out var timeSpan)) + { + // // shorthand seconds format (ie. "0.25") + if (!tsText.Contains(":") && double.TryParse(tsText, + NumberStyles.Float | NumberStyles.AllowThousands, + CultureInfo.InvariantCulture, out var seconds)) + timeSpan = TimeSpan.FromSeconds(seconds); + else + throw new XamlX.XamlLoadException($"Unable to parse {text} as a time span", node); + } + + + result = new XamlStaticOrTargetedReturnMethodCallNode(node, + type.FindMethod("FromTicks", type, false, types.Long), + new[] { new XamlConstantNode(node, types.Long, timeSpan.Ticks) }); + return true; + } + + if (type.Equals(types.FontFamily)) + { + result = new AvaloniaXamlIlFontFamilyAstNode(types, text, node); return true; } @@ -185,9 +211,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var scope = context.ParentNodes().OfType().FirstOrDefault(); if (scope == null) throw new XamlX.XamlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node); - if (!(node is XamlAstTextNode text)) - throw new XamlX.XamlLoadException("Property should be a text node", node); - result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text); + + result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text, scope.TargetType, node ); return true; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 3dec96dc43..58f4ddfe31 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using XamlX.Emit; using XamlX.IL; using XamlX.Transform; @@ -47,6 +48,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ReflectionBindingExtension { get; } public IXamlType RelativeSource { get; } + public IXamlType Long { get; } + public IXamlType Uri { get; } + public IXamlType FontFamily { get; } + public IXamlConstructor FontFamilyConstructorUriName { get; } + public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -104,6 +110,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ItemsRepeater = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsRepeater"); ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension"); RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource"); + Long = cfg.TypeSystem.GetType("System.Int64"); + Uri = cfg.TypeSystem.GetType("System.Uri"); + FontFamily = cfg.TypeSystem.GetType("Avalonia.Media.FontFamily"); + FontFamilyConstructorUriName = FontFamily.FindConstructor(new List { Uri, XamlIlTypes.String }); } } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 8de64e56ff..8e5631e198 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -16,7 +16,7 @@ namespace Avalonia.Markup.Parsers internal static class BindingExpressionGrammar { - public static (IList Nodes, SourceMode Mode) Parse(ref CharacterReader r) + public static (List Nodes, SourceMode Mode) Parse(ref CharacterReader r) { var nodes = new List(); var state = State.Start; diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 57da1c4d66..7fb146899b 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -69,7 +69,8 @@ namespace Avalonia.Win32 { UnmanagedMethods.WindowStyles style = UnmanagedMethods.WindowStyles.WS_POPUP | - UnmanagedMethods.WindowStyles.WS_CLIPSIBLINGS; + UnmanagedMethods.WindowStyles.WS_CLIPSIBLINGS | + UnmanagedMethods.WindowStyles.WS_CLIPCHILDREN; UnmanagedMethods.WindowStyles exStyle = UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW |