Преглед изворни кода

Merge branch 'master' into pr-template

Max Katz пре 3 година
родитељ
комит
2e008dea6e

+ 1 - 1
native/Avalonia.Native/src/OSX/WindowImpl.mm

@@ -121,7 +121,7 @@ void WindowImpl::BringToFront()
 {
     if(Window != nullptr)
     {
-        if (![Window isMiniaturized])
+        if ([Window isVisible] && ![Window isMiniaturized])
         {
             if(IsDialog())
             {

+ 1 - 0
samples/IntegrationTestApp/MainWindow.axaml.cs

@@ -99,6 +99,7 @@ namespace IntegrationTestApp
 
             foreach (var window in lifetime.Windows)
             {
+                window.Show();
                 if (window.WindowState == WindowState.Minimized)
                     window.WindowState = WindowState.Normal;
             }

+ 2 - 1
samples/IntegrationTestApp/ShowWindowTest.axaml

@@ -3,7 +3,7 @@
         x:Class="IntegrationTestApp.ShowWindowTest"
         Name="SecondaryWindow"
         Title="Show Window Test">
-  <Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
+  <Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
     <Label Grid.Column="0" Grid.Row="1">Client Size</Label>
     <TextBox Name="ClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
              Text="{Binding ClientSize, Mode=OneWay}"/>
@@ -31,5 +31,6 @@
       <ComboBoxItem>Maximized</ComboBoxItem>
       <ComboBoxItem>Fullscreen</ComboBoxItem>
     </ComboBox>
+    <Button Name="HideButton" Grid.Row="8" Command="{Binding $parent[Window].Hide}">Hide</Button>
   </Grid>
 </Window>

+ 17 - 8
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -8,7 +8,8 @@ using Avalonia.Input.Platform;
 using Avalonia.OpenGL.Egl;
 using Avalonia.Platform;
 using Avalonia.Rendering;
-using Avalonia.Skia;
+using Avalonia.Rendering.Composition;
+using Avalonia.OpenGL;
 
 namespace Avalonia
 {
@@ -16,10 +17,8 @@ namespace Avalonia
     {
         public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
         {
-            var options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
-
             return builder
-                .UseWindowingSubsystem(() => AndroidPlatform.Initialize(options), "Android")
+                .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android")
                 .UseSkia();
         }
     }
@@ -42,9 +41,11 @@ namespace Avalonia.Android
 
         public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
 
-        public static void Initialize(AndroidPlatformOptions options)
+        internal static Compositor Compositor { get; private set; }
+
+        public static void Initialize()
         {
-            Options = options;
+            Options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
 
             AvaloniaLocator.CurrentMutable
                 .Bind<IClipboard>().ToTransient<ClipboardImpl>()
@@ -58,16 +59,24 @@ namespace Avalonia.Android
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
                 .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
 
-            if (options.UseGpu)
+            if (Options.UseGpu)
             {
                 EglPlatformOpenGlInterface.TryInitialize();
             }
+            
+            if (Options.UseCompositor)
+            {
+                Compositor = new Compositor(
+                    AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
+                    AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>());
+            }
         }
     }
 
     public sealed class AndroidPlatformOptions
     {
-        public bool UseDeferredRendering { get; set; } = true;
+        public bool UseDeferredRendering { get; set; } = false;
         public bool UseGpu { get; set; } = true;
+        public bool UseCompositor { get; set; } = true;
     }
 }

+ 20 - 1
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@@ -14,6 +14,7 @@ namespace Avalonia.Android
     internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface
     {
         private Handler _handler;
+        private static Thread s_uiThread;
 
         public AndroidThreadingInterface()
         {
@@ -76,7 +77,25 @@ namespace Avalonia.Android
             EnsureInvokeOnMainThread(() => Signaled?.Invoke(null));
         }
 
-        public bool CurrentThreadIsLoopThread => Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
+        public bool CurrentThreadIsLoopThread
+        {
+            get
+            {
+                if (s_uiThread != null)
+                    return s_uiThread == Thread.CurrentThread;
+
+                var isOnMainThread = OperatingSystem.IsAndroidVersionAtLeast(23)
+                    ? Looper.MainLooper.IsCurrentThread
+                    : Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
+                if (isOnMainThread)
+                {
+                    s_uiThread = Thread.CurrentThread;
+                    return true;
+                }
+
+                return false;
+            }
+        }
         public event Action<DispatcherPriority?> Signaled;
     }
 }

+ 6 - 3
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -19,6 +19,7 @@ using Avalonia.OpenGL.Surfaces;
 using Avalonia.Platform;
 using Avalonia.Platform.Storage;
 using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
 
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
@@ -84,9 +85,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle };
 
         public IRenderer CreateRenderer(IRenderRoot root) =>
-            AndroidPlatform.Options.UseDeferredRendering
-            ? new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
-            : new ImmediateRenderer(root);
+            AndroidPlatform.Options.UseCompositor
+                ? new CompositingRenderer(root, AndroidPlatform.Compositor)
+                : AndroidPlatform.Options.UseDeferredRendering
+                    ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
+                    : new ImmediateRenderer(root);
 
         public virtual void Hide()
         {

+ 7 - 0
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@@ -78,6 +78,13 @@ namespace Avalonia.Rendering.Composition.Server
 
             if (Root == null) 
                 return;
+
+            if ((_renderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
+            {
+                _renderTarget!.Dispose();
+                _renderTarget = null;
+            }
+
             _renderTarget ??= _renderTargetFactory();
 
             Compositor.UpdateServerTime();

+ 8 - 2
src/Avalonia.Controls/TransitioningContentControl.cs

@@ -84,13 +84,19 @@ public class TransitioningContentControl : ContentControl
 
         _lastTransitionCts?.Cancel();
         _lastTransitionCts = new CancellationTokenSource();
+        var localToken = _lastTransitionCts.Token;
 
         if (PageTransition != null)
-            await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
+            await PageTransition.Start(this, null, true, localToken);
+
+        if (localToken.IsCancellationRequested)
+        {
+            return;
+        }
 
         CurrentContent = content;
 
         if (PageTransition != null)
-            await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
+            await PageTransition.Start(null, this, true, localToken);
     }
 }

+ 7 - 0
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@@ -146,6 +146,13 @@ namespace Avalonia.OpenGL.Controls
                 return false;
             }
 
+            if (_context == null)
+            {
+                Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+                    "Unable to initialize OpenGL: unable to create additional OpenGL context.");
+                return false;
+            }
+
             GlVersion = _context.Version;
             try
             {

+ 15 - 0
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@@ -64,6 +64,12 @@ namespace Avalonia.ReactiveUI
         public static readonly StyledProperty<string?> ViewContractProperty =
             AvaloniaProperty.Register<RoutedViewHost, string?>(nameof(ViewContract));
 
+        /// <summary>
+        /// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
+        /// </summary>
+        public static readonly StyledProperty<object?> DefaultContentProperty =
+            ViewModelViewHost.DefaultContentProperty.AddOwner<RoutedViewHost>();
+
         /// <summary>
         /// Initializes a new instance of the <see cref="RoutedViewHost"/> class.
         /// </summary>
@@ -106,6 +112,15 @@ namespace Avalonia.ReactiveUI
             set => SetValue(ViewContractProperty, value);
         }
 
+        /// <summary>
+        /// Gets or sets the content displayed whenever there is no page currently routed.
+        /// </summary>
+        public object? DefaultContent
+        {
+            get => GetValue(DefaultContentProperty);
+            set => SetValue(DefaultContentProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets the ReactiveUI view locator used by this router.
         /// </summary>

+ 0 - 80
src/Avalonia.ReactiveUI/TransitioningContentControl.cs

@@ -1,80 +0,0 @@
-using System;
-using System.Threading;
-
-using Avalonia.Animation;
-using Avalonia.Controls;
-using Avalonia.Styling;
-
-namespace Avalonia.ReactiveUI
-{
-    /// <summary>
-    /// A ContentControl that animates the transition when its content is changed.
-    /// </summary>
-    [Obsolete("Use TransitioningContentControl in Avalonia.Controls namespace")]
-    public class TransitioningContentControl : ContentControl, IStyleable
-    {
-        /// <summary>
-        /// <see cref="AvaloniaProperty"/> for the <see cref="PageTransition"/> property.
-        /// </summary>
-        public static readonly StyledProperty<IPageTransition?> PageTransitionProperty =
-            AvaloniaProperty.Register<TransitioningContentControl, IPageTransition?>(nameof(PageTransition),
-                new CrossFade(TimeSpan.FromSeconds(0.5)));
-
-        /// <summary>
-        /// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
-        /// </summary>
-        public static readonly StyledProperty<object?> DefaultContentProperty =
-            AvaloniaProperty.Register<TransitioningContentControl, object?>(nameof(DefaultContent));
-
-        private CancellationTokenSource? _lastTransitionCts;
-
-        /// <summary>
-        /// Gets or sets the animation played when content appears and disappears.
-        /// </summary>
-        public IPageTransition? PageTransition
-        {
-            get => GetValue(PageTransitionProperty);
-            set => SetValue(PageTransitionProperty, value);
-        }
-
-        /// <summary>
-        /// Gets or sets the content displayed whenever there is no page currently routed.
-        /// </summary>
-        public object? DefaultContent
-        {
-            get => GetValue(DefaultContentProperty);
-            set => SetValue(DefaultContentProperty, value);
-        }
-
-        /// <summary>
-        /// Gets or sets the content with animation.
-        /// </summary>
-        public new object? Content
-        {
-            get => base.Content;
-            set => UpdateContentWithTransition(value);
-        }
-        
-        /// <summary>
-        /// TransitioningContentControl uses the default ContentControl 
-        /// template from Avalonia default theme.
-        /// </summary>
-        Type IStyleable.StyleKey => typeof(ContentControl);
-
-        /// <summary>
-        /// Updates the content with transitions.
-        /// </summary>
-        /// <param name="content">New content to set.</param>
-        private async void UpdateContentWithTransition(object? content)
-        {
-            _lastTransitionCts?.Cancel();
-            _lastTransitionCts = new CancellationTokenSource();
-
-            if (PageTransition != null)
-                await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
-            base.Content = content;
-            if (PageTransition != null)
-                await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
-        }
-    }
-}

+ 18 - 0
src/Avalonia.ReactiveUI/ViewModelViewHost.cs

@@ -1,5 +1,8 @@
 using System;
 using System.Reactive.Disposables;
+
+using Avalonia.Controls;
+
 using ReactiveUI;
 using Splat;
 
@@ -24,6 +27,12 @@ namespace Avalonia.ReactiveUI
         public static readonly StyledProperty<string?> ViewContractProperty =
             AvaloniaProperty.Register<ViewModelViewHost, string?>(nameof(ViewContract));
 
+        /// <summary>
+        /// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
+        /// </summary>
+        public static readonly StyledProperty<object?> DefaultContentProperty =
+            AvaloniaProperty.Register<ViewModelViewHost, object?>(nameof(DefaultContent));
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
         /// </summary>
@@ -55,6 +64,15 @@ namespace Avalonia.ReactiveUI
             set => SetValue(ViewContractProperty, value);
         }
 
+        /// <summary>
+        /// Gets or sets the content displayed whenever there is no page currently routed.
+        /// </summary>
+        public object? DefaultContent
+        {
+            get => GetValue(DefaultContentProperty);
+            set => SetValue(DefaultContentProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets the view locator.
         /// </summary>

+ 2 - 1
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -285,11 +285,12 @@ namespace Avalonia.Win32
 
             set
             {
-                if (IsWindowVisible(_hwnd))
+                if (IsWindowVisible(_hwnd) && _lastWindowState != value)
                 {
                     ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated
                 }
 
+                _lastWindowState = value;
                 _showWindowState = value;
             }
         }

+ 41 - 1
tests/Avalonia.IntegrationTests.Appium/WindowTests.cs

@@ -1,7 +1,9 @@
 using System;
+using System.Linq;
 using System.Runtime.InteropServices;
 using System.Threading;
 using Avalonia.Controls;
+using OpenQA.Selenium;
 using OpenQA.Selenium.Appium;
 using OpenQA.Selenium.Interactions;
 using Xunit;
@@ -55,6 +57,43 @@ namespace Avalonia.IntegrationTests.Appium
                 }
             }
         }
+        
+        [PlatformFact(TestPlatforms.Windows)]
+        public void OnWindows_Docked_Windows_Retain_Size_Position_When_Restored()
+        {
+            using (OpenWindow(new Size(400, 400), ShowWindowMode.NonOwned, WindowStartupLocation.Manual))
+            {
+                var windowState = _session.FindElementByAccessibilityId("WindowState");
+
+                Assert.Equal("Normal", windowState.GetComboBoxValue());
+                
+                
+                var window = _session.FindElements(By.XPath("//Window")).First();
+                
+                new Actions(_session)
+                    .KeyDown(Keys.Meta)
+                    .SendKeys(Keys.Left)
+                    .KeyUp(Keys.Meta)
+                    .Perform();
+                
+                var original = GetWindowInfo();
+                
+                windowState.Click();
+                _session.FindElementByName("Minimized").SendClick();
+                
+                new Actions(_session)
+                    .KeyDown(Keys.Alt)
+                    .SendKeys(Keys.Tab)
+                    .KeyUp(Keys.Alt)
+                    .Perform();
+                
+                var current = GetWindowInfo();
+                
+                Assert.Equal(original.Position, current.Position);
+                Assert.Equal(original.FrameSize, current.FrameSize);
+
+            }
+        }
 
 
         [Theory]
@@ -92,7 +131,8 @@ namespace Avalonia.IntegrationTests.Appium
                 Assert.True(clientSize.Width >= current.ScreenRect.Width);
                 Assert.True(clientSize.Height >= current.ScreenRect.Height);
 
-                windowState.Click();
+                windowState.SendClick();
+                
                 _session.FindElementByName("Normal").SendClick();
 
                 current = GetWindowInfo();

+ 29 - 0
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@@ -211,6 +211,35 @@ namespace Avalonia.IntegrationTests.Appium
             }
         }
 
+        [PlatformFact(TestPlatforms.MacOS)]
+        public void Hidden_Child_Window_Is_Not_Reshown_When_Parent_Clicked()
+        {
+            var mainWindow = _session.FindElementByAccessibilityId("MainWindow");
+
+            // We don't use dispose to close the window here, because it seems that hiding and re-showing a window
+            // causes Appium to think it's a different window.
+            OpenWindow(null, ShowWindowMode.Owned, WindowStartupLocation.Manual);
+            
+            var secondaryWindow = FindWindow(_session, "SecondaryWindow");
+            var hideButton = secondaryWindow.FindElementByAccessibilityId("HideButton");
+
+            hideButton.Click();
+                
+            var windows = _session.FindElementsByXPath("XCUIElementTypeWindow");
+            Assert.Single(windows);
+                
+            mainWindow.Click();
+                
+            windows = _session.FindElementsByXPath("XCUIElementTypeWindow");
+            Assert.Single(windows);
+                
+            _session.FindElementByAccessibilityId("RestoreAll").Click();
+
+            // Close the window manually.
+            secondaryWindow = FindWindow(_session, "SecondaryWindow");
+            secondaryWindow.GetChromeButtons().close.Click();
+        }
+
         private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location)
         {
             var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize");

+ 0 - 8
tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs

@@ -12,14 +12,6 @@ namespace Avalonia.ReactiveUI.UnitTests
 {
     public class TransitioningContentControlTest
     {
-        [Fact]
-        public void Transitioning_Control_Should_Derive_Template_From_Content_Control()
-        {
-            var target = new TransitioningContentControl();
-            var stylable = (IStyledElement)target;
-            Assert.Equal(typeof(ContentControl),stylable.StyleKey);
-        }
-
         [Fact]
         public void Transitioning_Control_Template_Should_Be_Instantiated() 
         {