Browse Source

Merge branch 'master' into compile-time-brush-parsing

pull/6031/head
Jumar Macato 5 years ago
committed by GitHub
parent
commit
a30bf882b0
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. 36
      samples/RenderDemo/Pages/TransitionsPage.xaml
  8. 29
      src/Avalonia.Animation/Animation.cs
  9. 21
      src/Avalonia.Animation/AnimationInstance`1.cs
  10. 15
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  11. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  12. 4
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  13. 5
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  14. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
  15. 6
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
  16. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  17. 9
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  18. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
  19. 20
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  20. 23
      src/Avalonia.Input/KeyboardDevice.cs
  21. 2
      src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs
  22. 32
      src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs
  23. 59
      src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
  24. 3
      src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs
  25. 61
      src/Avalonia.Visuals/Media/PolyLineSegment.cs
  26. 9
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  27. 46
      tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
  28. 23
      tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs
  29. 6
      tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs

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

36
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -141,6 +141,39 @@
<Style Selector="Border.Shadow:pointerover">
<Setter Property="BoxShadow" Value="inset 30 30 20 30 Green, 20 40 20 10 Red"/>
</Style>
<Style Selector="Border.Rect10">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" Value="Red" />
</Style>
<Style Selector="Border.Rect10:pointerover">
<Setter Property="Background" Value="Orange" />
</Style>
<Style Selector="Border.Rect11">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" Value="Red" />
</Style>
<Style Selector="Border.Rect11:pointerover">
<Setter Property="Background" >
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter>
</Style>
</Styles>
</UserControl.Styles>
@ -166,6 +199,9 @@
<Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" />
<Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" />
<Border Classes="Test Rect10" />
<Border Classes="Test Rect11" />
</WrapPanel>
</StackPanel>
</Grid>

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

15
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -361,9 +361,9 @@ namespace Avalonia.Controls.Presenters
RenderInternal(context);
if (selectionStart == selectionEnd)
if (selectionStart == selectionEnd && _caretBlink)
{
var caretBrush = CaretBrush;
var caretBrush = CaretBrush?.ToImmutable();
if (caretBrush is null)
{
@ -382,13 +382,10 @@ namespace Avalonia.Controls.Presenters
}
}
if (_caretBlink)
{
var (p1, p2) = GetCaretPoints();
context.DrawLine(
new ImmutablePen(caretBrush, 1),
p1, p2);
}
var (p1, p2) = GetCaretPoints();
context.DrawLine(
new ImmutablePen(caretBrush, 1),
p1, p2);
}
}

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

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

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs

@ -26,9 +26,11 @@ namespace Avalonia.Diagnostics.ViewModels
public AvaloniaProperty Property { get; }
public override object Key => Property;
public override string Name { get; }
public bool IsAttached => Property.IsAttached;
public override bool? IsAttached =>
Property.IsAttached;
public string Priority => _priority;
public override string Priority =>
_priority;
public override string Type => _type;
@ -69,7 +71,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (val != null)
{
RaiseAndSetIfChanged(ref _priority, val.Priority.ToString(), nameof(Priority));
RaiseAndSetIfChanged(ref _group, IsAttached ? "Attached Properties" : "Properties", nameof(Group));
RaiseAndSetIfChanged(ref _group, IsAttached == true ? "Attached Properties" : "Properties", nameof(Group));
}
else
{

6
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs

@ -49,6 +49,12 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public override string Priority =>
string.Empty;
public override bool? IsAttached =>
default;
// [MemberNotNull(nameof(_type))]
public override void Update()
{

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -18,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
private readonly IVisual _control;
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex;
private AvaloniaPropertyViewModel? _selectedProperty;
private PropertyViewModel? _selectedProperty;
private bool _snapshotStyles;
private bool _showInactiveStyles;
private string? _styleStatus;
@ -139,7 +139,7 @@ namespace Avalonia.Diagnostics.ViewModels
public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; }
public AvaloniaPropertyViewModel? SelectedProperty
public PropertyViewModel? SelectedProperty
{
get => _selectedProperty;
set => RaiseAndSetIfChanged(ref _selectedProperty, value);

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

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs

@ -16,7 +16,9 @@ namespace Avalonia.Diagnostics.ViewModels
public abstract string Group { get; }
public abstract string Type { get; }
public abstract string Value { get; set; }
public abstract void Update();
public abstract string Priority { get; }
public abstract bool? IsAttached { get; }
public abstract void Update();
protected static string ConvertToString(object? value)
{

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

59
src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs

@ -0,0 +1,59 @@
using System;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Media;
#nullable enable
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="IBrush"/> type.
/// Only values of <see cref="ISolidColorBrush"/> will transition correctly at the moment.
/// </summary>
public class BrushTransition : Transition<IBrush?>
{
private static readonly ISolidColorBrushAnimator s_animator = new ISolidColorBrushAnimator();
public override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue)
{
var oldSolidColorBrush = TryGetSolidColorBrush(oldValue);
var newSolidColorBrush = TryGetSolidColorBrush(newValue);
if (oldSolidColorBrush != null && newSolidColorBrush != null)
{
return new AnimatorTransitionObservable<ISolidColorBrush, ISolidColorBrushAnimator>(
s_animator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
}
return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
}
private static ISolidColorBrush? TryGetSolidColorBrush(IBrush? brush)
{
if (brush is null)
{
return Brushes.Transparent;
}
return brush as ISolidColorBrush;
}
private class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?>
{
private readonly IBrush? _from;
private readonly IBrush? _to;
public IncompatibleTransitionObservable(IObservable<double> progress, Easing easing, IBrush? from, IBrush? to) : base(progress, easing)
{
_from = from;
_to = to;
}
protected override IBrush? ProduceValue(double progress)
{
return progress < 0.5 ? _from : _to;
}
}
}
}

3
src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Avalonia.Media.Immutable
{
@ -44,6 +45,8 @@ namespace Avalonia.Media.Immutable
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0)
{
Debug.Assert(!(brush is IMutableBrush));
Brush = brush;
Thickness = thickness;
LineCap = lineCap;

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) : "";
}
}

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

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

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

6
tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs

@ -59,9 +59,9 @@ namespace Avalonia.Visuals.UnitTests.Media
}
[Fact]
public void Equality_Is_Implemented_Between_Immutable_And_Mmutable_Pens()
public void Equality_Is_Implemented_Between_Immutable_And_Mutable_Pens()
{
var brush = new SolidColorBrush(Colors.Red);
var brush = new ImmutableSolidColorBrush(Colors.Red);
var target1 = new ImmutablePen(
brush: brush,
thickness: 2,
@ -83,7 +83,7 @@ namespace Avalonia.Visuals.UnitTests.Media
[Fact]
public void Equality_Is_Implemented_Between_Mutable_And_Immutable_DashStyles()
{
var brush = new SolidColorBrush(Colors.Red);
var brush = new ImmutableSolidColorBrush(Colors.Red);
var target1 = new ImmutablePen(
brush: brush,
thickness: 2,

Loading…
Cancel
Save