Browse Source

Merge remote-tracking branch 'upstream/master' into feature/invariantFamilyNames

pull/10620/head
Benedikt Stebner 4 years ago
parent
commit
bfb6315d6b
  1. 1
      .gitignore
  2. 2
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  3. 2
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  4. 2
      native/Avalonia.Native/src/OSX/WindowImpl.h
  5. 2
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  6. 13
      src/Avalonia.Base/AvaloniaProperty.cs
  7. 2
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  8. 4
      src/Avalonia.Base/Media/DrawingGroup.cs
  9. 2
      src/Avalonia.Base/Media/FontManager.cs
  10. 2
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  11. 1
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  12. 2
      src/Avalonia.Base/Rendering/DisplayDirtyRect.cs
  13. 1
      src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs
  14. 1
      src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs
  15. 1
      src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs
  16. 1
      src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs
  17. 2
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  18. 23
      src/Avalonia.Controls/ItemsSourceView.cs
  19. 2
      src/Avalonia.Controls/Platform/IInsetsManager.cs
  20. 2
      src/Avalonia.Controls/Selection/InternalSelectionModel.cs
  21. 17
      src/Avalonia.Controls/Selection/SelectedItems.cs
  22. 91
      src/Avalonia.Controls/Selection/SelectionModel.cs
  23. 8
      src/Avalonia.Controls/Selection/SelectionModelSelectionChangedEventArgs.cs
  24. 311
      src/Avalonia.Controls/Selection/SelectionNodeBase.cs
  25. 2
      src/Avalonia.Controls/SplitButton/SplitButton.cs
  26. 2
      src/Avalonia.Themes.Simple/Accents/Base.xaml
  27. 2
      src/Browser/Avalonia.Browser/BrowserAppBuilder.cs
  28. 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  29. 11
      tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs

1
.gitignore

@ -102,6 +102,7 @@ csx
AppPackages/ AppPackages/
# NCrunch # NCrunch
.NCrunch_*/
_NCrunch_*/ _NCrunch_*/
*.ncrunchsolution.user *.ncrunchsolution.user
nCrunchTemp_* nCrunchTemp_*

2
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -394,7 +394,7 @@
- (BOOL)windowShouldZoom:(NSWindow *_Nonnull)window toFrame:(NSRect)newFrame - (BOOL)windowShouldZoom:(NSWindow *_Nonnull)window toFrame:(NSRect)newFrame
{ {
return true; return _parent->CanZoom();
} }
-(void)windowDidResignKey:(NSNotification *)notification -(void)windowDidResignKey:(NSNotification *)notification

2
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -107,6 +107,8 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT GetInputMethod(IAvnTextInputMethod **retOut) override; virtual HRESULT GetInputMethod(IAvnTextInputMethod **retOut) override;
virtual bool CanZoom() { return false; }
protected: protected:
virtual NSWindowStyleMask CalculateStyleMask() = 0; virtual NSWindowStyleMask CalculateStyleMask() = 0;
virtual void UpdateStyle(); virtual void UpdateStyle();

2
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -97,6 +97,8 @@ BEGIN_INTERFACE_MAP()
bool CanBecomeKeyWindow (); bool CanBecomeKeyWindow ();
bool CanZoom() override { return _isEnabled && _canResize; }
protected: protected:
virtual NSWindowStyleMask CalculateStyleMask() override; virtual NSWindowStyleMask CalculateStyleMask() override;
void UpdateStyle () override; void UpdateStyle () override;

2
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -622,5 +622,5 @@ void WindowImpl::UpdateStyle() {
[miniaturizeButton setHidden:!hasTrafficLights]; [miniaturizeButton setHidden:!hasTrafficLights];
[miniaturizeButton setEnabled:_isEnabled]; [miniaturizeButton setEnabled:_isEnabled];
[zoomButton setHidden:!hasTrafficLights]; [zoomButton setHidden:!hasTrafficLights];
[zoomButton setEnabled:_isEnabled && _canResize]; [zoomButton setEnabled:CanZoom()];
} }

13
src/Avalonia.Base/AvaloniaProperty.cs

@ -257,7 +257,18 @@ namespace Avalonia
return result; return result;
} }
/// <inheritdoc cref="Register{TOwner, TValue}" /> /// <summary>
/// Registers an attached <see cref="AvaloniaProperty"/>.
/// </summary>
/// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <param name="name">The name of the property.</param>
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="coerce">A value coercion callback.</param>
/// <param name="enableDataValidation">if is set to true enable data validation.</param>
/// <param name="notifying"> /// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an /// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is /// object; the bool argument will be true before and false afterwards. This callback is

2
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -364,7 +364,7 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <remarks> /// <remarks>
/// You won't usually want to call this method directly, instead use the /// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{AvaloniaObject, TValue, TValue}, Action{AvaloniaObject, bool})"/> /// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{AvaloniaObject, TValue, TValue}, bool)"/>
/// method. /// method.
/// </remarks> /// </remarks>
public void Register(Type type, AvaloniaProperty property) public void Register(Type type, AvaloniaProperty property)

4
src/Avalonia.Base/Media/DrawingGroup.cs

@ -117,10 +117,10 @@ namespace Avalonia.Media
// root DrawingGroup, and be the same value as the root _currentDrawingGroup. // root DrawingGroup, and be the same value as the root _currentDrawingGroup.
// //
// Either way, _rootDrawing always references the root drawing. // Either way, _rootDrawing always references the root drawing.
protected Drawing? _rootDrawing; private Drawing? _rootDrawing;
// Current DrawingGroup that new children are added to // Current DrawingGroup that new children are added to
protected DrawingGroup? _currentDrawingGroup; private DrawingGroup? _currentDrawingGroup;
// Previous values of _currentDrawingGroup // Previous values of _currentDrawingGroup
private Stack<DrawingGroup?>? _previousDrawingGroupStack; private Stack<DrawingGroup?>? _previousDrawingGroupStack;

2
src/Avalonia.Base/Media/FontManager.cs

@ -107,7 +107,7 @@ namespace Avalonia.Media
source = new Uri(key.BaseUri, source); source = new Uri(key.BaseUri, source);
} }
if (!_fontCollections.TryGetValue(source, out var fontCollection)) if (!_fontCollections.TryGetValue(source, out var fontCollection) && (source.IsAbsoluteResm() || source.IsAvares()))
{ {
var embeddedFonts = new EmbeddedFontCollection(source, source); var embeddedFonts = new EmbeddedFontCollection(source, source);

2
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -658,7 +658,7 @@ namespace Avalonia.Media.TextFormatting
/// Performs text wrapping returns a list of text lines. /// Performs text wrapping returns a list of text lines.
/// </summary> /// </summary>
/// <param name="textRuns"></param> /// <param name="textRuns"></param>
/// <param name="canReuseTextRunList">Whether <see cref="textRuns"/> can be reused to store the split runs.</param> /// <param name="canReuseTextRunList">Whether <see cref="TextRun"/> can be reused to store the split runs.</param>
/// <param name="firstTextSourceIndex">The first text source index.</param> /// <param name="firstTextSourceIndex">The first text source index.</param>
/// <param name="paragraphWidth">The paragraph width.</param> /// <param name="paragraphWidth">The paragraph width.</param>
/// <param name="paragraphProperties">The text paragraph properties.</param> /// <param name="paragraphProperties">The text paragraph properties.</param>

1
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -128,6 +128,7 @@ namespace Avalonia.Platform
/// Pushes an opacity value. /// Pushes an opacity value.
/// </summary> /// </summary>
/// <param name="opacity">The opacity.</param> /// <param name="opacity">The opacity.</param>
/// <param name="bounds">where to apply the opacity.</param>
void PushOpacity(double opacity, Rect bounds); void PushOpacity(double opacity, Rect bounds);
/// <summary> /// <summary>

2
src/Avalonia.Base/Rendering/DisplayDirtyRect.cs

@ -3,7 +3,7 @@
namespace Avalonia.Rendering namespace Avalonia.Rendering
{ {
/// <summary> /// <summary>
/// Holds the state for a dirty rect rendered when <see cref="IRenderer.DrawDirtyRects"/> is set. /// Holds the state for a dirty rect rendered when <see cref="IRenderer.SceneInvalidated"/> is set.
/// </summary> /// </summary>
internal class DisplayDirtyRect internal class DisplayDirtyRect
{ {

1
src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs

@ -17,7 +17,6 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="brush">The fill brush.</param> /// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param> /// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param> /// <param name="geometry">The geometry.</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public GeometryNode(Matrix transform, public GeometryNode(Matrix transform,
IImmutableBrush? brush, IImmutableBrush? brush,
IPen? pen, IPen? pen,

1
src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs

@ -17,7 +17,6 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="pen">The stroke pen.</param> /// <param name="pen">The stroke pen.</param>
/// <param name="p1">The start point of the line.</param> /// <param name="p1">The start point of the line.</param>
/// <param name="p2">The end point of the line.</param> /// <param name="p2">The end point of the line.</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public LineNode( public LineNode(
Matrix transform, Matrix transform,
IPen pen, IPen pen,

1
src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs

@ -17,7 +17,6 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary> /// </summary>
/// <param name="mask">The opacity mask to push.</param> /// <param name="mask">The opacity mask to push.</param>
/// <param name="bounds">The bounds of the mask.</param> /// <param name="bounds">The bounds of the mask.</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public OpacityMaskNode(IImmutableBrush mask, Rect bounds) public OpacityMaskNode(IImmutableBrush mask, Rect bounds)
: base(default, Matrix.Identity, mask) : base(default, Matrix.Identity, mask)
{ {

1
src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs

@ -20,7 +20,6 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="pen">The stroke pen.</param> /// <param name="pen">The stroke pen.</param>
/// <param name="rect">The rectangle to draw.</param> /// <param name="rect">The rectangle to draw.</param>
/// <param name="boxShadows">The box shadow parameters</param> /// <param name="boxShadows">The box shadow parameters</param>
/// <param name="aux">Auxiliary data required to draw the brush.</param>
public RectangleNode( public RectangleNode(
Matrix transform, Matrix transform,
IImmutableBrush? brush, IImmutableBrush? brush,

2
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -52,7 +52,7 @@ namespace Avalonia.Styling
return result; return result;
} }
protected TypeNameAndClassSelector(Selector? previous) TypeNameAndClassSelector(Selector? previous)
{ {
_previous = previous; _previous = previous;
} }

23
src/Avalonia.Controls/ItemsSourceView.cs

@ -27,6 +27,7 @@ namespace Avalonia.Controls
private readonly IList _inner; private readonly IList _inner;
private NotifyCollectionChangedEventHandler? _collectionChanged; private NotifyCollectionChangedEventHandler? _collectionChanged;
private NotifyCollectionChangedEventHandler? _preCollectionChanged;
private NotifyCollectionChangedEventHandler? _postCollectionChanged; private NotifyCollectionChangedEventHandler? _postCollectionChanged;
private bool _listening; private bool _listening;
@ -70,7 +71,7 @@ namespace Avalonia.Controls
/// Gets a value that indicates whether the items source can provide a unique key for each item. /// Gets a value that indicates whether the items source can provide a unique key for each item.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// TODO: Not yet implemented in Avalonia. /// Not implemented in Avalonia, preserved here for ItemsRepeater's usage.
/// </remarks> /// </remarks>
internal bool HasKeyIndexMapping => false; internal bool HasKeyIndexMapping => false;
@ -92,6 +93,25 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Occurs when a collection has finished changing and all <see cref="CollectionChanged"/>
/// event handlers have been notified.
/// </summary>
internal event NotifyCollectionChangedEventHandler? PreCollectionChanged
{
add
{
AddListenerIfNecessary();
_preCollectionChanged += value;
}
remove
{
_preCollectionChanged -= value;
RemoveListenerIfNecessary();
}
}
/// <summary> /// <summary>
/// Occurs when a collection has finished changing and all <see cref="CollectionChanged"/> /// Occurs when a collection has finished changing and all <see cref="CollectionChanged"/>
/// event handlers have been notified. /// event handlers have been notified.
@ -229,6 +249,7 @@ namespace Avalonia.Controls
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{ {
_preCollectionChanged?.Invoke(this, e);
} }
void ICollectionChangedListener.Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) void ICollectionChangedListener.Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)

2
src/Avalonia.Controls/Platform/IInsetsManager.cs

@ -36,7 +36,7 @@ namespace Avalonia.Controls.Platform
SafeAreaPadding = safeArePadding; SafeAreaPadding = safeArePadding;
} }
/// <inheritdoc cref="IInsetsManager.GetSafeAreaPadding"/> /// <inheritdoc cref="IInsetsManager.SafeAreaPadding"/>
public Thickness SafeAreaPadding { get; } public Thickness SafeAreaPadding { get; }
} }

2
src/Avalonia.Controls/Selection/InternalSelectionModel.cs

@ -203,7 +203,7 @@ namespace Avalonia.Controls.Selection
} }
} }
private protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e) protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e)
{ {
if (e.Action == NotifyCollectionChangedAction.Reset) if (e.Action == NotifyCollectionChangedAction.Reset)
{ {

17
src/Avalonia.Controls/Selection/SelectedItems.cs

@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Controls.Selection namespace Avalonia.Controls.Selection
{ {
internal class SelectedItems<T> : IReadOnlyList<T> internal class SelectedItems<T> : IReadOnlyList<T?>
{ {
private readonly SelectionModel<T>? _owner; private readonly SelectionModel<T>? _owner;
private readonly ItemsSourceView<T>? _items; private readonly ItemsSourceView<T>? _items;
@ -19,12 +19,9 @@ namespace Avalonia.Controls.Selection
_items = items; _items = items;
} }
[MaybeNull] public T? this[int index]
public T this[int index]
{ {
#pragma warning disable CS8766
get get
#pragma warning restore CS8766
{ {
if (index >= Count) if (index >= Count)
{ {
@ -64,15 +61,13 @@ namespace Avalonia.Controls.Selection
private ItemsSourceView<T>? Items => _items ?? _owner?.ItemsView; private ItemsSourceView<T>? Items => _items ?? _owner?.ItemsView;
private IReadOnlyList<IndexRange>? Ranges => _ranges ?? _owner!.Ranges; private IReadOnlyList<IndexRange>? Ranges => _ranges ?? _owner!.Ranges;
public IEnumerator<T> GetEnumerator() public IEnumerator<T?> GetEnumerator()
{ {
if (_owner?.SingleSelect == true) if (_owner?.SingleSelect == true)
{ {
if (_owner.SelectedIndex >= 0) if (_owner.SelectedIndex >= 0)
{ {
#pragma warning disable CS8603
yield return _owner.SelectedItem; yield return _owner.SelectedItem;
#pragma warning restore CS8603
} }
} }
else else
@ -83,9 +78,7 @@ namespace Avalonia.Controls.Selection
{ {
for (var i = range.Begin; i <= range.End; ++i) for (var i = range.Begin; i <= range.End; ++i)
{ {
#pragma warning disable CS8603
yield return items is object ? items[i] : default; yield return items is object ? items[i] : default;
#pragma warning restore CS8603
} }
} }
} }
@ -102,8 +95,8 @@ namespace Avalonia.Controls.Selection
public class Untyped : IReadOnlyList<object?> public class Untyped : IReadOnlyList<object?>
{ {
private readonly IReadOnlyList<T> _source; private readonly IReadOnlyList<T?> _source;
public Untyped(IReadOnlyList<T> source) => _source = source; public Untyped(IReadOnlyList<T?> source) => _source = source;
public object? this[int index] => _source[index]; public object? this[int index] => _source[index];
public int Count => _source.Count; public int Count => _source.Count;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

91
src/Avalonia.Controls/Selection/SelectionModel.cs

@ -19,6 +19,7 @@ namespace Avalonia.Controls.Selection
private SelectedItems<T>.Untyped? _selectedItemsUntyped; private SelectedItems<T>.Untyped? _selectedItemsUntyped;
private EventHandler<SelectionModelSelectionChangedEventArgs>? _untypedSelectionChanged; private EventHandler<SelectionModelSelectionChangedEventArgs>? _untypedSelectionChanged;
private IList? _initSelectedItems; private IList? _initSelectedItems;
private bool _isSourceCollectionChanging;
public SelectionModel() public SelectionModel()
{ {
@ -55,7 +56,7 @@ namespace Avalonia.Controls.Selection
if (RangesEnabled && _selectedIndex >= 0) if (RangesEnabled && _selectedIndex >= 0)
{ {
CommitSelect(new IndexRange(_selectedIndex)); CommitSelect(_selectedIndex, _selectedIndex);
} }
RaisePropertyChanged(nameof(SingleSelect)); RaisePropertyChanged(nameof(SingleSelect));
@ -80,7 +81,7 @@ namespace Avalonia.Controls.Selection
{ {
get get
{ {
if (ItemsView is object) if (ItemsView is not null)
{ {
return GetItemAt(_selectedIndex); return GetItemAt(_selectedIndex);
} }
@ -93,21 +94,19 @@ namespace Avalonia.Controls.Selection
} }
set set
{ {
if (ItemsView is object) if (ItemsView is not null)
{ {
SelectedIndex = ItemsView.IndexOf(value!); SelectedIndex = ItemsView.IndexOf(value!);
} }
else else
{ {
Clear(); Clear();
#pragma warning disable CS8601 SetInitSelectedItems(new T[] { value! });
SetInitSelectedItems(new T[] { value });
#pragma warning restore CS8601
} }
} }
} }
public IReadOnlyList<T> SelectedItems public IReadOnlyList<T?> SelectedItems
{ {
get get
{ {
@ -206,7 +205,7 @@ namespace Avalonia.Controls.Selection
{ {
// If the collection is currently changing, commit the update when the // If the collection is currently changing, commit the update when the
// collection change finishes. // collection change finishes.
if (!IsSourceCollectionChanging) if (!_isSourceCollectionChanging)
{ {
CommitOperation(_operation); CommitOperation(_operation);
} }
@ -278,7 +277,7 @@ namespace Avalonia.Controls.Selection
{ {
if (base.Source != value) if (base.Source != value)
{ {
if (_operation is object) if (_operation is not null)
{ {
throw new InvalidOperationException("Cannot change source while update is in progress."); throw new InvalidOperationException("Cannot change source while update is in progress.");
} }
@ -296,7 +295,7 @@ namespace Avalonia.Controls.Selection
{ {
update.Operation.IsSourceUpdate = true; update.Operation.IsSourceUpdate = true;
if (_initSelectedItems is object && ItemsView is object) if (_initSelectedItems is object && ItemsView is not null)
{ {
foreach (T i in _initSelectedItems) foreach (T i in _initSelectedItems)
{ {
@ -315,17 +314,23 @@ namespace Avalonia.Controls.Selection
} }
} }
private protected override void OnIndexesChanged(int shiftIndex, int shiftDelta) protected override void OnIndexesChanged(int shiftIndex, int shiftDelta)
{ {
IndexesChanged?.Invoke(this, new SelectionModelIndexesChangedEventArgs(shiftIndex, shiftDelta)); IndexesChanged?.Invoke(this, new SelectionModelIndexesChangedEventArgs(shiftIndex, shiftDelta));
} }
private protected override void OnSourceReset() protected override void OnSourceCollectionChangeStarted()
{
base.OnSourceCollectionChangeStarted();
_isSourceCollectionChanging = true;
}
protected override void OnSourceReset()
{ {
_selectedIndex = _anchorIndex = -1; _selectedIndex = _anchorIndex = -1;
CommitDeselect(new IndexRange(0, int.MaxValue)); CommitDeselect(0, int.MaxValue);
if (SourceReset is object) if (SourceReset is not null)
{ {
SourceReset.Invoke(this, EventArgs.Empty); SourceReset.Invoke(this, EventArgs.Empty);
} }
@ -339,7 +344,7 @@ namespace Avalonia.Controls.Selection
} }
} }
private protected override void OnSelectionChanged(IReadOnlyList<T> deselectedItems) protected override void OnSelectionRemoved(int index, int count, IReadOnlyList<T> deselectedItems)
{ {
// Note: We're *not* putting this in a using scope. A collection update is still in progress // Note: We're *not* putting this in a using scope. A collection update is still in progress
// so the operation won't get committed by normal means: we have to commit it manually. // so the operation won't get committed by normal means: we have to commit it manually.
@ -347,7 +352,7 @@ namespace Avalonia.Controls.Selection
update.Operation.DeselectedItems = deselectedItems; update.Operation.DeselectedItems = deselectedItems;
if (_selectedIndex == -1 && LostSelection is object) if (_selectedIndex == -1 && LostSelection is not null)
{ {
LostSelection(this, EventArgs.Empty); LostSelection(this, EventArgs.Empty);
} }
@ -357,7 +362,7 @@ namespace Avalonia.Controls.Selection
CommitOperation(update.Operation, raisePropertyChanged: false); CommitOperation(update.Operation, raisePropertyChanged: false);
} }
private protected override CollectionChangeState OnItemsAdded(int index, IList items) protected override CollectionChangeState OnItemsAdded(int index, IList items)
{ {
var count = items.Count; var count = items.Count;
var shifted = SelectedIndex >= index; var shifted = SelectedIndex >= index;
@ -420,7 +425,7 @@ namespace Avalonia.Controls.Selection
}; };
} }
private protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e) protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e)
{ {
if (_operation?.UpdateCount > 0) if (_operation?.UpdateCount > 0)
{ {
@ -451,6 +456,16 @@ namespace Avalonia.Controls.Selection
} }
} }
private protected void SetInitSelectedItems(IList items)
{
if (Source is object)
{
throw new InvalidOperationException("Cannot set init selected items when Source is set.");
}
_initSelectedItems = items;
}
private protected override bool IsValidCollectionChange(NotifyCollectionChangedEventArgs e) private protected override bool IsValidCollectionChange(NotifyCollectionChangedEventArgs e)
{ {
if (!base.IsValidCollectionChange(e)) if (!base.IsValidCollectionChange(e))
@ -474,19 +489,11 @@ namespace Avalonia.Controls.Selection
return true; return true;
} }
private protected void SetInitSelectedItems(IList items)
{
if (Source is object)
{
throw new InvalidOperationException("Cannot set init selected items when Source is set.");
}
_initSelectedItems = items;
}
protected override void OnSourceCollectionChangeFinished() protected override void OnSourceCollectionChangeFinished()
{ {
if (_operation is object) _isSourceCollectionChanging = false;
if (_operation is not null)
{ {
CommitOperation(_operation); CommitOperation(_operation);
} }
@ -575,7 +582,7 @@ namespace Avalonia.Controls.Selection
{ {
index = Math.Max(index, -1); index = Math.Max(index, -1);
if (ItemsView is object && index >= ItemsView.Count) if (ItemsView is not null && index >= ItemsView.Count)
{ {
index = -1; index = -1;
} }
@ -585,7 +592,7 @@ namespace Avalonia.Controls.Selection
private IndexRange CoerceRange(int start, int end) private IndexRange CoerceRange(int start, int end)
{ {
var max = ItemsView is object ? ItemsView.Count - 1 : int.MaxValue; var max = ItemsView is not null ? ItemsView.Count - 1 : int.MaxValue;
if (start > max || (start < 0 && end < 0)) if (start > max || (start < 0 && end < 0))
{ {
@ -643,7 +650,7 @@ namespace Avalonia.Controls.Selection
var oldSelectedIndex = _selectedIndex; var oldSelectedIndex = _selectedIndex;
var indexesChanged = false; var indexesChanged = false;
if (operation.SelectedIndex == -1 && LostSelection is object && !operation.SkipLostSelection) if (operation.SelectedIndex == -1 && LostSelection is not null && !operation.SkipLostSelection)
{ {
operation.UpdateCount++; operation.UpdateCount++;
LostSelection?.Invoke(this, EventArgs.Empty); LostSelection?.Invoke(this, EventArgs.Empty);
@ -652,17 +659,23 @@ namespace Avalonia.Controls.Selection
_selectedIndex = operation.SelectedIndex; _selectedIndex = operation.SelectedIndex;
_anchorIndex = operation.AnchorIndex; _anchorIndex = operation.AnchorIndex;
if (operation.SelectedRanges is object) if (operation.SelectedRanges is not null)
{ {
indexesChanged |= CommitSelect(operation.SelectedRanges) > 0; foreach (var range in operation.SelectedRanges)
{
indexesChanged |= CommitSelect(range.Begin, range.End) > 0;
}
} }
if (operation.DeselectedRanges is object) if (operation.DeselectedRanges is not null)
{ {
indexesChanged |= CommitDeselect(operation.DeselectedRanges) > 0; foreach (var range in operation.DeselectedRanges)
{
indexesChanged |= CommitDeselect(range.Begin, range.End) > 0;
}
} }
if (SelectionChanged is object || _untypedSelectionChanged is object) if (SelectionChanged is not null || _untypedSelectionChanged is not null)
{ {
IReadOnlyList<IndexRange>? deselected = operation.DeselectedRanges; IReadOnlyList<IndexRange>? deselected = operation.DeselectedRanges;
IReadOnlyList<IndexRange>? selected = operation.SelectedRanges; IReadOnlyList<IndexRange>? selected = operation.SelectedRanges;
@ -690,14 +703,14 @@ namespace Avalonia.Controls.Selection
// CollectionChanged event. LostFocus may have caused another item to have been // CollectionChanged event. LostFocus may have caused another item to have been
// selected, but it can't have caused a deselection (as it was called due to // selected, but it can't have caused a deselection (as it was called due to
// selection being lost) so we're ok to discard `deselected` here. // selection being lost) so we're ok to discard `deselected` here.
var deselectedItems = operation.DeselectedItems ?? var deselectedItems = (IReadOnlyList<T?>?)operation.DeselectedItems ??
SelectedItems<T>.Create(deselected, deselectedSource); SelectedItems<T>.Create(deselected, deselectedSource);
var e = new SelectionModelSelectionChangedEventArgs<T>( var e = new SelectionModelSelectionChangedEventArgs<T>(
SelectedIndexes<T>.Create(deselected), SelectedIndexes<T>.Create(deselected),
SelectedIndexes<T>.Create(selected), SelectedIndexes<T>.Create(selected),
deselectedItems, deselectedItems,
SelectedItems<T>.Create(selected, ItemsView)); SelectedItems<T>.Create(selected, Source is not null ? ItemsView : null));
SelectionChanged?.Invoke(this, e); SelectionChanged?.Invoke(this, e);
_untypedSelectionChanged?.Invoke(this, e); _untypedSelectionChanged?.Invoke(this, e);
} }

8
src/Avalonia.Controls/Selection/SelectionModelSelectionChangedEventArgs.cs

@ -39,8 +39,8 @@ namespace Avalonia.Controls.Selection
public SelectionModelSelectionChangedEventArgs( public SelectionModelSelectionChangedEventArgs(
IReadOnlyList<int>? deselectedIndices = null, IReadOnlyList<int>? deselectedIndices = null,
IReadOnlyList<int>? selectedIndices = null, IReadOnlyList<int>? selectedIndices = null,
IReadOnlyList<T>? deselectedItems = null, IReadOnlyList<T?>? deselectedItems = null,
IReadOnlyList<T>? selectedItems = null) IReadOnlyList<T?>? selectedItems = null)
{ {
DeselectedIndexes = deselectedIndices ?? Array.Empty<int>(); DeselectedIndexes = deselectedIndices ?? Array.Empty<int>();
SelectedIndexes = selectedIndices ?? Array.Empty<int>(); SelectedIndexes = selectedIndices ?? Array.Empty<int>();
@ -61,12 +61,12 @@ namespace Avalonia.Controls.Selection
/// <summary> /// <summary>
/// Gets the items that were removed from the selection. /// Gets the items that were removed from the selection.
/// </summary> /// </summary>
public new IReadOnlyList<T> DeselectedItems { get; } public new IReadOnlyList<T?> DeselectedItems { get; }
/// <summary> /// <summary>
/// Gets the items that were added to the selection. /// Gets the items that were added to the selection.
/// </summary> /// </summary>
public new IReadOnlyList<T> SelectedItems { get; } public new IReadOnlyList<T?> SelectedItems { get; }
protected override IReadOnlyList<object?> GetUntypedDeselectedItems() protected override IReadOnlyList<object?> GetUntypedDeselectedItems()
{ {

311
src/Avalonia.Controls/Selection/SelectionNodeBase.cs

@ -2,37 +2,62 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq;
using Avalonia.Controls.Utils;
namespace Avalonia.Controls.Selection namespace Avalonia.Controls.Selection
{ {
public abstract class SelectionNodeBase<T> : ICollectionChangedListener /// <summary>
/// Base class for selection models.
/// </summary>
/// <typeparam name="T">The type of the element being selected.</typeparam>
public abstract class SelectionNodeBase<T>
{ {
private IEnumerable? _source; private IEnumerable? _source;
private bool _rangesEnabled; private bool _rangesEnabled;
private List<IndexRange>? _ranges; private List<IndexRange>? _ranges;
private int _collectionChanging;
/// <summary>
/// Gets or sets the source collection.
/// </summary>
protected IEnumerable? Source protected IEnumerable? Source
{ {
get => _source; get => _source;
set set
{ {
void OnPreChanged(object? sender, NotifyCollectionChangedEventArgs e) => OnSourceCollectionChangeStarted();
void OnChanged(object? sender, NotifyCollectionChangedEventArgs e) => OnSourceCollectionChanged(e);
void OnPostChanged(object? sender, NotifyCollectionChangedEventArgs e) => OnSourceCollectionChangeFinished();
if (_source != value) if (_source != value)
{ {
if (ItemsView?.Inner is INotifyCollectionChanged inccOld) if (ItemsView is not null)
CollectionChangedEventManager.Instance.RemoveListener(inccOld, this); {
ItemsView.PreCollectionChanged -= OnPreChanged;
ItemsView.CollectionChanged -= OnChanged;
ItemsView.PostCollectionChanged -= OnPostChanged;
}
_source = value; _source = value;
ItemsView = value is object ? ItemsSourceView.GetOrCreate<T>(value) : null; ItemsView = value is not null ? ItemsSourceView.GetOrCreate<T>(value) : null;
if (ItemsView?.Inner is INotifyCollectionChanged inccNew)
CollectionChangedEventManager.Instance.AddListener(inccNew, this); if (ItemsView is not null)
{
ItemsView.PreCollectionChanged += OnPreChanged;
ItemsView.CollectionChanged += OnChanged;
ItemsView.PostCollectionChanged += OnPostChanged;
}
} }
} }
} }
protected bool IsSourceCollectionChanging => _collectionChanging > 0; /// <summary>
/// Gets an <see cref="ItemsSourceView{T}"/> of the <see cref="Source"/>.
/// </summary>
protected internal ItemsSourceView<T>? ItemsView { get; set; }
/// <summary>
/// Gets or sets a value indicating whether range selection is currently enabled for
/// the selection node.
/// </summary>
protected bool RangesEnabled protected bool RangesEnabled
{ {
get => _rangesEnabled; get => _rangesEnabled;
@ -50,8 +75,6 @@ namespace Avalonia.Controls.Selection
} }
} }
internal ItemsSourceView<T>? ItemsView { get; set; }
internal IReadOnlyList<IndexRange> Ranges internal IReadOnlyList<IndexRange> Ranges
{ {
get get
@ -65,81 +88,170 @@ namespace Avalonia.Controls.Selection
} }
} }
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) /// <summary>
/// Called when the source collection starts changing.
/// </summary>
protected virtual void OnSourceCollectionChangeStarted()
{ {
++_collectionChanging;
} }
void ICollectionChangedListener.Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) /// <summary>
/// Called when the <see cref="Source"/> collection changes.
/// </summary>
/// <param name="e">The details of the collection change.</param>
/// <remarks>
/// The implementation in <see cref="SelectionNodeBase{T}"/> calls
/// <see cref="OnItemsAdded(int, IList)"/> and <see cref="OnItemsRemoved(int, IList)"/>
/// in order to calculate how the collection change affects the currently selected items.
/// It then calls <see cref="OnIndexesChanged(int, int)"/> and
/// <see cref="OnSelectionRemoved(int, int, IReadOnlyList{T})"/> if necessary, according
/// to the <see cref="CollectionChangeState"/> returned by those methods.
///
/// Override this method and <see cref="OnSourceCollectionChangeFinished"/> to provide
/// custom handling of source collection changes.
/// </remarks>
protected virtual void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e)
{ {
OnSourceCollectionChanged(e); var shiftDelta = 0;
} var shiftIndex = -1;
List<T>? removed = null;
void ICollectionChangedListener.PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) if (!IsValidCollectionChange(e))
{
if (--_collectionChanging == 0)
{ {
OnSourceCollectionChangeFinished(); return;
} }
}
protected abstract void OnSourceCollectionChangeFinished();
private protected abstract void OnIndexesChanged(int shiftIndex, int shiftDelta);
private protected abstract void OnSourceReset();
private protected abstract void OnSelectionChanged(IReadOnlyList<T> deselectedItems); switch (e.Action)
private protected int CommitSelect(IndexRange range)
{
if (RangesEnabled)
{ {
_ranges ??= new List<IndexRange>(); case NotifyCollectionChangedAction.Add:
return IndexRange.Add(_ranges, range); {
var change = OnItemsAdded(e.NewStartingIndex, e.NewItems!);
shiftIndex = change.ShiftIndex;
shiftDelta = change.ShiftDelta;
break;
}
case NotifyCollectionChangedAction.Remove:
{
var change = OnItemsRemoved(e.OldStartingIndex, e.OldItems!);
shiftIndex = change.ShiftIndex;
shiftDelta = change.ShiftDelta;
removed = change.RemovedItems;
break;
}
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
{
var removeChange = OnItemsRemoved(e.OldStartingIndex, e.OldItems!);
var addChange = OnItemsAdded(e.NewStartingIndex, e.NewItems!);
shiftIndex = removeChange.ShiftIndex;
shiftDelta = removeChange.ShiftDelta + addChange.ShiftDelta;
removed = removeChange.RemovedItems;
}
break;
case NotifyCollectionChangedAction.Reset:
OnSourceReset();
break;
} }
return 0; if (shiftDelta != 0)
OnIndexesChanged(shiftIndex, shiftDelta);
if (removed is not null)
OnSelectionRemoved(shiftIndex, -shiftDelta, removed);
} }
private protected int CommitSelect(IReadOnlyList<IndexRange> ranges) /// <summary>
/// Called when the source collection has finished changing, and all CollectionChanged
/// handlers have run.
/// </summary>
/// <remarks>
/// Override this method to respond to the end of a collection change instead of acting at
/// the end of <see cref="OnSourceCollectionChanged(NotifyCollectionChangedEventArgs)"/>
/// in order to ensure that all UI subscribers to the source collection change event have
/// had chance to run.
/// </remarks>
protected virtual void OnSourceCollectionChangeFinished()
{ {
if (RangesEnabled) }
{
_ranges ??= new List<IndexRange>();
return IndexRange.Add(_ranges, ranges);
}
return 0; /// <summary>
/// Called by <see cref="OnSourceCollectionChanged(NotifyCollectionChangedEventArgs)"/>,
/// detailing the indexes changed by the collection changing.
/// </summary>
/// <param name="shiftIndex">The first index that was shifted.</param>
/// <param name="shiftDelta">
/// If positive, the number of items inserted, or if negative the number of items removed.
/// </param>
protected virtual void OnIndexesChanged(int shiftIndex, int shiftDelta)
{
}
/// <summary>
/// Called by <see cref="OnSourceCollectionChanged(NotifyCollectionChangedEventArgs)"/>,
/// on collection reset.
/// </summary>
protected abstract void OnSourceReset();
/// <summary>
/// Called by <see cref="OnSourceCollectionChanged(NotifyCollectionChangedEventArgs)"/>,
/// detailing the items removed by a collection change.
/// </summary>
protected virtual void OnSelectionRemoved(int index, int count, IReadOnlyList<T> deselectedItems)
{
} }
private protected int CommitDeselect(IndexRange range) /// <summary>
/// If <see cref="RangesEnabled"/>, adds the specified range to the selection.
/// </summary>
/// <param name="begin">The inclusive index of the start of the range to select.</param>
/// <param name="end">The inclusive index of the end of the range to select.</param>
/// <returns>The number of items selected.</returns>
protected int CommitSelect(int begin, int end)
{ {
if (RangesEnabled) if (RangesEnabled)
{ {
_ranges ??= new List<IndexRange>(); _ranges ??= new List<IndexRange>();
return IndexRange.Remove(_ranges, range); return IndexRange.Add(_ranges, new IndexRange(begin, end));
} }
return 0; return 0;
} }
private protected int CommitDeselect(IReadOnlyList<IndexRange> ranges) /// <summary>
/// If <see cref="RangesEnabled"/>, removes the specified range from the selection.
/// </summary>
/// <param name="begin">The inclusive index of the start of the range to deselect.</param>
/// <param name="end">The inclusive index of the end of the range to deselect.</param>
/// <returns>The number of items selected.</returns>
protected int CommitDeselect(int begin, int end)
{ {
if (RangesEnabled && _ranges is object) if (RangesEnabled)
{ {
return IndexRange.Remove(_ranges, ranges); _ranges ??= new List<IndexRange>();
return IndexRange.Remove(_ranges, new IndexRange(begin, end));
} }
return 0; return 0;
} }
private protected virtual CollectionChangeState OnItemsAdded(int index, IList items) /// <summary>
/// Called by <see cref="OnSourceCollectionChanged(NotifyCollectionChangedEventArgs)"/>
/// when items are added to the source collection.
/// </summary>
/// <returns>
/// A <see cref="CollectionChangeState"/> struct containing the details of the adjusted
/// selection.
/// </returns>
/// <remarks>
/// The implementation in <see cref="SelectionNodeBase{T}"/> adjusts the selected ranges,
/// assigning new indexes. Override this method to carry out additional computation when
/// items are added.
/// </remarks>
protected virtual CollectionChangeState OnItemsAdded(int index, IList items)
{ {
var count = items.Count; var count = items.Count;
var shifted = false; var shifted = false;
if (_ranges is object) if (_ranges is not null)
{ {
List<IndexRange>? toAdd = null; List<IndexRange>? toAdd = null;
@ -150,7 +262,7 @@ namespace Avalonia.Controls.Selection
// The range is after the inserted items, need to shift the range right // The range is after the inserted items, need to shift the range right
if (range.End >= index) if (range.End >= index)
{ {
int begin = range.Begin; var begin = range.Begin;
// If the index left of newIndex is inside the range, // If the index left of newIndex is inside the range,
// Split the range and remember the left piece to add later // Split the range and remember the left piece to add later
@ -167,7 +279,7 @@ namespace Avalonia.Controls.Selection
} }
} }
if (toAdd is object) if (toAdd is not null)
{ {
foreach (var range in toAdd) foreach (var range in toAdd)
{ {
@ -183,14 +295,27 @@ namespace Avalonia.Controls.Selection
}; };
} }
/// <summary>
/// Called by <see cref="OnSourceCollectionChanged(NotifyCollectionChangedEventArgs)"/>
/// when items are removed from the source collection.
/// </summary>
/// <returns>
/// A <see cref="CollectionChangeState"/> struct containing the details of the adjusted
/// selection.
/// </returns>
/// <remarks>
/// The implementation in <see cref="SelectionNodeBase{T}"/> adjusts the selected ranges,
/// assigning new indexes. Override this method to carry out additional computation when
/// items are removed.
/// </remarks>
private protected virtual CollectionChangeState OnItemsRemoved(int index, IList items) private protected virtual CollectionChangeState OnItemsRemoved(int index, IList items)
{ {
var count = items.Count; var count = items.Count;
var removedRange = new IndexRange(index, index + count - 1); var removedRange = new IndexRange(index, index + count - 1);
bool shifted = false; var shifted = false;
List<T>? removed = null; List<T>? removed = null;
if (_ranges is object) if (_ranges is not null)
{ {
var deselected = new List<IndexRange>(); var deselected = new List<IndexRange>();
@ -227,60 +352,6 @@ namespace Avalonia.Controls.Selection
}; };
} }
private protected virtual void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e)
{
var shiftDelta = 0;
var shiftIndex = -1;
List<T>? removed = null;
if (!IsValidCollectionChange(e))
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
var change = OnItemsAdded(e.NewStartingIndex, e.NewItems!);
shiftIndex = change.ShiftIndex;
shiftDelta = change.ShiftDelta;
break;
}
case NotifyCollectionChangedAction.Remove:
{
var change = OnItemsRemoved(e.OldStartingIndex, e.OldItems!);
shiftIndex = change.ShiftIndex;
shiftDelta = change.ShiftDelta;
removed = change.RemovedItems;
break;
}
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
{
var removeChange = OnItemsRemoved(e.OldStartingIndex, e.OldItems!);
var addChange = OnItemsAdded(e.NewStartingIndex, e.NewItems!);
shiftIndex = removeChange.ShiftIndex;
shiftDelta = removeChange.ShiftDelta + addChange.ShiftDelta;
removed = removeChange.RemovedItems;
}
break;
case NotifyCollectionChangedAction.Reset:
OnSourceReset();
break;
}
if (shiftDelta != 0)
{
OnIndexesChanged(shiftIndex, shiftDelta);
}
if (removed is object)
{
OnSelectionChanged(removed);
}
}
private protected virtual bool IsValidCollectionChange(NotifyCollectionChangedEventArgs e) private protected virtual bool IsValidCollectionChange(NotifyCollectionChangedEventArgs e)
{ {
// If the selection is modified in a CollectionChanged handler before the selection // If the selection is modified in a CollectionChanged handler before the selection
@ -309,11 +380,27 @@ namespace Avalonia.Controls.Selection
return true; return true;
} }
private protected struct CollectionChangeState /// <summary>
/// Details the results of a collection change on the current selection;
/// </summary>
protected class CollectionChangeState
{ {
public int ShiftIndex; /// <summary>
public int ShiftDelta; /// Gets or sets the first index that was shifted as a result of the collection
public List<T>? RemovedItems; /// changing.
/// </summary>
public int ShiftIndex { get; set; }
/// <summary>
/// Gets or sets a value indicating how the indexes after <see cref="ShiftIndex"/>
/// were shifted.
/// </summary>
public int ShiftDelta { get; set; }
/// <summary>
/// Gets or sets the items removed by the collection change, if any.
/// </summary>
public List<T>? RemovedItems { get; set; }
} }
} }
} }

2
src/Avalonia.Controls/SplitButton/SplitButton.cs

@ -432,7 +432,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Called when the <see cref="FlyoutBase.Placement"/> property changes. /// Called when the <see cref="PopupFlyoutBase.Placement"/> property changes.
/// </summary> /// </summary>
private void Flyout_PlacementPropertyChanged(AvaloniaPropertyChangedEventArgs e) private void Flyout_PlacementPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{ {

2
src/Avalonia.Themes.Simple/Accents/Base.xaml

@ -76,7 +76,7 @@
<SolidColorBrush x:Key="RefreshVisualizerBackground" Color="Transparent" /> <SolidColorBrush x:Key="RefreshVisualizerBackground" Color="Transparent" />
</ResourceDictionary> </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries> </ResourceDictionary.ThemeDictionaries>
<FontFamily x:Key="ContentControlThemeFontFamily">fonts://Inter#Inter, $Default</FontFamily> <FontFamily x:Key="ContentControlThemeFontFamily">fonts:Inter#Inter, $Default</FontFamily>
<Color x:Key="ThemeAccentColor">#CC119EDA</Color> <Color x:Key="ThemeAccentColor">#CC119EDA</Color>
<Color x:Key="ThemeAccentColor2">#99119EDA</Color> <Color x:Key="ThemeAccentColor2">#99119EDA</Color>
<Color x:Key="ThemeAccentColor3">#66119EDA</Color> <Color x:Key="ThemeAccentColor3">#66119EDA</Color>

2
src/Browser/Avalonia.Browser/BrowserAppBuilder.cs

@ -16,7 +16,7 @@ public class BrowserPlatformOptions
public static class BrowserAppBuilder public static class BrowserAppBuilder
{ {
/// <summary> /// <summary>
/// Configures browser backend, loads avalonia javascript modules and creates a single view lifetime from the passed <see cref="mainDivId"/> parameter. /// Configures browser backend, loads avalonia javascript modules and creates a single view lifetime from the passed <see paramref="mainDivId"/> parameter.
/// </summary> /// </summary>
/// <param name="builder">Application builder.</param> /// <param name="builder">Application builder.</param>
/// <param name="mainDivId">ID of the html element where avalonia content should be rendered.</param> /// <param name="mainDivId">ID of the html element where avalonia content should be rendered.</param>

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

@ -29,7 +29,6 @@ namespace Avalonia.Direct2D1.Media
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DrawingContextImpl"/> class. /// Initializes a new instance of the <see cref="DrawingContextImpl"/> class.
/// </summary> /// </summary>
/// <param name="visualBrushRenderer">The visual brush renderer.</param>
/// <param name="renderTarget">The render target to draw to.</param> /// <param name="renderTarget">The render target to draw to.</param>
/// <param name="layerFactory"> /// <param name="layerFactory">
/// An object to use to create layers. May be null, in which case a /// An object to use to create layers. May be null, in which case a

11
tests/Avalonia.Skia.UnitTests/Media/FontManagerImplTests.cs

@ -76,5 +76,16 @@ namespace Avalonia.Skia.UnitTests.Media
Assert.Throws<InvalidOperationException>(() => new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Unknown").GlyphTypeface); Assert.Throws<InvalidOperationException>(() => new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Unknown").GlyphTypeface);
} }
} }
[Fact]
public void Should_Return_False_For_Unregistered_FontCollection_Uri()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new FontManagerImpl())))
{
var result = FontManager.Current.TryGetGlyphTypeface(new Typeface("fonts:invalid#Something"), out _);
Assert.False(result);
}
}
} }
} }

Loading…
Cancel
Save