diff --git a/build.sh b/build.sh index 40b1c225a6..a40e00f815 100755 --- a/build.sh +++ b/build.sh @@ -67,6 +67,8 @@ else fi fi +export PATH=$DOTNET_DIRECTORY:$PATH + echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index b86c679397..f9bfaf0b47 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -387,6 +387,9 @@ AVNCOM(IAvnClipboard, 0f) : IUnknown virtual HRESULT SetText (char* type, void* utf8Text) = 0; virtual HRESULT ObtainFormats(IAvnStringArray**ppv) = 0; virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) = 0; + virtual HRESULT SetBytes(char* type, void* utf8Text, int len) = 0; + virtual HRESULT GetBytes(char* type, IAvnString**ppv) = 0; + virtual HRESULT Clear() = 0; }; diff --git a/native/Avalonia.Native/src/OSX/AvnString.h b/native/Avalonia.Native/src/OSX/AvnString.h index 88bc4e6963..5d299374e5 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.h +++ b/native/Avalonia.Native/src/OSX/AvnString.h @@ -12,4 +12,5 @@ extern IAvnString* CreateAvnString(NSString* string); extern IAvnStringArray* CreateAvnStringArray(NSArray* array); extern IAvnStringArray* CreateAvnStringArray(NSString* string); +extern IAvnString* CreateByteArray(void* data, int len); #endif /* AvnString_h */ diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 6445a9fef1..00b748ef63 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -29,6 +29,13 @@ public: memcpy((void*)_cstring, (void*)cstring, _length); } + AvnStringImpl(void*ptr, int len) + { + _length = len; + _cstring = (const char*)malloc(_length); + memcpy((void*)_cstring, ptr, len); + } + virtual ~AvnStringImpl() { free((void*)_cstring); @@ -114,3 +121,8 @@ IAvnStringArray* CreateAvnStringArray(NSString* string) { return new AvnStringArrayImpl(string); } + +IAvnString* CreateByteArray(void* data, int len) +{ + return new AvnStringImpl(data, len); +} diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index 18d60d3853..116a08670e 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -82,6 +82,40 @@ public: return S_OK; } + + virtual HRESULT SetBytes(char* type, void* bytes, int len) override + { + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + auto data = [NSData dataWithBytes:bytes length:len]; + if(_item == nil) + [_pb setData:data forType:typeString]; + else + [_item setData:data forType:typeString]; + return S_OK; + } + + virtual HRESULT GetBytes(char* type, IAvnString**ppv) override + { + *ppv = nil; + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + NSData*data; + @try + { + if(_item) + data = [_item dataForType:typeString]; + else + data = [_pb dataForType:typeString]; + if(data == nil) + return E_FAIL; + } + @catch(NSException* e) + { + return E_FAIL; + } + *ppv = CreateByteArray((void*)data.bytes, (int)data.length); + return S_OK; + } + virtual HRESULT Clear() override { diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 20ca291910..c7a75f5a70 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -1,10 +1,8 @@ - - - - + + - + + + + diff --git a/samples/ControlCatalog/Pages/ButtonPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonPage.xaml.cs index 1d0c228a0e..5e555c8c91 100644 --- a/samples/ControlCatalog/Pages/ButtonPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonPage.xaml.cs @@ -5,5 +5,25 @@ namespace ControlCatalog.Pages { public class ButtonPage : UserControl { + private int repeatButtonClickCount = 0; + + public ButtonPage() + { + InitializeComponent(); + + this.FindControl("RepeatButton").Click += OnRepeatButtonClick; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public void OnRepeatButtonClick(object sender, object args) + { + repeatButtonClickCount++; + var textBlock = this.FindControl("RepeatButtonTextBlock"); + textBlock.Text = $"Repeat Button: {repeatButtonClickCount}"; + } } } diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml index 58f7b881fe..c6f5521e60 100644 --- a/samples/ControlCatalog/Pages/SliderPage.xaml +++ b/samples/ControlCatalog/Pages/SliderPage.xaml @@ -9,12 +9,14 @@ diff --git a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs index 51e0a1e799..7802f336fb 100644 --- a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs +++ b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs @@ -43,5 +43,11 @@ namespace Avalonia.Android.Platform return Task.FromResult(null); } + + public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException(); + + public Task GetFormatsAsync() => throw new PlatformNotSupportedException(); + + public Task GetDataAsync(string format) => throw new PlatformNotSupportedException(); } } diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 9e9b84537b..067d9f462f 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -93,17 +93,17 @@ namespace Avalonia.Animation var oldTransitions = change.OldValue.GetValueOrDefault(); var newTransitions = change.NewValue.GetValueOrDefault(); - if (oldTransitions is object) - { - oldTransitions.CollectionChanged -= TransitionsCollectionChanged; - RemoveTransitions(oldTransitions); - } - if (newTransitions is object) { newTransitions.CollectionChanged += TransitionsCollectionChanged; AddTransitions(newTransitions); } + + if (oldTransitions is object) + { + oldTransitions.CollectionChanged -= TransitionsCollectionChanged; + RemoveTransitions(oldTransitions); + } } else if (_transitionsEnabled && Transitions is object && @@ -111,8 +111,10 @@ namespace Avalonia.Animation !change.Property.IsDirect && change.Priority > BindingPriority.Animation) { - foreach (var transition in Transitions) + for (var i = Transitions.Count -1; i >= 0; --i) { + var transition = Transitions[i]; + if (transition.Property == change.Property) { var state = _transitionState[transition]; diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index f2ed86d2aa..84ef0fb695 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -1,7 +1,5 @@ using System; using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; using System.Reflection; using Avalonia.Utilities; @@ -31,7 +29,11 @@ namespace Avalonia.Data.Core.Plugins Contract.Requires(propertyName != null); reference.TryGetTarget(out object instance); - var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name == propertyName); + + const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | + BindingFlags.Static | BindingFlags.Instance; + + var p = instance.GetType().GetProperty(propertyName, bindingFlags); if (p != null) { diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index c19ee8dba7..5d694f4cf9 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -1,13 +1,16 @@ using System; -using System.Linq; +using System.Collections.Generic; +using System.Linq.Expressions; using System.Reflection; namespace Avalonia.Data.Core.Plugins { - class MethodAccessorPlugin : IPropertyAccessorPlugin + public class MethodAccessorPlugin : IPropertyAccessorPlugin { - public bool Match(object obj, string methodName) - => obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName); + private readonly Dictionary<(Type, string), MethodInfo> _methodLookup = + new Dictionary<(Type, string), MethodInfo>(); + + public bool Match(object obj, string methodName) => GetFirstMethodWithName(obj.GetType(), methodName) != null; public IPropertyAccessor Start(WeakReference reference, string methodName) { @@ -15,17 +18,22 @@ namespace Avalonia.Data.Core.Plugins Contract.Requires(methodName != null); reference.TryGetTarget(out object instance); - var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName); + + var method = GetFirstMethodWithName(instance.GetType(), methodName); if (method != null) { - if (method.GetParameters().Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) + var parameters = method.GetParameters(); + + if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) { - var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(methodName)); + var exception = new ArgumentException( + "Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", + nameof(methodName)); return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); } - return new Accessor(reference, method); + return new Accessor(reference, method, parameters); } else { @@ -35,31 +43,72 @@ namespace Avalonia.Data.Core.Plugins } } + private MethodInfo GetFirstMethodWithName(Type type, string methodName) + { + var key = (type, methodName); + + if (!_methodLookup.TryGetValue(key, out MethodInfo methodInfo)) + { + methodInfo = TryFindAndCacheMethod(type, methodName); + } + + return methodInfo; + } + + private MethodInfo TryFindAndCacheMethod(Type type, string methodName) + { + MethodInfo found = null; + + const BindingFlags bindingFlags = + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + var methods = type.GetMethods(bindingFlags); + + foreach (MethodInfo methodInfo in methods) + { + if (methodInfo.Name == methodName) + { + found = methodInfo; + + break; + } + } + + _methodLookup.Add((type, methodName), found); + + return found; + } + private sealed class Accessor : PropertyAccessorBase { - public Accessor(WeakReference reference, MethodInfo method) + public Accessor(WeakReference reference, MethodInfo method, ParameterInfo[] parameters) { Contract.Requires(reference != null); Contract.Requires(method != null); - var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); var returnType = method.ReturnType; - - if (returnType == typeof(void)) + bool hasReturn = returnType != typeof(void); + + var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length; + + var paramTypes = new Type[signatureTypeCount]; + + for (var i = 0; i < parameters.Length; i++) { - if (paramTypes.Length == 0) - { - PropertyType = typeof(Action); - } - else - { - PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes); - } + ParameterInfo parameter = parameters[i]; + + paramTypes[i] = parameter.ParameterType; + } + + if (hasReturn) + { + paramTypes[paramTypes.Length - 1] = returnType; + + PropertyType = Expression.GetFuncType(paramTypes); } else { - var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); - PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); + PropertyType = Expression.GetActionType(paramTypes); } if (method.IsStatic) diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index e104a8a664..1db47a13e7 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -41,13 +41,16 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty IsDirectionReversedProperty = AvaloniaProperty.Register(nameof(IsDirectionReversed)); + public static readonly StyledProperty IgnoreThumbDragProperty = + AvaloniaProperty.Register(nameof(IsThumbDragHandled)); + private double _minimum; private double _maximum = 100.0; private double _value; static Track() { - ThumbProperty.Changed.AddClassHandler((x,e) => x.ThumbChanged(e)); + ThumbProperty.Changed.AddClassHandler((x, e) => x.ThumbChanged(e)); IncreaseButtonProperty.Changed.AddClassHandler((x, e) => x.ButtonChanged(e)); DecreaseButtonProperty.Changed.AddClassHandler((x, e) => x.ButtonChanged(e)); AffectsArrange(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty); @@ -113,6 +116,12 @@ namespace Avalonia.Controls.Primitives set { SetValue(IsDirectionReversedProperty, value); } } + public bool IsThumbDragHandled + { + get { return GetValue(IgnoreThumbDragProperty); } + set { SetValue(IgnoreThumbDragProperty, value); } + } + private double ThumbCenterOffset { get; set; } private double Density { get; set; } @@ -422,6 +431,9 @@ namespace Avalonia.Controls.Primitives private void ThumbDragged(object sender, VectorEventArgs e) { + if (IsThumbDragHandled) + return; + Value = MathUtilities.Clamp( Value + ValueFromDistance(e.Vector.X, e.Vector.Y), Minimum, diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index e92c8faf20..ec23bfa396 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -1,12 +1,40 @@ using System; +using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; -using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; +using Avalonia.Utilities; namespace Avalonia.Controls { + + /// + /// Enum which describes how to position the ticks in a . + /// + public enum TickPlacement + { + /// + /// No tick marks will appear. + /// + None, + + /// + /// Tick marks will appear above the track for a horizontal , or to the left of the track for a vertical . + /// + TopLeft, + + /// + /// Tick marks will appear below the track for a horizontal , or to the right of the track for a vertical . + /// + BottomRight, + + /// + /// Tick marks appear on both sides of either a horizontal or vertical . + /// + Outside + } + /// /// A control that lets the user select from a range of values by moving a Thumb control along a Track. /// @@ -30,19 +58,31 @@ namespace Avalonia.Controls public static readonly StyledProperty TickFrequencyProperty = AvaloniaProperty.Register(nameof(TickFrequency), 0.0); + /// + /// Defines the property. + /// + public static readonly StyledProperty TickPlacementProperty = + AvaloniaProperty.Register(nameof(TickPlacement), 0d); + // Slider required parts + private bool _isDragging = false; private Track _track; private Button _decreaseButton; private Button _increaseButton; + private IDisposable _decreaseButtonPressDispose; + private IDisposable _decreaseButtonReleaseDispose; + private IDisposable _increaseButtonSubscription; + private IDisposable _increaseButtonReleaseDispose; + private IDisposable _pointerMovedDispose; /// /// Initializes static members of the class. /// static Slider() { + PressedMixin.Attach(); OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); Thumb.DragStartedEvent.AddClassHandler((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); - Thumb.DragDeltaEvent.AddClassHandler((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); } @@ -81,57 +121,88 @@ namespace Avalonia.Controls set { SetValue(TickFrequencyProperty, value); } } + /// + /// Gets or sets a value that indicates where to draw + /// tick marks in relation to the track. + /// + public TickPlacement TickPlacement + { + get { return GetValue(TickPlacementProperty); } + set { SetValue(TickPlacementProperty, value); } + } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { - if (_decreaseButton != null) - { - _decreaseButton.Click -= DecreaseClick; - } - - if (_increaseButton != null) - { - _increaseButton.Click -= IncreaseClick; - } + base.OnApplyTemplate(e); + + _decreaseButtonPressDispose?.Dispose(); + _decreaseButtonReleaseDispose?.Dispose(); + _increaseButtonSubscription?.Dispose(); + _increaseButtonReleaseDispose?.Dispose(); + _pointerMovedDispose?.Dispose(); _decreaseButton = e.NameScope.Find