Browse Source

Merge branch 'master' into fixes/LineBreakEnumerator

pull/5410/head
Benedikt Stebner 6 years ago
committed by GitHub
parent
commit
065538001e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 8
      .github/ISSUE_TEMPLATE/config.yml
  3. 1
      .github/ISSUE_TEMPLATE/feature_request.md
  4. 10
      native/Avalonia.Native/src/OSX/Screens.mm
  5. 1
      native/Avalonia.Native/src/OSX/common.h
  6. 10
      native/Avalonia.Native/src/OSX/main.mm
  7. 19
      native/Avalonia.Native/src/OSX/window.mm
  8. 16
      samples/ControlCatalog/Pages/ScreenPage.cs
  9. 13
      src/Avalonia.Controls/Button.cs
  10. 26
      src/Avalonia.Controls/MenuItem.cs
  11. 2
      src/Avalonia.Controls/Utils/AncestorFinder.cs
  12. 2
      src/Avalonia.Native/WindowImplBase.cs
  13. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
  14. 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  15. 49
      src/Markup/Avalonia.Markup/Data/Binding.cs
  16. 49
      src/Markup/Avalonia.Markup/Data/BindingBase.cs
  17. 14
      src/Markup/Avalonia.Markup/Data/MultiBinding.cs
  18. 2
      src/Markup/Avalonia.Markup/Data/RelativeSource.cs
  19. 17
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  20. 26
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  21. 21
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  22. 22
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  23. 2
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs
  24. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs
  25. 16
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs
  26. 36
      src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
  27. 42
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  28. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  29. 6
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  30. 12
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  31. 6
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  32. 6
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  33. 9
      src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
  34. 2
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  35. 30
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  36. 115
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  37. 31
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/IgnoredDirectivesTests.cs
  38. 4
      tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
  39. 28
      tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs

7
.github/ISSUE_TEMPLATE/bug_report.md

@ -4,7 +4,6 @@ about: Create a report to help us improve Avalonia
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@ -24,8 +24,9 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac, Linux (State distribution)]
- Version [e.g. 0.10.0-rc1 or 0.9.12]
- OS: [e.g. Windows, Mac, Linux (State distribution)]
- Version [e.g. 0.10.0-rc1 or 0.9.12]
**Additional context**
Add any other context about the problem here.

8
.github/ISSUE_TEMPLATE/config.yml

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Questions, Discussions, Ideas
url: https://github.com/AvaloniaUI/Avalonia/discussions/new
about: Please ask and answer questions here.
- name: Avalonia Community Support on Gitter
url: https://gitter.im/AvaloniaUI/Avalonia
about: Please ask and answer questions here.

1
.github/ISSUE_TEMPLATE/feature_request.md

@ -4,7 +4,6 @@ about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**

10
native/Avalonia.Native/src/OSX/Screens.mm

@ -5,12 +5,6 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
public:
FORWARD_IUNKNOWN()
private:
CGFloat PrimaryDisplayHeight()
{
return NSMaxY([[[NSScreen screens] firstObject] frame]);
}
public:
virtual HRESULT GetScreenCount (int* ret) override
{
@ -36,12 +30,12 @@ public:
ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width;
ret->Bounds.X = [screen frame].origin.x;
ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height;
ret->Bounds.Y = ConvertPointY(ToAvnPoint([screen frame].origin)).Y - ret->Bounds.Height;
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height;
ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor];

1
native/Avalonia.Native/src/OSX/common.h

@ -34,6 +34,7 @@ extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p);
extern CGFloat PrimaryDisplayHeight();
extern NSSize ToNSSize (AvnSize s);
#ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__)

10
native/Avalonia.Native/src/OSX/main.mm

@ -299,10 +299,14 @@ AvnPoint ToAvnPoint (NSPoint p)
AvnPoint ConvertPointY (AvnPoint p)
{
auto sw = [NSScreen.screens objectAtIndex:0].frame;
auto primaryDisplayHeight = NSMaxY([[[NSScreen screens] firstObject] frame]);
auto t = MAX(sw.origin.y, sw.origin.y + sw.size.height);
p.Y = t - p.Y;
p.Y = primaryDisplayHeight - p.Y;
return p;
}
CGFloat PrimaryDisplayHeight()
{
return NSMaxY([[[NSScreen screens] firstObject] frame]);
}

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

@ -1391,17 +1391,20 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[super viewDidChangeBackingProperties];
}
- (bool) ignoreUserInput
- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
{
auto parentWindow = objc_cast<AvnWindow>([self window]);
if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
{
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(window != nullptr)
if(trigerInputWhenDisabled)
{
window->WindowEvents->GotInputWhenDisabled();
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(window != nullptr)
{
window->WindowEvents->GotInputWhenDisabled();
}
}
return TRUE;
@ -1412,7 +1415,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{
if([self ignoreUserInput])
bool triggerInputWhenDisabled = type != Move;
if([self ignoreUserInput: triggerInputWhenDisabled])
{
return;
}
@ -1578,7 +1583,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
{
if([self ignoreUserInput])
if([self ignoreUserInput: false])
{
return;
}

16
samples/ControlCatalog/Pages/ScreenPage.cs

@ -29,7 +29,7 @@ namespace ControlCatalog.Pages
var screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling;
var drawBrush = Brushes.Green;
var drawBrush = Brushes.Black;
Pen p = new Pen(drawBrush);
if (screens != null)
foreach (Screen screen in screens)
@ -45,18 +45,16 @@ namespace ControlCatalog.Pages
screen.Bounds.Height / 10f);
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
screen.WorkingArea.Height / 10f);
context.DrawRectangle(p, boundsRect);
context.DrawRectangle(p, workingAreaRect);
FormattedText text = new FormattedText()
{
Typeface = Typeface.Default
};
text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}";
var text = new FormattedText() { Typeface = new Typeface("Arial"), FontSize = 18 };
text.Text = $"Bounds: {screen.Bounds.TopLeft} {screen.Bounds.Width}:{screen.Bounds.Height}";
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height), text);
text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
text.Text = $"WorkArea: {screen.WorkingArea.TopLeft} {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
text.Text = $"Scaling: {screen.PixelDensity * 100}%";
@ -69,7 +67,7 @@ namespace ControlCatalog.Pages
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
}
context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));
context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10));
}
}
}

13
src/Avalonia.Controls/Button.cs

@ -80,6 +80,7 @@ namespace Avalonia.Controls
private ICommand _command;
private bool _commandCanExecute = true;
private KeyGesture _hotkey;
/// <summary>
/// Initializes static members of the <see cref="Button"/> class.
@ -207,6 +208,11 @@ namespace Avalonia.Controls
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
{
HotKey = _hotkey;
}
base.OnAttachedToLogicalTree(e);
if (Command != null)
@ -217,6 +223,13 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
// This will cause the hotkey manager to dispose the observer and the reference to this control
if (HotKey != null)
{
_hotkey = HotKey;
HotKey = null;
}
base.OnDetachedFromLogicalTree(e);
if (Command != null)

26
src/Avalonia.Controls/MenuItem.cs

@ -102,6 +102,7 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
private Popup? _popup;
private KeyGesture _hotkey;
/// <summary>
/// Initializes static members of the <see cref="MenuItem"/> class.
@ -111,6 +112,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
PressedMixin.Attach<MenuItem>();
CommandProperty.Changed.Subscribe(CommandChanged);
CommandParameterProperty.Changed.Subscribe(CommandParameterChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
HeaderProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.HeaderChanged(e));
IconProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IconChanged(e));
@ -337,6 +339,11 @@ namespace Avalonia.Controls
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
{
HotKey = _hotkey;
}
base.OnAttachedToLogicalTree(e);
if (Command != null)
@ -347,6 +354,13 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
// This will cause the hotkey manager to dispose the observer and the reference to this control
if (HotKey != null)
{
_hotkey = HotKey;
HotKey = null;
}
base.OnDetachedFromLogicalTree(e);
if (Command != null)
@ -493,6 +507,18 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="CommandParameter"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void CommandParameterChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is MenuItem menuItem)
{
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
}
}
/// <summary>
/// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
/// </summary>

2
src/Avalonia.Controls/Utils/AncestorFinder.cs

@ -47,6 +47,8 @@ namespace Avalonia.Controls.Utils
public void Dispose()
{
_child?.Dispose();
_subject.Dispose();
_disposable.Dispose();
}
}

2
src/Avalonia.Native/WindowImplBase.cs

@ -383,7 +383,7 @@ namespace Avalonia.Native
_native.BeginMoveDrag();
}
public Size MaxAutoSizeHint => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
public Size MaxAutoSizeHint => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(1))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
public void SetTopmost(bool value)

5
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs

@ -15,7 +15,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (d.Namespace == XamlNamespaces.Xaml2006)
{
if (d.Name == "Precompile" || d.Name == "Class")
if (d.Name == "Precompile" ||
d.Name == "Class" ||
d.Name == "FieldModifier" ||
d.Name == "ClassModifier")
no.Children.Remove(d);
}
}

2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -2,6 +2,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace>
<Nullable>Enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<None Remove="Markup\Parsers\Nodes\ExpressionGrammer" />

49
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -39,17 +39,17 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the name of the element to use as the binding source.
/// </summary>
public string ElementName { get; set; }
public string? ElementName { get; set; }
/// <summary>
/// Gets or sets the relative source for the binding.
/// </summary>
public RelativeSource RelativeSource { get; set; }
public RelativeSource? RelativeSource { get; set; }
/// <summary>
/// Gets or sets the source for the binding.
/// </summary>
public object Source { get; set; }
public object? Source { get; set; }
/// <summary>
/// Gets or sets the binding path.
@ -59,24 +59,36 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets a function used to resolve types from names in the binding path.
/// </summary>
public Func<string, string, Type> TypeResolver { get; set; }
public Func<string, string, Type>? TypeResolver { get; set; }
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object? anchor, bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
anchor = anchor ?? DefaultAnchor?.Target;
_ = target ?? throw new ArgumentNullException(nameof(target));
anchor ??= DefaultAnchor?.Target;
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
INameScope nameScope = null;
INameScope? nameScope = null;
NameScope?.TryGetTarget(out nameScope);
var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope);
if (node is null)
{
throw new InvalidOperationException("Could not parse binding expression.");
}
IStyledElement GetSource()
{
return target as IStyledElement ??
anchor as IStyledElement ??
throw new ArgumentException("Could not find binding source: either target or anchor must be an IStyledElement.");
}
if (ElementName != null)
{
return CreateElementObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
GetSource(),
ElementName,
node);
}
@ -96,9 +108,7 @@ namespace Avalonia.Data
}
else
{
return CreateSourceObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateSourceObserver(GetSource(), node);
}
}
else if (RelativeSource.Mode == RelativeSourceMode.DataContext)
@ -111,15 +121,11 @@ namespace Avalonia.Data
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
return CreateSourceObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateSourceObserver(GetSource(), node);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
return CreateTemplatedParentObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateTemplatedParentObserver(GetSource(), node);
}
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
{
@ -128,10 +134,7 @@ namespace Avalonia.Data
throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
}
return CreateFindAncestorObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
RelativeSource,
node);
return CreateFindAncestorObserver(GetSource(), RelativeSource, node);
}
else
{

49
src/Markup/Avalonia.Markup/Data/BindingBase.cs

@ -38,22 +38,22 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
/// </summary>
public IValueConverter Converter { get; set; }
public IValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
/// </summary>
public object FallbackValue { get; set; }
public object? FallbackValue { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding result is null.
/// </summary>
public object TargetNullValue { get; set; }
public object? TargetNullValue { get; set; }
/// <summary>
/// Gets or sets the binding mode.
@ -68,26 +68,27 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string StringFormat { get; set; }
public string? StringFormat { get; set; }
public WeakReference DefaultAnchor { get; set; }
public WeakReference? DefaultAnchor { get; set; }
public WeakReference<INameScope> NameScope { get; set; }
public WeakReference<INameScope>? NameScope { get; set; }
protected abstract ExpressionObserver CreateExpressionObserver(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor,
object? anchor,
bool enableDataValidation);
/// <inheritdoc/>
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
object? anchor = null,
bool enableDataValidation = false)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
anchor = anchor ?? DefaultAnchor?.Target;
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
@ -133,18 +134,13 @@ namespace Avalonia.Data
IAvaloniaObject target,
ExpressionNode node,
bool targetIsDataContext,
object anchor)
object? anchor)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
if (!(target is IDataContextProvider))
{
target = anchor as IDataContextProvider;
if (target == null)
{
throw new InvalidOperationException("Cannot find a DataContext to bind to.");
}
target = anchor as IDataContextProvider ?? throw new InvalidOperationException("Cannot find a DataContext to bind to.");
}
if (!targetIsDataContext)
@ -171,10 +167,9 @@ namespace Avalonia.Data
string elementName,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
NameScope.TryGetTarget(out var scope);
if (scope == null)
if (NameScope is null || !NameScope.TryGetTarget(out var scope) || scope is null)
throw new InvalidOperationException("Name scope is null or was already collected");
var result = new ExpressionObserver(
NameScopeLocator.Track(scope, elementName),
@ -188,9 +183,9 @@ namespace Avalonia.Data
RelativeSource relativeSource,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
IObservable<object> controlLocator;
IObservable<object?> controlLocator;
switch (relativeSource.Tree)
{
@ -220,7 +215,7 @@ namespace Avalonia.Data
object source,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(source != null);
_ = source ?? throw new ArgumentNullException(nameof(source));
return new ExpressionObserver(source, node);
}
@ -229,7 +224,7 @@ namespace Avalonia.Data
IAvaloniaObject target,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
var result = new ExpressionObserver(
() => target.GetValue(StyledElement.TemplatedParentProperty),
@ -240,7 +235,7 @@ namespace Avalonia.Data
return result;
}
protected IObservable<object> GetParentDataContext(IAvaloniaObject target)
protected IObservable<object?> GetParentDataContext(IAvaloniaObject target)
{
// The DataContext is based on the visual parent and not the logical parent: this may
// seem counter intuitive considering the fact that property inheritance works on the logical
@ -252,7 +247,7 @@ namespace Avalonia.Data
.Select(x =>
{
return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ??
Observable.Return((object)null);
Observable.Return((object?)null);
}).Switch();
}

14
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@ -22,12 +22,12 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the <see cref="IMultiValueConverter"/> to use.
/// </summary>
public IMultiValueConverter Converter { get; set; }
public IMultiValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
@ -52,12 +52,12 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the relative source for the binding.
/// </summary>
public RelativeSource RelativeSource { get; set; }
public RelativeSource? RelativeSource { get; set; }
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string StringFormat { get; set; }
public string? StringFormat { get; set; }
public MultiBinding()
{
@ -69,7 +69,7 @@ namespace Avalonia.Data
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
object? anchor = null,
bool enableDataValidation = false)
{
var targetType = targetProperty?.PropertyType ?? typeof(object);
@ -105,7 +105,7 @@ namespace Avalonia.Data
}
}
private object ConvertValue(IList<object> values, Type targetType, IMultiValueConverter converter)
private object ConvertValue(IList<object?> values, Type targetType, IMultiValueConverter? converter)
{
for (var i = 0; i < values.Count; ++i)
{
@ -116,7 +116,7 @@ namespace Avalonia.Data
}
var culture = CultureInfo.CurrentCulture;
values = new System.Collections.ObjectModel.ReadOnlyCollection<object>(values);
values = new System.Collections.ObjectModel.ReadOnlyCollection<object?>(values);
object converted;
if (converter != null)
{

2
src/Markup/Avalonia.Markup/Data/RelativeSource.cs

@ -94,7 +94,7 @@ namespace Avalonia.Data
/// <summary>
/// Gets the type of ancestor to look for when in <see cref="RelativeSourceMode.FindAncestor"/> mode.
/// </summary>
public Type AncestorType { get; set; }
public Type? AncestorType { get; set; }
/// <summary>
/// Gets or sets a value that describes the type of relative source lookup.

17
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -17,8 +17,8 @@ namespace Avalonia.Data
ISetterValue
{
private bool _isSetterValue;
private IStyledElement _target;
private Type _targetType;
private IStyledElement _target = default!;
private Type? _targetType;
public TemplateBinding()
{
@ -33,7 +33,7 @@ namespace Avalonia.Data
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
object? anchor = null,
bool enableDataValidation = false)
{
// Usually each `TemplateBinding` will only be instantiated once; in this case we can
@ -68,12 +68,12 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
/// </summary>
public IValueConverter Converter { get; set; }
public IValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the binding mode.
@ -83,7 +83,7 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the name of the source property on the templated parent.
/// </summary>
public AvaloniaProperty Property { get; set; }
public AvaloniaProperty? Property { get; set; }
/// <inheritdoc/>
public string Description => "TemplateBinding: " + Property;
@ -164,10 +164,7 @@ namespace Avalonia.Data
{
if (e.Property == StyledElement.TemplatedParentProperty)
{
var oldValue = (IAvaloniaObject)e.OldValue;
var newValue = (IAvaloniaObject)e.OldValue;
if (oldValue != null)
if (e.OldValue is IAvaloniaObject oldValue)
{
oldValue.PropertyChanged -= TemplatedParentPropertyChanged;
}

26
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs

@ -6,6 +6,8 @@ using Avalonia.Utilities;
using System;
using System.Collections.Generic;
#nullable enable
namespace Avalonia.Markup.Parsers
{
internal enum SourceMode
@ -271,8 +273,8 @@ namespace Avalonia.Markup.Parsers
}
else if (mode.SequenceEqual("parent".AsSpan()))
{
string ancestorNamespace = null;
string ancestorType = null;
string? ancestorNamespace = null;
string? ancestorType = null;
var ancestorLevel = 0;
if (PeekOpenBracket(ref r))
{
@ -424,19 +426,19 @@ namespace Avalonia.Markup.Parsers
public class PropertyNameNode : INode
{
public string PropertyName { get; set; }
public string PropertyName { get; set; } = string.Empty;
}
public class AttachedPropertyNameNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
public string PropertyName { get; set; }
public string Namespace { get; set; } = string.Empty;
public string TypeName { get; set; } = string.Empty;
public string PropertyName { get; set; } = string.Empty;
}
public class IndexerNode : INode
{
public IList<string> Arguments { get; set; }
public IList<string> Arguments { get; set; } = Array.Empty<string>();
}
public class NotNode : INode, ITransformNode { }
@ -447,20 +449,20 @@ namespace Avalonia.Markup.Parsers
public class NameNode : INode
{
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
}
public class AncestorNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
public string? Namespace { get; set; }
public string? TypeName { get; set; }
public int Level { get; set; }
}
public class TypeCastNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
public string Namespace { get; set; } = string.Empty;
public string TypeName { get; set; } = string.Empty;
}
}
}

21
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs

@ -8,8 +8,8 @@ namespace Avalonia.Markup.Parsers
{
public static class ExpressionObserverBuilder
{
internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type> typeResolver = null,
INameScope nameScope = null)
internal static (ExpressionNode? Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type>? typeResolver = null,
INameScope? nameScope = null)
{
if (string.IsNullOrWhiteSpace(expression))
{
@ -32,8 +32,8 @@ namespace Avalonia.Markup.Parsers
object root,
string expression,
bool enableDataValidation = false,
string description = null,
Func<string, string, Type> typeResolver = null)
string? description = null,
Func<string, string, Type>? typeResolver = null)
{
return new ExpressionObserver(
root,
@ -45,10 +45,11 @@ namespace Avalonia.Markup.Parsers
IObservable<object> rootObservable,
string expression,
bool enableDataValidation = false,
string description = null,
Func<string, string, Type> typeResolver = null)
string? description = null,
Func<string, string, Type>? typeResolver = null)
{
Contract.Requires<ArgumentNullException>(rootObservable != null);
_ = rootObservable ?? throw new ArgumentNullException(nameof(rootObservable));
return new ExpressionObserver(
rootObservable,
Parse(expression, enableDataValidation, typeResolver).Node,
@ -61,10 +62,10 @@ namespace Avalonia.Markup.Parsers
string expression,
IObservable<Unit> update,
bool enableDataValidation = false,
string description = null,
Func<string, string, Type> typeResolver = null)
string? description = null,
Func<string, string, Type>? typeResolver = null)
{
Contract.Requires<ArgumentNullException>(rootGetter != null);
_ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter));
return new ExpressionObserver(
rootGetter,

22
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@ -11,25 +11,25 @@ namespace Avalonia.Markup.Parsers
internal class ExpressionParser
{
private readonly bool _enableValidation;
private readonly Func<string, string, Type> _typeResolver;
private readonly INameScope _nameScope;
private readonly Func<string, string, Type>? _typeResolver;
private readonly INameScope? _nameScope;
public ExpressionParser(bool enableValidation, Func<string, string, Type> typeResolver, INameScope nameScope)
public ExpressionParser(bool enableValidation, Func<string, string, Type>? typeResolver, INameScope? nameScope)
{
_typeResolver = typeResolver;
_nameScope = nameScope;
_enableValidation = enableValidation;
}
public (ExpressionNode Node, SourceMode Mode) Parse(ref CharacterReader r)
public (ExpressionNode? Node, SourceMode Mode) Parse(ref CharacterReader r)
{
ExpressionNode rootNode = null;
ExpressionNode node = null;
ExpressionNode? rootNode = null;
ExpressionNode? node = null;
var (astNodes, mode) = BindingExpressionGrammar.Parse(ref r);
foreach (var astNode in astNodes)
{
ExpressionNode nextNode = null;
ExpressionNode? nextNode = null;
switch (astNode)
{
case BindingExpressionGrammar.EmptyExpressionNode _:
@ -57,13 +57,13 @@ namespace Avalonia.Markup.Parsers
nextNode = ParseFindAncestor(ancestor);
break;
case BindingExpressionGrammar.NameNode elementName:
nextNode = new ElementNameNode(_nameScope, elementName.Name);
nextNode = new ElementNameNode(_nameScope ?? throw new NotSupportedException("Invalid element name binding with null name scope!"), elementName.Name);
break;
case BindingExpressionGrammar.TypeCastNode typeCast:
nextNode = ParseTypeCastNode(typeCast);
break;
}
if (rootNode is null)
if (node is null)
{
rootNode = node = nextNode;
}
@ -79,7 +79,7 @@ namespace Avalonia.Markup.Parsers
private FindAncestorNode ParseFindAncestor(BindingExpressionGrammar.AncestorNode node)
{
Type ancestorType = null;
Type? ancestorType = null;
var ancestorLevel = node.Level;
if (!(node.Namespace is null) && !(node.TypeName is null))
@ -97,7 +97,7 @@ namespace Avalonia.Markup.Parsers
private TypeCastNode ParseTypeCastNode(BindingExpressionGrammar.TypeCastNode node)
{
Type castType = null;
Type? castType = null;
if (!(node.Namespace is null) && !(node.TypeName is null))
{
if (_typeResolver == null)

2
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs

@ -9,7 +9,7 @@ namespace Avalonia.Markup.Parsers.Nodes
{
private readonly WeakReference<INameScope> _nameScope;
private readonly string _name;
private IDisposable _subscription;
private IDisposable? _subscription;
public ElementNameNode(INameScope nameScope, string name)
{

6
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs

@ -7,10 +7,10 @@ namespace Avalonia.Markup.Parsers.Nodes
public class FindAncestorNode : ExpressionNode
{
private readonly int _level;
private readonly Type _ancestorType;
private IDisposable _subscription;
private readonly Type? _ancestorType;
private IDisposable? _subscription;
public FindAncestorNode(Type ancestorType, int level)
public FindAncestorNode(Type? ancestorType, int level)
{
_level = level;
_ancestorType = ancestorType;

16
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs

@ -29,15 +29,15 @@ namespace Avalonia.Markup.Parsers.Nodes
var list = target as IList;
var dictionary = target as IDictionary;
var indexerProperty = GetIndexer(typeInfo);
var indexerParameters = indexerProperty?.GetIndexParameters();
ParameterInfo[] indexerParameters;
if (indexerProperty != null && indexerParameters.Length == Arguments.Count)
if (indexerProperty != null && (indexerParameters = indexerProperty.GetIndexParameters()).Length == Arguments.Count)
{
var convertedObjectArray = new object[indexerParameters.Length];
for (int i = 0; i < Arguments.Count; i++)
{
object temp = null;
object? temp = null;
if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp))
{
@ -125,7 +125,7 @@ namespace Avalonia.Markup.Parsers.Nodes
public IList<string> Arguments { get; }
public override Type PropertyType
public override Type? PropertyType
{
get
{
@ -144,15 +144,15 @@ namespace Avalonia.Markup.Parsers.Nodes
var list = target as IList;
var dictionary = target as IDictionary;
var indexerProperty = GetIndexer(typeInfo);
var indexerParameters = indexerProperty?.GetIndexParameters();
ParameterInfo[] indexerParameters;
if (indexerProperty != null && indexerParameters.Length == Arguments.Count)
if (indexerProperty != null && (indexerParameters = indexerProperty.GetIndexParameters()).Length == Arguments.Count)
{
var convertedObjectArray = new object[indexerParameters.Length];
for (int i = 0; i < Arguments.Count; i++)
{
object temp = null;
object? temp = null;
if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp))
{
@ -246,7 +246,7 @@ namespace Avalonia.Markup.Parsers.Nodes
return true;
}
private static PropertyInfo GetIndexer(TypeInfo typeInfo)
private static PropertyInfo? GetIndexer(TypeInfo? typeInfo)
{
PropertyInfo indexer;

36
src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs

@ -3,6 +3,8 @@ using System.Collections.Generic;
using Avalonia.Data.Core;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Markup.Parsers
{
#if !BUILDTASK
@ -30,7 +32,7 @@ namespace Avalonia.Markup.Parsers
var parsed = new List<ISyntax>();
while (state != State.End)
{
ISyntax syntax = null;
ISyntax? syntax = null;
if (state == State.Start)
(state, syntax) = ParseStart(ref r);
else if (state == State.Next)
@ -53,7 +55,7 @@ namespace Avalonia.Markup.Parsers
return parsed;
}
private static (State, ISyntax) ParseNext(ref CharacterReader r)
private static (State, ISyntax?) ParseNext(ref CharacterReader r)
{
r.SkipWhitespace();
if (r.End)
@ -106,7 +108,7 @@ namespace Avalonia.Markup.Parsers
});
}
static (string ns, string name) ParseXamlIdentifier(ref CharacterReader r)
static (string? ns, string name) ParseXamlIdentifier(ref CharacterReader r)
{
var ident = r.ParseIdentifier();
if (ident.IsEmpty)
@ -147,7 +149,7 @@ namespace Avalonia.Markup.Parsers
return true;
}
private static (State, ISyntax) ParseAfterProperty(ref CharacterReader r)
private static (State, ISyntax?) ParseAfterProperty(ref CharacterReader r)
{
if (TryParseCasts(ref r, out var rv))
return rv;
@ -184,20 +186,20 @@ namespace Avalonia.Markup.Parsers
public class PropertySyntax : ISyntax
{
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
=> obj is PropertySyntax other
&& other.Name == Name;
}
public class TypeQualifiedPropertySyntax : ISyntax
{
public string Name { get; set; }
public string TypeName { get; set; }
public string TypeNamespace { get; set; }
public string Name { get; set; } = string.Empty;
public string TypeName { get; set; } = string.Empty;
public string? TypeNamespace { get; set; }
public override bool Equals(object obj)
public override bool Equals(object? obj)
=> obj is TypeQualifiedPropertySyntax other
&& other.Name == Name
&& other.TypeName == TypeName
@ -207,14 +209,14 @@ namespace Avalonia.Markup.Parsers
public class ChildTraversalSyntax : ISyntax
{
public static ChildTraversalSyntax Instance { get; } = new ChildTraversalSyntax();
public override bool Equals(object obj) => obj is ChildTraversalSyntax;
public override bool Equals(object? obj) => obj is ChildTraversalSyntax;
}
public class EnsureTypeSyntax : ISyntax
{
public string TypeName { get; set; }
public string TypeNamespace { get; set; }
public override bool Equals(object obj)
public string TypeName { get; set; } = string.Empty;
public string? TypeNamespace { get; set; }
public override bool Equals(object? obj)
=> obj is EnsureTypeSyntax other
&& other.TypeName == TypeName
&& other.TypeNamespace == TypeNamespace;
@ -222,9 +224,9 @@ namespace Avalonia.Markup.Parsers
public class CastTypeSyntax : ISyntax
{
public string TypeName { get; set; }
public string TypeNamespace { get; set; }
public override bool Equals(object obj)
public string TypeName { get; set; } = string.Empty;
public string? TypeNamespace { get; set; }
public override bool Equals(object? obj)
=> obj is CastTypeSyntax other
&& other.TypeName == TypeName
&& other.TypeNamespace == TypeNamespace;

42
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -4,6 +4,8 @@ using System.Linq;
using Avalonia.Data.Core;
using Avalonia.Utilities;
#nullable enable
// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the
// only reason they have overridden Equals methods is for unit testing.
#pragma warning disable 659
@ -39,7 +41,7 @@ namespace Avalonia.Markup.Parsers
var selector = new List<ISyntax>();
while (!r.End && state != State.End)
{
ISyntax syntax = null;
ISyntax? syntax = null;
switch (state)
{
case State.Start:
@ -110,7 +112,7 @@ namespace Avalonia.Markup.Parsers
return State.TypeName;
}
private static (State, ISyntax) ParseMiddle(ref CharacterReader r, char? end)
private static (State, ISyntax?) ParseMiddle(ref CharacterReader r, char? end)
{
if (r.TakeIf(':'))
{
@ -190,7 +192,7 @@ namespace Avalonia.Markup.Parsers
}
}
private static (State, ISyntax) ParseTraversal(ref CharacterReader r)
private static (State, ISyntax?) ParseTraversal(ref CharacterReader r)
{
r.SkipWhitespace();
if (r.TakeIf('>'))
@ -325,11 +327,11 @@ namespace Avalonia.Markup.Parsers
public class OfTypeSyntax : ISyntax, ITypeSyntax
{
public string TypeName { get; set; }
public string TypeName { get; set; } = string.Empty;
public string Xmlns { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
var other = obj as OfTypeSyntax;
return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns;
@ -338,11 +340,11 @@ namespace Avalonia.Markup.Parsers
public class IsSyntax : ISyntax, ITypeSyntax
{
public string TypeName { get; set; }
public string TypeName { get; set; } = string.Empty;
public string Xmlns { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
var other = obj as IsSyntax;
return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns;
@ -351,9 +353,9 @@ namespace Avalonia.Markup.Parsers
public class ClassSyntax : ISyntax
{
public string Class { get; set; }
public string Class { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is ClassSyntax && ((ClassSyntax)obj).Class == Class;
}
@ -361,9 +363,9 @@ namespace Avalonia.Markup.Parsers
public class NameSyntax : ISyntax
{
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is NameSyntax && ((NameSyntax)obj).Name == Name;
}
@ -371,11 +373,11 @@ namespace Avalonia.Markup.Parsers
public class PropertySyntax : ISyntax
{
public string Property { get; set; }
public string Property { get; set; } = string.Empty;
public string Value { get; set; }
public string Value { get; set; } = string.Empty;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is PropertySyntax &&
((PropertySyntax)obj).Property == Property &&
@ -385,7 +387,7 @@ namespace Avalonia.Markup.Parsers
public class ChildSyntax : ISyntax
{
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is ChildSyntax;
}
@ -393,7 +395,7 @@ namespace Avalonia.Markup.Parsers
public class DescendantSyntax : ISyntax
{
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is DescendantSyntax;
}
@ -401,7 +403,7 @@ namespace Avalonia.Markup.Parsers
public class TemplateSyntax : ISyntax
{
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is TemplateSyntax;
}
@ -409,9 +411,9 @@ namespace Avalonia.Markup.Parsers
public class NotSyntax : ISyntax
{
public IEnumerable<ISyntax> Argument { get; set; }
public IEnumerable<ISyntax> Argument { get; set; } = Enumerable.Empty<ISyntax>();
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return (obj is NotSyntax not) && Argument.SequenceEqual(not.Argument);
}
@ -419,7 +421,7 @@ namespace Avalonia.Markup.Parsers
public class CommaSyntax : ISyntax
{
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is CommaSyntax or;
}

6
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@ -31,13 +31,13 @@ namespace Avalonia.Markup.Parsers
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The parsed selector.</returns>
public Selector Parse(string s)
public Selector? Parse(string s)
{
var syntax = SelectorGrammar.Parse(s);
return Create(syntax);
}
private Selector Create(IEnumerable<SelectorGrammar.ISyntax> syntax)
private Selector? Create(IEnumerable<SelectorGrammar.ISyntax> syntax)
{
var result = default(Selector);
var results = default(List<Selector>);
@ -110,7 +110,7 @@ namespace Avalonia.Markup.Parsers
results = new List<Selector>();
}
results.Add(result);
results.Add(result ?? throw new NotSupportedException("Invalid selector!"));
result = null;
break;
default:

6
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -32,7 +32,7 @@ namespace Avalonia.Skia
weight -= weight % 100; // make sure we start at a full weight
for (var i = (int)key.Style; i < 2; i++)
for (var i = 0; i < 2; i++)
{
// only try 2 font weights in each direction
for (var j = 0; j < 200; j += 100)
@ -57,8 +57,8 @@ namespace Avalonia.Skia
}
}
//Nothing was found so we use the first typeface we can get.
return typefaces.Values.FirstOrDefault();
//Nothing was found so we try to get a regular typeface.
return typefaces.TryGetValue(new Typeface(key.FontFamily), out typeface) ? typeface : null;
}
}
}

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

@ -21,6 +21,7 @@ namespace Avalonia.Direct2D1.Media
private readonly ILayerFactory _layerFactory;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly DeviceContext _deviceContext;
private readonly bool _ownsDeviceContext;
private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback;
@ -51,10 +52,12 @@ namespace Avalonia.Direct2D1.Media
if (_renderTarget is DeviceContext deviceContext)
{
_deviceContext = deviceContext;
_ownsDeviceContext = false;
}
else
{
_deviceContext = _renderTarget.QueryInterface<DeviceContext>();
_ownsDeviceContext = true;
}
_deviceContext.BeginDraw();
@ -96,6 +99,13 @@ namespace Avalonia.Direct2D1.Media
{
throw new RenderTargetCorruptedException(ex);
}
finally
{
if (_ownsDeviceContext)
{
_deviceContext.Dispose();
}
}
}
/// <summary>
@ -151,7 +161,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 geometry = new SharpDX.Direct2D1.RectangleGeometry(_deviceContext.Factory, destRect.ToDirect2D()))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
{
if (d2dOpacityMask.PlatformBrush != null)
{

6
src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs

@ -33,13 +33,13 @@ namespace Avalonia.Direct2D1.Media
/// <inheritdoc/>
public IGeometryImpl Intersect(IGeometryImpl geometry)
{
var result = new PathGeometry(Geometry.Factory);
var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory);
using (var sink = result.Open())
{
Geometry.Combine(((GeometryImpl)geometry).Geometry, CombineMode.Intersect, sink);
return new StreamGeometryImpl(result);
sink.Close();
}
return new StreamGeometryImpl(result);
}
/// <inheritdoc/>

6
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -13,7 +13,7 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public class WicBitmapImpl : BitmapImpl
{
private BitmapDecoder _decoder;
private readonly BitmapDecoder _decoder;
private static BitmapInterpolationMode ConvertInterpolationMode(Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode)
{
@ -41,7 +41,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="fileName">The filename of the bitmap to load.</param>
public WicBitmapImpl(string fileName)
{
using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand))
using (var decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand))
{
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
Dpi = new Vector(96, 96);
@ -177,7 +177,7 @@ namespace Avalonia.Direct2D1.Media
/// <returns>The Direct2D bitmap.</returns>
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{
FormatConverter converter = new FormatConverter(Direct2D1Platform.ImagingFactory);
using var converter = new FormatConverter(Direct2D1Platform.ImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
}

9
src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs

@ -29,9 +29,12 @@ namespace Avalonia.Direct2D1.Media
public IStreamGeometryImpl Clone()
{
var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory);
var sink = result.Open();
((PathGeometry)Geometry).Stream(sink);
sink.Close();
using (var sink = result.Open())
{
((PathGeometry)Geometry).Stream(sink);
sink.Close();
}
return new StreamGeometryImpl(result);
}

2
src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs

@ -111,7 +111,7 @@ namespace Avalonia.Direct2D1
/// <returns>The Direct2D brush.</returns>
public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.IPen pen, SharpDX.Direct2D1.RenderTarget renderTarget)
{
return pen.ToDirect2DStrokeStyle(renderTarget.Factory);
return pen.ToDirect2DStrokeStyle(Direct2D1Platform.Direct2D1Factory);
}
/// <summary>

30
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -156,15 +156,35 @@ namespace Avalonia.Controls.UnitTests
root.Child = null;
Assert.Equal(0, command.SubscriptionCount);
}
[Fact]
public void MenuItem_Invokes_CanExecute_When_CommandParameter_Changed()
{
var command = new TestCommand(p => p is bool value && value);
var target = new MenuItem { Command = command };
target.CommandParameter = true;
Assert.True(target.IsEffectivelyEnabled);
target.CommandParameter = false;
Assert.False(target.IsEffectivelyEnabled);
}
private class TestCommand : ICommand
{
private bool _enabled;
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
private EventHandler _canExecuteChanged;
public TestCommand(bool enabled = true)
: this(_ => enabled, _ => { })
{
}
public TestCommand(Func<object, bool> canExecute, Action<object> execute = null)
{
_enabled = enabled;
_canExecute = canExecute;
_execute = execute ?? (_ => { });
}
public int SubscriptionCount { get; private set; }
@ -175,11 +195,9 @@ namespace Avalonia.Controls.UnitTests
remove { _canExecuteChanged -= value; --SubscriptionCount; }
}
public bool CanExecute(object parameter) => _enabled;
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter)
{
}
public void Execute(object parameter) => _execute(parameter);
}
}
}

115
tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@ -1,9 +1,14 @@
using Moq;
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
using Xunit;
using System;
using Avalonia.Input.Raw;
@ -53,7 +58,115 @@ namespace Avalonia.Controls.UnitTests.Utils
HotKeyManager.SetHotKey(button, null);
Assert.Empty(tl.KeyBindings);
}
}
[Fact]
public void HotKeyManager_Should_Release_Reference_When_Control_Detached()
{
using (AvaloniaLocator.EnterScope())
{
var styler = new Mock<Styler>();
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
.Bind<IStyler>().ToConstant(styler.Object);
var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
WeakReference reference = null;
var tl = new Window();
new Action(() =>
{
var button = new Button();
reference = new WeakReference(button, true);
tl.Content = button;
tl.Template = CreateWindowTemplate();
tl.ApplyTemplate();
tl.Presenter.ApplyTemplate();
HotKeyManager.SetHotKey(button, gesture1);
// Detach the button from the logical tree, so there is no reference to it
tl.Content = null;
tl.ApplyTemplate();
})();
// The button should be collected since it's detached from the listbox
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.Null(reference?.Target);
}
}
[Fact]
public void HotKeyManager_Should_Release_Reference_When_Control_In_Item_Template_Detached()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var styler = new Mock<Styler>();
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
.Bind<IStyler>().ToConstant(styler.Object);
var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
var weakReferences = new List<WeakReference>();
var tl = new Window { SizeToContent = SizeToContent.WidthAndHeight, IsVisible = true };
var lm = tl.LayoutManager;
var keyGestures = new AvaloniaList<KeyGesture> { gesture1 };
var listBox = new ListBox
{
Width = 100,
Height = 100,
VirtualizationMode = ItemVirtualizationMode.None,
// Create a button with binding to the KeyGesture in the template and add it to references list
ItemTemplate = new FuncDataTemplate(typeof(KeyGesture), (o, scope) =>
{
var keyGesture = o as KeyGesture;
var button = new Button
{
DataContext = keyGesture, [!Button.HotKeyProperty] = new Binding("")
};
weakReferences.Add(new WeakReference(button, true));
return button;
})
};
// Add the listbox and render it
tl.Content = listBox;
lm.ExecuteInitialLayoutPass();
listBox.Items = keyGestures;
lm.ExecuteLayoutPass();
// Let the button detach when clearing the source items
keyGestures.Clear();
lm.ExecuteLayoutPass();
// Add it again to double check,and render
keyGestures.Add(gesture1);
lm.ExecuteLayoutPass();
keyGestures.Clear();
lm.ExecuteLayoutPass();
// The button should be collected since it's detached from the listbox
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.True(weakReferences.Count > 0);
foreach (var weakReference in weakReferences)
{
Assert.Null(weakReference.Target);
}
}
}

31
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/IgnoredDirectivesTests.cs

@ -0,0 +1,31 @@
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class IgnoredDirectivesTests : XamlTestBase
{
[Fact]
public void Ignored_Directives_Should_Compile()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
const string xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<TextBlock x:Name='target' x:FieldModifier='Public' Text='Foo'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.FindControl<TextBlock>("target");
window.ApplyTemplate();
target.ApplyTemplate();
Assert.Equal("Foo", target.Text);
}
}
}
}

4
tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs

@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media
continue;
}
typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);
typeface = new Typeface(customTypeface.FontFamily, fontStyle, fontWeight);
return true;
}
@ -83,7 +83,7 @@ namespace Avalonia.Skia.UnitTests.Media
case "Noto Mono":
{
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily);
skTypeface = typefaceCollection.Get(typeface);
skTypeface = typefaceCollection.Get(_defaultTypeface);
break;
}
default:

28
tests/Avalonia.Skia.UnitTests/Media/SKTypefaceCollectionCacheTests.cs

@ -7,25 +7,33 @@ namespace Avalonia.Skia.UnitTests.Media
public class SKTypefaceCollectionCacheTests
{
[Fact]
public void Should_Load_Typefaces_From_Invalid_Name()
public void Should_Get_Near_Matching_Typeface()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var notoMono =
new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono");
var colorEmoji =
new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Twitter Color Emoji");
var notoMonoCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(notoMono);
var typeface = new Typeface("ABC", FontStyle.Italic, FontWeight.Bold);
Assert.Equal("Noto Mono", notoMonoCollection.Get(typeface).FamilyName);
var notoColorEmojiCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(colorEmoji);
Assert.Equal("Noto Mono",
notoMonoCollection.Get(new Typeface(notoMono, weight: FontWeight.Bold)).FamilyName);
}
}
[Fact]
public void Should_Get_Null_For_Invalid_FamilyName()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var notoMono =
new FontFamily("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono");
var notoMonoCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(notoMono);
Assert.Equal("Twitter Color Emoji", notoColorEmojiCollection.Get(typeface).FamilyName);
var typeface = notoMonoCollection.Get(new Typeface("ABC"));
Assert.Null(typeface);
}
}
}

Loading…
Cancel
Save