Browse Source

Merge branch 'master' into feat-transition-direct-props

pull/12716/head
Bill Henning 3 years ago
committed by GitHub
parent
commit
98749a122e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      samples/ControlCatalog/Pages/PointerCanvas.cs
  2. 1
      samples/ControlCatalog/Pages/PointerContactsTab.cs
  3. 1
      samples/ControlCatalog/Pages/PointersPage.xaml.cs
  4. 1
      src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
  5. 15
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
  6. 4
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  7. 6
      src/Avalonia.Base/Input/InputElement.cs
  8. 8
      src/Avalonia.Base/Input/Pointer.cs
  9. 109
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  10. 2
      src/Avalonia.Controls/Notifications/NotificationCard.cs
  11. 7
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  12. 12
      src/Avalonia.Controls/ToggleSwitch.cs
  13. 10
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  14. 6
      src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml
  15. 12
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  16. 11
      src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
  17. 34
      tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
  18. 6
      tests/Avalonia.UnitTests/TouchTestHelper.cs

1
samples/ControlCatalog/Pages/PointerCanvas.cs

@ -74,6 +74,7 @@ public class PointerCanvas : Control
public void HandleEvent(PointerEventArgs e, Visual v)
{
e.Handled = true;
e.PreventGestureRecognition();
var currentPoint = e.GetCurrentPoint(v);
if (e.RoutedEvent == PointerPressedEvent)
AddPoint(currentPoint.Position, Brushes.Green, 10);

1
samples/ControlCatalog/Pages/PointerContactsTab.cs

@ -72,6 +72,7 @@ public class PointerContactsTab : Control
UpdatePointer(e);
e.Pointer.Capture(this);
e.Handled = true;
e.PreventGestureRecognition();
base.OnPointerPressed(e);
}

1
samples/ControlCatalog/Pages/PointersPage.xaml.cs

@ -72,6 +72,7 @@ Position: ??? ???";
{
e.Pointer.Capture(sender as Border);
e.Handled = true;
e.PreventGestureRecognition();
}
private void InitializeComponent()

1
src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs

@ -71,6 +71,7 @@ public class Rotate3DTransition: PageSlide
{
Easing = SlideOutEasing,
Duration = Duration,
FillMode = FillMode.Forward,
Children =
{
CreateKeyFrame(0d, 0d, 2),

15
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs

@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Controls;
@ -59,6 +60,20 @@ namespace Avalonia.Input.GestureRecognizers
return e.Handled;
}
internal void HandleCaptureLost(IPointer pointer)
{
if (_recognizers == null || pointer is not Pointer p)
return;
foreach (var r in _recognizers)
{
if (p.CapturedGestureRecognizer == r)
continue;
r.PointerCaptureLostInternal(pointer);
}
}
internal bool HandlePointerReleased(PointerReleasedEventArgs e)
{
if (_recognizers == null)

4
src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs

@ -1,4 +1,5 @@
using Avalonia.Input.GestureRecognizers;
using System.Diagnostics;
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
@ -110,6 +111,7 @@ namespace Avalonia.Input
_secondContact = null;
}
Target?.RaiseEvent(new PinchEndedEventArgs());
}
}

6
src/Avalonia.Base/Input/InputElement.cs

@ -230,6 +230,7 @@ namespace Avalonia.Input
PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true);
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true);
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true);
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerCaptureLost(e), handledEventsToo: true);
}
public InputElement()
@ -615,6 +616,11 @@ namespace Avalonia.Input
}
}
private void OnGesturePointerCaptureLost(PointerCaptureLostEventArgs e)
{
_gestureRecognizers?.HandleCaptureLost(e.Pointer);
}
private void OnGesturePointerPressed(PointerPressedEventArgs e)
{
if (!e.IsGestureRecognitionSkipped)

8
src/Avalonia.Base/Input/Pointer.cs

@ -91,12 +91,16 @@ namespace Avalonia.Input
internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer)
{
if (CapturedGestureRecognizer != gestureRecognizer)
{
CapturedGestureRecognizer?.PointerCaptureLostInternal(this);
}
CapturedGestureRecognizer = gestureRecognizer;
if (gestureRecognizer != null)
{
Capture(null);
CapturedGestureRecognizer = gestureRecognizer;
}
}
}
}

109
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -1,4 +1,4 @@
// (c) Copyright Microsoft Corporation.
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
@ -165,6 +165,16 @@ namespace Avalonia.Controls
/// </summary>
private bool _allowWrite;
/// <summary>
/// A boolean indicating if a cancellation was requested
/// </summary>
private bool _cancelRequested;
/// <summary>
/// A boolean indicating if filtering is in action
/// </summary>
private bool _filterInAction;
/// <summary>
/// The TextBox template part.
/// </summary>
@ -1379,6 +1389,15 @@ namespace Avalonia.Controls
/// </summary>
private void RefreshView()
{
// If we have a running filter, trigger a request first
if (_filterInAction)
{
_cancelRequested = true;
}
// Indicate that filtering is ongoing
_filterInAction = true;
if (_items == null)
{
ClearView();
@ -1391,79 +1410,65 @@ namespace Avalonia.Controls
// Determine if any filtering mode is on
bool stringFiltering = TextFilter != null;
bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;
int view_index = 0;
int view_count = _view!.Count;
List<object> items = _items;
// cache properties
var textFilter = TextFilter;
var itemFilter = ItemFilter;
var _newViewItems = new Collection<object>();
// if the mode is objectFiltering and itemFilter is null, we throw an exception
if (objectFiltering && itemFilter is null)
{
// indicate that filtering is not ongoing anymore
_filterInAction = false;
_cancelRequested = false;
throw new Exception(
"ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom");
}
foreach (object item in items)
{
// Exit the fitter when requested if cancellation is requested
if (_cancelRequested)
{
return;
}
bool inResults = !(stringFiltering || objectFiltering);
if (!inResults)
{
if (stringFiltering)
{
inResults = TextFilter!(text, FormatValue(item));
inResults = textFilter!(text, FormatValue(item));
}
else
else if (objectFiltering)
{
if (ItemFilter is null)
{
throw new Exception("ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom");
}
else
{
inResults = ItemFilter(text, item);
}
inResults = itemFilter!(text, item);
}
}
if (view_count > view_index && inResults && _view[view_index] == item)
if (inResults)
{
// Item is still in the view
view_index++;
}
else if (inResults)
{
// Insert the item
if (view_count > view_index && _view[view_index] != item)
{
// Replace item
// Unfortunately replacing via index throws a fatal
// exception: View[view_index] = item;
// Cost: O(n) vs O(1)
_view.RemoveAt(view_index);
_view.Insert(view_index, item);
view_index++;
}
else
{
// Add the item
if (view_index == view_count)
{
// Constant time is preferred (Add).
_view.Add(item);
}
else
{
_view.Insert(view_index, item);
}
view_index++;
view_count++;
}
}
else if (view_count > view_index && _view[view_index] == item)
{
// Remove the item
_view.RemoveAt(view_index);
view_count--;
_newViewItems.Add(item);
}
}
_view?.Clear();
_view?.AddRange(_newViewItems);
// Clear the evaluator to discard a reference to the last item
if (_valueBindingEvaluator != null)
{
_valueBindingEvaluator.ClearDataContext();
}
// indicate that filtering is not ongoing anymore
_filterInAction = false;
_cancelRequested = false;
}
/// <summary>

2
src/Avalonia.Controls/Notifications/NotificationCard.cs

@ -39,7 +39,7 @@ namespace Avalonia.Controls.Notifications
this.GetObservable(ContentProperty)
.Subscribe(x =>
{
if (x is Notification notification)
if (x is INotification notification)
{
switch (notification.Type)
{

7
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -110,6 +110,13 @@ namespace Avalonia.Controls.Primitives
get
{
var rc = new Rect(default, _overlayLayer.AvailableSize);
var topLevel = TopLevel.GetTopLevel(this);
if(topLevel != null)
{
var padding = topLevel.InsetsManager?.SafeAreaPadding ?? default;
rc = rc.Deflate(padding);
}
return new[] {new ManagedPopupPositionerScreenInfo(rc, rc)};
}
}

12
src/Avalonia.Controls/ToggleSwitch.cs

@ -243,10 +243,6 @@ namespace Avalonia.Controls
}
UpdateKnobTransitions();
}
else
{
base.Toggle();
}
_isDragging = false;
@ -276,14 +272,6 @@ namespace Avalonia.Controls
}
}
protected override void Toggle()
{
if ((_switchKnob != null) && (!_switchKnob.IsPointerOver))
{
base.Toggle();
}
}
private void UpdateKnobPos(bool value)
{
if ((_switchKnob != null) && (_knobsPanel != null))

10
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -29,7 +29,7 @@ namespace Avalonia.DesignerSupport
if (assemblyPath != null)
{
if (xamlFileProjectPath == null)
xamlFileProjectPath = "/Designer/Fake.xaml";
xamlFileProjectPath = "/Fake.xaml";
//Fabricate fake Uri
baseUri =
new Uri($"avares://{Path.GetFileNameWithoutExtension(assemblyPath)}{xamlFileProjectPath}");
@ -43,7 +43,7 @@ namespace Avalonia.DesignerSupport
{
LocalAssembly = localAsm,
DesignMode = true,
UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue ) && parsedValue
UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue) && parsedValue
});
var style = loaded as IStyle;
var resources = loaded as ResourceDictionary;
@ -90,14 +90,14 @@ namespace Avalonia.DesignerSupport
};
}
else if (loaded is Application)
control = new TextBlock {Text = "Application can't be previewed in design view"};
control = new TextBlock { Text = "Application can't be previewed in design view" };
else
control = (Control) loaded;
control = (Control)loaded;
window = control as Window;
if (window == null)
{
window = new Window() {Content = (Control)control};
window = new Window() { Content = (Control)control };
}
if (window.PlatformImpl is OffscreenTopLevelImplBase offscreenImpl)

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

@ -71,6 +71,9 @@
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pointerover /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<!-- Pressed State -->
<Style Selector="^:pressed">
@ -82,6 +85,9 @@
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<Style Selector="^:pressed /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<!-- Disabled State -->
<Style Selector="^:disabled /template/ Border#RootBorder">

12
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -32,14 +32,20 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>();
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
var targetObject = provideTarget?.TargetObject;
var targetProperty = provideTarget?.TargetProperty;
var targetProperty = provideTarget?.TargetProperty switch
{
AvaloniaProperty ap => ap,
PropertyInfo pi => new Avalonia.Data.Core.ReflectionClrPropertyInfo(pi),
_ => provideTarget?.TargetProperty,
};
var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant
?? GetDictionaryVariant(serviceProvider);
var targetType = targetProperty switch
{
AvaloniaProperty ap => ap.PropertyType,
PropertyInfo pi => pi.PropertyType,
Avalonia.Data.Core.IPropertyInfo cpi => cpi.PropertyType,
_ => null
};
@ -62,7 +68,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
}
if (targetObject is Control target &&
targetProperty is PropertyInfo property)
targetProperty is Avalonia.Data.Core.IPropertyInfo property)
{
// This is stored locally to avoid allocating closure in the outer scope.
var localTargetType = targetType;

11
src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Logging;
namespace Avalonia.Markup.Data
@ -56,11 +57,11 @@ namespace Avalonia.Markup.Data
/// <param name="target">The control.</param>
/// <param name="property">The property on the control to bind to.</param>
/// <param name="value">A function which returns the value.</param>
public static void Add(StyledElement target, PropertyInfo property, Func<StyledElement, object?> value)
public static void Add(StyledElement target, IPropertyInfo property, Func<StyledElement, object?> value)
{
if (target.IsInitialized)
{
property.SetValue(target, value(target));
property.Set(target, value(target));
}
else
{
@ -125,20 +126,20 @@ namespace Avalonia.Markup.Data
private class ClrPropertyValueEntry : Entry
{
public ClrPropertyValueEntry(PropertyInfo property, Func<StyledElement, object?> value)
public ClrPropertyValueEntry(IPropertyInfo property, Func<StyledElement, object?> value)
{
Property = property;
Value = value;
}
public PropertyInfo Property { get; }
public IPropertyInfo Property { get; }
public Func<StyledElement, object?> Value { get; }
public override void Apply(StyledElement control)
{
try
{
Property.SetValue(control, Value(control));
Property.Set(control, Value(control));
}
catch (Exception e)
{

34
tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs

@ -487,6 +487,40 @@ namespace Avalonia.Base.UnitTests.Input
Assert.True(raised);
}
[Fact]
public void Gestures_Should_Be_Cancelled_When_Pointer_Capture_Is_Lost()
{
Border border = new Border()
{
Width = 100,
Height = 100,
Background = new SolidColorBrush(Colors.Red)
};
border.GestureRecognizers.Add(new PinchGestureRecognizer());
var root = new TestRoot
{
Child = border
};
var raised = false;
root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10);
var firstTouch = new TouchTestHelper();
var secondTouch = new TouchTestHelper();
firstTouch.Down(border, position: firstPoint);
firstTouch.Cancel();
secondTouch.Down(border, position: secondPoint);
secondTouch.Move(border, position: new Point(20, 20));
Assert.False(raised);
}
[Fact]
public void Scrolling_Should_Start_After_Start_Distance_Is_Exceeded()
{

6
tests/Avalonia.UnitTests/TouchTestHelper.cs

@ -61,5 +61,11 @@ namespace Avalonia.UnitTests
Down(target, source, position, modifiers);
Up(target, source, position, modifiers);
}
public void Cancel()
{
_pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
}
}
}

Loading…
Cancel
Save