Browse Source

Merge branch 'master' into feature/undo-no-timer

pull/6024/head
Deadpikle 5 years ago
committed by GitHub
parent
commit
3c9a2d4fa6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      native/Avalonia.Native/src/OSX/window.mm
  2. 5
      samples/ControlCatalog/MainWindow.xaml.cs
  3. 3
      samples/RenderDemo/MainWindow.xaml
  4. 25
      samples/RenderDemo/Pages/CustomAnimatorPage.xaml
  5. 27
      samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs
  6. 16
      samples/RenderDemo/Pages/CustomStringAnimator.cs
  7. 29
      src/Avalonia.Animation/Animation.cs
  8. 21
      src/Avalonia.Animation/AnimationInstance`1.cs
  9. 24
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  10. 3
      src/Avalonia.Controls/ApiCompatBaseline.txt
  11. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  12. 36
      src/Avalonia.Controls/ScrollViewer.cs
  13. 4
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  14. 5
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  15. 9
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  16. 20
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  17. 23
      src/Avalonia.Input/KeyboardDevice.cs
  18. 2
      src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs
  19. 32
      src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs
  20. 3
      src/Avalonia.Themes.Default/ListBox.xaml
  21. 3
      src/Avalonia.Themes.Default/TextBox.xaml
  22. 3
      src/Avalonia.Themes.Default/TreeView.xaml
  23. 3
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  24. 3
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  25. 3
      src/Avalonia.Themes.Fluent/Controls/TreeView.xaml
  26. 61
      src/Avalonia.Visuals/Media/PolyLineSegment.cs
  27. 47
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  28. 13
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  29. 4
      src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
  30. 32
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  31. 7
      src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
  32. 11
      src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
  33. 46
      tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
  34. 40
      tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
  35. 36
      tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
  36. 34
      tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
  37. 23
      tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs
  38. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png
  39. BIN
      tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png
  40. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png
  41. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png
  42. BIN
      tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png
  43. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png

11
native/Avalonia.Native/src/OSX/window.mm

@ -513,6 +513,7 @@ private:
bool _fullScreenActive;
SystemDecorations _decorations;
AvnWindowState _lastWindowState;
AvnWindowState _actualWindowState;
bool _inSetWindowState;
NSRect _preZoomSize;
bool _transitioningWindowState;
@ -539,6 +540,7 @@ private:
_transitioningWindowState = false;
_inSetWindowState = false;
_lastWindowState = Normal;
_actualWindowState = Normal;
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
@ -633,7 +635,7 @@ private:
void WindowStateChanged () override
{
if(!_inSetWindowState && !_transitioningWindowState)
if(_shown && !_inSetWindowState && !_transitioningWindowState)
{
AvnWindowState state;
GetWindowState(&state);
@ -963,14 +965,14 @@ private:
{
@autoreleasepool
{
if(_lastWindowState == state)
if(_actualWindowState == state)
{
return S_OK;
}
_inSetWindowState = true;
auto currentState = _lastWindowState;
auto currentState = _actualWindowState;
_lastWindowState = state;
if(currentState == Normal)
@ -1049,8 +1051,11 @@ private:
}
break;
}
_actualWindowState = _lastWindowState;
}
_inSetWindowState = false;
return S_OK;

5
samples/ControlCatalog/MainWindow.xaml.cs

@ -17,7 +17,10 @@ namespace ControlCatalog
public MainWindow()
{
this.InitializeComponent();
this.AttachDevTools();
this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
{
StartupScreenIndex = 1,
});
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;

3
samples/RenderDemo/MainWindow.xaml

@ -36,6 +36,9 @@
<TabItem Header="Transitions">
<pages:TransitionsPage/>
</TabItem>
<TabItem Header="Custom Animator">
<pages:CustomAnimatorPage/>
</TabItem>
<TabItem Header="Clipping">
<pages:ClippingPage/>
</TabItem>

25
samples/RenderDemo/Pages/CustomAnimatorPage.xaml

@ -0,0 +1,25 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:RenderDemo.Pages"
x:Class="RenderDemo.Pages.CustomAnimatorPage"
MaxWidth="600">
<Grid>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Styles>
<Style Selector="TextBlock">
<Style.Animations>
<Animation Duration="0:0:1" IterationCount="Infinite">
<KeyFrame Cue="0%">
<Setter Property="Text" Value="" Animation.Animator="{x:Type pages:CustomStringAnimator}"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Text" Value="0123456789" Animation.Animator="{x:Type pages:CustomStringAnimator}"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</TextBlock.Styles>
</TextBlock>
</Grid>
</UserControl>

27
samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs

@ -0,0 +1,27 @@
using System.Reactive.Linq;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using RenderDemo.ViewModels;
namespace RenderDemo.Pages
{
public class CustomAnimatorPage : UserControl
{
public CustomAnimatorPage()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

16
samples/RenderDemo/Pages/CustomStringAnimator.cs

@ -0,0 +1,16 @@
using Avalonia.Animation.Animators;
namespace RenderDemo.Pages
{
public class CustomStringAnimator : Animator<string>
{
public override string Interpolate(double progress, string oldValue, string newValue)
{
if (newValue.Length == 0) return "";
var step = 1.0 / newValue.Length;
var length = (int)(progress / step);
var result = newValue.Substring(0, length + 1);
return result;
}
}
}

29
src/Avalonia.Animation/Animation.cs

@ -194,6 +194,33 @@ namespace Avalonia.Animation
[Content]
public KeyFrames Children { get; } = new KeyFrames();
// Store values for the Animator attached properties for IAnimationSetter objects.
private static readonly Dictionary<IAnimationSetter, Type> s_animators = new Dictionary<IAnimationSetter, Type>();
/// <summary>
/// Gets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <returns>The property animator type.</returns>
public static Type GetAnimator(IAnimationSetter setter)
{
if (s_animators.TryGetValue(setter, out var type))
{
return type;
}
return null;
}
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
public static void SetAnimator(IAnimationSetter setter, Type value)
{
s_animators[setter] = value;
}
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
{
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
@ -248,7 +275,7 @@ namespace Avalonia.Animation
{
foreach (var setter in keyframe.Setters)
{
var handler = GetAnimatorType(setter.Property);
var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property);
if (handler == null)
{

21
src/Avalonia.Animation/AnimationInstance`1.cs

@ -5,6 +5,7 @@ using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Data;
using Avalonia.Reactive;
using JetBrains.Annotations;
namespace Avalonia.Animation
{
@ -45,8 +46,9 @@ namespace Avalonia.Animation
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
_neutralValue = (T)_targetControl.GetValue(_animator.Property);
control.PropertyChanged += ControlPropertyChanged;
UpdateNeutralValue();
FetchProperties();
}
@ -216,5 +218,22 @@ namespace Avalonia.Animation
}
}
}
private void UpdateNeutralValue()
{
var property = _animator.Property;
var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue);
_neutralValue = baseValue != AvaloniaProperty.UnsetValue ?
(T)baseValue : (T)_targetControl.GetValue(property);
}
private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation)
{
UpdateNeutralValue();
}
}
}
}

24
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -585,6 +585,30 @@ namespace Avalonia
});
}
/// <summary>
/// Subscribes to a property changed notifications for changes that originate from a
/// <typeparamref name="TTarget"/>.
/// </summary>
/// <typeparam name="TTarget">The type of the property change sender.</typeparam>
/// /// <typeparam name="TValue">The type of the property..</typeparam>
/// <param name="observable">The property changed observable.</param>
/// <param name="action">
/// The method to call. The parameters are the sender and the event args.
/// </param>
/// <returns>A disposable that can be used to terminate the subscription.</returns>
public static IDisposable AddClassHandler<TTarget, TValue>(
this IObservable<AvaloniaPropertyChangedEventArgs<TValue>> observable,
Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> action) where TTarget : AvaloniaObject
{
return observable.Subscribe(e =>
{
if (e.Sender is TTarget target)
{
action(target, e);
}
});
}
/// <summary>
/// Subscribes to a property changed notifications for changes that originate from a
/// <typeparamref name="TTarget"/>.

3
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -4,6 +4,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
@ -11,4 +12,4 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 12
Total Issues: 13

2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -70,7 +70,7 @@ namespace Avalonia.Controls.Remote
public override void Render(DrawingContext context)
{
if (_lastFrame != null)
if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
{
var fmt = (PixelFormat) _lastFrame.Format;
if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width ||

36
src/Avalonia.Controls/ScrollViewer.cs

@ -176,8 +176,10 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="AllowAutoHide"/> property.
/// </summary>
public static readonly StyledProperty<bool> AllowAutoHideProperty =
ScrollBar.AllowAutoHideProperty.AddOwner<ScrollViewer>();
public static readonly AttachedProperty<bool> AllowAutoHideProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, bool>(
nameof(AllowAutoHide),
true);
/// <summary>
/// Defines the <see cref="ScrollChanged"/> event.
@ -207,8 +209,8 @@ namespace Avalonia.Controls
/// </summary>
static ScrollViewer()
{
HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>((x, e) => x.ScrollBarVisibilityChanged(e));
VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>((x, e) => x.ScrollBarVisibilityChanged(e));
HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer, ScrollBarVisibility>((x, e) => x.ScrollBarVisibilityChanged(e));
VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer, ScrollBarVisibility>((x, e) => x.ScrollBarVisibilityChanged(e));
}
/// <summary>
@ -526,6 +528,26 @@ namespace Avalonia.Controls
return control.GetValue(VerticalScrollBarVisibilityProperty);
}
/// <summary>
/// Gets the value of the AllowAutoHideProperty attached property.
/// </summary>
/// <param name="control">The control to set the value on.</param>
/// <param name="value">The value of the property.</param>
public static void SetAllowAutoHide(Control control, bool value)
{
control.SetValue(AllowAutoHideProperty, value);
}
/// <summary>
/// Gets the value of the AllowAutoHideProperty attached property.
/// </summary>
/// <param name="control">The control to read the value from.</param>
/// <returns>The value of the property.</returns>
public static bool GetAllowAutoHide(Control control)
{
return control.GetValue(AllowAutoHideProperty);
}
/// <summary>
/// Gets the value of the VerticalScrollBarVisibility attached property.
/// </summary>
@ -604,10 +626,10 @@ namespace Avalonia.Controls
CalculatedPropertiesChanged();
}
private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs<ScrollBarVisibility> e)
{
var wasEnabled = !ScrollBarVisibility.Disabled.Equals(e.OldValue);
var isEnabled = !ScrollBarVisibility.Disabled.Equals(e.NewValue);
var wasEnabled = e.OldValue.GetValueOrDefault() != ScrollBarVisibility.Disabled;
var isEnabled = e.NewValue.GetValueOrDefault() != ScrollBarVisibility.Disabled;
if (wasEnabled != isEnabled)
{

4
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -10,7 +10,8 @@ namespace Avalonia.Diagnostics
{
public static class DevTools
{
private static readonly Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>();
private static readonly Dictionary<TopLevel, MainWindow> s_open =
new Dictionary<TopLevel, MainWindow>();
public static IDisposable Attach(TopLevel root, KeyGesture gesture)
{
@ -52,6 +53,7 @@ namespace Avalonia.Diagnostics
Width = options.Size.Width,
Height = options.Size.Height,
};
window.SetOptions(options);
window.Closed += DevToolsClosed;
s_open.Add(root, window);

5
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -22,5 +22,10 @@ namespace Avalonia.Diagnostics
/// Gets or sets the initial size of the DevTools window. The default value is 1280x720.
/// </summary>
public Size Size { get; set; } = new Size(1280, 720);
/// <summary>
/// Get or set the startup screen index where the DevTools window will be displayed.
/// </summary>
public int? StartupScreenIndex { get; set; }
}
}

9
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -151,7 +151,7 @@ namespace Avalonia.Diagnostics.ViewModels
get { return _pointerOverElement; }
private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
}
private void UpdateConsoleContext(ConsoleContext context)
{
context.root = _root;
@ -213,5 +213,12 @@ namespace Avalonia.Diagnostics.ViewModels
tree.SelectControl(control);
}
}
public int? StartupScreenIndex { get; private set; } = default;
public void SetOptions(DevToolsOptions options)
{
StartupScreenIndex = options.StartupScreenIndex;
}
}
}

20
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -24,6 +24,23 @@ namespace Avalonia.Diagnostics.Views
_keySubscription = InputManager.Instance.Process
.OfType<RawKeyEventArgs>()
.Subscribe(RawKeyDown);
EventHandler? lh = default;
lh = (s, e) =>
{
this.Opened -= lh;
if ((DataContext as MainViewModel)?.StartupScreenIndex is int index)
{
var screens = this.Screens;
if (index > -1 && index < screens.ScreenCount)
{
var screen = screens.All[index];
this.Position = screen.Bounds.TopLeft;
this.WindowState = WindowState.Maximized;
}
}
};
this.Opened += lh;
}
public TopLevel? Root
@ -115,5 +132,8 @@ namespace Avalonia.Diagnostics.Views
}
private void RootClosed(object? sender, EventArgs e) => Close();
public void SetOptions(DevToolsOptions options) =>
(DataContext as MainViewModel)?.SetOptions(options);
}
}

23
src/Avalonia.Input/KeyboardDevice.cs

@ -217,12 +217,31 @@ namespace Avalonia.Input
{
var bindings = (currentHandler as IInputElement)?.KeyBindings;
if (bindings != null)
{
KeyBinding[]? bindingsCopy = null;
// Create a copy of the KeyBindings list if there's a binding which matches the event.
// If we don't do this the foreach loop will throw an InvalidOperationException when the KeyBindings list is changed.
// This can happen when a new view is loaded which adds its own KeyBindings to the handler.
foreach (var binding in bindings)
{
if (ev.Handled)
if (binding.Gesture?.Matches(ev) == true)
{
bindingsCopy = bindings.ToArray();
break;
binding.TryHandle(ev);
}
}
if (bindingsCopy is object)
{
foreach (var binding in bindingsCopy)
{
if (ev.Handled)
break;
binding.TryHandle(ev);
}
}
}
currentHandler = currentHandler.VisualParent;
}

2
src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs

@ -33,6 +33,6 @@ namespace Avalonia.Styling.Activators
void IObserver<object>.OnCompleted() { }
void IObserver<object>.OnError(Exception error) { }
void IObserver<object>.OnNext(object value) => PublishNext(Equals(value, _value));
void IObserver<object>.OnNext(object value) => PublishNext(PropertyEqualsSelector.Compare(_property.PropertyType, value, _value));
}
}

32
src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs

@ -1,4 +1,6 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using Avalonia.Styling.Activators;
@ -75,11 +77,37 @@ namespace Avalonia.Styling
}
else
{
var result = (control.GetValue(_property) ?? string.Empty).Equals(_value);
return result ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
return Compare(_property.PropertyType, control.GetValue(_property), _value)
? SelectorMatch.AlwaysThisInstance
: SelectorMatch.NeverThisInstance;
}
}
protected override Selector? MovePrevious() => _previous;
internal static bool Compare(Type propertyType, object propertyValue, object? value)
{
if (propertyType == typeof(object) &&
propertyValue?.GetType() is Type inferredType)
{
propertyType = inferredType;
}
var valueType = value?.GetType();
if (valueType is null || propertyType.IsAssignableFrom(valueType))
{
return Equals(propertyValue, value);
}
var converter = TypeDescriptor.GetConverter(propertyType);
if (converter?.CanConvertFrom(valueType) == true)
{
return Equals(propertyValue, converter.ConvertFrom(null, CultureInfo.InvariantCulture, value));
}
return false;
}
}
}

3
src/Avalonia.Themes.Default/ListBox.xaml

@ -13,7 +13,8 @@
<ScrollViewer Name="PART_ScrollViewer"
Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

3
src/Avalonia.Themes.Default/TextBox.xaml

@ -57,7 +57,8 @@
<ContentPresenter Grid.Column="0" Grid.ColumnSpan="1" Content="{TemplateBinding InnerLeftContent}"/>
<ScrollViewer Grid.Column="1" Grid.ColumnSpan="1"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<Panel>
<TextBlock Name="watermark"
Opacity="0.5"

3
src/Avalonia.Themes.Default/TreeView.xaml

@ -11,7 +11,8 @@
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

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

@ -24,7 +24,8 @@
<ScrollViewer Name="PART_ScrollViewer"
Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

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

@ -65,7 +65,8 @@
Text="{TemplateBinding Watermark}"
DockPanel.Dock="Top" />
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<Panel>
<TextBlock Name="PART_Watermark"
Opacity="0.5"

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

@ -12,7 +12,8 @@
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

61
src/Avalonia.Visuals/Media/PolyLineSegment.cs

@ -0,0 +1,61 @@
using System.Collections.Generic;
using Avalonia.Collections;
namespace Avalonia.Media
{
/// <summary>
/// Represents a set of line segments defined by a points collection with each Point specifying the end point of a line segment.
/// </summary>
public sealed class PolyLineSegment : PathSegment
{
/// <summary>
/// Defines the <see cref="Points"/> property.
/// </summary>
public static readonly StyledProperty<Points> PointsProperty
= AvaloniaProperty.Register<PolyLineSegment, Points>(nameof(Points));
/// <summary>
/// Gets or sets the points.
/// </summary>
/// <value>
/// The points.
/// </value>
public AvaloniaList<Point> Points
{
get => GetValue(PointsProperty);
set => SetValue(PointsProperty, value);
}
/// <summary>
/// Initializes a new instance of the <see cref="PolyLineSegment"/> class.
/// </summary>
public PolyLineSegment()
{
Points = new Points();
}
/// <summary>
/// Initializes a new instance of the <see cref="PolyLineSegment"/> class.
/// </summary>
/// <param name="points">The points.</param>
public PolyLineSegment(IEnumerable<Point> points) : this()
{
Points.AddRange(points);
}
protected internal override void ApplyTo(StreamGeometryContext ctx)
{
var points = Points;
if (points.Count > 0)
{
for (int i = 0; i < points.Count; i++)
{
ctx.LineTo(points[i]);
}
}
}
public override string ToString()
=> Points.Count >= 1 ? "L " + string.Join(" ", Points) : "";
}
}

47
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -164,7 +164,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawLine(IPen pen, Point p1, Point p2)
{
using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
using (var paint = CreatePaint(_strokePaint, pen, new Rect(p1, p2).Normalize()))
{
if (paint.Paint is object)
{
@ -177,10 +177,10 @@ namespace Avalonia.Skia
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
var impl = (GeometryImpl) geometry;
var size = geometry.Bounds.Size;
var rect = geometry.Bounds;
using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper))
using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, size) : default(PaintWrapper))
using (var fill = brush != null ? CreatePaint(_fillPaint, brush, rect) : default(PaintWrapper))
using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, rect) : default(PaintWrapper))
{
if (fill.Paint != null)
{
@ -354,7 +354,7 @@ namespace Avalonia.Skia
if (brush != null)
{
using (var paint = CreatePaint(_fillPaint, brush, rect.Rect.Size))
using (var paint = CreatePaint(_fillPaint, brush, rect.Rect))
{
if (isRounded)
{
@ -397,7 +397,7 @@ namespace Avalonia.Skia
if (pen?.Brush != null)
{
using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size))
using (var paint = CreatePaint(_strokePaint, pen, rect.Rect))
{
if (paint.Paint is object)
{
@ -417,7 +417,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds.Size))
using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds))
{
var textImpl = (FormattedTextImpl) text;
textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint, _canTextUseLcdRendering);
@ -427,7 +427,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size))
using (var paintWrapper = CreatePaint(_fillPaint, foreground, new Rect(glyphRun.Size)))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@ -537,7 +537,7 @@ namespace Avalonia.Skia
var paint = new SKPaint();
Canvas.SaveLayer(paint);
_maskStack.Push(CreatePaint(paint, mask, bounds.Size, true));
_maskStack.Push(CreatePaint(paint, mask, bounds, true));
}
/// <inheritdoc />
@ -593,18 +593,19 @@ namespace Avalonia.Skia
/// <param name="paintWrapper">Paint wrapper.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="gradientBrush">Gradient brush.</param>
private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush)
private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush)
{
var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode();
var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray();
var position = targetRect.Position.ToSKPoint();
switch (gradientBrush)
{
case ILinearGradientBrush linearGradient:
{
var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
var start = position + linearGradient.StartPoint.ToPixels(targetRect.Size).ToSKPoint();
var end = position + linearGradient.EndPoint.ToPixels(targetRect.Size).ToSKPoint();
// would be nice to cache these shaders possibly?
using (var shader =
@ -617,10 +618,10 @@ namespace Avalonia.Skia
}
case IRadialGradientBrush radialGradient:
{
var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
var radius = (float)(radialGradient.Radius * targetSize.Width);
var center = position + radialGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
var radius = (float)(radialGradient.Radius * targetRect.Width);
var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
var origin = position + radialGradient.GradientOrigin.ToPixels(targetRect.Size).ToSKPoint();
if (origin.Equals(center))
{
@ -665,7 +666,7 @@ namespace Avalonia.Skia
}
case IConicGradientBrush conicGradient:
{
var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint();
var center = position + conicGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
// Skia's default is that angle 0 is from the right hand side of the center point
// but we are matching CSS where the vertical point above the center is 0.
@ -867,10 +868,10 @@ namespace Avalonia.Skia
/// </summary>
/// <param name="paint">The paint to wrap.</param>
/// <param name="brush">Source brush.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="targetRect">Target rect.</param>
/// <param name="disposePaint">Optional dispose of the supplied paint.</param>
/// <returns>Paint wrapper for given brush.</returns>
internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize, bool disposePaint = false)
internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Rect targetRect, bool disposePaint = false)
{
var paintWrapper = new PaintWrapper(paint, disposePaint);
@ -889,7 +890,7 @@ namespace Avalonia.Skia
if (brush is IGradientBrush gradient)
{
ConfigureGradientBrush(ref paintWrapper, targetSize, gradient);
ConfigureGradientBrush(ref paintWrapper, targetRect, gradient);
return paintWrapper;
}
@ -909,7 +910,7 @@ namespace Avalonia.Skia
if (tileBrush != null && tileBrushImage != null)
{
ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage);
ConfigureTileBrush(ref paintWrapper, targetRect.Size, tileBrush, tileBrushImage);
}
else
{
@ -924,10 +925,10 @@ namespace Avalonia.Skia
/// </summary>
/// <param name="paint">The paint to wrap.</param>
/// <param name="pen">Source pen.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="targetRect">Target rect.</param>
/// <param name="disposePaint">Optional dispose of the supplied paint.</param>
/// <returns></returns>
private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize, bool disposePaint = false)
private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Rect targetRect, bool disposePaint = false)
{
// In Skia 0 thickness means - use hairline rendering
// and for us it means - there is nothing rendered.
@ -936,7 +937,7 @@ namespace Avalonia.Skia
return default;
}
var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint);
var rv = CreatePaint(paint, pen.Brush, targetRect, disposePaint);
paint.IsStroke = true;
paint.StrokeWidth = (float) pen.Thickness;

13
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Media;
using Avalonia.Platform;
@ -175,7 +176,8 @@ namespace Avalonia.Skia
foreach (var line in _skiaLines.Where(l =>
(l.Start + l.Length) > index &&
lastIndex >= l.Start))
lastIndex >= l.Start &&
!l.IsEmptyTrailingLine))
{
int lineEndIndex = line.Start + (line.Length > 0 ? line.Length - 1 : 0);
@ -276,9 +278,9 @@ namespace Avalonia.Skia
if (fb != null)
{
//TODO: figure out how to get the brush size
//TODO: figure out how to get the brush rect
currentWrapper = context.CreatePaint(new SKPaint { IsAntialias = true }, fb,
new Size());
default);
}
else
{
@ -466,7 +468,8 @@ namespace Avalonia.Skia
for (int i = line.Start; i < line.Start + line.TextLength; i++)
{
float w = _paint.MeasureText(Text[i].ToString());
var c = Text[i];
var w = line.IsEmptyTrailingLine ? 0 :_paint.MeasureText(Text[i].ToString());
_rects.Add(new Rect(
prevRight,
@ -611,6 +614,7 @@ namespace Avalonia.Skia
lastLine.Width = lastLineWidth;
lastLine.Height = _lineHeight;
lastLine.Top = curY;
lastLine.IsEmptyTrailingLine = true;
_skiaLines.Add(lastLine);
@ -713,6 +717,7 @@ namespace Avalonia.Skia
public int TextLength;
public float Top;
public float Width;
public bool IsEmptyTrailingLine;
};
private struct FBrushRange

4
src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs

@ -34,10 +34,10 @@ namespace Avalonia.Direct2D1.Media
{
var wrapper = clientDrawingEffect as BrushWrapper;
// TODO: Work out how to get the size below rather than passing new Size().
// TODO: Work out how to get the rect below rather than passing default.
var brush = (wrapper == null) ?
_foreground :
_context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush;
_context.CreateBrush(wrapper.Brush, default).PlatformBrush;
_renderTarget.DrawGlyphRun(
new RawVector2 { X = baselineOriginX, Y = baselineOriginY },

32
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -192,7 +192,7 @@ namespace Avalonia.Direct2D1.Media
{
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
{
if (d2dOpacityMask.PlatformBrush != null)
@ -217,9 +217,7 @@ namespace Avalonia.Direct2D1.Media
{
if (pen != null)
{
var size = new Rect(p1, p2).Size;
using (var d2dBrush = CreateBrush(pen.Brush, size))
using (var d2dBrush = CreateBrush(pen.Brush, new Rect(p1, p2).Normalize()))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
@ -245,7 +243,7 @@ namespace Avalonia.Direct2D1.Media
{
if (brush != null)
{
using (var d2dBrush = CreateBrush(brush, geometry.Bounds.Size))
using (var d2dBrush = CreateBrush(brush, geometry.Bounds))
{
if (d2dBrush.PlatformBrush != null)
{
@ -257,7 +255,7 @@ namespace Avalonia.Direct2D1.Media
if (pen != null)
{
using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size))
using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen)))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
@ -282,7 +280,7 @@ namespace Avalonia.Direct2D1.Media
if (brush != null)
{
using (var b = CreateBrush(brush, rect.Size))
using (var b = CreateBrush(brush, rect))
{
if (b.PlatformBrush != null)
{
@ -311,7 +309,7 @@ namespace Avalonia.Direct2D1.Media
if (pen?.Brush != null)
{
using (var wrapper = CreateBrush(pen.Brush, rect.Size))
using (var wrapper = CreateBrush(pen.Brush, rect))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (wrapper.PlatformBrush != null)
@ -349,7 +347,7 @@ namespace Avalonia.Direct2D1.Media
{
var impl = (FormattedTextImpl)text;
using (var brush = CreateBrush(foreground, impl.Bounds.Size))
using (var brush = CreateBrush(foreground, impl.Bounds))
using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush))
{
if (brush.PlatformBrush != null)
@ -367,7 +365,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="glyphRun">The glyph run.</param>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
using (var brush = CreateBrush(foreground, glyphRun.Size))
using (var brush = CreateBrush(foreground, new Rect(glyphRun.Size)))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@ -458,9 +456,9 @@ namespace Avalonia.Direct2D1.Media
/// Creates a Direct2D brush wrapper for a Avalonia brush.
/// </summary>
/// <param name="brush">The avalonia brush.</param>
/// <param name="destinationSize">The size of the brush's target area.</param>
/// <param name="destinationRect">The brush's target area.</param>
/// <returns>The Direct2D brush wrapper.</returns>
public BrushImpl CreateBrush(IBrush brush, Size destinationSize)
public BrushImpl CreateBrush(IBrush brush, Rect destinationRect)
{
var solidColorBrush = brush as ISolidColorBrush;
var linearGradientBrush = brush as ILinearGradientBrush;
@ -475,11 +473,11 @@ namespace Avalonia.Direct2D1.Media
}
else if (linearGradientBrush != null)
{
return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize);
return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationRect);
}
else if (radialGradientBrush != null)
{
return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize);
return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationRect);
}
else if (conicGradientBrush != null)
{
@ -492,7 +490,7 @@ namespace Avalonia.Direct2D1.Media
imageBrush,
_deviceContext,
(BitmapImpl)imageBrush.Source.PlatformImpl.Item,
destinationSize);
destinationRect.Size);
}
else if (visualBrush != null)
{
@ -523,7 +521,7 @@ namespace Avalonia.Direct2D1.Media
visualBrush,
_deviceContext,
new D2DBitmapImpl(intermediate.Bitmap),
destinationSize);
destinationRect.Size);
}
}
}
@ -574,7 +572,7 @@ namespace Avalonia.Direct2D1.Media
ContentBounds = PrimitiveExtensions.RectangleInfinite,
MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
Opacity = 1,
OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush
OpacityBrush = CreateBrush(mask, bounds).PlatformBrush
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);

7
src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs

@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
public LinearGradientBrushImpl(
ILinearGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Size destinationSize)
Rect destinationRect)
{
if (brush.GradientStops.Count == 0)
{
@ -21,8 +21,9 @@ namespace Avalonia.Direct2D1.Media
Position = (float)s.Offset
}).ToArray();
var startPoint = brush.StartPoint.ToPixels(destinationSize);
var endPoint = brush.EndPoint.ToPixels(destinationSize);
var position = destinationRect.Position;
var startPoint = position + brush.StartPoint.ToPixels(destinationRect.Size);
var endPoint = position + brush.EndPoint.ToPixels(destinationRect.Size);
using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,

11
src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs

@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
public RadialGradientBrushImpl(
IRadialGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Size destinationSize)
Rect destinationRect)
{
if (brush.GradientStops.Count == 0)
{
@ -21,12 +21,13 @@ namespace Avalonia.Direct2D1.Media
Position = (float)s.Offset
}).ToArray();
var centerPoint = brush.Center.ToPixels(destinationSize);
var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize) - centerPoint;
var position = destinationRect.Position;
var centerPoint = position + brush.Center.ToPixels(destinationRect.Size);
var gradientOrigin = position + brush.GradientOrigin.ToPixels(destinationRect.Size) - centerPoint;
// Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property
var radiusX = brush.Radius * destinationSize.Width;
var radiusY = brush.Radius * destinationSize.Height;
var radiusX = brush.Radius * destinationRect.Width;
var radiusY = brush.Radius * destinationRect.Height;
using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,

46
tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs

@ -1,5 +1,9 @@
using Avalonia.Input.Raw;
using System;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.UnitTests;
using Moq;
using Xunit;
@ -86,5 +90,45 @@ namespace Avalonia.Input.UnitTests
focused.Verify(x => x.RaiseEvent(It.IsAny<TextInputEventArgs>()));
}
[Fact]
public void Can_Change_KeyBindings_In_Keybinding_Event_Handler()
{
var target = new KeyboardDevice();
var button = new Button();
var root = new TestRoot(button);
var raised = 0;
button.KeyBindings.Add(new KeyBinding
{
Gesture = new KeyGesture(Key.O, KeyModifiers.Control),
Command = new DelegateCommand(() =>
{
button.KeyBindings.Clear();
++raised;
}),
});
target.SetFocusedElement(button, NavigationMethod.Pointer, 0);
target.ProcessRawEvent(
new RawKeyEventArgs(
target,
0,
root,
RawKeyEventType.KeyDown,
Key.O,
RawInputModifiers.Control));
Assert.Equal(1, raised);
}
private class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action) => _action = action;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _action();
}
}
}

40
tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs

@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Media;
using System;
using System.Threading.Tasks;
using Xunit;
@ -174,5 +175,44 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task ConicGradientBrush_DrawingContext()
{
var brush = new ConicGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Yellow, Offset = 0.1667 },
new GradientStop { Color = Colors.Lime, Offset = 0.3333 },
new GradientStop { Color = Colors.Aqua, Offset = 0.5000 },
new GradientStop { Color = Colors.Blue, Offset = 0.6667 },
new GradientStop { Color = Colors.Magenta, Offset = 0.8333 },
new GradientStop { Color = Colors.Red, Offset = 1 },
}
};
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
}),
};
await RenderToFile(target);
CompareImages();
}
private class DrawnControl : Control
{
private readonly Action<DrawingContext> _render;
public DrawnControl(Action<DrawingContext> render) => _render = render;
public override void Render(DrawingContext context) => _render(context);
}
}
}

36
tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs

@ -72,5 +72,41 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task LinearGradientBrush_DrawingContext()
{
var brush = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
};
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
}),
};
await RenderToFile(target);
CompareImages();
}
private class DrawnControl : Control
{
private readonly Action<DrawingContext> _render;
public DrawnControl(Action<DrawingContext> render) => _render = render;
public override void Render(DrawingContext context) => _render(context);
}
}
}

34
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@ -165,5 +165,39 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task RadialGradientBrush_DrawingContext()
{
var brush = new RadialGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
};
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
}),
};
await RenderToFile(target);
CompareImages();
}
private class DrawnControl : Control
{
private readonly Action<DrawingContext> _render;
public DrawnControl(Action<DrawingContext> render) => _render = render;
public override void Render(DrawingContext context) => _render(context);
}
}
}

23
tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs

@ -22,6 +22,23 @@ namespace Avalonia.Styling.UnitTests
Assert.False(await activator.Take(1));
}
[Theory]
[InlineData("Bar", FooBar.Bar)]
[InlineData("352", 352)]
[InlineData("0.1", 0.1)]
public async Task PropertyEquals_Matches_When_Property_Has_Matching_Value_And_Different_Type(string literal, object value)
{
var control = new TextBlock();
var target = default(Selector).PropertyEquals(TextBlock.TagProperty, literal);
var activator = target.Match(control).Activator.ToObservable();
Assert.False(await activator.Take(1));
control.Tag = value;
Assert.True(await activator.Take(1));
control.Tag = null;
Assert.False(await activator.Take(1));
}
[Fact]
public void OfType_PropertyEquals_Doesnt_Match_Control_Of_Wrong_Type()
{
@ -40,5 +57,11 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal("TextBlock[Text=foo]", target.ToString());
}
private enum FooBar
{
Foo,
Bar
}
}
}

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

BIN
tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Loading…
Cancel
Save