diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 14fe60ab0b..7a6e7dc72f 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -641,6 +641,7 @@ private: [Window setCanBecomeKeyAndMain]; [Window disableCursorRects]; [Window setTabbingMode:NSWindowTabbingModeDisallowed]; + [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; } void HideOrShowTrafficLights () @@ -1091,14 +1092,7 @@ private: { _fullScreenActive = true; - [Window setHasShadow:YES]; - [Window setTitleVisibility:NSWindowTitleVisible]; - [Window setTitlebarAppearsTransparent:NO]; [Window setTitle:_lastTitle]; - - Window.styleMask = Window.styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable; - Window.styleMask = Window.styleMask & ~NSWindowStyleMaskFullSizeContentView; - [Window toggleFullScreen:nullptr]; } @@ -1672,6 +1666,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent switch(event.buttonNumber) { + case 2: case 3: _isMiddlePressed = true; [self mouseEvent:event withType:MiddleButtonDown]; @@ -1704,6 +1699,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { switch(event.buttonNumber) { + case 2: case 3: _isMiddlePressed = false; [self mouseEvent:event withType:MiddleButtonUp]; diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index 2c7f34c5be..2f1cb2888e 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -280,8 +280,8 @@ namespace Avalonia.Collections /// /// Gets a range of items from the collection. /// - /// The first index to remove. - /// The number of items to remove. + /// The zero-based index at which the range starts. + /// The number of elements in the range. public IEnumerable GetRange(int index, int count) { return _inner.GetRange(index, count); @@ -455,7 +455,7 @@ namespace Avalonia.Collections } /// - /// Ensures that the capacity of the list is at least . + /// Ensures that the capacity of the list is at least . /// /// The capacity. public void EnsureCapacity(int capacity) diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs index 9d8ebbfac1..587dd228a3 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs @@ -83,8 +83,9 @@ namespace Avalonia.Collections if (key == null) key = item; - if (_valueConverter != null) - key = _valueConverter.Convert(key, typeof(object), level, culture); + var valueConverter = ValueConverter; + if (valueConverter != null) + key = valueConverter.Convert(key, typeof(object), level, culture); return key; } @@ -99,6 +100,8 @@ namespace Avalonia.Collections } public override string PropertyName => _propertyPath; + public IValueConverter ValueConverter { get => _valueConverter; set => _valueConverter = value; } + private Type GetPropertyType(object o) { return o.GetType().GetNestedPropertyType(_propertyPath); diff --git a/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs b/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs index 6ac77fbb99..1d1a595ccf 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs @@ -1,10 +1,8 @@ using Avalonia.Data; using Avalonia.Reactive; using System; -using System.ComponentModel.DataAnnotations; using System.Collections.Generic; using System.Reactive.Subjects; -using System.Text; namespace Avalonia.Controls.Utils { @@ -67,11 +65,14 @@ namespace Avalonia.Controls.Utils private void SetSourceValue(object value) { - _settingSourceValue = true; + if (!_settingSourceValue) + { + _settingSourceValue = true; - _sourceSubject.OnNext(value); + _sourceSubject.OnNext(value); - _settingSourceValue = false; + _settingSourceValue = false; + } } private void SetControlValue(object value) { @@ -157,4 +158,4 @@ namespace Avalonia.Controls.Utils } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 5a6e78f441..0e946126ea 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -2094,7 +2094,21 @@ namespace Avalonia.Controls bool inResults = !(stringFiltering || objectFiltering); if (!inResults) { - inResults = stringFiltering ? TextFilter(text, FormatValue(item)) : ItemFilter(text, item); + if (stringFiltering) + { + inResults = TextFilter(text, FormatValue(item)); + } + else + { + if (ItemFilter is null) + { + throw new Exception("ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom"); + } + else + { + inResults = ItemFilter(text, item); + } + } } if (view_count > view_index && inResults && _view[view_index] == item) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 0eade8d6df..9eae928eeb 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -145,6 +145,18 @@ namespace Avalonia.Controls (o, v) => o.UndoLimit = v, unsetValue: -1); + public static readonly RoutedEvent CopyingToClipboardEvent = + RoutedEvent.Register( + "CopyingToClipboard", RoutingStrategies.Bubble); + + public static readonly RoutedEvent CuttingToClipboardEvent = + RoutedEvent.Register( + "CuttingToClipboard", RoutingStrategies.Bubble); + + public static readonly RoutedEvent PastingFromClipboardEvent = + RoutedEvent.Register( + "PastingFromClipboard", RoutingStrategies.Bubble); + readonly struct UndoRedoState : IEquatable { public string Text { get; } @@ -500,6 +512,24 @@ namespace Avalonia.Controls } } + public event EventHandler CopyingToClipboard + { + add => AddHandler(CopyingToClipboardEvent, value); + remove => RemoveHandler(CopyingToClipboardEvent, value); + } + + public event EventHandler CuttingToClipboard + { + add => AddHandler(CuttingToClipboardEvent, value); + remove => RemoveHandler(CuttingToClipboardEvent, value); + } + + public event EventHandler PastingFromClipboard + { + add => AddHandler(PastingFromClipboardEvent, value); + remove => RemoveHandler(PastingFromClipboardEvent, value); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -638,27 +668,54 @@ namespace Avalonia.Controls public async void Cut() { var text = GetSelection(); - if (text is null) return; + if (string.IsNullOrEmpty(text)) + { + return; + } - SnapshotUndoRedo(); - Copy(); - DeleteSelection(); + var eventArgs = new RoutedEventArgs(CuttingToClipboardEvent); + RaiseEvent(eventArgs); + if (!eventArgs.Handled) + { + SnapshotUndoRedo(); + await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))) + .SetTextAsync(text); + DeleteSelection(); + } } public async void Copy() { var text = GetSelection(); - if (text is null) return; + if (string.IsNullOrEmpty(text)) + { + return; + } - await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))) - .SetTextAsync(text); + var eventArgs = new RoutedEventArgs(CopyingToClipboardEvent); + RaiseEvent(eventArgs); + if (!eventArgs.Handled) + { + await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))) + .SetTextAsync(text); + } } public async void Paste() { + var eventArgs = new RoutedEventArgs(PastingFromClipboardEvent); + RaiseEvent(eventArgs); + if (eventArgs.Handled) + { + return; + } + var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync(); - if (text is null) return; + if (string.IsNullOrEmpty(text)) + { + return; + } SnapshotUndoRedo(); HandleTextInput(text); diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs index 5c4af68d79..5082265ea6 100644 --- a/src/Avalonia.Input/AccessKeyHandler.cs +++ b/src/Avalonia.Input/AccessKeyHandler.cs @@ -157,10 +157,9 @@ namespace Avalonia.Input _restoreFocusElement?.Focus(); _restoreFocusElement = null; + + e.Handled = true; } - - // We always handle the Alt key. - e.Handled = true; } else if (_altIsDown) { diff --git a/src/Avalonia.Input/Navigation/TabNavigation.cs b/src/Avalonia.Input/Navigation/TabNavigation.cs index ed7df67bf2..c8290cb3b7 100644 --- a/src/Avalonia.Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Input/Navigation/TabNavigation.cs @@ -234,7 +234,7 @@ namespace Avalonia.Input.Navigation // Return the first visible element. var uiElement = e as InputElement; - if (uiElement is null || uiElement.IsVisible) + if (uiElement is null || IsVisibleAndEnabled(uiElement)) { if (e is IVisual elementAsVisual) { @@ -245,7 +245,7 @@ namespace Avalonia.Input.Navigation { if (children[i] is InputElement ie) { - if (ie.IsVisible) + if (IsVisibleAndEnabled(ie)) return ie; else { @@ -270,7 +270,7 @@ namespace Avalonia.Input.Navigation // Return the last visible element. var uiElement = e as InputElement; - if (uiElement == null || uiElement.IsVisible) + if (uiElement == null || IsVisibleAndEnabled(uiElement)) { var elementAsVisual = e as IVisual; @@ -283,7 +283,7 @@ namespace Avalonia.Input.Navigation { if (children[i] is InputElement ie) { - if (ie.IsVisible) + if (IsVisibleAndEnabled(ie)) return ie; else { @@ -600,7 +600,7 @@ namespace Avalonia.Input.Navigation var vchild = children[i]; if (vchild == elementAsVisual) break; - if (vchild.IsVisible == true && vchild is IInputElement ie) + if (vchild is IInputElement ie && IsVisibleAndEnabled(ie)) prev = ie; } return prev; @@ -668,5 +668,6 @@ namespace Avalonia.Input.Navigation } private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e); + private static bool IsVisibleAndEnabled(IInputElement e) => e.IsVisible && e.IsEnabled; } } diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index cda95d2ebb..8533e1e176 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 589f85f208..0597e88ed6 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -1,4 +1,5 @@ using Avalonia.Platform; +using Avalonia.Win32; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -21,7 +22,7 @@ namespace Avalonia.Direct2D1 protected override Size2F GetWindowDpi() { - if (UnmanagedMethods.ShCoreAvailable) + if (UnmanagedMethods.ShCoreAvailable && Win32Platform.WindowsVersion > PlatformConstants.Windows8) { uint dpix, dpiy; diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs index 5b04c5d7ff..bc0a399d7f 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -150,6 +150,7 @@ namespace Avalonia.Win32.Interop.Wpf if (_image == null || _oldDpi.X != dpi.X || _oldDpi.Y != dpi.Y) { _image = new D3DImage(dpi.X, dpi.Y); + _oldDpi = dpi; } _impl.ImageSource = _image; diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index 699dc7c25d..0240ac9701 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -87,7 +87,7 @@ namespace Avalonia.Win32 private Vector GetCurrentDpi() { - if (UnmanagedMethods.ShCoreAvailable) + if (UnmanagedMethods.ShCoreAvailable && Win32Platform.WindowsVersion > PlatformConstants.Windows8) { var monitor = UnmanagedMethods.MonitorFromWindow(_hwnd, UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); diff --git a/src/Windows/Avalonia.Win32/PlatformConstants.cs b/src/Windows/Avalonia.Win32/PlatformConstants.cs index af1eca29be..9dd4780637 100644 --- a/src/Windows/Avalonia.Win32/PlatformConstants.cs +++ b/src/Windows/Avalonia.Win32/PlatformConstants.cs @@ -1,8 +1,13 @@ +using System; + namespace Avalonia.Win32 { - static class PlatformConstants + public static class PlatformConstants { public const string WindowHandleType = "HWND"; public const string CursorHandleType = "HCURSOR"; + + public static readonly Version Windows8 = new Version(6, 2); + public static readonly Version Windows7 = new Version(6, 1); } } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index e159ba3b78..e70ea52106 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -1,4 +1,3 @@ -using System; using Avalonia.OpenGL; using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; @@ -9,7 +8,6 @@ namespace Avalonia.Win32 { static class Win32GlManager { - private static readonly Version Windows7 = new Version(6, 1); public static void Initialize() { @@ -22,7 +20,7 @@ namespace Avalonia.Win32 return wgl; } - if (opts?.AllowEglInitialization ?? Win32Platform.WindowsVersion > Windows7) + if (opts?.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7) { var egl = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index a881c45cd0..c011a458c3 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -94,7 +94,7 @@ namespace Avalonia namespace Avalonia.Win32 { - class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl + public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0b7bd13082..3b7d3efa2f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -764,8 +764,8 @@ namespace Avalonia.Win32 RegisterTouchWindow(_hwnd, 0); } - if (ShCoreAvailable) - { + if (ShCoreAvailable && Win32Platform.WindowsVersion > PlatformConstants.Windows8) + { var monitor = MonitorFromWindow( _hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index b346fca330..c8bd289e54 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -105,6 +105,16 @@ namespace Avalonia.Controls.UnitTests }); } + [Fact] + public void Custom_FilterMode_Without_ItemFilter_Setting_Throws_Exception() + { + RunTest((control, textbox) => + { + control.FilterMode = AutoCompleteFilterMode.Custom; + Assert.Throws(() => { control.Text = "a"; }); + }); + } + [Fact] public void Text_Completion_Via_Text_Property() { diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 2a3db9992c..b0eb694944 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -10,7 +10,6 @@ using Avalonia.Styling; using Avalonia.UnitTests; using Moq; using Xunit; -using System; using Avalonia.Input.Raw; using Factory = System.Func, Avalonia.Controls.Window, Avalonia.AvaloniaObject>; diff --git a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs index 7730cee78c..c354dbe72e 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs @@ -2,7 +2,6 @@ using System.Windows.Input; using Avalonia.Controls; using Avalonia.Input.Raw; -using Avalonia.Interactivity; using Avalonia.UnitTests; using Moq; using Xunit; @@ -126,7 +125,7 @@ namespace Avalonia.Input.UnitTests { private readonly Action _action; public DelegateCommand(Action action) => _action = action; - public event EventHandler CanExecuteChanged; + public event EventHandler CanExecuteChanged { add { } remove { } } public bool CanExecute(object parameter) => true; public void Execute(object parameter) => _action(); } diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs index edcbf75a1d..9a117bb71d 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs @@ -1225,5 +1225,32 @@ namespace Avalonia.Input.UnitTests "Button2", "Button3", "Button5", "Button1", "Button6", "Button4" }, result); } + + [Fact] + public void Cannot_Focus_Child_Of_Disabled_Control() + { + Button start; + Button expected; + + var top = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = + { + (start = new Button { Name = "Button1" }), + new Border + { + IsEnabled = false, + Child = new Button { Name = "Button2" }, + }, + (expected = new Button { Name = "Button3" }), + } + }; + + var current = (IInputElement)start; + var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next); + + Assert.Same(expected, result); + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index e7f0230254..592dbfc0d1 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -923,7 +923,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions public bool HasResources => true; public List RequestedResources { get; } = new List(); - public event EventHandler OwnerChanged; + public event EventHandler OwnerChanged { add { } remove { } } public void AddOwner(IResourceHost owner) => Owner = owner; public void RemoveOwner(IResourceHost owner) => Owner = null;