Browse Source

Merge branch 'master' into pr-template

pull/8712/head
Max Katz 4 years ago
committed by GitHub
parent
commit
2e008dea6e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  2. 1
      samples/IntegrationTestApp/MainWindow.axaml.cs
  3. 3
      samples/IntegrationTestApp/ShowWindowTest.axaml
  4. 25
      src/Android/Avalonia.Android/AndroidPlatform.cs
  5. 21
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  6. 9
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  7. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  8. 10
      src/Avalonia.Controls/TransitioningContentControl.cs
  9. 7
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  10. 15
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  11. 80
      src/Avalonia.ReactiveUI/TransitioningContentControl.cs
  12. 18
      src/Avalonia.ReactiveUI/ViewModelViewHost.cs
  13. 3
      src/Windows/Avalonia.Win32/WindowImpl.cs
  14. 42
      tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
  15. 29
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
  16. 8
      tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs

2
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
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;
}

3
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>

25
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;
}
}

21
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;
}
}

9
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
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();

10
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
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
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>

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
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>

3
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;
}
}

42
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
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");

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()
{

Loading…
Cancel
Save