Browse Source

Merge branch 'master' into fixes/resizing-wasm

pull/7394/head
Dan Walmsley 4 years ago
parent
commit
8dfd144cf9
  1. 3
      src/Avalonia.Controls/ApiCompatBaseline.txt
  2. 96
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  3. 6
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  4. 8
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  5. 3
      src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
  6. 3
      src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml
  7. 4
      src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml
  8. 8
      src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml
  9. 3
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  10. 8
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  11. 4
      src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml
  12. 6
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  13. 3
      src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
  14. 8
      src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml
  15. 3
      src/Avalonia.Themes.Fluent/Controls/TabItem.xaml
  16. 3
      src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml
  17. 2
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  18. 3
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  19. 3
      src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml
  20. 4
      src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml
  21. 45
      src/Avalonia.Visuals/Media/PreciseEllipticArcHelper.cs
  22. 24
      src/Avalonia.Visuals/Media/StreamGeometryContext.cs
  23. 1
      src/Shared/RenderHelpers/RenderHelpers.projitems
  24. 176
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
  25. 56
      tests/Avalonia.RenderTests/Media/StreamGeometryTests.cs
  26. BIN
      tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png
  27. BIN
      tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.immediate.expected.png
  28. BIN
      tests/TestFiles/Skia/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png
  29. BIN
      tests/TestFiles/Skia/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.immediate.expected.png

3
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -36,6 +36,7 @@ CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.WindowBase' does not i
InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.TryShutdown(System.Int32)' is present in the implementation but not in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Embedding.EmbeddableControlRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
@ -62,4 +63,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 63
Total Issues: 64

96
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -76,36 +76,21 @@ namespace Avalonia.Controls.ApplicationLifetimes
return;
if (ShutdownMode == ShutdownMode.OnLastWindowClose && _windows.Count == 0)
Shutdown();
else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow)
Shutdown();
TryShutdown();
else if (ShutdownMode == ShutdownMode.OnMainWindowClose && ReferenceEquals(window, MainWindow))
TryShutdown();
}
public void Shutdown(int exitCode = 0)
{
if (_isShuttingDown)
throw new InvalidOperationException("Application is already shutting down.");
_exitCode = exitCode;
_isShuttingDown = true;
DoShutdown(new ShutdownRequestedEventArgs(), true, exitCode);
}
try
{
foreach (var w in Windows)
w.Close();
var e = new ControlledApplicationLifetimeExitEventArgs(exitCode);
Exit?.Invoke(this, e);
_exitCode = e.ApplicationExitCode;
}
finally
{
_cts?.Cancel();
_cts = null;
_isShuttingDown = false;
}
public bool TryShutdown(int exitCode = 0)
{
return DoShutdown(new ShutdownRequestedEventArgs(), false, exitCode);
}
public int Start(string[] args)
{
Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
@ -114,7 +99,10 @@ namespace Avalonia.Controls.ApplicationLifetimes
if(options != null && options.ProcessUrlActivationCommandLine && args.Length > 0)
{
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(args);
if (Application.Current is IApplicationPlatformEvents events)
{
events.RaiseUrlsOpened(args);
}
}
var lifetimeEvents = AvaloniaLocator.Current.GetService<IPlatformLifetimeEventsImpl>();
@ -145,23 +133,57 @@ namespace Avalonia.Controls.ApplicationLifetimes
if (_activeLifetime == this)
_activeLifetime = null;
}
private void OnShutdownRequested(object sender, ShutdownRequestedEventArgs e)
private bool DoShutdown(ShutdownRequestedEventArgs e, bool force = false, int exitCode = 0)
{
ShutdownRequested?.Invoke(this, e);
if (!force)
{
ShutdownRequested?.Invoke(this, e);
if (e.Cancel)
return;
if (e.Cancel)
return false;
if (_isShuttingDown)
throw new InvalidOperationException("Application is already shutting down.");
}
_exitCode = exitCode;
_isShuttingDown = true;
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
// owners.
foreach (var w in Windows)
if (w.Owner is null)
w.Close();
if (Windows.Count > 0)
e.Cancel = true;
try
{
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
// owners.
foreach (var w in Windows)
{
if (w.Owner is null)
{
w.Close();
}
}
if (!force && Windows.Count > 0)
{
e.Cancel = true;
return false;
}
var args = new ControlledApplicationLifetimeExitEventArgs(exitCode);
Exit?.Invoke(this, args);
_exitCode = args.ApplicationExitCode;
}
finally
{
_cts?.Cancel();
_cts = null;
_isShuttingDown = false;
}
return true;
}
private void OnShutdownRequested(object sender, ShutdownRequestedEventArgs e) => DoShutdown(e);
}
public class ClassicDesktopStyleApplicationLifetimeOptions

6
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@ -9,6 +9,12 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// </summary>
public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime
{
/// <summary>
/// Tries to Shutdown the application. <see cref="ShutdownRequested" /> event can be used to cancel the shutdown.
/// </summary>
/// <param name="exitCode">An integer exit code for an application. The default exit code is 0.</param>
bool TryShutdown(int exitCode = 0);
/// <summary>
/// Gets the arguments passed to the
/// <see cref="ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime{T}(T, string[], ShutdownMode)"/>

8
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -133,9 +133,13 @@ namespace Avalonia.Native
var quitItem = new NativeMenuItem("Quit") { Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta) };
quitItem.Click += (_, _) =>
{
if (Application.Current is { ApplicationLifetime: IControlledApplicationLifetime lifetime })
if (Application.Current is { ApplicationLifetime: IClassicDesktopStyleApplicationLifetime lifetime })
{
lifetime.Shutdown();
lifetime.TryShutdown();
}
else if(Application.Current is {ApplicationLifetime: IControlledApplicationLifetime controlledLifetime})
{
controlledLifetime.Shutdown();
}
};

3
src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml

@ -1,7 +1,8 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls">
xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20"
Background="Black">

3
src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml

@ -7,7 +7,8 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Margin="20, 20, 20, 200">
<CalendarDatePicker Width="200"

4
src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml

@ -6,7 +6,9 @@
-->
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True"
x:DataType="CalendarItem">
<Design.PreviewWith>
<Border Padding="20">
<Calendar />

8
src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml

@ -1,6 +1,8 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System">
xmlns:sys="using:System"
x:CompileBindings="True"
x:DataType="DataValidationErrors">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="20">
@ -27,7 +29,7 @@
<Style Selector="DataValidationErrors">
<Style.Resources>
<DataTemplate x:Key="InlineDataValidationErrorTemplate">
<ItemsControl Items="{Binding}" TextBlock.Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}">
<ItemsControl Items="{Binding}" x:DataType="DataValidationErrors" TextBlock.Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}">
<ItemsControl.Styles>
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
@ -54,7 +56,7 @@
</DockPanel>
</ControlTemplate>
<DataTemplate x:Key="TooltipDataValidationErrorTemplate">
<DataTemplate x:DataType="DataValidationErrors" x:Key="TooltipDataValidationErrorTemplate">
<Panel Name="PART_InlineErrorTemplatePanel" Background="Transparent">
<Panel.Styles>
<Style Selector="Panel#PART_InlineErrorTemplatePanel">

3
src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml

@ -7,7 +7,8 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<DatePicker CornerRadius="10" />

8
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -1,4 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Orientation="Vertical" Spacing="20" Width="350">
@ -69,13 +71,13 @@
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}"
IsEnabled="{TemplateBinding IsEnabled}" />
<Border x:Name="ExpanderContent"
Padding="{DynamicResource ExpanderContentPadding}"
Background="{DynamicResource ExpanderDropDownBackground}"
BorderBrush="{DynamicResource ExpanderDropDownBorderBrush}"
IsVisible="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
IsVisible="{TemplateBinding IsExpanded, Mode=TwoWay}">
<ContentPresenter x:Name="PART_ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

4
src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml

@ -142,7 +142,7 @@
<Rectangle Fill="{DynamicResource SystemControlHighlightAltBaseMediumLowBrush}" Height="1" Margin="0,5,0,0" DockPanel.Dock="Bottom"/>
<DockPanel Margin="4,0">
<Button Command="{Binding GoUp}" DockPanel.Dock="Left">
<Path Data="M 0 7 L 7 0 L 14 7 M 7 0 L 7 16" Stroke="{Binding $parent[Button].Foreground}" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,1,0,-1"/>
<Path Data="M 0 7 L 7 0 L 14 7 M 7 0 L 7 16" Stroke="{CompiledBinding $parent[Button].Foreground}" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,1,0,-1"/>
</Button>
<TextBox x:Name="Location" Text="{Binding Location}">
<TextBox.KeyBindings>
@ -321,4 +321,4 @@
<Style Selector="dialogs|ManagedFileChooser /template/ UniformGrid#Finalize > Button /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
</Styles>
</Styles>

6
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -1,7 +1,9 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:DataType="MenuItem"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20"
Width="400"
@ -158,7 +160,7 @@
</ContentPresenter>
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
MinWidth="{ReflectionBinding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"
OverlayInputPassThroughElement="{Binding $parent[Menu]}">

3
src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml

@ -1,6 +1,7 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Avalonia.Themes.Fluent"
x:CompileBindings="True"
Selector="NativeMenuBar">
<Style.Resources>
<local:InverseBooleanValueConverter x:Key="AvaloniaThemesDefaultNativeMenuBarInverseBooleanValueConverter" Default="True"/>
@ -11,7 +12,7 @@
IsVisible="{Binding $parent[TopLevel].(NativeMenu.IsNativeMenuExported), Converter={StaticResource AvaloniaThemesDefaultNativeMenuBarInverseBooleanValueConverter}}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles>
<Style Selector="MenuItem">
<Style x:DataType="NativeMenuItem" Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="InputGesture" Value="{Binding Gesture}"/>
<Setter Property="Items" Value="{Binding Menu.Items}"/>

8
src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml

@ -1,4 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="10">
@ -29,8 +31,8 @@
<Border x:Name="IndeterminateProgressBarIndicator" CornerRadius="{TemplateBinding CornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
<Border x:Name="IndeterminateProgressBarIndicator2" CornerRadius="{TemplateBinding CornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
</Panel>
<LayoutTransformControl x:Name="PART_LayoutTransformControl" HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{Binding ShowProgressText, RelativeSource={RelativeSource TemplatedParent}}">
<TextBlock Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
<LayoutTransformControl x:Name="PART_LayoutTransformControl" HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{TemplateBinding ShowProgressText}">
<TextBlock Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" Text="{ReflectionBinding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
</LayoutTransformControl>
</Panel>
</Border>

3
src/Avalonia.Themes.Fluent/Controls/TabItem.xaml

@ -1,5 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="20">

3
src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml

@ -1,5 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="20">

2
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -166,7 +166,7 @@
<Setter Property="InnerRightContent">
<Template>
<ToggleButton Classes="passwordBoxRevealButton"
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}" />
IsChecked="{CompiledBinding $parent[TextBox].RevealPassword, Mode=TwoWay}" />
</Template>
</Setter>
</Style>

3
src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml

@ -7,7 +7,8 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:CompileBindings="True">
<Styles.Resources>
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double>

3
src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml

@ -1,6 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:CompileBindings="True">
<Design.PreviewWith>
<Grid RowDefinitions="Auto,Auto"

4
src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml

@ -1,5 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:DataType="WindowNotificationManager"
x:CompileBindings="True">
<Style Selector="WindowNotificationManager">
<Setter Property="Margin" Value="0 0"/>
<Setter Property="Template">

45
src/Shared/RenderHelpers/ArcToHelper.cs → src/Avalonia.Visuals/Media/PreciseEllipticArcHelper.cs

@ -1,5 +1,6 @@
// Copyright © 2003-2004, Luc Maisonobe
// 2015 - Alexey Rozanov <thehdotx@gmail.com> - Adaptations for Avalonia and oval center computations
// 2022 - Alexey Rozanov <thehdotx@gmail.com> - Fix for arcs sometimes drawn in inverted order.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with
@ -49,12 +50,10 @@
// Adapted from http://www.spaceroots.org/documents/ellipse/EllipticalArc.java
using System;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.RenderHelpers
namespace Avalonia.Media
{
static class ArcToHelper
static class PreciseEllipticArcHelper
{
/// <summary>
/// This class represents an elliptical arc on a 2D plane.
@ -292,6 +291,8 @@ namespace Avalonia.RenderHelpers
/// </summary>
internal double G2;
public bool DrawInOppositeDirection { get; set; }
/// <summary>
/// Builds an elliptical arc composed of the full unit circle around (0,0)
/// </summary>
@ -850,7 +851,7 @@ namespace Avalonia.RenderHelpers
/// Builds the arc outline using given StreamGeometryContext and default (max) Bezier curve degree and acceptable error of half a pixel (0.5)
/// </summary>
/// <param name="path">A StreamGeometryContext to output the path commands to</param>
public void BuildArc(IStreamGeometryContextImpl path)
public void BuildArc(StreamGeometryContext path)
{
BuildArc(path, _maxDegree, _defaultFlatness, true);
}
@ -862,7 +863,7 @@ namespace Avalonia.RenderHelpers
/// <param name="degree">degree of the Bezier curve to use</param>
/// <param name="threshold">acceptable error</param>
/// <param name="openNewFigure">if true, a new figure will be started in the specified StreamGeometryContext</param>
public void BuildArc(IStreamGeometryContextImpl path, int degree, double threshold, bool openNewFigure)
public void BuildArc(StreamGeometryContext path, int degree, double threshold, bool openNewFigure)
{
if (degree < 1 || degree > _maxDegree)
throw new ArgumentException($"degree should be between {1} and {_maxDegree}", nameof(degree));
@ -888,8 +889,18 @@ namespace Avalonia.RenderHelpers
}
n = n << 1;
}
dEta = (Eta2 - Eta1) / n;
etaB = Eta1;
if (!DrawInOppositeDirection)
{
dEta = (Eta2 - Eta1) / n;
etaB = Eta1;
}
else
{
dEta = (Eta1 - Eta2) / n;
etaB = Eta2;
}
double cosEtaB = Math.Cos(etaB);
double sinEtaB = Math.Sin(etaB);
double aCosEtaB = A * cosEtaB;
@ -922,6 +933,7 @@ namespace Avalonia.RenderHelpers
*/
//otherwise we're supposed to be already at the (xB,yB)
double t = Math.Tan(0.5 * dEta);
double alpha = Math.Sin(dEta) * (Math.Sqrt(4 + 3 * t * t) - 1) / 3;
@ -1012,7 +1024,7 @@ namespace Avalonia.RenderHelpers
/// <param name="theta">Ellipse theta (angle measured from the abscissa)</param>
/// <param name="isLargeArc">Large Arc Indicator</param>
/// <param name="clockwise">Clockwise direction flag</param>
public static void BuildArc(IStreamGeometryContextImpl path, Point p1, Point p2, Size size, double theta, bool isLargeArc, bool clockwise)
public static void BuildArc(StreamGeometryContext path, Point p1, Point p2, Size size, double theta, bool isLargeArc, bool clockwise)
{
// var orthogonalizer = new RotateTransform(-theta);
@ -1058,7 +1070,7 @@ namespace Avalonia.RenderHelpers
}
double multiplier = Math.Sqrt(numerator / denominator);
double multiplier = Math.Sqrt(Math.Abs(numerator / denominator));
Point mulVec = new Point(rx * p1S.Y / ry, -ry * p1S.X / rx);
int sign = (clockwise != isLargeArc) ? 1 : -1;
@ -1104,9 +1116,16 @@ namespace Avalonia.RenderHelpers
// path.LineTo(c, true, true);
// path.LineTo(clockwise ? p1 : p2, true,true);
path.LineTo(clockwise ? p1 : p2);
var arc = new EllipticalArc(c.X, c.Y, rx, ry, theta, thetaStart, thetaEnd, false);
double ManhattanDistance(Point p1, Point p2) => Math.Abs(p1.X - p2.X) + Math.Abs(p1.Y - p2.Y);
if (ManhattanDistance(p2, new Point(arc.X2, arc.Y2)) > ManhattanDistance(p2, new Point(arc.X1, arc.Y1)))
{
arc.DrawInOppositeDirection = true;
}
arc.BuildArc(path, arc._maxDegree, arc._defaultFlatness, false);
//path.LineTo(p2);
//uncomment this to draw a pie
//path.LineTo(c, true, true);
@ -1136,9 +1155,9 @@ namespace Avalonia.RenderHelpers
}
}
public static void ArcTo(IStreamGeometryContextImpl streamGeometryContextImpl, Point currentPoint, Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
public static void ArcTo(StreamGeometryContext streamGeometryContextImpl, Point currentPoint, Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
EllipticalArc.BuildArc(streamGeometryContextImpl, currentPoint, point, size, rotationAngle*Math.PI/180,
EllipticalArc.BuildArc(streamGeometryContextImpl, currentPoint, point, size, rotationAngle*(Math.PI/180),
isLargeArc,
sweepDirection == SweepDirection.Clockwise);
}

24
src/Avalonia.Visuals/Media/StreamGeometryContext.cs

@ -15,6 +15,8 @@ namespace Avalonia.Media
{
private readonly IStreamGeometryContextImpl _impl;
private Point _currentPoint;
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryContext"/> class.
/// </summary>
@ -47,6 +49,24 @@ namespace Avalonia.Media
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
_impl.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection);
_currentPoint = point;
}
/// <summary>
/// Draws an arc to the specified point using polylines, quadratic or cubic Bezier curves
/// Significantly more precise when drawing elliptic arcs with extreme width:height ratios.
/// </summary>
/// <param name="point">The destination point.</param>
/// <param name="size">The radii of an oval whose perimeter is used to draw the angle.</param>
/// <param name="rotationAngle">The rotation angle of the oval that specifies the curve.</param>
/// <param name="isLargeArc">true to draw the arc greater than 180 degrees; otherwise, false.</param>
/// <param name="sweepDirection">
/// A value that indicates whether the arc is drawn in the Clockwise or Counterclockwise direction.
/// </param>
public void PreciseArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
PreciseEllipticArcHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection);
}
/// <summary>
@ -57,6 +77,7 @@ namespace Avalonia.Media
public void BeginFigure(Point startPoint, bool isFilled)
{
_impl.BeginFigure(startPoint, isFilled);
_currentPoint = startPoint;
}
/// <summary>
@ -68,6 +89,7 @@ namespace Avalonia.Media
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
_impl.CubicBezierTo(point1, point2, point3);
_currentPoint = point3;
}
/// <summary>
@ -78,6 +100,7 @@ namespace Avalonia.Media
public void QuadraticBezierTo(Point control, Point endPoint)
{
_impl.QuadraticBezierTo(control, endPoint);
_currentPoint = endPoint;
}
/// <summary>
@ -87,6 +110,7 @@ namespace Avalonia.Media
public void LineTo(Point point)
{
_impl.LineTo(point);
_currentPoint = point;
}
/// <summary>

1
src/Shared/RenderHelpers/RenderHelpers.projitems

@ -9,7 +9,6 @@
<Import_RootNamespace>Avalonia.RenderHelpers</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)ArcToHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)QuadBezierHelper.cs" />
</ItemGroup>
</Project>

176
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Moq;
using Xunit;
@ -57,7 +55,7 @@ namespace Avalonia.Controls.UnitTests
var hasExit = false;
lifetime.Exit += (s, e) => hasExit = true;
lifetime.Exit += (_, _) => hasExit = true;
var windowA = new Window();
@ -91,7 +89,7 @@ namespace Avalonia.Controls.UnitTests
var hasExit = false;
lifetime.Exit += (s, e) => hasExit = true;
lifetime.Exit += (_, _) => hasExit = true;
var mainWindow = new Window();
@ -119,7 +117,7 @@ namespace Avalonia.Controls.UnitTests
var hasExit = false;
lifetime.Exit += (s, e) => hasExit = true;
lifetime.Exit += (_, _) => hasExit = true;
var windowA = new Window();
@ -226,7 +224,7 @@ namespace Avalonia.Controls.UnitTests
window.Show();
lifetime.ShutdownRequested += (s, e) =>
lifetime.ShutdownRequested += (_, e) =>
{
e.Cancel = true;
++raised;
@ -238,5 +236,171 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { window }, lifetime.Windows);
}
}
[Fact]
public void MainWindow_Closed_Shutdown_Should_Be_Cancellable()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
var hasExit = false;
lifetime.Exit += (_, _) => hasExit = true;
var mainWindow = new Window();
mainWindow.Show();
lifetime.MainWindow = mainWindow;
var window = new Window();
window.Show();
var raised = 0;
lifetime.ShutdownRequested += (_, e) =>
{
e.Cancel = true;
++raised;
};
mainWindow.Close();
Assert.Equal(1, raised);
Assert.False(hasExit);
}
}
[Fact]
public void LastWindow_Closed_Shutdown_Should_Be_Cancellable()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose;
var hasExit = false;
lifetime.Exit += (_, _) => hasExit = true;
var windowA = new Window();
windowA.Show();
var windowB = new Window();
windowB.Show();
var raised = 0;
lifetime.ShutdownRequested += (_, e) =>
{
e.Cancel = true;
++raised;
};
windowA.Close();
Assert.False(hasExit);
windowB.Close();
Assert.Equal(1, raised);
Assert.False(hasExit);
}
}
[Fact]
public void TryShutdown_Cancellable_By_Preventing_Window_Close()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
var hasExit = false;
lifetime.Exit += (_, _) => hasExit = true;
var windowA = new Window();
windowA.Show();
var windowB = new Window();
windowB.Show();
var raised = 0;
windowA.Closing += (_, e) =>
{
e.Cancel = true;
++raised;
};
lifetime.TryShutdown();
Assert.Equal(1, raised);
Assert.False(hasExit);
}
}
[Fact]
public void Shutdown_NotCancellable_By_Preventing_Window_Close()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
var hasExit = false;
lifetime.Exit += (_, _) => hasExit = true;
var windowA = new Window();
windowA.Show();
var windowB = new Window();
windowB.Show();
var raised = 0;
windowA.Closing += (_, e) =>
{
e.Cancel = true;
++raised;
};
lifetime.Shutdown();
Assert.Equal(1, raised);
Assert.True(hasExit);
}
}
[Fact]
public void Shutdown_Doesnt_Raise_Shutdown_Requested()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
var hasExit = false;
lifetime.Exit += (_, _) => hasExit = true;
var raised = 0;
lifetime.ShutdownRequested += (_, _) =>
{
++raised;
};
lifetime.Shutdown();
Assert.Equal(0, raised);
Assert.True(hasExit);
}
}
}
}

56
tests/Avalonia.RenderTests/Media/StreamGeometryTests.cs

@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Xunit;
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
#else
namespace Avalonia.Direct2D1.RenderTests.Media
#endif
{
public class StreamGeometryTests : TestBase
{
public StreamGeometryTests()
: base(@"Media\StreamGeometry")
{
}
[Fact]
public async Task PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions()
{
var grid = new Avalonia.Controls.Primitives.UniformGrid() { Columns = 2, Rows = 4, Width = 320, Height = 400 };
foreach (var sweepDirection in new[] { SweepDirection.Clockwise, SweepDirection.CounterClockwise })
foreach (var isLargeArc in new[] { false, true })
foreach (var isPrecise in new[] { false, true })
{
Point Pt(double x, double y) => new Point(x, y);
Size Sz(double w, double h) => new Size(w, h);
var streamGeometry = new StreamGeometry();
using (var context = streamGeometry.Open())
{
context.BeginFigure(Pt(20, 20), true);
if(isPrecise)
context.PreciseArcTo(Pt(40, 40), Sz(20, 20), 0, isLargeArc, sweepDirection);
else
context.ArcTo(Pt(40, 40), Sz(20, 20), 0, isLargeArc, sweepDirection);
context.LineTo(Pt(40, 20));
context.LineTo(Pt(20, 20));
context.EndFigure(true);
}
var pathShape = new Avalonia.Controls.Shapes.Path();
pathShape.Data = streamGeometry;
pathShape.Stroke = new SolidColorBrush(Colors.CornflowerBlue);
pathShape.Fill = new SolidColorBrush(Colors.Gold);
pathShape.StrokeThickness = 2;
pathShape.Margin = new Thickness(20);
grid.Children.Add(pathShape);
}
await RenderToFile(grid);
}
}
}

BIN
tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.immediate.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
tests/TestFiles/Skia/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
tests/TestFiles/Skia/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.immediate.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Loading…
Cancel
Save