From e42bccd79b68dfad5aea0a7c5b046a96cf71a283 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 29 May 2021 11:47:37 +0200 Subject: [PATCH 01/73] Added TabIndex property. And also added `IsTabStop` property to `InputElement`. --- src/Avalonia.Input/InputElement.cs | 31 +++++++++++++++++++++++ src/Avalonia.Input/KeyboardNavigation.cs | 32 ++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 65b9acae76..63080e74e4 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -71,6 +71,12 @@ namespace Avalonia.Input public static readonly DirectProperty IsPointerOverProperty = AvaloniaProperty.RegisterDirect(nameof(IsPointerOver), o => o.IsPointerOver); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsTabStopProperty = + KeyboardNavigation.IsTabStopProperty.AddOwner(); + /// /// Defines the event. /// @@ -99,6 +105,12 @@ namespace Avalonia.Input "KeyUp", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + /// + /// Defines the property. + /// + public static readonly StyledProperty TabIndexProperty = + KeyboardNavigation.TabIndexProperty.AddOwner(); + /// /// Defines the event. /// @@ -426,6 +438,15 @@ namespace Avalonia.Input internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); } } + /// + /// Gets or sets a value that indicates whether the control is included in tab navigation. + /// + public bool IsTabStop + { + get => GetValue(IsTabStopProperty); + set => SetValue(IsTabStopProperty, value); + } + /// public bool IsEffectivelyEnabled { @@ -437,6 +458,16 @@ namespace Avalonia.Input } } + /// + /// Gets or sets a value that determines the order in which elements receive focus when the + /// user navigates through controls by pressing the Tab key. + /// + public int TabIndex + { + get => GetValue(TabIndexProperty); + set => SetValue(TabIndexProperty, value); + } + public List KeyBindings { get; } = new List(); /// diff --git a/src/Avalonia.Input/KeyboardNavigation.cs b/src/Avalonia.Input/KeyboardNavigation.cs index 6ef3c4fd60..ffccd8f7a0 100644 --- a/src/Avalonia.Input/KeyboardNavigation.cs +++ b/src/Avalonia.Input/KeyboardNavigation.cs @@ -5,6 +5,14 @@ namespace Avalonia.Input /// public static class KeyboardNavigation { + /// + /// Defines the TabIndex attached property. + /// + public static readonly AttachedProperty TabIndexProperty = + AvaloniaProperty.RegisterAttached( + "TabIndex", + typeof(KeyboardNavigation)); + /// /// Defines the TabNavigation attached property. /// @@ -42,6 +50,26 @@ namespace Avalonia.Input typeof(KeyboardNavigation), true); + /// + /// Gets the for an element. + /// + /// The container. + /// The for the container. + public static int GetTabIndex(IInputElement element) + { + return ((IAvaloniaObject)element).GetValue(TabIndexProperty); + } + + /// + /// Sets the for an element. + /// + /// The element. + /// The tab index. + public static void SetTabIndex(IInputElement element, int value) + { + ((IAvaloniaObject)element).SetValue(TabIndexProperty, value); + } + /// /// Gets the for a container. /// @@ -83,7 +111,7 @@ namespace Avalonia.Input } /// - /// Sets the for a container. + /// Sets the for an element. /// /// The container. /// Value indicating whether the container is a tab stop. @@ -93,7 +121,7 @@ namespace Avalonia.Input } /// - /// Gets the for a container. + /// Gets the for an element. /// /// The container. /// Whether the container is a tab stop. From 4684237331021c30a021255f92050f44d9881ea7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 29 May 2021 22:00:58 +0200 Subject: [PATCH 02/73] Ported tab navigation code from WPF. --- src/Avalonia.Input/FocusManager.cs | 13 + src/Avalonia.Input/KeyboardNavigation.cs | 3 +- .../KeyboardNavigationHandler.cs | 20 +- .../Navigation/TabNavigation.cs | 709 +++++++++++++----- .../KeyboardNavigationTests_Tab.cs | 29 + 5 files changed, 589 insertions(+), 185 deletions(-) diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs index 474b212a21..1432092ba1 100644 --- a/src/Avalonia.Input/FocusManager.cs +++ b/src/Avalonia.Input/FocusManager.cs @@ -92,6 +92,17 @@ namespace Avalonia.Input } } + public IInputElement? GetFocusedElement(IInputElement e) + { + if (e is IFocusScope scope) + { + _focusScopes.TryGetValue(scope, out var result); + return result; + } + + return null; + } + /// /// Sets the currently focused element in the specified scope. /// @@ -151,6 +162,8 @@ namespace Avalonia.Input Focus(e); } + public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope; + /// /// Checks if the specified element can be focused. /// diff --git a/src/Avalonia.Input/KeyboardNavigation.cs b/src/Avalonia.Input/KeyboardNavigation.cs index ffccd8f7a0..a25aed6811 100644 --- a/src/Avalonia.Input/KeyboardNavigation.cs +++ b/src/Avalonia.Input/KeyboardNavigation.cs @@ -11,7 +11,8 @@ namespace Avalonia.Input public static readonly AttachedProperty TabIndexProperty = AvaloniaProperty.RegisterAttached( "TabIndex", - typeof(KeyboardNavigation)); + typeof(KeyboardNavigation), + int.MaxValue); /// /// Defines the TabNavigation attached property. diff --git a/src/Avalonia.Input/KeyboardNavigationHandler.cs b/src/Avalonia.Input/KeyboardNavigationHandler.cs index dbefe63789..53c9b008ff 100644 --- a/src/Avalonia.Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Input/KeyboardNavigationHandler.cs @@ -64,7 +64,13 @@ namespace Avalonia.Input } else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) { - return TabNavigation.GetNextInTabOrder((IInputElement)customHandler, direction, true); + var e = (IInputElement)customHandler; + return direction switch + { + NavigationDirection.Next => TabNavigation.GetNextTab(e, false), + NavigationDirection.Previous => TabNavigation.GetPrevTab(e, null, false), + _ => throw new NotSupportedException(), + }; } else { @@ -73,14 +79,12 @@ namespace Avalonia.Input } } - if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) + return direction switch { - return TabNavigation.GetNextInTabOrder(element, direction); - } - else - { - throw new NotSupportedException(); - } + NavigationDirection.Next => TabNavigation.GetNextTab(element, false), + NavigationDirection.Previous => TabNavigation.GetPrevTab(element, null, false), + _ => throw new NotSupportedException(), + }; } /// diff --git a/src/Avalonia.Input/Navigation/TabNavigation.cs b/src/Avalonia.Input/Navigation/TabNavigation.cs index 6f6d68940b..d1862c2fa5 100644 --- a/src/Avalonia.Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Input/Navigation/TabNavigation.cs @@ -10,277 +10,634 @@ namespace Avalonia.Input.Navigation /// internal static class TabNavigation { - /// - /// Gets the next control in the specified tab direction. - /// - /// The element. - /// The tab direction. Must be Next or Previous. - /// - /// If true will not descend into to find next control. - /// - /// - /// The next element in the specified direction, or null if - /// was the last in the requested direction. - /// - public static IInputElement? GetNextInTabOrder( - IInputElement element, - NavigationDirection direction, - bool outsideElement = false) + public static IInputElement? GetNextTab(IInputElement e, bool goDownOnly) { - element = element ?? throw new ArgumentNullException(nameof(element)); - - if (direction != NavigationDirection.Next && direction != NavigationDirection.Previous) - { - throw new ArgumentException("Invalid direction: must be Next or Previous."); - } + return GetNextTab(e, GetGroupParent(e), goDownOnly); + } - var container = element.GetVisualParent(); + public static IInputElement? GetNextTab(IInputElement? e, IInputElement container, bool goDownOnly) + { + var tabbingType = GetKeyNavigationMode(container); - if (container != null) + if (e == null) { - var mode = KeyboardNavigation.GetTabNavigation((InputElement)container); + if (IsTabStop(container)) + return container; - switch (mode) + // Using ActiveElement if set + var activeElement = GetActiveElement(container); + if (activeElement != null) + return GetNextTab(null, activeElement, true); + } + else + { + if (tabbingType == KeyboardNavigationMode.Once || tabbingType == KeyboardNavigationMode.None) { - case KeyboardNavigationMode.Continue: - return GetNextInContainer(element, container, direction, outsideElement) ?? - GetFirstInNextContainer(element, element, direction); - case KeyboardNavigationMode.Cycle: - return GetNextInContainer(element, container, direction, outsideElement) ?? - GetFocusableDescendant(container, direction); - case KeyboardNavigationMode.Contained: - return GetNextInContainer(element, container, direction, outsideElement); - default: - return GetFirstInNextContainer(element, container, direction); + if (container != e) + { + if (goDownOnly) + return null; + var parentContainer = GetGroupParent(container); + return GetNextTab(container, parentContainer, goDownOnly); + } } } - else + + // All groups + IInputElement? loopStartElement = null; + var nextTabElement = e; + var currentTabbingType = tabbingType; + + // Search down inside the container + while ((nextTabElement = GetNextTabInGroup(nextTabElement, container, currentTabbingType)) != null) { - return GetFocusableDescendants(element, direction).FirstOrDefault(); + // Avoid the endless loop here for Cycle groups + if (loopStartElement == nextTabElement) + break; + if (loopStartElement == null) + loopStartElement = nextTabElement; + + var firstTabElementInside = GetNextTab(null, nextTabElement, true); + if (firstTabElementInside != null) + return firstTabElementInside; + + // If we want to continue searching inside the Once groups, we should change the navigation mode + if (currentTabbingType == KeyboardNavigationMode.Once) + currentTabbingType = KeyboardNavigationMode.Contained; } - } - /// - /// Gets the first or last focusable descendant of the specified element. - /// - /// The element. - /// The direction to search. - /// The element or null if not found.## - private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction) - { - return direction == NavigationDirection.Next ? - GetFocusableDescendants(container, direction).FirstOrDefault() : - GetFocusableDescendants(container, direction).LastOrDefault(); + // If there is no next element in the group (nextTabElement == null) + + // Search up in the tree if allowed + // consider: Use original tabbingType instead of currentTabbingType + if (!goDownOnly && currentTabbingType != KeyboardNavigationMode.Contained && GetParent(container) != null) + { + return GetNextTab(container, GetGroupParent(container), false); + } + + return null; } - /// - /// Gets the focusable descendants of the specified element. - /// - /// The element. - /// The tab direction. Must be Next or Previous. - /// The element's focusable descendants. - private static IEnumerable GetFocusableDescendants(IInputElement element, - NavigationDirection direction) + public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly) { - var mode = KeyboardNavigation.GetTabNavigation((InputElement)element); + if (e is null && container is null) + throw new InvalidOperationException("Either 'e' or 'container' must be non-null."); - if (mode == KeyboardNavigationMode.None) - { - yield break; - } + if (container is null) + container = GetGroupParent(e!); - var children = element.GetVisualChildren().OfType(); + KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container); - if (mode == KeyboardNavigationMode.Once) + if (e == null) { - var active = KeyboardNavigation.GetTabOnceActiveElement((InputElement)element); - - if (active != null) + // Using ActiveElement if set + var activeElement = GetActiveElement(container); + if (activeElement != null) + return GetPrevTab(null, activeElement, true); + else { - yield return active; - yield break; + // If we Shift+Tab on a container with KeyboardNavigationMode=Once, and ActiveElement is null + // then we want to go to the first item (not last) within the container + if (tabbingType == KeyboardNavigationMode.Once) + { + var firstTabElement = GetNextTabInGroup(null, container, tabbingType); + if (firstTabElement == null) + { + if (IsTabStop(container)) + return container; + if (goDownOnly) + return null; + + return GetPrevTab(container, null, false); + } + else + { + return GetPrevTab(null, firstTabElement, true); + } + } } - else + } + else + { + if (tabbingType == KeyboardNavigationMode.Once || tabbingType == KeyboardNavigationMode.None) { - children = children.Take(1); + if (goDownOnly || container == e) + return null; + + // FocusedElement should not be e otherwise we will delegate focus to the same element + if (IsTabStop(container)) + return container; + + return GetPrevTab(container, null, false); } } - foreach (var child in children) + // All groups (except Once) - continue + IInputElement? loopStartElement = null; + IInputElement? nextTabElement = e; + + // Look for element with the same TabIndex before the current element + while ((nextTabElement = GetPrevTabInGroup(nextTabElement, container, tabbingType)) != null) { - var customNext = GetCustomNext(child, direction); + // At this point nextTabElement is TabStop or TabGroup + // In case it is a TabStop only return the element + if (IsTabStop(nextTabElement) && !IsGroup(nextTabElement)) + return nextTabElement; + + // Avoid the endless loop here + if (loopStartElement == nextTabElement) + break; + if (loopStartElement == null) + loopStartElement = nextTabElement; + + // At this point nextTabElement is TabGroup + var lastTabElementInside = GetPrevTab(null, nextTabElement, true); + if (lastTabElementInside != null) + return lastTabElementInside; + } - if (customNext.handled) - { - yield return customNext.next!; - } - else + if (tabbingType == KeyboardNavigationMode.Contained) + return null; + + if (e != container && IsTabStop(container)) + return container; + + // If end of the subtree is reached or there no other elements above + if (!goDownOnly && GetParent(container) != null) + { + return GetPrevTab(container, null, false); + } + + return null; + } + + private static IInputElement? FocusedElement(IInputElement e) + { + var iie = e; + // Focus delegation is enabled only if keyboard focus is outside the container + if (iie != null && !iie.IsKeyboardFocusWithin) + { + var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e); + if (focusedElement != null) { - if (child.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement)child)) + if (!IsFocusScope(e)) { - yield return child; + // Verify if focusedElement is a visual descendant of e + if (focusedElement is IVisual visualFocusedElement && + visualFocusedElement != e && + e.IsVisualAncestorOf(visualFocusedElement)) + { + return focusedElement; + } } + } + } - if (child.CanFocusDescendants()) + return null; + } + + private static IInputElement? GetFirstChild(IInputElement e) + { + // If the element has a FocusedElement it should be its first child + if (FocusedElement(e) is IInputElement focusedElement) + return focusedElement; + + // Return the first visible element. + var uiElement = e as InputElement; + + if (uiElement is null || uiElement.IsVisible) + { + if (e is IVisual elementAsVisual) + { + var children = elementAsVisual.VisualChildren; + var count = children.Count; + + for (int i = 0; i < count; i++) { - foreach (var descendant in GetFocusableDescendants(child, direction)) + if (children[i] is InputElement ie) { - if (KeyboardNavigation.GetIsTabStop((InputElement)descendant)) + if (ie.IsVisible) + return ie; + else { - yield return descendant; + var firstChild = GetFirstChild(ie); + if (firstChild != null) + return firstChild; } } } } } + + return null; } - /// - /// Gets the next item that should be focused in the specified container. - /// - /// The starting element/ - /// The container. - /// The direction. - /// - /// If true will not descend into to find next control. - /// - /// The next element, or null if the element is the last. - private static IInputElement? GetNextInContainer( - IInputElement element, - IInputElement container, - NavigationDirection direction, - bool outsideElement) + private static IInputElement? GetLastChild(IInputElement e) { - IInputElement? e = element; - - if (direction == NavigationDirection.Next && !outsideElement) - { - var descendant = GetFocusableDescendants(element, direction).FirstOrDefault(); + // If the element has a FocusedElement it should be its last child + if (FocusedElement(e) is IInputElement focusedElement) + return focusedElement; - if (descendant != null) - { - return descendant; - } - } + // Return the last visible element. + var uiElement = e as InputElement; - if (container != null) + if (uiElement == null || uiElement.IsVisible) { - var navigable = container as INavigableContainer; + var elementAsVisual = e as IVisual; - // TODO: Do a spatial search here if the container doesn't implement - // INavigableContainer. - if (navigable != null) + if (elementAsVisual != null) { - while (e != null) - { - e = navigable.GetControl(direction, e, false); + var children = elementAsVisual.VisualChildren; + var count = children.Count; - if (e != null && - e.CanFocus() && - KeyboardNavigation.GetIsTabStop((InputElement)e)) + for (int i = count - 1; i >= 0; i--) + { + if (children[i] is InputElement ie) { - break; + if (ie.IsVisible) + return ie; + else + { + var lastChild = GetLastChild(ie); + if (lastChild != null) + return lastChild; + } } } } - else + } + + return null; + } + + private static IInputElement? GetFirstTabInGroup(IInputElement container) + { + IInputElement? firstTabElement = null; + int minIndexFirstTab = int.MinValue; + + var currElement = container; + while ((currElement = GetNextInTree(currElement, container)) != null) + { + if (IsTabStopOrGroup(currElement)) { - // TODO: Do a spatial search here if the container doesn't implement - // INavigableContainer. - e = null; + int currPriority = KeyboardNavigation.GetTabIndex(currElement); + + if (currPriority < minIndexFirstTab || firstTabElement == null) + { + minIndexFirstTab = currPriority; + firstTabElement = currElement; + } } + } + return firstTabElement; + } + + private static IInputElement? GetLastInTree(IInputElement container) + { + IInputElement? result; + IInputElement? c = container; + + do + { + result = c; + c = GetLastChild(c); + } while (c != null && !IsGroup(c)); + + if (c != null) + return c; + + return result; + } - if (e != null && direction == NavigationDirection.Previous) + private static IInputElement? GetLastTabInGroup(IInputElement container) + { + IInputElement? lastTabElement = null; + int maxIndexFirstTab = int.MaxValue; + var currElement = GetLastInTree(container); + while (currElement != null && currElement != container) + { + if (IsTabStopOrGroup(currElement)) { - var descendant = GetFocusableDescendants(e, direction).LastOrDefault(); + int currPriority = KeyboardNavigation.GetTabIndex(currElement); - if (descendant != null) + if (currPriority > maxIndexFirstTab || lastTabElement == null) { - return descendant; + maxIndexFirstTab = currPriority; + lastTabElement = currElement; } } + currElement = GetPreviousInTree(currElement, container); + } + return lastTabElement; + } + + private static IInputElement? GetNextInTree(IInputElement e, IInputElement container) + { + IInputElement? result = null; - return e; + if (e == container || !IsGroup(e)) + result = GetFirstChild(e); + + if (result != null || e == container) + return result; + + IInputElement? parent = e; + do + { + var sibling = GetNextSibling(parent); + if (sibling != null) + return sibling; + + parent = GetParent(parent); + } while (parent != null && parent != container); + + return null; + } + + private static IInputElement? GetNextSibling(IInputElement e) + { + if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual) + { + var children = parentAsVisual.VisualChildren; + var count = children.Count; + var i = 0; + + //go till itself + for (; i < count; i++) + { + var vchild = children[i]; + if (vchild == elementAsVisual) + break; + } + i++; + //search ahead + for (; i < count; i++) + { + var visual = children[i]; + if (visual is IInputElement ie) + return ie; + } } return null; } - /// - /// Gets the first item that should be focused in the next container. - /// - /// The element being navigated away from. - /// The container. - /// The direction of the search. - /// The first element, or null if there are no more elements. - private static IInputElement? GetFirstInNextContainer( - IInputElement element, - IInputElement container, - NavigationDirection direction) + private static IInputElement? GetNextTabInGroup(IInputElement? e, IInputElement container, KeyboardNavigationMode tabbingType) { - var parent = container.GetVisualParent(); - IInputElement? next = null; + // None groups: Tab navigation is not supported + if (tabbingType == KeyboardNavigationMode.None) + return null; + + // e == null or e == container -> return the first TabStopOrGroup + if (e == null || e == container) + { + return GetFirstTabInGroup(container); + } + + if (tabbingType == KeyboardNavigationMode.Once) + return null; + + var nextTabElement = GetNextTabWithSameIndex(e, container); + if (nextTabElement != null) + return nextTabElement; + + return GetNextTabWithNextIndex(e, container, tabbingType); + } - if (parent != null) + private static IInputElement? GetNextTabWithSameIndex(IInputElement e, IInputElement container) + { + var elementTabPriority = KeyboardNavigation.GetTabIndex(e); + var currElement = e; + while ((currElement = GetNextInTree(currElement, container)) != null) { - if (direction == NavigationDirection.Previous && - parent.CanFocus() && - KeyboardNavigation.GetIsTabStop((InputElement) parent)) + if (IsTabStopOrGroup(currElement) && KeyboardNavigation.GetTabIndex(currElement) == elementTabPriority) { - return parent; + return currElement; } + } - var allSiblings = parent.GetVisualChildren() - .OfType() - .Where(FocusExtensions.CanFocusDescendants); - var siblings = direction == NavigationDirection.Next ? - allSiblings.SkipWhile(x => x != container).Skip(1) : - allSiblings.TakeWhile(x => x != container).Reverse(); + return null; + } - foreach (var sibling in siblings) + private static IInputElement? GetNextTabWithNextIndex(IInputElement e, IInputElement container, KeyboardNavigationMode tabbingType) + { + // Find the next min index in the tree + // min (index>currentTabIndex) + IInputElement? nextTabElement = null; + IInputElement? firstTabElement = null; + int minIndexFirstTab = int.MinValue; + int minIndex = int.MinValue; + int elementTabPriority = KeyboardNavigation.GetTabIndex(e); + + IInputElement? currElement = container; + while ((currElement = GetNextInTree(currElement, container)) != null) + { + if (IsTabStopOrGroup(currElement)) { - var customNext = GetCustomNext(sibling, direction); - if (customNext.handled) + int currPriority = KeyboardNavigation.GetTabIndex(currElement); + if (currPriority > elementTabPriority) { - return customNext.next; + if (currPriority < minIndex || nextTabElement == null) + { + minIndex = currPriority; + nextTabElement = currElement; + } } - if (sibling.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement) sibling)) + if (currPriority < minIndexFirstTab || firstTabElement == null) { - return sibling; + minIndexFirstTab = currPriority; + firstTabElement = currElement; } + } + } + + // Cycle groups: if not found - return first element + if (tabbingType == KeyboardNavigationMode.Cycle && nextTabElement == null) + nextTabElement = firstTabElement; - next = direction == NavigationDirection.Next ? - GetFocusableDescendants(sibling, direction).FirstOrDefault() : - GetFocusableDescendants(sibling, direction).LastOrDefault(); + return nextTabElement; + } - if (next != null) + private static IInputElement? GetPrevTabInGroup(IInputElement? e, IInputElement container, KeyboardNavigationMode tabbingType) + { + // None groups: Tab navigation is not supported + if (tabbingType == KeyboardNavigationMode.None) + return null; + + // Search the last index inside the group + if (e == null) + { + return GetLastTabInGroup(container); + } + + if (tabbingType == KeyboardNavigationMode.Once) + return null; + + if (e == container) + return null; + + var nextTabElement = GetPrevTabWithSameIndex(e, container); + if (nextTabElement != null) + return nextTabElement; + + return GetPrevTabWithPrevIndex(e, container, tabbingType); + } + + private static IInputElement? GetPrevTabWithSameIndex(IInputElement e, IInputElement container) + { + int elementTabPriority = KeyboardNavigation.GetTabIndex(e); + var currElement = GetPreviousInTree(e, container); + while (currElement != null) + { + if (IsTabStopOrGroup(currElement) && KeyboardNavigation.GetTabIndex(currElement) == elementTabPriority && currElement != container) + { + return currElement; + } + currElement = GetPreviousInTree(currElement, container); + } + return null; + } + + private static IInputElement? GetPrevTabWithPrevIndex(IInputElement e, IInputElement container, KeyboardNavigationMode tabbingType) + { + // Find the next max index in the tree + // max (index maxIndex || nextTabElement == null) + { + maxIndex = currPriority; + nextTabElement = currElement; + } + } + + if (currPriority > maxIndexFirstTab || lastTabElement == null) + { + maxIndexFirstTab = currPriority; + lastTabElement = currElement; } } - next = GetFirstInNextContainer(element, parent, direction); + currElement = GetPreviousInTree(currElement, container); + } + + // Cycle groups: if not found - return first element + if (tabbingType == KeyboardNavigationMode.Cycle && nextTabElement == null) + nextTabElement = lastTabElement; + + return nextTabElement; + } + + private static IInputElement? GetPreviousInTree(IInputElement e, IInputElement container) + { + if (e == container) + return null; + + var result = GetPreviousSibling(e); + + if (result != null) + { + if (IsGroup(result)) + return result; + else + return GetLastInTree(result); } else + return GetParent(e); + } + + private static IInputElement? GetPreviousSibling(IInputElement e) + { + if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual) { - next = direction == NavigationDirection.Next ? - GetFocusableDescendants(container, direction).FirstOrDefault() : - GetFocusableDescendants(container, direction).LastOrDefault(); + var children = parentAsVisual.VisualChildren; + var count = children.Count; + IInputElement? prev = null; + + for (int i = 0; i < count; i++) + { + var vchild = children[i]; + if (vchild == elementAsVisual) + break; + if (vchild.IsVisible == true && vchild is IInputElement ie) + prev = ie; + } + return prev; } + return null; + } - return next; + private static IInputElement? GetActiveElement(IInputElement e) + { + return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabOnceActiveElementProperty); } - private static (bool handled, IInputElement? next) GetCustomNext(IInputElement element, - NavigationDirection direction) + private static IInputElement GetGroupParent(IInputElement e) => GetGroupParent(e, false); + + private static IInputElement GetGroupParent(IInputElement element, bool includeCurrent) { - if (element is ICustomKeyboardNavigation custom) + var result = element; // Keep the last non null element + var e = element; + + // If we don't want to include the current element, + // start at the parent of the element. If the element + // is the root, then just return it as the group parent. + if (!includeCurrent) + { + result = e; + e = GetParent(e); + if (e == null) + return result; + } + + while (e != null) { - return custom.GetNext(element, direction); + if (IsGroup(e)) + return e; + + result = e; + e = GetParent(e); } - return (false, null); + return result; + } + + private static IInputElement? GetParent(IInputElement e) + { + // For Visual - go up the visual parent chain until we find Visual. + if (e is IVisual v) + return v.FindAncestorOfType(); + + // This will need to be implemented when we have non-visual input elements. + throw new NotSupportedException(); + } + + private static KeyboardNavigationMode GetKeyNavigationMode(IInputElement e) + { + return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabNavigationProperty); + } + private static bool IsFocusScope(IInputElement e) => FocusManager.GetIsFocusScope(e) || GetParent(e) == null; + private static bool IsGroup(IInputElement e) => GetKeyNavigationMode(e) != KeyboardNavigationMode.Continue; + + private static bool IsTabStop(IInputElement e) + { + if (e is InputElement ie) + return ie.Focusable && KeyboardNavigation.GetIsTabStop(ie) && ie.IsVisible && ie.IsEnabled; + return false; } + + private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e); } } diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs index 1efbbed2e8..f471ae4029 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs @@ -13,6 +13,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -49,6 +50,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -85,6 +87,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -122,6 +125,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -165,6 +169,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -193,6 +198,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -222,6 +228,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -263,6 +270,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { (next = new Button { Name = "Button1" }), @@ -282,6 +290,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -324,6 +333,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -361,6 +371,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -398,6 +409,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -434,6 +446,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -471,6 +484,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -509,6 +523,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { (container = new StackPanel @@ -548,6 +563,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -586,6 +602,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { (container = new StackPanel @@ -625,6 +642,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -661,6 +679,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -697,6 +716,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -725,6 +745,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -767,6 +788,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -828,6 +850,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -865,6 +888,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -902,6 +926,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -938,6 +963,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -975,6 +1001,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel @@ -1013,6 +1040,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { (container = new StackPanel @@ -1052,6 +1080,7 @@ namespace Avalonia.Input.UnitTests var top = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { new StackPanel From ec5131831507b7d8f8b9cd55a218d647caea6378 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 29 May 2021 23:53:09 +0200 Subject: [PATCH 03/73] Make custom keyboard navigation work again. --- src/Avalonia.Input/Avalonia.Input.csproj | 3 + .../ICustomKeyboardNavigation.cs | 17 ++- .../KeyboardNavigationHandler.cs | 111 +++++++++++++----- .../Navigation/TabNavigation.cs | 26 ++++ .../KeyboardNavigationTests_Custom.cs | 33 ++++++ tests/Avalonia.UnitTests/TestRoot.cs | 1 + 6 files changed, 157 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Input/Avalonia.Input.csproj b/src/Avalonia.Input/Avalonia.Input.csproj index c39c81a965..69a80290d1 100644 --- a/src/Avalonia.Input/Avalonia.Input.csproj +++ b/src/Avalonia.Input/Avalonia.Input.csproj @@ -4,6 +4,9 @@ Enable CS8600;CS8602;CS8603 + + + diff --git a/src/Avalonia.Input/ICustomKeyboardNavigation.cs b/src/Avalonia.Input/ICustomKeyboardNavigation.cs index 3d2927c632..357395c42f 100644 --- a/src/Avalonia.Input/ICustomKeyboardNavigation.cs +++ b/src/Avalonia.Input/ICustomKeyboardNavigation.cs @@ -1,4 +1,5 @@ - +#nullable enable + namespace Avalonia.Input { /// @@ -6,6 +7,18 @@ namespace Avalonia.Input /// public interface ICustomKeyboardNavigation { - (bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction); + /// + /// Gets the next element in the specified navigation direction. + /// + /// The element being navigated from. + /// The navigation direction. + /// + /// A tuple consisting of: + /// - A boolean indicating whether the request was handled. If false is returned then + /// custom navigation will be ignored and default navigation will take place. + /// - If handled is true: the next element in the navigation direction, or null if default + /// navigation should continue outside the element. + /// + (bool handled, IInputElement? next) GetNext(IInputElement element, NavigationDirection direction); } } diff --git a/src/Avalonia.Input/KeyboardNavigationHandler.cs b/src/Avalonia.Input/KeyboardNavigationHandler.cs index 53c9b008ff..6493777105 100644 --- a/src/Avalonia.Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Input/KeyboardNavigationHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Avalonia.Input.Navigation; using Avalonia.VisualTree; @@ -48,43 +49,24 @@ namespace Avalonia.Input { element = element ?? throw new ArgumentNullException(nameof(element)); - var customHandler = element.GetSelfAndVisualAncestors() - .OfType() - .FirstOrDefault(); + // If there's a custom keyboard navigation handler as an ancestor, use that. + var custom = element.FindAncestorOfType(true); + if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce)) + return ce; - if (customHandler != null) - { - var (handled, next) = customHandler.GetNext(element, direction); - - if (handled) - { - if (next != null) - { - return next; - } - else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) - { - var e = (IInputElement)customHandler; - return direction switch - { - NavigationDirection.Next => TabNavigation.GetNextTab(e, false), - NavigationDirection.Previous => TabNavigation.GetPrevTab(e, null, false), - _ => throw new NotSupportedException(), - }; - } - else - { - return null; - } - } - } - - return direction switch + var result = direction switch { NavigationDirection.Next => TabNavigation.GetNextTab(element, false), NavigationDirection.Previous => TabNavigation.GetPrevTab(element, null, false), _ => throw new NotSupportedException(), }; + + // If there wasn't a custom navigation handler as an ancestor of the current element, + // but there is one as an ancestor of the new element, use that. + if (custom is null && HandlePostCustomNavigation(element, result, direction, out ce)) + return ce; + + return result; } /// @@ -94,7 +76,7 @@ namespace Avalonia.Input /// The direction to move. /// Any key modifiers active at the time of focus. public void Move( - IInputElement element, + IInputElement element, NavigationDirection direction, KeyModifiers keyModifiers = KeyModifiers.None) { @@ -128,5 +110,70 @@ namespace Avalonia.Input e.Handled = true; } } + + private static bool HandlePreCustomNavigation( + ICustomKeyboardNavigation customHandler, + IInputElement element, + NavigationDirection direction, + [NotNullWhen(true)] out IInputElement? result) + { + if (customHandler != null) + { + var (handled, next) = customHandler.GetNext(element, direction); + + if (handled) + { + if (next != null) + { + result = next; + return true; + } + else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) + { + var r = direction switch + { + NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler), + NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler), + _ => throw new NotSupportedException(), + }; + + if (r is object) + { + result = r; + return true; + } + } + } + } + + result = null; + return false; + } + + private static bool HandlePostCustomNavigation( + IInputElement element, + IInputElement? newElement, + NavigationDirection direction, + [NotNullWhen(true)] out IInputElement? result) + { + if (newElement is object) + { + var customHandler = newElement.FindAncestorOfType(true); + + if (customHandler is object) + { + var (handled, next) = customHandler.GetNext(element, direction); + + if (handled && next is object) + { + result = next; + return true; + } + } + } + + result = null; + return false; + } } } diff --git a/src/Avalonia.Input/Navigation/TabNavigation.cs b/src/Avalonia.Input/Navigation/TabNavigation.cs index d1862c2fa5..12842e4f40 100644 --- a/src/Avalonia.Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Input/Navigation/TabNavigation.cs @@ -78,6 +78,19 @@ namespace Avalonia.Input.Navigation return null; } + public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e) + { + if (e is IInputElement container) + { + var last = GetLastInTree(container); + + if (last is object) + return GetNextTab(last, false); + } + + return null; + } + public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly) { if (e is null && container is null) @@ -171,6 +184,19 @@ namespace Avalonia.Input.Navigation return null; } + public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e) + { + if (e is IInputElement container) + { + var first = GetFirstChild(container); + + if (first is object) + return GetPrevTab(first, null, false); + } + + return null; + } + private static IInputElement? FocusedElement(IInputElement e) { var iie = e; diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs index f72d6ba9c9..f9c85ee4ca 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs @@ -95,6 +95,7 @@ namespace Avalonia.Input.UnitTests var root = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { target, @@ -125,6 +126,7 @@ namespace Avalonia.Input.UnitTests var root = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { (current = new Button { Content = "Outside" }), @@ -137,6 +139,36 @@ namespace Avalonia.Input.UnitTests Assert.Same(next, result); } + [Fact] + public void ShiftTab_Should_Navigate_Outside_When_Null_Returned_As_Next() + { + Button current; + Button next; + var target = new CustomNavigatingStackPanel + { + Children = + { + new Button { Content = "Button 1" }, + (current = new Button { Content = "Button 2" }), + new Button { Content = "Button 3" }, + }, + }; + + var root = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = + { + target, + (next = new Button { Content = "Outside" }), + } + }; + + var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Previous); + + Assert.Same(next, result); + } + [Fact] public void Tab_Should_Navigate_Outside_When_Null_Returned_As_Next() { @@ -154,6 +186,7 @@ namespace Avalonia.Input.UnitTests var root = new StackPanel { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, Children = { target, diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index b69bf990d9..4601dd7e5b 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -21,6 +21,7 @@ namespace Avalonia.UnitTests Renderer = Mock.Of(); LayoutManager = new LayoutManager(this); IsVisible = true; + KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); } public TestRoot(IControl child) From 90ddccc54e3d9e3ad0b720c225d42ca6a967d2ad Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 29 May 2021 23:58:01 +0200 Subject: [PATCH 04/73] Toplevels now need to explicitly specify tab cycle mode. --- src/Avalonia.Controls/TopLevel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 7a92836ddf..90d6fb1f63 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -83,6 +83,7 @@ namespace Avalonia.Controls /// static TopLevel() { + KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(KeyboardNavigationMode.Cycle); AffectsMeasure(ClientSizeProperty); TransparencyLevelHintProperty.Changed.AddClassHandler( From 91ef2124349047fa3e150a73071400fe3024a1c7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 30 May 2021 14:19:36 +0200 Subject: [PATCH 05/73] Added a couple of TabIndex tests. --- .../KeyboardNavigationTests_Tab.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs index f471ae4029..edcbf75a1d 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Avalonia.Controls; using Xunit; @@ -1132,5 +1133,97 @@ namespace Avalonia.Input.UnitTests Assert.Null(result); } + + [Fact] + public void Respects_TabIndex_Moving_Forwards() + { + Button start; + + var top = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = + { + new StackPanel + { + Children = + { + new Button { Name = "Button1", TabIndex = 5 }, + (start = new Button { Name = "Button2", TabIndex = 2 }), + new Button { Name = "Button3", TabIndex = 1 }, + } + }, + new StackPanel + { + Children = + { + new Button { Name = "Button4", TabIndex = 3 }, + new Button { Name = "Button5", TabIndex = 6 }, + new Button { Name = "Button6", TabIndex = 4 }, + } + }, + } + }; + + var result = new List(); + var current = (IInputElement)start; + + do + { + result.Add(((IControl)current).Name); + current = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next); + } while (current is object && current != start); + + Assert.Equal(new[] + { + "Button2", "Button4", "Button6", "Button1", "Button5", "Button3" + }, result); + } + + [Fact] + public void Respects_TabIndex_Moving_Backwards() + { + Button start; + + var top = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = + { + new StackPanel + { + Children = + { + new Button { Name = "Button1", TabIndex = 5 }, + (start = new Button { Name = "Button2", TabIndex = 2 }), + new Button { Name = "Button3", TabIndex = 1 }, + } + }, + new StackPanel + { + Children = + { + new Button { Name = "Button4", TabIndex = 3 }, + new Button { Name = "Button5", TabIndex = 6 }, + new Button { Name = "Button6", TabIndex = 4 }, + } + }, + } + }; + + var result = new List(); + var current = (IInputElement)start; + + do + { + result.Add(((IControl)current).Name); + current = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Previous); + } while (current is object && current != start); + + Assert.Equal(new[] + { + "Button2", "Button3", "Button5", "Button1", "Button6", "Button4" + }, result); + } } } From ca4dee94fd2ab4884031072da4a92c9f00a5fcd2 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Tue, 8 Jun 2021 18:00:04 -0400 Subject: [PATCH 06/73] Remove timer from undo helper, improve u/r for textbox --- src/Avalonia.Controls/TextBox.cs | 57 ++++++++++++------- src/Avalonia.Controls/Utils/UndoRedoHelper.cs | 11 +--- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 1bee15bccd..31ec12c22f 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -31,7 +31,7 @@ namespace Avalonia.Controls public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current .GetService()?.Paste.FirstOrDefault(); - + public static readonly StyledProperty AcceptsReturnProperty = AvaloniaProperty.Register(nameof(AcceptsReturn)); @@ -117,7 +117,7 @@ namespace Avalonia.Controls public static readonly StyledProperty RevealPasswordProperty = AvaloniaProperty.Register(nameof(RevealPassword)); - + public static readonly DirectProperty CanCutProperty = AvaloniaProperty.RegisterDirect( nameof(CanCut), @@ -135,7 +135,7 @@ namespace Avalonia.Controls public static readonly StyledProperty IsUndoEnabledProperty = AvaloniaProperty.Register( - nameof(IsUndoEnabled), + nameof(IsUndoEnabled), defaultValue: true); public static readonly DirectProperty UndoLimitProperty = @@ -174,6 +174,10 @@ namespace Avalonia.Controls private string _newLine = Environment.NewLine; private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; + private int _selectedTextChangesMadeSinceLastUndoSnapshot; + private bool _hasDoneSnapshotOnce; + private const int _maxCharsBeforeUndoSnapshot = 7; + static TextBox() { FocusableProperty.OverrideDefaultValue(typeof(TextBox), true); @@ -202,7 +206,8 @@ namespace Avalonia.Controls horizontalScrollBarVisibility, BindingPriority.Style); _undoRedoHelper = new UndoRedoHelper(this); - + _selectedTextChangesMadeSinceLastUndoSnapshot = 0; + _hasDoneSnapshotOnce = false; UpdatePseudoclasses(); } @@ -331,6 +336,7 @@ namespace Avalonia.Controls if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) { _undoRedoHelper.Clear(); + SnapshotUndoRedo(); // so we always have an initial state } } } @@ -341,7 +347,6 @@ namespace Avalonia.Controls get { return GetSelection(); } set { - SnapshotUndoRedo(); if (string.IsNullOrEmpty(value)) { DeleteSelection(); @@ -350,7 +355,6 @@ namespace Avalonia.Controls { HandleTextInput(value); } - SnapshotUndoRedo(); } } @@ -422,7 +426,7 @@ namespace Avalonia.Controls get { return _newLine; } set { SetAndRaise(NewLineProperty, ref _newLine, value); } } - + /// /// Clears the current selection, maintaining the /// @@ -480,11 +484,13 @@ namespace Avalonia.Controls var oldValue = _undoRedoHelper.Limit; _undoRedoHelper.Limit = value; RaisePropertyChanged(UndoLimitProperty, oldValue, value); - } + } // from docs at // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: // "Setting UndoLimit clears the undo queue." _undoRedoHelper.Clear(); + _selectedTextChangesMadeSinceLastUndoSnapshot = 0; + _hasDoneSnapshotOnce = false; } } @@ -515,6 +521,8 @@ namespace Avalonia.Controls // Therefore, if you disable undo and then re-enable it, undo commands still do not work // because the undo stack was emptied when you disabled undo." _undoRedoHelper.Clear(); + _selectedTextChangesMadeSinceLastUndoSnapshot = 0; + _hasDoneSnapshotOnce = false; } } @@ -577,23 +585,25 @@ namespace Avalonia.Controls { return; } - + input = RemoveInvalidCharacters(input); - + if (string.IsNullOrEmpty(input)) { return; } - + _selectedTextChangesMadeSinceLastUndoSnapshot++; + SnapshotUndoRedo(ignoreChangeCount: false); + string text = Text ?? string.Empty; int caretIndex = CaretIndex; int newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd); - + if (MaxLength > 0 && newLength > MaxLength) { input = input.Remove(Math.Max(0, input.Length - (newLength - MaxLength))); } - + if (!string.IsNullOrEmpty(input)) { DeleteSelection(); @@ -696,6 +706,7 @@ namespace Avalonia.Controls { try { + SnapshotUndoRedo(); _isUndoingRedoing = true; _undoRedoHelper.Undo(); } @@ -830,7 +841,6 @@ namespace Avalonia.Controls CaretIndex -= removedCharacters; ClearSelection(); } - SnapshotUndoRedo(); handled = true; break; @@ -858,7 +868,6 @@ namespace Avalonia.Controls SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters)); } - SnapshotUndoRedo(); handled = true; break; @@ -868,7 +877,6 @@ namespace Avalonia.Controls { SnapshotUndoRedo(); HandleTextInput(NewLine); - SnapshotUndoRedo(); handled = true; } @@ -879,7 +887,6 @@ namespace Avalonia.Controls { SnapshotUndoRedo(); HandleTextInput("\t"); - SnapshotUndoRedo(); handled = true; } else @@ -889,6 +896,10 @@ namespace Avalonia.Controls break; + case Key.Space: + SnapshotUndoRedo(); // always snapshot in between words + break; + default: handled = false; break; @@ -1306,11 +1317,19 @@ namespace Avalonia.Controls } } - private void SnapshotUndoRedo() + private void SnapshotUndoRedo(bool ignoreChangeCount = true) { if (IsUndoEnabled) { - _undoRedoHelper.Snapshot(); + if (ignoreChangeCount || + !_hasDoneSnapshotOnce || + (!ignoreChangeCount && + _selectedTextChangesMadeSinceLastUndoSnapshot >= _maxCharsBeforeUndoSnapshot)) + { + _undoRedoHelper.Snapshot(); + _selectedTextChangesMadeSinceLastUndoSnapshot = 0; + _hasDoneSnapshotOnce = true; + } } } } diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 7374f20a0c..fd1ca54b57 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -7,7 +7,7 @@ using Avalonia.Utilities; namespace Avalonia.Controls.Utils { - class UndoRedoHelper : WeakTimer.IWeakTimerSubscriber where TState : struct, IEquatable + class UndoRedoHelper { private readonly IUndoRedoHost _host; @@ -31,7 +31,6 @@ namespace Avalonia.Controls.Utils public UndoRedoHelper(IUndoRedoHost host) { _host = host; - WeakTimer.StartWeakTimer(this, TimeSpan.FromSeconds(1)); } public void Undo() @@ -61,7 +60,7 @@ namespace Avalonia.Controls.Utils if (_states.Last != null) { _states.Last.Value = state; - } + } } public void UpdateLastState() @@ -103,11 +102,5 @@ namespace Avalonia.Controls.Utils _states.Clear(); _currentNode = null; } - - bool WeakTimer.IWeakTimerSubscriber.Tick() - { - Snapshot(); - return true; - } } } From 2c215d1b3917af0c31e55cb22e2ba07db203b535 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Tue, 8 Jun 2021 18:02:29 -0400 Subject: [PATCH 07/73] Make sure to snapshot undo before delete selected --- src/Avalonia.Controls/TextBox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 31ec12c22f..23c0d70f20 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -349,6 +349,8 @@ namespace Avalonia.Controls { if (string.IsNullOrEmpty(value)) { + _selectedTextChangesMadeSinceLastUndoSnapshot++; + SnapshotUndoRedo(ignoreChangeCount: false); DeleteSelection(); } else From 11138b1fc5937bb268460b36dd259a64c6791caf Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Tue, 8 Jun 2021 18:05:02 -0400 Subject: [PATCH 08/73] Remove extra snapshots after cut, paste --- src/Avalonia.Controls/TextBox.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 23c0d70f20..dad2401921 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -639,7 +639,6 @@ namespace Avalonia.Controls SnapshotUndoRedo(); Copy(); DeleteSelection(); - SnapshotUndoRedo(); } public async void Copy() @@ -659,7 +658,6 @@ namespace Avalonia.Controls SnapshotUndoRedo(); HandleTextInput(text); - SnapshotUndoRedo(); } protected override void OnKeyDown(KeyEventArgs e) From 2fe25f8acf3567ad1869942eb20c6bc068b44387 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Tue, 8 Jun 2021 18:48:12 -0400 Subject: [PATCH 09/73] Fix equality for UndoRedoState In UndoRedoHelper, the .Equals call in Snapshot() did not call Equals(UndoRedoState other). It called the default Equals(object obj) implementation. This could throw of the undo redo snapshotting. Fix that by overriding Equals(object obj). --- src/Avalonia.Controls/TextBox.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index dad2401921..edd268a86a 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -157,6 +157,13 @@ namespace Avalonia.Controls } public bool Equals(UndoRedoState other) => ReferenceEquals(Text, other.Text) || Equals(Text, other.Text); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (obj.GetType() != GetType()) return false; + return Equals((UndoRedoState)obj); + } } private string _text; From f92804ff05b6361a0e355ad894e59c453927c6c7 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Tue, 8 Jun 2021 18:57:18 -0400 Subject: [PATCH 10/73] Improve UndoRedoState.Equals(object obj) impl --- src/Avalonia.Controls/TextBox.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index edd268a86a..b89bd22038 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -158,12 +158,9 @@ namespace Avalonia.Controls public bool Equals(UndoRedoState other) => ReferenceEquals(Text, other.Text) || Equals(Text, other.Text); - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (obj.GetType() != GetType()) return false; - return Equals((UndoRedoState)obj); - } + public override bool Equals(object obj) => obj is UndoRedoState other && Equals(other); + + public override int GetHashCode() => Text.GetHashCode(); } private string _text; From 091ffe5fa8e91959d676d6b625eaebf616dd5398 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Jul 2021 18:55:04 +0200 Subject: [PATCH 11/73] Remove activateAppropriateChild logic. It was causing both parent and child windows to think they were the key window at the same time and it's not really needed anymore since the implementation of real child windows as far as I can see. --- native/Avalonia.Native/src/OSX/window.mm | 34 +++--------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index c0936356d2..d206d63a89 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -2055,22 +2055,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent return _canBecomeKeyAndMain; } --(bool) activateAppropriateChild: (bool)activating -{ - for(NSWindow* uch in [self childWindows]) - { - auto ch = objc_cast(uch); - if(ch == nil) - continue; - [ch activateAppropriateChild:false]; - return FALSE; - } - - if(!activating) - [self makeKeyAndOrderFront:self]; - return TRUE; -} - -(bool)shouldTryToHandleEvents { return _isEnabled; @@ -2081,26 +2065,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _isEnabled = enable; } --(void)makeKeyWindow -{ - if([self activateAppropriateChild: true]) - { - [super makeKeyWindow]; - } -} - -(void)becomeKeyWindow { [self showWindowMenuWithAppMenu]; - if([self activateAppropriateChild: true]) + if(_parent != nullptr) { - if(_parent != nullptr) - { - _parent->BaseEvents->Activated(); - } + _parent->BaseEvents->Activated(); } - + [super becomeKeyWindow]; } @@ -2110,7 +2083,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent if(parent != nil) { [parent removeChildWindow:self]; - [parent activateAppropriateChild: false]; } } From cd51d638e93bad0eeb40408509a2e074ab16b56e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 5 Jul 2021 08:51:29 +0200 Subject: [PATCH 12/73] Pass dialog flag to window impl Show. Not doing anything with it yet. --- .../Platform/IWindowBaseImpl.cs | 4 +++- src/Avalonia.Controls/Window.cs | 4 ++-- src/Avalonia.Controls/WindowBase.cs | 2 +- .../Remote/PreviewerWindowImpl.cs | 6 +----- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 7 +------ src/Avalonia.Native/PopupImpl.cs | 4 ++-- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.X11/X11Window.cs | 2 +- src/Windows/Avalonia.Win32/PopupImpl.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 ++-- .../ContextMenuTests.cs | 16 ++++++++-------- .../WindowBaseTests.cs | 2 +- .../Avalonia.UnitTests/MockWindowingPlatform.cs | 2 +- 15 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 0d303a6666..5172569726 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -7,7 +7,9 @@ namespace Avalonia.Platform /// /// Shows the window. /// - void Show(bool activate); + /// Whether to activate the shown window. + /// Whether the window is being shown as a dialog. + void Show(bool activate, bool isDialog); /// /// Hides the window. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 700c3d9bad..f5a86b8b21 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -671,7 +671,7 @@ namespace Avalonia.Controls SetWindowStartupLocation(Owner?.PlatformImpl); - PlatformImpl?.Show(ShowActivated); + PlatformImpl?.Show(ShowActivated, false); Renderer?.Start(); } OnOpened(EventArgs.Empty); @@ -743,7 +743,7 @@ namespace Avalonia.Controls SetWindowStartupLocation(owner.PlatformImpl); - PlatformImpl?.Show(ShowActivated); + PlatformImpl?.Show(ShowActivated, true); Renderer?.Start(); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index cdcb499e98..3080def895 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -162,7 +162,7 @@ namespace Avalonia.Controls LayoutManager.ExecuteInitialLayoutPass(); _hasExecutedInitialLayoutPass = true; } - PlatformImpl?.Show(true); + PlatformImpl?.Show(true, false); Renderer?.Start(); OnOpened(EventArgs.Empty); } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 787f44887f..e82ec0fb0f 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -20,7 +20,7 @@ namespace Avalonia.DesignerSupport.Remote ClientSize = new Size(1, 1); } - public void Show(bool activate) + public void Show(bool activate, bool isDialog) { } @@ -99,10 +99,6 @@ namespace Avalonia.DesignerSupport.Remote { } - public void ShowDialog(IWindowImpl parent) - { - } - public void SetSystemDecorations(SystemDecorations enabled) { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index eedfc52d9d..e5fd5a91e6 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -77,7 +77,7 @@ namespace Avalonia.DesignerSupport.Remote { } - public void Show(bool activate) + public void Show(bool activate, bool isDialog) { } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index af522f3e36..3c0dc5f6e2 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -75,7 +75,7 @@ namespace Avalonia.Headless public Action Closed { get; set; } public IMouseDevice MouseDevice { get; } - public void Show(bool activate) + public void Show(bool activate, bool isDialog) { if (activate) Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); @@ -147,11 +147,6 @@ namespace Avalonia.Headless } - public void ShowDialog(IWindowImpl parent) - { - Show(true); - } - public void SetSystemDecorations(bool enabled) { diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index c36675afcd..8740dd6f12 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -60,14 +60,14 @@ namespace Avalonia.Native } } - public override void Show(bool activate) + public override void Show(bool activate, bool isDialog) { var parent = _parent; while (parent is PopupImpl p) parent = p._parent; if (parent is WindowImpl w) w.Native.TakeFocusFromChildren(); - base.Show(false); + base.Show(false, isDialog); } public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f716464d14..f444db5bc1 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -351,7 +351,7 @@ namespace Avalonia.Native } - public virtual void Show(bool activate) + public virtual void Show(bool activate, bool isDialog) { _native.Show(activate.AsComBool()); } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 37260aa78b..26edf10671 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -808,7 +808,7 @@ namespace Avalonia.X11 XSetTransientForHint(_x11.Display, _handle, parent.Handle.Handle); } - public void Show(bool activate) + public void Show(bool activate, bool isDialog) { _wasMappedAtLeastOnce = true; XMapWindow(_x11.Display, _handle); diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index dd3fd1342c..7b2d20a986 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -17,7 +17,7 @@ namespace Avalonia.Win32 [ThreadStatic] private static IntPtr s_parentHandle; - public override void Show(bool activate) + public override void Show(bool activate, bool isDialog) { // Popups are always shown non-activated. UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index d92bd08d01..e3b01e8071 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -246,7 +246,7 @@ namespace Avalonia.Win32 public IWindowImpl CreateEmbeddableWindow() { var embedded = new EmbeddedWindowImpl(); - embedded.Show(true); + embedded.Show(true, false); return embedded; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3a3342fd14..c46cbb3274 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -570,7 +570,7 @@ namespace Avalonia.Win32 _shown = false; } - public virtual void Show(bool activate) + public virtual void Show(bool activate, bool isDialog) { SetParent(_parent); ShowWindow(_showWindowState, activate); @@ -1120,7 +1120,7 @@ namespace Avalonia.Win32 SetParent(null); if (shown) - Show(activated); + Show(activated, false); } } else diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index f3a1316c7d..3d0c3b4c84 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -128,7 +128,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var sut = new ContextMenu(); @@ -148,7 +148,7 @@ namespace Avalonia.Controls.UnitTests _mouse.Click(target); Assert.False(sut.IsOpen); - popupImpl.Verify(x => x.Show(true), Times.Once); + popupImpl.Verify(x => x.Show(true, false), Times.Once); popupImpl.Verify(x => x.Hide(), Times.Once); } } @@ -158,7 +158,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var sut = new ContextMenu(); @@ -179,7 +179,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(sut.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Once); - popupImpl.Verify(x => x.Show(true), Times.Exactly(2)); + popupImpl.Verify(x => x.Show(true, false), Times.Exactly(2)); } } @@ -226,7 +226,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); bool eventCalled = false; var sut = new ContextMenu(); @@ -242,7 +242,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(eventCalled); Assert.False(sut.IsOpen); - popupImpl.Verify(x => x.Show(true), Times.Never); + popupImpl.Verify(x => x.Show(true, false), Times.Never); } } @@ -346,7 +346,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); bool eventCalled = false; @@ -370,7 +370,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(eventCalled); Assert.True(sut.IsOpen); - popupImpl.Verify(x => x.Show(true), Times.Once()); + popupImpl.Verify(x => x.Show(true, false), Times.Once()); popupImpl.Verify(x => x.Hide(), Times.Never); } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 8109b037c5..1b4214e0c7 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -137,7 +137,7 @@ namespace Avalonia.Controls.UnitTests var target = new TestWindowBase(windowImpl.Object); target.IsVisible = true; - windowImpl.Verify(x => x.Show(true)); + windowImpl.Verify(x => x.Show(true, false)); } } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 8a24a8366f..713283aa2b 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -58,7 +58,7 @@ namespace Avalonia.UnitTests windowImpl.Object.Resized?.Invoke(clientSize); }); - windowImpl.Setup(x => x.Show(true)).Callback(() => + windowImpl.Setup(x => x.Show(true, It.IsAny())).Callback(() => { windowImpl.Object.Activated?.Invoke(); }); From 32c0eac41529158153ce0bc1294f73e5010ada4c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 5 Jul 2021 09:27:46 +0200 Subject: [PATCH 13/73] Prevent dialog parent from becoming key window. --- native/Avalonia.Native/src/OSX/window.h | 1 + native/Avalonia.Native/src/OSX/window.mm | 42 +++++++++++++++++++++--- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.Native/avn.idl | 2 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index b1f64bca88..e2f69c8359 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -34,6 +34,7 @@ class WindowBaseImpl; -(double) getScaling; -(double) getExtendedTitleBarHeight; -(void) setIsExtended:(bool)value; +-(bool) isDialog; @end struct INSWindowHolder diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index d206d63a89..4d8303a173 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -105,7 +105,7 @@ public: return Window; } - virtual HRESULT Show(bool activate) override + virtual HRESULT Show(bool activate, bool isDialog) override { @autoreleasepool { @@ -488,6 +488,11 @@ public: return S_OK; } + virtual bool IsDialog() + { + return false; + } + protected: virtual NSWindowStyleMask GetStyle() { @@ -518,6 +523,7 @@ private: NSRect _preZoomSize; bool _transitioningWindowState; bool _isClientAreaExtended; + bool _isDialog; AvnExtendClientAreaChromeHints _extendClientHints; FORWARD_IUNKNOWN() @@ -573,11 +579,12 @@ private: } } - virtual HRESULT Show (bool activate) override + virtual HRESULT Show (bool activate, bool isDialog) override { @autoreleasepool - { - WindowBaseImpl::Show(activate); + { + _isDialog = isDialog; + WindowBaseImpl::Show(activate, isDialog); HideOrShowTrafficLights(); @@ -1070,6 +1077,11 @@ private: } } + virtual bool IsDialog() override + { + return _isDialog; + } + protected: virtual NSWindowStyleMask GetStyle() override { @@ -1858,6 +1870,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _isExtended = value; } +-(bool) isDialog +{ + return _parent->IsDialog(); +} + -(double) getScaling { return _lastScaling; @@ -2047,7 +2064,22 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent -(BOOL)canBecomeKeyWindow { - return _canBecomeKeyAndMain; + if (_canBecomeKeyAndMain) + { + // If the window has a child window being shown as a dialog then don't allow it to become the key window. + for(NSWindow* uch in [self childWindows]) + { + auto ch = objc_cast(uch); + if(ch == nil) + continue; + if (ch.isDialog) + return false; + } + + return true; + } + + return false; } -(BOOL)canBecomeMainWindow diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f444db5bc1..0dba11af5a 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -353,7 +353,7 @@ namespace Avalonia.Native public virtual void Show(bool activate, bool isDialog) { - _native.Show(activate.AsComBool()); + _native.Show(activate.AsComBool(), isDialog.AsComBool()); } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index adcbeb2d3a..697b66f21a 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -430,7 +430,7 @@ interface IAvnString : IUnknown [uuid(e5aca675-02b7-4129-aa79-d6e417210bda)] interface IAvnWindowBase : IUnknown { - HRESULT Show(bool activate); + HRESULT Show(bool activate, bool isDialog); HRESULT Hide(); HRESULT Close(); HRESULT Activate(); From f0e72f73f62e234c9afc79034f01420ab3a44e73 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 5 Jul 2021 10:50:47 +0200 Subject: [PATCH 14/73] Update ApiCompatBaseline.txt --- src/Avalonia.Controls/ApiCompatBaseline.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 46c12ebd39..1b2fbc5144 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -12,4 +12,7 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 13 +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean, System.Boolean)' is present in the implementation but not in the contract. +Total Issues: 16 From 05361cdb748a29d67d480246702544165fa1c0bd Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Mon, 5 Jul 2021 15:56:34 +0200 Subject: [PATCH 15/73] Adding option to inspect Popup visual tree --- .../Diagnostics/ViewModels/MainViewModel.cs | 1 - .../Diagnostics/ViewModels/VisualTreeNode.cs | 30 ++++-- .../Diagnostics/Views/MainWindow.xaml.cs | 91 ++++++++++++------- 3 files changed, 81 insertions(+), 41 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 3f367165ac..07ae222a9c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; - using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index 48fa636664..b9981babf7 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Collections; +using Avalonia.Controls.Primitives; using Avalonia.Styling; using Avalonia.VisualTree; @@ -24,8 +25,7 @@ namespace Avalonia.Diagnostics.ViewModels public static VisualTreeNode[] Create(object control) { - var visual = control as IVisual; - return visual != null ? new[] { new VisualTreeNode(visual, null) } : Array.Empty(); + return control is IVisual visual ? new[] { new VisualTreeNode(visual, null) } : Array.Empty(); } internal class VisualTreeNodeCollection : TreeNodeCollection @@ -46,10 +46,28 @@ namespace Avalonia.Diagnostics.ViewModels protected override void Initialize(AvaloniaList nodes) { - _subscription = _control.VisualChildren.ForEachItem( - (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)), - (i, item) => nodes.RemoveAt(i), - () => nodes.Clear()); + if (_control is Popup p) + { + _subscription = p.GetObservable(Popup.ChildProperty).Subscribe(child => + { + if (child != null) + { + nodes.Add(new VisualTreeNode(child, Owner)); + } + else + { + nodes.Clear(); + } + + }); + } + else + { + _subscription = _control.VisualChildren.ForEachItem( + (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)), + (i, item) => nodes.RemoveAt(i), + () => nodes.Clear()); + } } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index d1232b749a..9d37b5acdc 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -27,19 +27,19 @@ namespace Avalonia.Diagnostics.Views EventHandler? lh = default; lh = (s, e) => - { - this.Opened -= lh; - if ((DataContext as MainViewModel)?.StartupScreenIndex is int index) - { - var screens = this.Screens; - if (index > -1 && index < screens.ScreenCount) - { - var screen = screens.All[index]; - this.Position = screen.Bounds.TopLeft; - this.WindowState = WindowState.Maximized; - } - } - }; + { + this.Opened -= lh; + if ((DataContext as MainViewModel)?.StartupScreenIndex is { } index) + { + var screens = this.Screens; + if (index > -1 && index < screens.ScreenCount) + { + var screen = screens.All[index]; + this.Position = screen.Bounds.TopLeft; + this.WindowState = WindowState.Maximized; + } + } + }; this.Opened += lh; } @@ -91,6 +91,24 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } + private IControl? GetHoveredControl(TopLevel topLevel) + { +#pragma warning disable CS0618 // Type or member is obsolete + var point = (topLevel as IInputRoot)?.MouseDevice?.GetPosition(topLevel) ?? default; +#pragma warning restore CS0618 // Type or member is obsolete + + return (IControl?)topLevel.GetVisualsAt(point, x => + { + if (x is AdornerLayer || !x.IsVisible) + { + return false; + } + + return !(x is IInputElement ie) || ie.IsHitTestVisible; + }) + .FirstOrDefault(); + } + private void RawKeyDown(RawKeyEventArgs e) { var vm = (MainViewModel?)DataContext; @@ -99,34 +117,39 @@ namespace Avalonia.Diagnostics.Views return; } - const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift; - - if (e.Modifiers == modifiers) + switch (e.Modifiers) { -#pragma warning disable CS0618 // Type or member is obsolete - var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default; -#pragma warning restore CS0618 // Type or member is obsolete + case RawInputModifiers.Control | RawInputModifiers.Shift: + { + IControl? control = null; - var control = Root.GetVisualsAt(point, x => + foreach (var popup in Root.GetVisualDescendants().OfType()) { - if (x is AdornerLayer || !x.IsVisible) return false; - if (!(x is IInputElement ie)) return true; - return ie.IsHitTestVisible; - }) - .FirstOrDefault(); + if (popup.Host?.HostedVisualTreeRoot is PopupRoot popupRoot) + { + control = GetHoveredControl(popupRoot); + + if (control != null) + { + break; + } + } + } - if (control != null) - { - vm.SelectControl((IControl)control); + control ??= GetHoveredControl(Root); + + if (control != null) + { + vm.SelectControl(control); + } + + break; } - } - else if (e.Modifiers == RawInputModifiers.Alt) - { - if (e.Key == Key.S || e.Key == Key.D) + case RawInputModifiers.Alt when e.Key == Key.S || e.Key == Key.D: { - var enable = e.Key == Key.S; + vm.EnableSnapshotStyles(e.Key == Key.S); - vm.EnableSnapshotStyles(enable); + break; } } } From 31d80a9fb67165417d327e008190bb0344563aae Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 6 Jul 2021 10:30:24 +0200 Subject: [PATCH 16/73] Make tree roots bold --- .../Diagnostics/ViewModels/TreeNode.cs | 25 ++++++------------- .../Diagnostics/Views/TreePageView.xaml | 2 +- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 4cb470eeac..14d704db93 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -4,24 +4,28 @@ using System.Collections.Specialized; using System.Reactive; using System.Reactive.Linq; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.LogicalTree; +using Avalonia.Media; using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { internal abstract class TreeNode : ViewModelBase, IDisposable { - private IDisposable? _classesSubscription; + private readonly IDisposable? _classesSubscription; private string _classes; private bool _isExpanded; - public TreeNode(IVisual visual, TreeNode? parent) + protected TreeNode(IVisual visual, TreeNode? parent) { Parent = parent; Type = visual.GetType().Name; Visual = visual; _classes = string.Empty; + FontWeight = Visual is TopLevel or Popup ? FontWeight.Bold : FontWeight.Normal; + if (visual is IControl control) { ElementName = control.Name; @@ -52,6 +56,8 @@ namespace Avalonia.Diagnostics.ViewModels } } + public FontWeight FontWeight { get; } + public abstract TreeNodeCollection Children { get; @@ -95,20 +101,5 @@ namespace Avalonia.Diagnostics.ViewModels _classesSubscription?.Dispose(); Children.Dispose(); } - - private static int IndexOf(IReadOnlyList collection, TreeNode item) - { - var count = collection.Count; - - for (var i = 0; i < count; ++i) - { - if (collection[i] == item) - { - return i; - } - } - - throw new AvaloniaInternalException("TreeNode was not present in parent Children collection."); - } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml index a5328716fc..bb661f7f4c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml @@ -11,7 +11,7 @@ - + From cd56e1a9fc6e914571b2b38ab042fcc0275feaeb Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 6 Jul 2021 10:40:07 +0200 Subject: [PATCH 17/73] Remove "or" --- src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 14d704db93..f4c04dbca6 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -24,7 +24,7 @@ namespace Avalonia.Diagnostics.ViewModels Visual = visual; _classes = string.Empty; - FontWeight = Visual is TopLevel or Popup ? FontWeight.Bold : FontWeight.Normal; + FontWeight = Visual is TopLevel || Visual is Popup ? FontWeight.Bold : FontWeight.Normal; if (visual is IControl control) { From 97c33f432a32b6a5f6f5bc0273e6bbd29089caf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miha=20Marki=C4=8D?= Date: Sat, 10 Jul 2021 15:46:08 +0200 Subject: [PATCH 18/73] Applies spell checking on mostly comments, but also non public members and arguments --- src/Avalonia.Base/AvaloniaObject.cs | 2 +- src/Avalonia.Base/AvaloniaProperty.cs | 4 ++-- .../AvaloniaPropertyChangedEventArgs.cs | 4 ++-- .../Collections/Pooled/PooledList.cs | 2 +- .../Data/Core/Plugins/IDataValidationPlugin.cs | 2 +- src/Avalonia.Base/DirectPropertyBase.cs | 6 +++--- src/Avalonia.Base/DirectPropertyMetadata`1.cs | 2 +- .../Collections/DataGridCollectionView.cs | 8 ++++---- src/Avalonia.Controls.DataGrid/DataGrid.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridCell.cs | 2 +- .../DataGridCellCoordinates.cs | 2 +- .../DataGridClipboard.cs | 2 +- .../DataGridColumn.cs | 2 +- .../DataGridDisplayData.cs | 8 ++++---- src/Avalonia.Controls.DataGrid/DataGridRow.cs | 6 +++--- src/Avalonia.Controls.DataGrid/DataGridRows.cs | 18 +++++++++--------- .../DataGridColumnHeadersPresenter.cs | 2 +- src/Avalonia.Controls/AppBuilderBase.cs | 2 +- .../Converters/MarginMultiplierConverter.cs | 10 +++++----- .../MenuScrollingVisibilityConverter.cs | 4 ++-- .../DateTimePickers/DatePicker.cs | 2 +- .../DateTimePickers/DateTimePickerPanel.cs | 12 ++++++------ src/Avalonia.Controls/DefinitionBase.cs | 8 ++++---- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 2 +- src/Avalonia.Controls/Grid.cs | 18 +++++++++--------- src/Avalonia.Controls/ListBox.cs | 2 +- .../NativeMenuItemSeparator.cs | 2 +- .../Platform/ExtendClientAreaChromeHints.cs | 2 +- .../PopupPositioning/ManagedPopupPositioner.cs | 2 +- src/Avalonia.Controls/Primitives/RangeBase.cs | 2 +- .../Primitives/SelectingItemsControl.cs | 2 +- .../Repeater/IElementFactory.cs | 2 +- .../Repeater/ItemsRepeater.cs | 4 ++-- src/Avalonia.Controls/Repeater/ViewManager.cs | 2 +- .../Repeater/ViewportManager.cs | 2 +- src/Avalonia.Controls/ScrollViewer.cs | 2 +- .../Selection/SelectionModel.cs | 2 +- src/Avalonia.Controls/TickBar.cs | 8 ++++---- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.Controls/WindowBase.cs | 2 +- src/Avalonia.Input/Cursor.cs | 2 +- .../ScrollGestureRecognizer.cs | 2 +- src/Avalonia.Input/ICommandSource.cs | 2 +- .../TextInput/ITextInputMethodClient.cs | 4 ++-- src/Avalonia.Interactivity/Interactive.cs | 2 +- src/Avalonia.Layout/ElementManager.cs | 2 +- src/Avalonia.Layout/FlowLayoutAlgorithm.cs | 4 ++-- .../AvaloniaNativeMenuExporter.cs | 4 ++-- src/Avalonia.Native/IAvnMenu.cs | 8 ++++---- src/Avalonia.Native/IAvnMenuItem.cs | 6 +++--- .../Unicode/LineBreakEnumerator.cs | 2 +- .../Transformation/InterpolationUtilities.cs | 10 +++++----- 52 files changed, 109 insertions(+), 109 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 6a9cff6b71..ce5b37043f 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -861,7 +861,7 @@ namespace Avalonia } /// - /// Logs a mesage if the notification represents a binding error. + /// Logs a message if the notification represents a binding error. /// /// The property being bound. /// The binding notification. diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 5117fdb170..94aefb8869 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -465,9 +465,9 @@ namespace Avalonia /// Uses the visitor pattern to resolve an untyped property to a typed property. /// /// The type of user data passed. - /// The visitor which will accept the typed property. + /// The visitor which will accept the typed property. /// The user data to pass. - public abstract void Accept(IAvaloniaPropertyVisitor vistor, ref TData data) + public abstract void Accept(IAvaloniaPropertyVisitor visitor, ref TData data) where TData : struct; /// diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs index c1a2832fde..896d86e29d 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs @@ -58,8 +58,8 @@ namespace Avalonia /// /// This will usually be true, except in /// - /// which recieves notifications for all changes to property values, whether a value with a higher - /// priority is present or not. When this property is false, the change that is being signalled + /// which receives notifications for all changes to property values, whether a value with a higher + /// priority is present or not. When this property is false, the change that is being signaled /// has not resulted in a change to the property value on the object. /// public bool IsEffectiveValueChange { get; private set; } diff --git a/src/Avalonia.Base/Collections/Pooled/PooledList.cs b/src/Avalonia.Base/Collections/Pooled/PooledList.cs index e50e100d32..2cd9758f12 100644 --- a/src/Avalonia.Base/Collections/Pooled/PooledList.cs +++ b/src/Avalonia.Base/Collections/Pooled/PooledList.cs @@ -1271,7 +1271,7 @@ namespace Avalonia.Collections.Pooled /// Reverses the elements in a range of this list. Following a call to this /// method, an element in the range given by index and count /// which was previously located at index i will now be located at - /// index index + (index + count - i - 1). + /// index + (index + count - i - 1). /// public void Reverse(int index, int count) { diff --git a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs index 324279e9f0..2a580fe75f 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs @@ -20,7 +20,7 @@ namespace Avalonia.Data.Core.Plugins /// /// A weak reference to the object. /// The property name. - /// The inner property accessor used to aceess the property. + /// The inner property accessor used to access the property. /// /// An interface through which future interactions with the /// property will be made. diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index e6cc1edfdf..a057ad2254 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -13,7 +13,7 @@ namespace Avalonia /// The type of the property's value. /// /// Whereas is typed on the owner type, this base - /// class provides a non-owner-typed interface to a direct poperty. + /// class provides a non-owner-typed interface to a direct property. /// public abstract class DirectPropertyBase : AvaloniaProperty { @@ -123,9 +123,9 @@ namespace Avalonia } /// - public override void Accept(IAvaloniaPropertyVisitor vistor, ref TData data) + public override void Accept(IAvaloniaPropertyVisitor visitor, ref TData data) { - vistor.Visit(this, ref data); + visitor.Visit(this, ref data); } /// diff --git a/src/Avalonia.Base/DirectPropertyMetadata`1.cs b/src/Avalonia.Base/DirectPropertyMetadata`1.cs index 205967984d..eabdef05ed 100644 --- a/src/Avalonia.Base/DirectPropertyMetadata`1.cs +++ b/src/Avalonia.Base/DirectPropertyMetadata`1.cs @@ -38,7 +38,7 @@ namespace Avalonia /// /// Data validation is validation performed at the target of a binding, for example in a /// view model using the INotifyDataErrorInfo interface. Only certain properties on a - /// control (such as a TextBox's Text property) will be interested in recieving data + /// control (such as a TextBox's Text property) will be interested in receiving data /// validation messages so this feature must be explicitly enabled by setting this flag. /// public bool? EnableDataValidation { get; private set; } diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs index 5b1e43b8f4..fe6acdc532 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs @@ -877,7 +877,7 @@ namespace Avalonia.Collections if (!CheckFlag(CollectionViewFlags.IsMoveToPageDeferred)) { // if the temporaryGroup was not created yet and is out of sync - // then create it so that we can use it as a refernce while paging. + // then create it so that we can use it as a reference while paging. if (IsGrouping && _temporaryGroup.ItemCount != InternalList.Count) { PrepareTemporaryGroups(); @@ -889,7 +889,7 @@ namespace Avalonia.Collections else if (IsGrouping) { // if the temporaryGroup was not created yet and is out of sync - // then create it so that we can use it as a refernce while paging. + // then create it so that we can use it as a reference while paging. if (_temporaryGroup.ItemCount != InternalList.Count) { // update the groups that get created for the @@ -1951,7 +1951,7 @@ namespace Avalonia.Collections EnsureCollectionInSync(); VerifyRefreshNotDeferred(); - // for indicies larger than the count + // for indices larger than the count if (index >= Count || index < 0) { throw new ArgumentOutOfRangeException("index"); @@ -3800,7 +3800,7 @@ namespace Avalonia.Collections /// /// /// This method can be called from a constructor - it does not call - /// any virtuals. The 'count' parameter is substitute for the real Count, + /// any virtuals. The 'count' parameter is substitute for the real Count, /// used only when newItem is null. /// In that case, this method sets IsCurrentAfterLast to true if and only /// if newPosition >= count. This distinguishes between a null belonging diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 83f13fe199..21585bd9fc 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -5731,7 +5731,7 @@ namespace Avalonia.Controls { if (SelectionMode == DataGridSelectionMode.Single || !ctrl) { - // Unselect the currectly selected rows except the new selected row + // Unselect the currently selected rows except the new selected row action = DataGridSelectionAction.SelectCurrent; } else diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index 445dc541a7..0de4612958 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -197,7 +197,7 @@ namespace Avalonia.Controls } // Makes sure the right gridline has the proper stroke and visibility. If lastVisibleColumn is specified, the - // right gridline will be collapsed if this cell belongs to the lastVisibileColumn and there is no filler column + // right gridline will be collapsed if this cell belongs to the lastVisibleColumn and there is no filler column internal void EnsureGridLine(DataGridColumn lastVisibleColumn) { if (OwningGrid != null && _rightGridLine != null) diff --git a/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs b/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs index 2f723154be..26f0b53952 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs @@ -40,7 +40,7 @@ namespace Avalonia.Controls return false; } - // There is build warning if this is missiing + // There is build warning if this is missing public override int GetHashCode() { return base.GetHashCode(); diff --git a/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs b/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs index a4bab8b304..ee69f1c768 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs @@ -189,7 +189,7 @@ namespace Avalonia.Controls } /// - /// DataGrid row item used for proparing the ClipboardRowContent. + /// DataGrid row item used for preparing the ClipboardRowContent. /// public object Item { diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 9141fb2463..0849f48686 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -787,7 +787,7 @@ namespace Avalonia.Controls } /// - /// If the DataGrid is using using layout rounding, the pixel snapping will force all widths to + /// If the DataGrid is using layout rounding, the pixel snapping will force all widths to /// whole numbers. Since the column widths aren't visual elements, they don't go through the normal /// rounding process, so we need to do it ourselves. If we don't, then we'll end up with some /// pixel gaps and/or overlaps between columns. diff --git a/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs b/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs index e659438b43..2b8055dd22 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs @@ -79,7 +79,7 @@ namespace Avalonia.Controls set; } - internal void AddRecylableRow(DataGridRow row) + internal void AddRecyclableRow(DataGridRow row) { Debug.Assert(!_recyclableRows.Contains(row)); row.DetachFromDataGrid(true); @@ -120,7 +120,7 @@ namespace Avalonia.Controls { if (row.IsRecyclable) { - AddRecylableRow(row); + AddRecyclableRow(row); } else { @@ -193,7 +193,7 @@ namespace Avalonia.Controls internal void FullyRecycleElements() { - // Fully recycle Recycleable rows and transfer them to Recycled rows + // Fully recycle Recyclable rows and transfer them to Recycled rows while (_recyclableRows.Count > 0) { DataGridRow row = _recyclableRows.Pop(); @@ -202,7 +202,7 @@ namespace Avalonia.Controls Debug.Assert(!_fullyRecycledRows.Contains(row)); _fullyRecycledRows.Push(row); } - // Fully recycle Recycleable GroupHeaders and transfer them to Recycled GroupHeaders + // Fully recycle Recyclable GroupHeaders and transfer them to Recycled GroupHeaders while (_recyclableGroupHeaders.Count > 0) { DataGridRowGroupHeader groupHeader = _recyclableGroupHeaders.Pop(); diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index c3562c53a4..7546970498 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -392,7 +392,7 @@ namespace Avalonia.Controls set; } - // Height that the row will eventually end up at after a possible detalis animation has completed + // Height that the row will eventually end up at after a possible details animation has completed internal double TargetHeight { get @@ -517,7 +517,7 @@ namespace Avalonia.Controls return base.MeasureOverride(availableSize); } - //Allow the DataGrid specific componets to adjust themselves based on new values + //Allow the DataGrid specific components to adjust themselves based on new values if (_headerElement != null) { _headerElement.InvalidateMeasure(); @@ -722,7 +722,7 @@ namespace Avalonia.Controls if (_bottomGridLine != null) { // It looks like setting Visibility sometimes has side effects so make sure the value is actually - // diffferent before setting it + // different before setting it bool newVisibility = OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.Horizontal || OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.All; if (newVisibility != _bottomGridLine.IsVisible) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 4bfbd7d818..1d5c899993 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -1193,7 +1193,7 @@ namespace Avalonia.Controls else { groupHeader = element as DataGridRowGroupHeader; - Debug.Assert(groupHeader != null); // Nothig other and Rows and RowGroups now + Debug.Assert(groupHeader != null); // Nothing other and Rows and RowGroups now if (groupHeader != null) { groupHeader.TotalIndent = (groupHeader.Level == 0) ? 0 : RowGroupSublevelIndents[groupHeader.Level - 1]; @@ -1636,7 +1636,7 @@ namespace Avalonia.Controls if (slot >= DisplayData.FirstScrollingSlot && slot <= DisplayData.LastScrollingSlot) { - // Additional row takes the spot of a displayed row - it is necessarilly displayed + // Additional row takes the spot of a displayed row - it is necessarily displayed return true; } else if (DisplayData.FirstScrollingSlot == -1 && @@ -1825,7 +1825,7 @@ namespace Avalonia.Controls if (MathUtilities.LessThan(firstRowHeight, NegVerticalOffset)) { // We've scrolled off more of the first row than what's possible. This can happen - // if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling + // if the first row got shorter (Ex: Collapsing RowDetails) or if the user has a recycling // cleanup issue. In this case, simply try to display the next row as the first row instead if (newFirstScrollingSlot < SlotCount - 1) { @@ -2014,7 +2014,7 @@ namespace Avalonia.Controls if (recycleRow) { - DisplayData.AddRecylableRow(dataGridRow); + DisplayData.AddRecyclableRow(dataGridRow); } else { @@ -2265,7 +2265,7 @@ namespace Avalonia.Controls if (parentGroupInfo.LastSubItemSlot - parentGroupInfo.Slot == 1) { // We just added the first item to a RowGroup so the header should transition from Empty to either Expanded or Collapsed - EnsureAnscestorsExpanderButtonChecked(parentGroupInfo); + EnsureAncestorsExpanderButtonChecked(parentGroupInfo); } } } @@ -2407,7 +2407,7 @@ namespace Avalonia.Controls return treeCount; } - private void EnsureAnscestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo) + private void EnsureAncestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo) { if (IsSlotVisible(parentGroupInfo.Slot)) { @@ -2789,11 +2789,11 @@ namespace Avalonia.Controls return null; } - internal void OnRowGroupHeaderToggled(DataGridRowGroupHeader groupHeader, bool newIsVisibile, bool setCurrent) + internal void OnRowGroupHeaderToggled(DataGridRowGroupHeader groupHeader, bool newIsVisible, bool setCurrent) { Debug.Assert(groupHeader.RowGroupInfo.CollectionViewGroup.ItemCount > 0); - if (WaitForLostFocus(delegate { OnRowGroupHeaderToggled(groupHeader, newIsVisibile, setCurrent); }) || !CommitEdit()) + if (WaitForLostFocus(delegate { OnRowGroupHeaderToggled(groupHeader, newIsVisible, setCurrent); }) || !CommitEdit()) { return; } @@ -2804,7 +2804,7 @@ namespace Avalonia.Controls UpdateSelectionAndCurrency(CurrentColumnIndex, groupHeader.RowGroupInfo.Slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false); } - UpdateRowGroupVisibility(groupHeader.RowGroupInfo, newIsVisibile, isDisplayed: true); + UpdateRowGroupVisibility(groupHeader.RowGroupInfo, newIsVisible, isDisplayed: true); ComputeScrollBarsLayout(); // We need force arrange since our Scrollings Rows could update without automatically triggering layout diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs index 1c350a4f14..4eed119240 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs @@ -140,7 +140,7 @@ namespace Avalonia.Controls.Primitives if (dataGridColumn.IsFrozen) { columnHeader.Arrange(new Rect(frozenLeftEdge, 0, dataGridColumn.LayoutRoundedWidth, finalSize.Height)); - columnHeader.Clip = null; // The layout system could have clipped this becaues it's not aware of our render transform + columnHeader.Clip = null; // The layout system could have clipped this because it's not aware of our render transform if (DragColumn == dataGridColumn && DragIndicator != null) { dragIndicatorLeftEdge = frozenLeftEdge + DragIndicatorOffset; diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index f616a42cac..d44b2ab0db 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -273,7 +273,7 @@ namespace Avalonia.Controls } /// - /// Sets up the platform-speciic services for the . + /// Sets up the platform-specific services for the . /// private void Setup() { diff --git a/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs b/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs index 9f3a6da9da..46affcbe33 100644 --- a/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs +++ b/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs @@ -26,13 +26,13 @@ namespace Avalonia.Controls.Converters Right ? Indent * scalarDepth : 0, Bottom ? Indent * scalarDepth : 0); } - else if (value is Thickness thinknessDepth) + else if (value is Thickness thicknessDepth) { return new Thickness( - Left ? Indent * thinknessDepth.Left : 0, - Top ? Indent * thinknessDepth.Top : 0, - Right ? Indent * thinknessDepth.Right : 0, - Bottom ? Indent * thinknessDepth.Bottom : 0); + Left ? Indent * thicknessDepth.Left : 0, + Top ? Indent * thicknessDepth.Top : 0, + Right ? Indent * thicknessDepth.Right : 0, + Bottom ? Indent * thicknessDepth.Bottom : 0); } return new Thickness(0); diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs index e6420fe342..65f95808ff 100644 --- a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs +++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs @@ -16,7 +16,7 @@ namespace Avalonia.Controls.Converters if (parameter == null || values == null || values.Count != 4 || - !(values[0] is ScrollBarVisibility visiblity) || + !(values[0] is ScrollBarVisibility visibility) || !(values[1] is double offset) || !(values[2] is double extent) || !(values[3] is double viewport)) @@ -24,7 +24,7 @@ namespace Avalonia.Controls.Converters return AvaloniaProperty.UnsetValue; } - if (visiblity == ScrollBarVisibility.Auto) + if (visibility == ScrollBarVisibility.Auto) { if (extent == viewport) { diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 43bc7d1df9..5893a02b04 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -71,7 +71,7 @@ namespace Avalonia.Controls x => x.MonthVisible, (x, v) => x.MonthVisible = v); /// - /// Defiens the Property + /// Defines the Property /// public static readonly DirectProperty YearFormatProperty = AvaloniaProperty.RegisterDirect(nameof(YearFormat), diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs index a0e8c03195..e16e609a15 100644 --- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs +++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs @@ -220,15 +220,15 @@ namespace Avalonia.Controls.Primitives if (dy > 0) // Scroll Down { - int numContsToMove = 0; + int numCountsToMove = 0; for (int i = 0; i < children.Count; i++) { if (children[i].Bounds.Bottom - dy < 0) - numContsToMove++; + numCountsToMove++; else break; } - children.MoveRange(0, numContsToMove, children.Count); + children.MoveRange(0, numCountsToMove, children.Count); var scrollHeight = _extent.Height - Viewport.Height; if (ShouldLoop && value.Y >= scrollHeight - _extentOne) @@ -236,15 +236,15 @@ namespace Avalonia.Controls.Primitives } else if (dy < 0) // Scroll Up { - int numContsToMove = 0; + int numCountsToMove = 0; for (int i = children.Count - 1; i >= 0; i--) { if (children[i].Bounds.Top - dy > Bounds.Height) - numContsToMove++; + numCountsToMove++; else break; } - children.MoveRange(children.Count - numContsToMove, numContsToMove, 0); + children.MoveRange(children.Count - numCountsToMove, numCountsToMove, 0); if (ShouldLoop && value.Y < _extentOne) _offset = new Vector(0, value.Y + (_extentOne * 50)); } diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 3237f6f37b..37b8691ce9 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls if (_sharedState == null) { // start with getting SharedSizeGroup value. - // this property is NOT inhereted which should result in better overall perf. + // this property is NOT inherited which should result in better overall perf. string sharedSizeGroupId = SharedSizeGroup; if (sharedSizeGroupId != null) { @@ -52,7 +52,7 @@ namespace Avalonia.Controls } /// - /// Callback to notify about exitting model tree. + /// Callback to notify about exiting model tree. /// internal void OnExitParentTree() { @@ -458,7 +458,7 @@ namespace Avalonia.Controls private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's - private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure + private double _measureSize; // size, calculated to be the input constraint size for Child.Measure private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) @@ -556,7 +556,7 @@ namespace Avalonia.Controls } /// - /// Propogates invalidations for all registered definitions. + /// Propagates invalidations for all registered definitions. /// Resets its own state. /// internal void Invalidate() diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index e4b68c62fd..b9da1138d1 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -485,7 +485,7 @@ namespace Avalonia.Controls.Primitives internal static void SetPresenterClasses(IControl presenter, Classes classes) { - //Remove any classes no longer in use, ignoring pseudoclasses + //Remove any classes no longer in use, ignoring pseudo classes for (int i = presenter.Classes.Count - 1; i >= 0; i--) { if (!classes.Contains(presenter.Classes[i]) && diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index a14df1eb43..4e60b52f83 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -330,7 +330,7 @@ namespace Avalonia.Controls // value of Auto column), "cell 2 1" needs to be calculated first, // as it contributes to the Auto column's calculated value. // At the same time in order to accurately calculate constraint - // height for "cell 2 1", "cell 1 2" needs to be calcualted first, + // height for "cell 2 1", "cell 1 2" needs to be calculated first, // as it contributes to Auto row height, which is used in the // computation of Star row resolved height. // @@ -405,11 +405,11 @@ namespace Avalonia.Controls // // where: // * all [Measure GroupN] - regular children measure process - - // each cell is measured given contraint size as an input + // each cell is measured given constraint size as an input // and each cell's desired size is accumulated on the // corresponding column / row; // * [Measure Group2'] - is when each cell is measured with - // infinit height as a constraint and a cell's desired + // infinite height as a constraint and a cell's desired // height is ignored; // * [Measure Groups''] - is when each cell is measured (second // time during single Grid.MeasureOverride) regularly but its @@ -780,7 +780,7 @@ namespace Avalonia.Controls } /// - /// Initializes DefinitionsU memeber either to user supplied ColumnDefinitions collection + /// Initializes DefinitionsU member either to user supplied ColumnDefinitions collection /// or to a default single element collection. DefinitionsU gets trimmed to size. /// /// @@ -821,7 +821,7 @@ namespace Avalonia.Controls } /// - /// Initializes DefinitionsV memeber either to user supplied RowDefinitions collection + /// Initializes DefinitionsV member either to user supplied RowDefinitions collection /// or to a default single element collection. DefinitionsV gets trimmed to size. /// /// @@ -2132,7 +2132,7 @@ namespace Avalonia.Controls // // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer) // each being allocated a large number of pixels (~50 or greater), and - // people don't even notice the kind of 1-pixel anomolies that are + // people don't even notice the kind of 1-pixel anomalies that are // theoretically inevitable, or don't care if they do. At least they shouldn't // care - no one should be using the results WPF's grid layout to make // quantitative decisions; its job is to produce a reasonable display, not @@ -2597,7 +2597,7 @@ namespace Avalonia.Controls if (scale < 0.0) { // if one of the *-weights is Infinity, adjust the weights by mapping - // Infinty to 1.0 and everything else to 0.0: the infinite items share the + // Infinity to 1.0 and everything else to 0.0: the infinite items share the // available space equally, everyone else gets nothing. return (Double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; } @@ -2655,7 +2655,7 @@ namespace Avalonia.Controls private enum Flags { // - // the foolowing flags let grid tracking dirtiness in more granular manner: + // the following flags let grid tracking dirtiness in more granular manner: // * Valid???Structure flags indicate that elements were added or removed. // * Valid???Layout flags indicate that layout time portion of the information // stored on the objects should be updated. @@ -2684,7 +2684,7 @@ namespace Avalonia.Controls /// /// ShowGridLines property. This property is used mostly - /// for simplification of visual debuggig. When it is set + /// for simplification of visual debugging. When it is set /// to true grid lines are drawn to visualize location /// of grid lines. /// diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index f4185650bb..43b4908482 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -89,7 +89,7 @@ namespace Avalonia.Controls /// /// /// Note that the selection mode only applies to selections made via user interaction. - /// Multiple selections can be made programatically regardless of the value of this property. + /// Multiple selections can be made programmatically regardless of the value of this property. /// public new SelectionMode SelectionMode { diff --git a/src/Avalonia.Controls/NativeMenuItemSeparator.cs b/src/Avalonia.Controls/NativeMenuItemSeparator.cs index d3d3721c89..49b36e714d 100644 --- a/src/Avalonia.Controls/NativeMenuItemSeparator.cs +++ b/src/Avalonia.Controls/NativeMenuItemSeparator.cs @@ -3,7 +3,7 @@ namespace Avalonia.Controls { - [Obsolete("This class exists to maintain backwards compatiblity with existing code. Use NativeMenuItemSeparator instead")] + [Obsolete("This class exists to maintain backwards compatibility with existing code. Use NativeMenuItemSeparator instead")] public class NativeMenuItemSeperator : NativeMenuItemSeparator { } diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs index bb3c0288eb..8513dd1697 100644 --- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs +++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs @@ -25,7 +25,7 @@ namespace Avalonia.Platform /// /// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used. - /// This is because Windows Chrome can not be shown ontop of user content. + /// This is because Windows Chrome can not be shown on top of user content. /// PreferSystemChrome = 0x02, diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index dd839a0e9b..0f0dd7311d 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs @@ -27,7 +27,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// /// An implementation for platforms on which a popup can be - /// aritrarily positioned. + /// arbitrarily positioned. /// public class ManagedPopupPositioner : IPopupPositioner { diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index 0b716ec1ca..acb8e0f006 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.cs @@ -170,7 +170,7 @@ namespace Avalonia.Controls.Primitives } /// - /// Checks if the double value is not inifinity nor NaN. + /// Checks if the double value is not infinity nor NaN. /// /// The value. private static bool ValidateDouble(double value) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 34d3347434..402c34e3e5 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -310,7 +310,7 @@ namespace Avalonia.Controls.Primitives /// /// /// Note that the selection mode only applies to selections made via user interaction. - /// Multiple selections can be made programatically regardless of the value of this property. + /// Multiple selections can be made programmatically regardless of the value of this property. /// protected SelectionMode SelectionMode { diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls/Repeater/IElementFactory.cs index 6a899a6f26..f424ae29b7 100644 --- a/src/Avalonia.Controls/Repeater/IElementFactory.cs +++ b/src/Avalonia.Controls/Repeater/IElementFactory.cs @@ -46,7 +46,7 @@ namespace Avalonia.Controls } /// - /// A data template that supports creating and recyling elements for an . + /// A data template that supports creating and recycling elements for an . /// public interface IElementFactory : IDataTemplate { diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index fb2da09e73..01200e87e3 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -441,9 +441,9 @@ namespace Avalonia.Controls base.OnPropertyChanged(change); } - internal IControl GetElementImpl(int index, bool forceCreate, bool supressAutoRecycle) + internal IControl GetElementImpl(int index, bool forceCreate, bool suppressAutoRecycle) { - var element = _viewManager.GetElement(index, forceCreate, supressAutoRecycle); + var element = _viewManager.GetElement(index, forceCreate, suppressAutoRecycle); return element; } diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index cf2066b373..a7b6cf7f18 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -174,7 +174,7 @@ namespace Avalonia.Controls } else { - // We could not find a candiate. + // We could not find a candidate. _lastFocusedElement = null; } } diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index da3c2b15e6..1a90da5830 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -186,7 +186,7 @@ namespace Avalonia.Controls _expectedViewportShift.X + _layoutExtent.X - extent.X, _expectedViewportShift.Y + _layoutExtent.Y - extent.Y); - // We tolerate viewport imprecisions up to 1 pixel to avoid invaliding layout too much. + // We tolerate viewport imprecisions up to 1 pixel to avoid invalidating layout too much. if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1) { Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Expecting viewport shift of ({Shift})", diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 559edeb204..eee6216587 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -653,7 +653,7 @@ namespace Avalonia.Controls private void CalculatedPropertiesChanged() { // Pass old values of 0 here because we don't have the old values at this point, - // and it shouldn't matter as only the template uses these properies. + // and it shouldn't matter as only the template uses these properties. RaisePropertyChanged(HorizontalScrollBarMaximumProperty, 0, HorizontalScrollBarMaximum); RaisePropertyChanged(HorizontalScrollBarValueProperty, 0, HorizontalScrollBarValue); RaisePropertyChanged(HorizontalScrollBarViewportSizeProperty, 0, HorizontalScrollBarViewportSize); diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs index 6ae53a4d59..138a765b43 100644 --- a/src/Avalonia.Controls/Selection/SelectionModel.cs +++ b/src/Avalonia.Controls/Selection/SelectionModel.cs @@ -345,7 +345,7 @@ namespace Avalonia.Controls.Selection private protected override void OnSelectionChanged(IReadOnlyList deselectedItems) { // Note: We're *not* putting this in a using scope. A collection update is still in progress - // so the operation won't get commited 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. var update = BatchUpdate(); update.Operation.DeselectedItems = deselectedItems; diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs index 237bc2ce1d..12ae766052 100644 --- a/src/Avalonia.Controls/TickBar.cs +++ b/src/Avalonia.Controls/TickBar.cs @@ -193,7 +193,7 @@ namespace Avalonia.Controls /// /// TickBar will use ReservedSpaceProperty for left and right spacing (for horizontal orientation) or - /// top and bottom spacing (for vertical orienation). + /// top and bottom spacing (for vertical orientation). /// The space on both sides of TickBar is half of specified ReservedSpace. /// This property has type of . /// @@ -210,7 +210,7 @@ namespace Avalonia.Controls /// This function also draw selection-tick(s) if IsSelectionRangeEnabled is 'true' and /// SelectionStart and SelectionEnd are valid. /// - /// The primary ticks (for Mininum and Maximum value) height will be 100% of TickBar's render size (use Width or Height + /// The primary ticks (for Minimum and Maximum value) height will be 100% of TickBar's render size (use Width or Height /// depends on Placement property). /// /// The secondary ticks (all other ticks, including selection-tics) height will be 75% of TickBar's render size. @@ -221,7 +221,7 @@ namespace Avalonia.Controls { var size = new Size(Bounds.Width, Bounds.Height); var range = Maximum - Minimum; - var tickLen = 0.0d; // Height for Primary Tick (for Mininum and Maximum value) + var tickLen = 0.0d; // Height for Primary Tick (for Minimum and Maximum value) var tickLen2 = 0.0d; // Height for Secondary Tick var logicalToPhysical = 1.0; var startPoint = new Point(); @@ -285,7 +285,7 @@ namespace Avalonia.Controls tickLen2 = tickLen * 0.75; - // Invert direciton of the ticks + // Invert direction of the ticks if (IsDirectionReversed) { logicalToPhysical *= -1; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 7028dca769..5b0590c6f0 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -224,7 +224,7 @@ namespace Avalonia.Controls } /// - /// Gets the acheived that the platform was able to provide. + /// Gets the achieved that the platform was able to provide. /// public WindowTransparencyLevel ActualTransparencyLevel { diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 2b31cef8bd..72b6e4543b 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -264,7 +264,7 @@ namespace Avalonia.Controls } /// - /// Called durung the arrange pass to set the size of the window. + /// Called during the arrange pass to set the size of the window. /// /// The requested size of the window. /// The actual size of the window. diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 2b99c51472..122838f682 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -37,7 +37,7 @@ namespace Avalonia.Input BottomSize = BottomSide // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ - // We might enable them later, preferably, by loading pixmax direclty from theme with fallback image + // We might enable them later, preferably, by loading pixmax directly from theme with fallback image // SizeNorthWestSouthEast, // SizeNorthEastSouthWest, } diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs index 3858cc04f2..84a26a0cc3 100644 --- a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -78,7 +78,7 @@ namespace Avalonia.Input.GestureRecognizers // Arbitrary chosen value, probably need to move that to platform settings or something private const double ScrollStartDistance = 30; - // Pixels per second speed that is considered to be the stop of inertiall scroll + // Pixels per second speed that is considered to be the stop of inertial scroll private const double InertialScrollSpeedEnd = 5; public void PointerMoved(PointerEventArgs e) diff --git a/src/Avalonia.Input/ICommandSource.cs b/src/Avalonia.Input/ICommandSource.cs index ba2e8eed4e..eed71759d5 100644 --- a/src/Avalonia.Input/ICommandSource.cs +++ b/src/Avalonia.Input/ICommandSource.cs @@ -22,7 +22,7 @@ namespace Avalonia.Input /// - /// Bor the bheavior CanExecuteChanged + /// Bor the behavior CanExecuteChanged /// /// /// diff --git a/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs index d385f5b162..2b5d8958cc 100644 --- a/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs +++ b/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs @@ -22,11 +22,11 @@ namespace Avalonia.Input.TextInput /// event EventHandler TextViewVisualChanged; /// - /// Indicates if TextViewVisual is capable of displaying non-commited input on the cursor position + /// Indicates if TextViewVisual is capable of displaying non-committed input on the cursor position /// bool SupportsPreedit { get; } /// - /// Sets the non-commited input string + /// Sets the non-committed input string /// void SetPreeditText(string text); /// diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 4cd810af20..2497150b1a 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -143,7 +143,7 @@ namespace Avalonia.Interactivity /// The routed event. /// An describing the route. /// - /// Usually, calling is sufficent to raise a routed + /// Usually, calling is sufficient to raise a routed /// event, however there are situations in which the construction of the event args is expensive /// and should be avoided if there are no handlers for an event. In these cases you can call /// this method to build the event route and check the diff --git a/src/Avalonia.Layout/ElementManager.cs b/src/Avalonia.Layout/ElementManager.cs index 3f106708e6..c2f829d020 100644 --- a/src/Avalonia.Layout/ElementManager.cs +++ b/src/Avalonia.Layout/ElementManager.cs @@ -40,7 +40,7 @@ namespace Avalonia.Layout { if (IsVirtualizingContext) { - // We proactively clear elements laid out outside of the realizaton + // We proactively clear elements laid out outside of the realization // rect so that they are available for reuse during the current // measure pass. // This is useful during fast panning scenarios in which the realization diff --git a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs index eace54d2e0..63343fd1a7 100644 --- a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs +++ b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs @@ -22,7 +22,7 @@ namespace Avalonia.Layout private int _firstRealizedDataIndexInsideRealizationWindow = -1; private int _lastRealizedDataIndexInsideRealizationWindow = -1; - // If the scroll orientation is the same as the folow orientation + // If the scroll orientation is the same as the follow orientation // we will only have one line since we will never wrap. In that case // we do not want to align the line. We could potentially switch the // meaning of line alignment in this case, but I'll hold off on that @@ -429,7 +429,7 @@ namespace Avalonia.Layout // If we did not reach the top or bottom of the extent, we realized one // extra item before we knew we were outside the realization window. Do not - // account for that element in the indicies inside the realization window. + // account for that element in the indices inside the realization window. if (direction == GenerateDirection.Forward) { int dataCount = _context.ItemCount; diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 2e3408eca5..89efa6af0c 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -138,7 +138,7 @@ namespace Avalonia.Native { _nativeMenu = (__MicroComIAvnMenuProxy)__MicroComIAvnMenuProxy.Create(_factory); - _nativeMenu.Initialise(this, appMenuHolder, ""); + _nativeMenu.Initialize(this, appMenuHolder, ""); setMenu = true; } @@ -159,7 +159,7 @@ namespace Avalonia.Native { _nativeMenu = __MicroComIAvnMenuProxy.Create(_factory); - _nativeMenu.Initialise(this, menu, ""); + _nativeMenu.Initialize(this, menu, ""); setMenu = true; } diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs index f76e9450fc..0e6fdd2df0 100644 --- a/src/Avalonia.Native/IAvnMenu.cs +++ b/src/Avalonia.Native/IAvnMenu.cs @@ -96,7 +96,7 @@ namespace Avalonia.Native.Interop.Impl _menuItems.Remove(item); RemoveItem(item); - item.Deinitialise(); + item.Deinitialize(); item.Dispose(); } @@ -113,7 +113,7 @@ namespace Avalonia.Native.Interop.Impl { var result = CreateNew(factory, item); - result.Initialise(item); + result.Initialize(item); _menuItemLookup.Add(result.ManagedMenuItem, result); _menuItems.Insert(index, result); @@ -133,7 +133,7 @@ namespace Avalonia.Native.Interop.Impl return nativeItem; } - internal void Initialise(AvaloniaNativeMenuExporter exporter, NativeMenu managedMenu, string title) + internal void Initialize(AvaloniaNativeMenuExporter exporter, NativeMenu managedMenu, string title) { _exporter = exporter; ManagedMenu = managedMenu; @@ -150,7 +150,7 @@ namespace Avalonia.Native.Interop.Impl foreach (var item in _menuItems) { - item.Deinitialise(); + item.Deinitialize(); item.Dispose(); } } diff --git a/src/Avalonia.Native/IAvnMenuItem.cs b/src/Avalonia.Native/IAvnMenuItem.cs index 97838f8dea..ca99cbea4b 100644 --- a/src/Avalonia.Native/IAvnMenuItem.cs +++ b/src/Avalonia.Native/IAvnMenuItem.cs @@ -85,7 +85,7 @@ namespace Avalonia.Native.Interop.Impl SetAction(action, callback); } - internal void Initialise(NativeMenuItemBase nativeMenuItem) + internal void Initialize(NativeMenuItemBase nativeMenuItem) { ManagedMenuItem = nativeMenuItem; @@ -123,7 +123,7 @@ namespace Avalonia.Native.Interop.Impl } } - internal void Deinitialise() + internal void Deinitialize() { if (_subMenu != null) { @@ -150,7 +150,7 @@ namespace Avalonia.Native.Interop.Impl { _subMenu = __MicroComIAvnMenuProxy.Create(factory); - _subMenu.Initialise(exporter, item.Menu, item.Header); + _subMenu.Initialize(exporter, item.Menu, item.Header); SetSubMenu(_subMenu); } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs index 4d02f94cad..40891a700d 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs @@ -284,7 +284,7 @@ namespace Avalonia.Media.TextFormatting.Unicode // - U+0028 (Left Opening Parenthesis) // - U+005B (Opening Square Bracket) // - U+007B (Left Curly Bracket) - // See custom colums|rules in the text pair table. + // See custom columns|rules in the text pair table. // https://www.unicode.org/Public/13.0.0/ucd/auxiliary/LineBreakTest.html _lb30 = _alphaNumericCount > 0 && cls == LineBreakClass.OpenPunctuation diff --git a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs index 1e80eabfc8..742bb9c804 100644 --- a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs +++ b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs @@ -25,14 +25,14 @@ namespace Avalonia.Media.Transformation Matrix.CreateScale(decomposed.Scale); } - public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progres) + public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progress) { Matrix.Decomposed result = default; - result.Translate = InterpolateVectors(from.Translate, to.Translate, progres); - result.Scale = InterpolateVectors(from.Scale, to.Scale, progres); - result.Skew = InterpolateVectors(from.Skew, to.Skew, progres); - result.Angle = InterpolateScalars(from.Angle, to.Angle, progres); + result.Translate = InterpolateVectors(from.Translate, to.Translate, progress); + result.Scale = InterpolateVectors(from.Scale, to.Scale, progress); + result.Skew = InterpolateVectors(from.Skew, to.Skew, progress); + result.Angle = InterpolateScalars(from.Angle, to.Angle, progress); return result; } From 641b8887456c2518a52206d4d73a0115a5d52c85 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Mon, 12 Jul 2021 18:59:08 +0300 Subject: [PATCH 19/73] Invalidating measure on TransformedBounds update --- src/Avalonia.Controls/Primitives/AdornerLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index a397608aba..7c02832af6 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -154,7 +154,7 @@ namespace Avalonia.Controls.Primitives info.Subscription = adorned.GetObservable(TransformedBoundsProperty).Subscribe(x => { info.Bounds = x; - InvalidateArrange(); + InvalidateMeasure(); }); } } From ba369a9059ccee6379e2809d495815b606212944 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 14 Jul 2021 11:26:31 +0200 Subject: [PATCH 20/73] Fix auto-scaling on win32. - Adds a "resize reason" to platform `Resized` events - Which is used by the auto-sizing code to determine whether to reset `SizeToContent` - Also other improvements to the reset logic for `SizeToContent`: - Don't reset it if the size hasn't changed - Don't reset it if `CanResize == false` - Only reset the dimensions whose size has changed - Obsolete the `BeginAutoSizing`/`AutoSizing` feature in `WindowBase` --- native/Avalonia.Native/src/OSX/window.h | 21 +++ native/Avalonia.Native/src/OSX/window.mm | 21 ++- .../Offscreen/OffscreenTopLevelImpl.cs | 4 +- .../Platform/ITopLevelImpl.cs | 37 ++++- src/Avalonia.Controls/Platform/IWindowImpl.cs | 4 +- src/Avalonia.Controls/Primitives/PopupRoot.cs | 9 +- src/Avalonia.Controls/TopLevel.cs | 3 +- src/Avalonia.Controls/Window.cs | 142 +++++++++--------- src/Avalonia.Controls/WindowBase.cs | 27 +--- .../Remote/PreviewerWindowImpl.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 6 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 6 +- src/Avalonia.Native/PopupImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 12 +- src/Avalonia.Native/avn.idl | 13 +- src/Avalonia.X11/X11Window.cs | 16 +- .../FramebufferToplevelImpl.cs | 2 +- .../Wpf/WpfTopLevelImpl.cs | 6 +- src/Windows/Avalonia.Win32/PopupImpl.cs | 2 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 36 +++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 29 +++- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 +- .../TopLevelTests.cs | 2 +- .../WindowTests.cs | 120 ++++++++++++++- .../MockWindowingPlatform.cs | 7 +- 25 files changed, 377 insertions(+), 154 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index b1f64bca88..039d09f663 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -10,6 +10,8 @@ class WindowBaseImpl; -(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose; -(void) onClosed; -(AvnPixelSize) getPixelSize; +-(AvnPlatformResizeReason) getResizeReason; +-(void) setResizeReason:(AvnPlatformResizeReason)reason; @end @interface AutoFitContentView : NSView @@ -50,4 +52,23 @@ struct IWindowStateChanged virtual AvnWindowState WindowState () = 0; }; +class ResizeScope +{ +public: + ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason) + { + _view = view; + _restore = [view getResizeReason]; + [view setResizeReason:reason]; + } + + ~ResizeScope() + { + [_view setResizeReason:_restore]; + } +private: + AvnView* _Nonnull _view; + AvnPlatformResizeReason _restore; +}; + #endif /* window_h */ diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 8360795036..55fca743bf 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -275,9 +275,10 @@ public: } } - virtual HRESULT Resize(double x, double y) override + virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override { START_COM_CALL; + auto resizeBlock = ResizeScope(View, reason); @autoreleasepool { @@ -306,7 +307,7 @@ public: if(!_shown) { - BaseEvents->Resized(AvnSize{x,y}); + BaseEvents->Resized(AvnSize{x,y}, reason); } [StandardContainer setFrameSize:NSSize{x,y}]; @@ -1357,6 +1358,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent bool _lastKeyHandled; AvnPixelSize _lastPixelSize; NSObject* _renderTarget; + AvnPlatformResizeReason _resizeReason; } - (void)onClosed @@ -1468,7 +1470,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _lastPixelSize.Height = (int)fsize.height; [self updateRenderTarget]; - _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); + auto reason = [self inLiveResize] ? ResizeUser : _resizeReason; + _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason); } } @@ -1967,6 +1970,16 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } +- (AvnPlatformResizeReason)getResizeReason +{ + return _resizeReason; +} + +- (void)setResizeReason:(AvnPlatformResizeReason)reason +{ + _resizeReason = reason; +} + @end @@ -2373,7 +2386,7 @@ protected: return NSWindowStyleMaskBorderless; } - virtual HRESULT Resize(double x, double y) override + virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override { @autoreleasepool { diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 83470f161d..e2afbd3bdc 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Controls.Embedding.Offscreen set { _clientSize = value; - Resized?.Invoke(value); + Resized?.Invoke(value, PlatformResizeReason.Unspecified); } } @@ -49,7 +49,7 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action TransparencyLevelChanged { get; set; } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 6e53233898..f4f4d29168 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -3,11 +3,46 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Layout; using Avalonia.Rendering; using JetBrains.Annotations; namespace Avalonia.Platform { + /// + /// Describes the reason for a message. + /// + public enum PlatformResizeReason + { + /// + /// The resize reason is unknown or unspecified. + /// + Unspecified, + + /// + /// The resize was due to the user resizing the window, for example by dragging the + /// window frame. + /// + User, + + /// + /// The resize was initiated by the application, for example by setting one of the sizing- + /// related properties on such as or + /// . + /// + Application, + + /// + /// The resize was initiated by the layout system. + /// + Layout, + + /// + /// The resize was due to a change in DPI. + /// + DpiChange, + } + /// /// Defines a platform-specific top-level window implementation. /// @@ -57,7 +92,7 @@ namespace Avalonia.Platform /// /// Gets or sets a method called when the toplevel is resized. /// - Action Resized { get; set; } + Action Resized { get; set; } /// /// Gets or sets a method called when the toplevel's scaling changes. diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 8a1554d344..17a90eddfe 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -110,7 +110,9 @@ namespace Avalonia.Platform /// /// Sets the client size of the top level. /// - void Resize(Size clientSize); + /// The new client size. + /// The reason for the resize. + void Resize(Size clientSize, PlatformResizeReason reason = PlatformResizeReason.Application); /// /// Sets the client size of the top level. diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index da7352b77f..1a11778db2 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -161,12 +161,9 @@ namespace Avalonia.Controls.Primitives protected override sealed Size ArrangeSetBounds(Size size) { - using (BeginAutoSizing()) - { - _positionerParameters.Size = size; - UpdatePosition(); - return ClientSize; - } + _positionerParameters.Size = size; + UpdatePosition(); + return ClientSize; } } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 7028dca769..c5e7a7204f 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -380,7 +380,8 @@ namespace Avalonia.Controls /// Handles a resize notification from . /// /// The new client size. - protected virtual void HandleResized(Size clientSize) + /// The reason for the resize. + protected virtual void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; FrameSize = PlatformImpl.FrameSize; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ae314a33ce..132944a765 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -244,7 +244,7 @@ namespace Avalonia.Controls impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; - this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x)); + this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, PlatformResizeReason.Application)); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar); } @@ -258,6 +258,18 @@ namespace Avalonia.Controls /// /// Gets or sets a value indicating how the window will size itself to fit its content. /// + /// + /// If has a value other than , + /// is automatically set to + /// if a user resizes the window by using the resize grip or dragging the border. + /// + /// NOTE: Because of a limitation of X11, will be reset on X11 to + /// on any resize - including the resize that happens when + /// the window is first shown. This is because X11 resize notifications are asynchronous and + /// there is no way to know whether a resize came from the user or the layout system. To avoid + /// this, consider setting to false, which will disable user resizing + /// of the window. + /// public SizeToContent SizeToContent { get { return GetValue(SizeToContentProperty); } @@ -583,28 +595,23 @@ namespace Avalonia.Controls return; } - using (BeginAutoSizing()) - { - Renderer?.Stop(); + Renderer?.Stop(); - if (Owner is Window owner) - { - owner.RemoveChild(this); - } + if (Owner is Window owner) + { + owner.RemoveChild(this); + } - if (_children.Count > 0) + if (_children.Count > 0) + { + foreach (var child in _children.ToArray()) { - foreach (var child in _children.ToArray()) - { - child.child.Hide(); - } + child.child.Hide(); } - - Owner = null; - - PlatformImpl?.Hide(); } + Owner = null; + PlatformImpl?.Hide(); IsVisible = false; } @@ -675,29 +682,23 @@ namespace Avalonia.Controls if (initialSize != ClientSize) { - using (BeginAutoSizing()) - { - PlatformImpl?.Resize(initialSize); - } + PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout); } LayoutManager.ExecuteInitialLayoutPass(); - using (BeginAutoSizing()) + if (parent != null) { - if (parent != null) - { - PlatformImpl?.SetParent(parent.PlatformImpl); - } - - Owner = parent; - parent?.AddChild(this, false); - - SetWindowStartupLocation(Owner?.PlatformImpl); - - PlatformImpl?.Show(ShowActivated); - Renderer?.Start(); + PlatformImpl?.SetParent(parent.PlatformImpl); } + + Owner = parent; + parent?.AddChild(this, false); + + SetWindowStartupLocation(Owner?.PlatformImpl); + + PlatformImpl?.Show(ShowActivated); + Renderer?.Start(); OnOpened(EventArgs.Empty); } @@ -760,41 +761,34 @@ namespace Avalonia.Controls if (initialSize != ClientSize) { - using (BeginAutoSizing()) - { - PlatformImpl?.Resize(initialSize); - } + PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout); } LayoutManager.ExecuteInitialLayoutPass(); var result = new TaskCompletionSource(); - using (BeginAutoSizing()) - { - PlatformImpl?.SetParent(owner.PlatformImpl); - Owner = owner; - owner.AddChild(this, true); - - SetWindowStartupLocation(owner.PlatformImpl); - - PlatformImpl?.Show(ShowActivated); + PlatformImpl?.SetParent(owner.PlatformImpl); + Owner = owner; + owner.AddChild(this, true); - Renderer?.Start(); + SetWindowStartupLocation(owner.PlatformImpl); - Observable.FromEventPattern( - x => Closed += x, - x => Closed -= x) - .Take(1) - .Subscribe(_ => - { - owner.Activate(); - result.SetResult((TResult)(_dialogResult ?? default(TResult))); - }); + PlatformImpl?.Show(ShowActivated); - OnOpened(EventArgs.Empty); - } + Renderer?.Start(); + Observable.FromEventPattern( + x => Closed += x, + x => Closed -= x) + .Take(1) + .Subscribe(_ => + { + owner.Activate(); + result.SetResult((TResult)(_dialogResult ?? default(TResult))); + }); + + OnOpened(EventArgs.Empty); return result.Task; } @@ -937,11 +931,8 @@ namespace Avalonia.Controls protected sealed override Size ArrangeSetBounds(Size size) { - using (BeginAutoSizing()) - { - PlatformImpl?.Resize(size); - return ClientSize; - } + PlatformImpl?.Resize(size, PlatformResizeReason.Layout); + return ClientSize; } protected sealed override void HandleClosed() @@ -959,17 +950,32 @@ namespace Avalonia.Controls } /// - protected sealed override void HandleResized(Size clientSize) + protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason) { - if (!AutoSizing) + if (ClientSize == clientSize) + return; + + var sizeToContent = SizeToContent; + + // If auto-sizing is enabled, and the resize came from a user resize (or the reason was + // unspecified) then turn off auto-resizing for any window dimension that is not equal + // to the requested size. + if (sizeToContent != SizeToContent.Manual && + CanResize && + reason == PlatformResizeReason.Unspecified || + reason == PlatformResizeReason.User) { - SizeToContent = SizeToContent.Manual; + if (clientSize.Width != ClientSize.Width) + sizeToContent &= ~SizeToContent.Width; + if (clientSize.Height != ClientSize.Height) + sizeToContent &= ~SizeToContent.Height; + SizeToContent = sizeToContent; } Width = clientSize.Width; Height = clientSize.Height; - base.HandleResized(clientSize); + base.HandleResized(clientSize, reason); } /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 2b31cef8bd..e64eaec563 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -39,7 +39,6 @@ namespace Avalonia.Controls public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); - private int _autoSizing; private bool _hasExecutedInitialLayoutPass; private bool _isActive; private bool _ignoreVisibilityChange; @@ -95,10 +94,8 @@ namespace Avalonia.Controls public Screens Screens { get; private set; } - /// - /// Whether an auto-size operation is in progress. - /// - protected bool AutoSizing => _autoSizing > 0; + [Obsolete("No longer used. Always returns false.")] + protected bool AutoSizing => false; /// /// Gets or sets the owner of the window. @@ -172,20 +169,9 @@ namespace Avalonia.Controls } } - /// - /// Begins an auto-resize operation. - /// - /// A disposable used to finish the operation. - /// - /// When an auto-resize operation is in progress any resize events received will not be - /// cause the new size to be written to the and - /// properties. - /// - protected IDisposable BeginAutoSizing() - { - ++_autoSizing; - return Disposable.Create(() => --_autoSizing); - } + + [Obsolete("No longer used. Has no effect.")] + protected IDisposable BeginAutoSizing() => Disposable.Empty; /// /// Ensures that the window is initialized. @@ -219,7 +205,8 @@ namespace Avalonia.Controls /// Handles a resize notification from . /// /// The new client size. - protected override void HandleResized(Size clientSize) + /// The reason for the resize. + protected override void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; FrameSize = PlatformImpl.FrameSize; diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 787f44887f..946cb5f9a8 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -58,7 +58,7 @@ namespace Avalonia.DesignerSupport.Remote base.OnMessage(transport, obj); } - public void Resize(Size clientSize) + public void Resize(Size clientSize, PlatformResizeReason reason) { _transport.Send(new RequestViewportResizeMessage { diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index c8203686f9..1f5272fa5d 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -27,7 +27,7 @@ namespace Avalonia.DesignerSupport.Remote public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Func Closing { get; set; } public Action Closed { get; set; } @@ -54,7 +54,7 @@ namespace Avalonia.DesignerSupport.Remote PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, (_, size, __) => { - Resize(size); + Resize(size, PlatformResizeReason.Unspecified); })); } @@ -98,7 +98,7 @@ namespace Avalonia.DesignerSupport.Remote { } - public void Resize(Size clientSize) + public void Resize(Size clientSize, PlatformResizeReason reason) { } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 7f4b9face4..3a74b1b15c 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -47,7 +47,7 @@ namespace Avalonia.Headless public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public IRenderer CreateRenderer(IRenderRoot root) @@ -108,7 +108,7 @@ namespace Avalonia.Headless public Action Activated { get; set; } public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB"); public Size MaxClientSize { get; } = new Size(1920, 1280); - public void Resize(Size clientSize) + public void Resize(Size clientSize, PlatformResizeReason reason) { // Emulate X11 behavior here if (IsPopup) @@ -126,7 +126,7 @@ namespace Avalonia.Headless if (ClientSize != clientSize) { ClientSize = clientSize; - Resized?.Invoke(clientSize); + Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified); } } diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index c36675afcd..f357da3a4f 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -32,7 +32,7 @@ namespace Avalonia.Native private void MoveResize(PixelPoint position, Size size, double scaling) { Position = position; - Resize(size); + Resize(size, PlatformResizeReason.Layout); //TODO: We ignore the scaling override for now } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index ced9cea3a8..6010ce22c7 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -87,7 +87,7 @@ namespace Avalonia.Native var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) .FirstOrDefault(m => m.Bounds.Contains(Position)); - Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d)); + Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout); } public Size ClientSize @@ -146,7 +146,7 @@ namespace Avalonia.Native public Action LostFocus { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action Closed { get; set; } public IMouseDevice MouseDevice => _mouse; public abstract IPopupImpl CreatePopup(); @@ -186,13 +186,13 @@ namespace Avalonia.Native _parent.Paint?.Invoke(new Rect(0, 0, s.Width, s.Height)); } - void IAvnWindowBaseEvents.Resized(AvnSize* size) + void IAvnWindowBaseEvents.Resized(AvnSize* size, AvnPlatformResizeReason reason) { if (_parent?._native != null) { var s = new Size(size->Width, size->Height); _parent._savedLogicalSize = s; - _parent.Resized?.Invoke(s); + _parent.Resized?.Invoke(s, (PlatformResizeReason)reason); } } @@ -320,9 +320,9 @@ namespace Avalonia.Native } } - public void Resize(Size clientSize) + public void Resize(Size clientSize, PlatformResizeReason reason) { - _native.Resize(clientSize.Width, clientSize.Height); + _native.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason); } public IRenderer CreateRenderer(IRenderRoot root) diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 89e20463d8..19facf6e96 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -400,6 +400,15 @@ enum AvnExtendClientAreaChromeHints AvnDefaultChrome = AvnPreferSystemChrome, } +enum AvnPlatformResizeReason +{ + ResizeUnspecified, + ResizeUser, + ResizeApplication, + ResizeLayout, + ResizeDpiChange, +} + [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] interface IAvaloniaNativeFactory : IUnknown { @@ -438,7 +447,7 @@ interface IAvnWindowBase : IUnknown HRESULT GetFrameSize(AvnSize*ret); HRESULT GetScaling(double*ret); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); - HRESULT Resize(double width, double height); + HRESULT Resize(double width, double height, AvnPlatformResizeReason reason); HRESULT Invalidate(AvnRect rect); HRESULT BeginMoveDrag(); HRESULT BeginResizeDrag(AvnWindowEdge edge); @@ -492,7 +501,7 @@ interface IAvnWindowBaseEvents : IUnknown void Closed(); void Activated(); void Deactivated(); - void Resized([const] AvnSize& size); + void Resized([const] AvnSize& size, AvnPlatformResizeReason reason); void PositionChanged(AvnPoint position); void RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index bcb655245a..3debd7ff0b 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -336,7 +336,7 @@ namespace Avalonia.X11 public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } //TODO public Action ScalingChanged { get; set; } public Action Deactivated { get; set; } @@ -510,7 +510,7 @@ namespace Avalonia.X11 UpdateImePosition(); if (changedSize && !updatedSizeViaScaling && !_popup) - Resized?.Invoke(ClientSize); + Resized?.Invoke(ClientSize, PlatformResizeReason.Unspecified); Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); }, DispatcherPriority.Layout); @@ -565,7 +565,7 @@ namespace Avalonia.X11 UpdateImePosition(); SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize); if(!skipResize) - Resize(oldScaledSize, true); + Resize(oldScaledSize, true, PlatformResizeReason.DpiChange); return true; } @@ -616,7 +616,7 @@ namespace Avalonia.X11 { // Occurs once the window has been mapped, which is the earliest the extents // can be retrieved, so invoke event to force update of TopLevel.FrameSize. - Resized.Invoke(ClientSize); + Resized.Invoke(ClientSize, PlatformResizeReason.Unspecified); } if (atom == _x11.Atoms._NET_WM_STATE) @@ -862,19 +862,19 @@ namespace Avalonia.X11 } - public void Resize(Size clientSize) => Resize(clientSize, false); + public void Resize(Size clientSize, PlatformResizeReason reason) => Resize(clientSize, false, reason); public void Move(PixelPoint point) => Position = point; private void MoveResize(PixelPoint position, Size size, double scaling) { Move(position); _scalingOverride = scaling; UpdateScaling(true); - Resize(size, true); + Resize(size, true, PlatformResizeReason.Layout); } PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling)); - void Resize(Size clientSize, bool force) + void Resize(Size clientSize, bool force, PlatformResizeReason reason) { if (!force && clientSize == ClientSize) return; @@ -891,7 +891,7 @@ namespace Avalonia.X11 if (force || !_wasMappedAtLeastOnce || (_popup && needImmediatePopupResize)) { _realSize = pixelSize; - Resized?.Invoke(ClientSize); + Resized?.Invoke(ClientSize, reason); } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 5a1da9058a..b097e0917f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -69,7 +69,7 @@ namespace Avalonia.LinuxFramebuffer public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action TransparencyLevelChanged { get; set; } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 73e46b9e13..d36db107e3 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -44,7 +44,7 @@ namespace Avalonia.Win32.Interop.Wpf ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); } - protected override void HandleResized(Size clientSize) + protected override void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; LayoutManager.ExecuteLayoutPass(); @@ -114,7 +114,7 @@ namespace Avalonia.Win32.Interop.Wpf if (_finalSize == _previousSize) return finalSize; _previousSize = _finalSize; - _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize()); + _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize(), PlatformResizeReason.Unspecified); return base.ArrangeOverride(finalSize); } @@ -236,7 +236,7 @@ namespace Avalonia.Win32.Interop.Wpf Action ITopLevelImpl.Input { get; set; } //TODO Action ITopLevelImpl.Paint { get; set; } - Action ITopLevelImpl.Resized { get; set; } + Action ITopLevelImpl.Resized { get; set; } Action ITopLevelImpl.ScalingChanged { get; set; } Action ITopLevelImpl.TransparencyLevelChanged { get; set; } diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index dd3fd1342c..800f884c45 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -135,7 +135,7 @@ namespace Avalonia.Win32 private void MoveResize(PixelPoint position, Size size, double scaling) { Move(position); - Resize(size); + Resize(size, PlatformResizeReason.Layout); //TODO: We ignore the scaling override for now } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 8e755a33bc..d163b3d068 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -2,9 +2,10 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Avalonia.Controls; -using Avalonia.Controls.Platform; +using Avalonia.Controls.Remote; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Platform; using Avalonia.Win32.Input; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -18,7 +19,6 @@ namespace Avalonia.Win32 { const double wheelDelta = 120.0; uint timestamp = unchecked((uint)GetMessageTime()); - RawInputEventArgs e = null; var shouldTakeFocus = false; @@ -94,14 +94,19 @@ namespace Avalonia.Win32 var newDisplayRect = Marshal.PtrToStructure(lParam); _scaling = dpi / 96.0; ScalingChanged?.Invoke(_scaling); - SetWindowPos(hWnd, - IntPtr.Zero, - newDisplayRect.left, - newDisplayRect.top, - newDisplayRect.right - newDisplayRect.left, - newDisplayRect.bottom - newDisplayRect.top, - SetWindowPosFlags.SWP_NOZORDER | - SetWindowPosFlags.SWP_NOACTIVATE); + + using (SetResizeReason(PlatformResizeReason.DpiChange)) + { + SetWindowPos(hWnd, + IntPtr.Zero, + newDisplayRect.left, + newDisplayRect.top, + newDisplayRect.right - newDisplayRect.left, + newDisplayRect.bottom - newDisplayRect.top, + SetWindowPosFlags.SWP_NOZORDER | + SetWindowPosFlags.SWP_NOACTIVATE); + } + return IntPtr.Zero; } @@ -364,6 +369,11 @@ namespace Avalonia.Win32 return IntPtr.Zero; } + + case WindowsMessage.WM_ENTERSIZEMOVE: + _resizeReason = PlatformResizeReason.User; + break; + case WindowsMessage.WM_SIZE: { using(NonPumpingSyncContext.Use()) @@ -379,7 +389,7 @@ namespace Avalonia.Win32 size == SizeCommand.Maximized)) { var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); - Resized(clientSize / RenderScaling); + Resized(clientSize / RenderScaling, _resizeReason); } var windowState = size == SizeCommand.Maximized ? @@ -403,6 +413,10 @@ namespace Avalonia.Win32 return IntPtr.Zero; } + case WindowsMessage.WM_EXITSIZEMOVE: + _resizeReason = PlatformResizeReason.Unspecified; + break; + case WindowsMessage.WM_MOVE: { PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 646a6f5739..89ff58c7e2 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -53,6 +53,7 @@ namespace Avalonia.Win32 private double _extendTitleBarHint = -1; private bool _isUsingComposition; private IBlurHost _blurHost; + private PlatformResizeReason _resizeReason; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; @@ -160,7 +161,7 @@ namespace Avalonia.Win32 public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } @@ -482,7 +483,7 @@ namespace Avalonia.Win32 : new ImmediateRenderer(root); } - public void Resize(Size value) + public void Resize(Size value, PlatformResizeReason reason) { int requestedClientWidth = (int)(value.Width * RenderScaling); int requestedClientHeight = (int)(value.Height * RenderScaling); @@ -494,6 +495,7 @@ namespace Avalonia.Win32 { GetWindowRect(_hwnd, out var windowRect); + using var scope = SetResizeReason(reason); SetWindowPos( _hwnd, IntPtr.Zero, @@ -930,7 +932,7 @@ namespace Avalonia.Win32 _offScreenMargin = new Thickness(); _extendedMargins = new Thickness(); - Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling)); + Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout); } if(!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && @@ -1311,6 +1313,13 @@ namespace Avalonia.Win32 /// public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0); + private ResizeReasonScope SetResizeReason(PlatformResizeReason reason) + { + var old = _resizeReason; + _resizeReason = reason; + return new ResizeReasonScope(this, old); + } + private struct SavedWindowInfo { public WindowStyles Style { get; set; } @@ -1325,5 +1334,19 @@ namespace Avalonia.Win32 public SystemDecorations Decorations; public bool IsFullScreen; } + + private struct ResizeReasonScope : IDisposable + { + private readonly WindowImpl _owner; + private readonly PlatformResizeReason _restore; + + public ResizeReasonScope(WindowImpl owner, PlatformResizeReason restore) + { + _owner = owner; + _restore = restore; + } + + public void Dispose() => _owner._resizeReason = _restore; + } } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 0371a7759a..bf990a51ce 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -96,7 +96,7 @@ namespace Avalonia.iOS public IEnumerable Surfaces { get; set; } public Action Input { get; set; } public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action TransparencyLevelChanged { get; set; } public Action Closed { get; set; } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 6b30aed257..9c2d760733 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests // The user has resized the window, so we can no longer auto-size. var target = new TestTopLevel(impl.Object); - impl.Object.Resized(new Size(100, 200)); + impl.Object.Resized(new Size(100, 200), PlatformResizeReason.Unspecified); Assert.Equal(100, target.Width); Assert.Equal(200, target.Height); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 6b9921d83d..eb128ef038 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -663,10 +663,11 @@ namespace Avalonia.Controls.UnitTests var clientSize = new Size(200, 200); var maxClientSize = new Size(480, 480); - windowImpl.Setup(x => x.Resize(It.IsAny())).Callback(size => + windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) + .Callback((size, reason) => { clientSize = size.Constrain(maxClientSize); - windowImpl.Object.Resized?.Invoke(clientSize); + windowImpl.Object.Resized?.Invoke(clientSize, reason); }); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); @@ -739,6 +740,36 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void SizeToContent_Should_Not_Be_Lost_On_Scaling_Change() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var child = new Canvas + { + Width = 209, + Height = 117, + }; + + var target = new Window() + { + SizeToContent = SizeToContent.WidthAndHeight, + Content = child + }; + + Show(target); + + // Size before and after DPI change is a real-world example, with size after DPI + // change coming from Win32 WM_DPICHANGED. + target.PlatformImpl.ScalingChanged(1.5); + target.PlatformImpl.Resized( + new Size(210.66666666666666, 118.66666666666667), + PlatformResizeReason.DpiChange); + + Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent); + } + } + [Fact] public void Width_Height_Should_Be_Updated_When_SizeToContent_Is_WidthAndHeight() { @@ -791,8 +822,91 @@ namespace Avalonia.Controls.UnitTests target.LayoutManager.ExecuteLayoutPass(); var windowImpl = Mock.Get(target.PlatformImpl); - windowImpl.Verify(x => x.Resize(new Size(410, 800))); + windowImpl.Verify(x => x.Resize(new Size(410, 800), PlatformResizeReason.Application)); + Assert.Equal(410, target.Width); + } + } + + + [Fact] + public void User_Resize_Of_Window_Width_Should_Reset_SizeToContent() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window() + { + SizeToContent = SizeToContent.WidthAndHeight, + Content = new Canvas + { + Width = 400, + Height = 800, + }, + }; + + Show(target); + Assert.Equal(400, target.Width); + Assert.Equal(800, target.Height); + + target.PlatformImpl.Resized(new Size(410, 800), PlatformResizeReason.User); + Assert.Equal(410, target.Width); + Assert.Equal(800, target.Height); + Assert.Equal(SizeToContent.Height, target.SizeToContent); + } + } + + [Fact] + public void User_Resize_Of_Window_Height_Should_Reset_SizeToContent() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window() + { + SizeToContent = SizeToContent.WidthAndHeight, + Content = new Canvas + { + Width = 400, + Height = 800, + }, + }; + + Show(target); + Assert.Equal(400, target.Width); + Assert.Equal(800, target.Height); + + target.PlatformImpl.Resized(new Size(400, 810), PlatformResizeReason.User); + + Assert.Equal(400, target.Width); + Assert.Equal(810, target.Height); + Assert.Equal(SizeToContent.Width, target.SizeToContent); + } + } + + [Fact] + public void Window_Resize_Should_Not_Reset_SizeToContent_If_CanResize_False() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window() + { + SizeToContent = SizeToContent.WidthAndHeight, + CanResize = false, + Content = new Canvas + { + Width = 400, + Height = 800, + }, + }; + + Show(target); + Assert.Equal(400, target.Width); + Assert.Equal(800, target.Height); + + target.PlatformImpl.Resized(new Size(410, 810), PlatformResizeReason.Unspecified); + + Assert.Equal(400, target.Width); + Assert.Equal(800, target.Height); + Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent); } } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 8a24a8366f..1beafb011c 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -52,10 +52,11 @@ namespace Avalonia.UnitTests windowImpl.Object.PositionChanged?.Invoke(x); }); - windowImpl.Setup(x => x.Resize(It.IsAny())).Callback(x => + windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) + .Callback((x, y) => { clientSize = x.Constrain(s_screenSize); - windowImpl.Object.Resized?.Invoke(clientSize); + windowImpl.Object.Resized?.Invoke(clientSize, y); }); windowImpl.Setup(x => x.Show(true)).Callback(() => @@ -75,7 +76,7 @@ namespace Avalonia.UnitTests { clientSize = size.Constrain(s_screenSize); popupImpl.Object.PositionChanged?.Invoke(pos); - popupImpl.Object.Resized?.Invoke(clientSize); + popupImpl.Object.Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified); }); var positioner = new ManagedPopupPositioner(positionerHelper); From 21a96b16f588eb78d9682ffdcfed158a15392dea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 14 Jul 2021 11:28:51 +0200 Subject: [PATCH 21/73] Update ApiCompatBaseline.txt --- src/Avalonia.Controls/ApiCompatBaseline.txt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index bb133900d9..78fe2fc6d1 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -29,13 +29,27 @@ MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChang MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.TopLevel.HandleResized(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.Window.HandleResized(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.WindowBase.HandleResized(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Action Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the contract but not in the implementation. +MembersMustExist : Member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 39 +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. +Total Issues: 53 From 8b3afa18d098e39e85b472dc02ac6d79cca869b6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 14 Jul 2021 11:44:36 +0200 Subject: [PATCH 22/73] Update mobile backends with changed API. --- .../Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs | 4 ++-- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index a72742580c..0afb1db141 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -67,7 +67,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public Action Paint { get; set; } - public Action Resized { get; set; } + public Action Resized { get; set; } public Action ScalingChanged { get; set; } @@ -134,7 +134,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform protected virtual void OnResized(Size size) { - Resized?.Invoke(size); + Resized?.Invoke(size, PlatformResizeReason.Unspecified); } class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index bf990a51ce..5bb2f64879 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -127,7 +127,7 @@ namespace Avalonia.iOS public override void LayoutSubviews() { - _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize); + _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize, PlatformResizeReason.Layout); base.LayoutSubviews(); } From a9affd64bf67d2ff39e3853b7f0adc168be71f3f Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 15 Jul 2021 15:46:47 +0200 Subject: [PATCH 23/73] Add support for Flyouts, ToolTips & ContextMenus --- .../Diagnostics/IPopupHostProvider.cs | 23 +++++ .../Diagnostics/ToolTipDiagnostics.cs | 12 +++ src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 25 ++++- src/Avalonia.Controls/ToolTip.cs | 40 +++++--- .../Diagnostics/ViewModels/TreeNode.cs | 11 ++- .../Diagnostics/ViewModels/VisualTreeNode.cs | 97 ++++++++++++++----- 6 files changed, 163 insertions(+), 45 deletions(-) create mode 100644 src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs create mode 100644 src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs diff --git a/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs new file mode 100644 index 0000000000..11d3b1792a --- /dev/null +++ b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs @@ -0,0 +1,23 @@ +using System; +using Avalonia.Controls.Primitives; + +#nullable enable + +namespace Avalonia.Controls.Diagnostics +{ + /// + /// Diagnostics interface to retrieve an associated . + /// + public interface IPopupHostProvider + { + /// + /// The popup host. + /// + IPopupHost? PopupHost { get; } + + /// + /// Raised when the popup host changes. + /// + event Action? PopupHostChanged; + } +} diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs new file mode 100644 index 0000000000..58174b1039 --- /dev/null +++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs @@ -0,0 +1,12 @@ +#nullable enable + +namespace Avalonia.Controls.Diagnostics +{ + /// + /// Helper class to provide some diagnostics insides into . + /// + public static class ToolTipDiagnostics + { + public static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty; + } +} diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index e4b68c62fd..6b72b8c887 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -1,16 +1,16 @@ using System; using System.ComponentModel; +using Avalonia.Controls.Diagnostics; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; -using Avalonia.Rendering; #nullable enable namespace Avalonia.Controls.Primitives { - public abstract class FlyoutBase : AvaloniaObject + public abstract class FlyoutBase : AvaloniaObject, IPopupHostProvider { static FlyoutBase() { @@ -55,6 +55,7 @@ namespace Avalonia.Controls.Primitives private Rect? _enlargedPopupRect; private PixelRect? _enlargePopupRectScreenPixelRect; private IDisposable? _transientDisposable; + private Action? _popupHostChangedHandler; protected Popup? Popup { get; private set; } @@ -94,6 +95,14 @@ namespace Avalonia.Controls.Primitives private set => SetAndRaise(TargetProperty, ref _target, value); } + IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host; + + event Action? IPopupHostProvider.PopupHostChanged + { + add => _popupHostChangedHandler += value; + remove => _popupHostChangedHandler -= value; + } + public event EventHandler? Closed; public event EventHandler? Closing; public event EventHandler? Opened; @@ -322,9 +331,11 @@ namespace Avalonia.Controls.Primitives private void InitPopup() { - Popup = new Popup(); - Popup.WindowManagerAddShadowHint = false; - Popup.IsLightDismissEnabled = true; + Popup = new Popup + { + WindowManagerAddShadowHint = false, + IsLightDismissEnabled = true + }; Popup.Opened += OnPopupOpened; Popup.Closed += OnPopupClosed; @@ -333,11 +344,15 @@ namespace Avalonia.Controls.Primitives private void OnPopupOpened(object sender, EventArgs e) { IsOpen = true; + + _popupHostChangedHandler?.Invoke(Popup!.Host); } private void OnPopupClosed(object sender, EventArgs e) { HideCore(); + + _popupHostChangedHandler?.Invoke(null); } private void PositionPopup(bool showAtPointer) diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index ab507d07a2..ab310d60ef 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -1,9 +1,8 @@ #nullable enable using System; -using System.Reactive.Linq; +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; -using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -17,7 +16,7 @@ namespace Avalonia.Controls /// assigning the content that you want displayed. /// [PseudoClasses(":open")] - public class ToolTip : ContentControl + public class ToolTip : ContentControl, IPopupHostProvider { /// /// Defines the ToolTip.Tip attached property. @@ -61,7 +60,8 @@ namespace Avalonia.Controls internal static readonly AttachedProperty ToolTipProperty = AvaloniaProperty.RegisterAttached("ToolTip"); - private IPopupHost? _popup; + private IPopupHost? _popupHost; + private Action? _popupHostChangedHandler; /// /// Initializes static members of the class. @@ -251,35 +251,45 @@ namespace Avalonia.Controls tooltip.RecalculatePosition(control); } + + IPopupHost? IPopupHostProvider.PopupHost => _popupHost; + + event Action? IPopupHostProvider.PopupHostChanged + { + add => _popupHostChangedHandler += value; + remove => _popupHostChangedHandler -= value; + } internal void RecalculatePosition(Control control) { - _popup?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control))); + _popupHost?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control))); } private void Open(Control control) { Close(); - _popup = OverlayPopupHost.CreatePopupHost(control, null); - _popup.SetChild(this); - ((ISetLogicalParent)_popup).SetParent(control); + _popupHost = OverlayPopupHost.CreatePopupHost(control, null); + _popupHost.SetChild(this); + ((ISetLogicalParent)_popupHost).SetParent(control); - _popup.ConfigurePosition(control, GetPlacement(control), + _popupHost.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control))); - WindowManagerAddShadowHintChanged(_popup, false); + WindowManagerAddShadowHintChanged(_popupHost, false); - _popup.Show(); + _popupHost.Show(); + _popupHostChangedHandler?.Invoke(_popupHost); } private void Close() { - if (_popup != null) + if (_popupHost != null) { - _popup.SetChild(null); - _popup.Dispose(); - _popup = null; + _popupHost.SetChild(null); + _popupHost.Dispose(); + _popupHost = null; + _popupHostChangedHandler?.Invoke(null); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index f4c04dbca6..9667751e54 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Reactive; using System.Reactive.Linq; using Avalonia.Controls; +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.LogicalTree; using Avalonia.Media; @@ -19,12 +20,11 @@ namespace Avalonia.Diagnostics.ViewModels protected TreeNode(IVisual visual, TreeNode? parent) { + _classes = string.Empty; Parent = parent; Type = visual.GetType().Name; Visual = visual; - _classes = string.Empty; - - FontWeight = Visual is TopLevel || Visual is Popup ? FontWeight.Bold : FontWeight.Normal; + FontWeight = IsRoot ? FontWeight.Bold : FontWeight.Normal; if (visual is IControl control) { @@ -56,6 +56,11 @@ namespace Avalonia.Diagnostics.ViewModels } } + private bool IsRoot => Visual is TopLevel || + Visual is Popup || + Visual is ContextMenu || + Visual is IPopupHost; + public FontWeight FontWeight { get; } public abstract TreeNodeCollection Children diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index b9981babf7..6d803a1a7b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -1,5 +1,10 @@ using System; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Security.Cryptography.X509Certificates; using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.Styling; using Avalonia.VisualTree; @@ -13,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels { Children = new VisualTreeNodeCollection(this, visual); - if ((Visual is IStyleable styleable)) + if (Visual is IStyleable styleable) { IsInTemplate = styleable.TemplatedParent != null; } @@ -25,13 +30,15 @@ namespace Avalonia.Diagnostics.ViewModels public static VisualTreeNode[] Create(object control) { - return control is IVisual visual ? new[] { new VisualTreeNode(visual, null) } : Array.Empty(); + return control is IVisual visual ? + new[] { new VisualTreeNode(visual, null) } : + Array.Empty(); } internal class VisualTreeNodeCollection : TreeNodeCollection { private readonly IVisual _control; - private IDisposable? _subscription; + private readonly CompositeDisposable _subscriptions = new CompositeDisposable(2); public VisualTreeNodeCollection(TreeNode owner, IVisual control) : base(owner) @@ -41,33 +48,79 @@ namespace Avalonia.Diagnostics.ViewModels public override void Dispose() { - _subscription?.Dispose(); + _subscriptions.Dispose(); } - protected override void Initialize(AvaloniaList nodes) + private static IObservable? GetHostedPopupRootObservable(IVisual visual) { - if (_control is Popup p) + static IObservable GetPopupHostObservable(IPopupHostProvider popupHostProvider) { - _subscription = p.GetObservable(Popup.ChildProperty).Subscribe(child => - { - if (child != null) - { - nodes.Add(new VisualTreeNode(child, Owner)); - } - else - { - nodes.Clear(); - } - - }); + return Observable.FromEvent( + x => popupHostProvider.PopupHostChanged += x, + x => popupHostProvider.PopupHostChanged -= x) + .StartWith(popupHostProvider.PopupHost) + .Select(x => x is IControl c ? c : null); } - else + + return visual switch { - _subscription = _control.VisualChildren.ForEachItem( + Popup p => p.GetObservable(Popup.ChildProperty), + Control c => Observable.CombineLatest( + c.GetObservable(Control.ContextFlyoutProperty), + c.GetObservable(Control.ContextMenuProperty), + c.GetObservable(FlyoutBase.AttachedFlyoutProperty), + c.GetObservable(ToolTipDiagnostics.ToolTipProperty), + (ContextFlyout, ContextMenu, AttachedFlyout, ToolTip) => + { + if (ContextMenu != null) + { + //Note: ContextMenus are special since all the items are added as visual children. + //So we don't need to go via Popup + return Observable.Return(ContextMenu); + } + + if ((ContextFlyout ?? (IPopupHostProvider?) AttachedFlyout ?? ToolTip) is { } popupHostProvider) + { + return GetPopupHostObservable(popupHostProvider); + } + + return Observable.Return(null); + }) + .Switch(), + _ => null + }; + } + + protected override void Initialize(AvaloniaList nodes) + { + _subscriptions.Clear(); + + if (GetHostedPopupRootObservable(_control) is { } popupRootObservable) + { + VisualTreeNode? childNode = null; + + _subscriptions.Add( + popupRootObservable + .Subscribe(root => + { + if (root != null) + { + childNode = new VisualTreeNode(root, Owner); + + nodes.Add(childNode); + } + else if (childNode != null) + { + nodes.Remove(childNode); + } + })); + } + + _subscriptions.Add( + _control.VisualChildren.ForEachItem( (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)), (i, item) => nodes.RemoveAt(i), - () => nodes.Clear()); - } + () => nodes.Clear())); } } } From 4d9131091e04db53b53622238ade11f635a751e1 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 15 Jul 2021 16:19:56 +0200 Subject: [PATCH 24/73] Add custom names to PopupRoot --- src/Avalonia.Controls/Primitives/Popup.cs | 17 ++++- .../Diagnostics/ViewModels/TreeNode.cs | 6 +- .../Diagnostics/ViewModels/VisualTreeNode.cs | 72 ++++++++++++------- 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index b445de0472..bdc71f9a62 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -1,6 +1,6 @@ using System; -using System.Linq; using System.Reactive.Disposables; +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Input; @@ -17,7 +17,7 @@ namespace Avalonia.Controls.Primitives /// /// Displays a popup window. /// - public class Popup : Control, IVisualTreeHost + public class Popup : Control, IVisualTreeHost, IPopupHostProvider { public static readonly StyledProperty WindowManagerAddShadowHintProperty = AvaloniaProperty.Register(nameof(WindowManagerAddShadowHint), true); @@ -133,6 +133,7 @@ namespace Avalonia.Controls.Primitives private bool _ignoreIsOpenChanged; private PopupOpenState? _openState; private IInputElement _overlayInputPassThroughElement; + private Action? _popupHostChangedHandler; /// /// Initializes static members of the class. @@ -348,6 +349,14 @@ namespace Avalonia.Controls.Primitives /// IVisual? IVisualTreeHost.Root => _openState?.PopupHost.HostedVisualTreeRoot; + IPopupHost? IPopupHostProvider.PopupHost => Host; + + event Action? IPopupHostProvider.PopupHostChanged + { + add => _popupHostChangedHandler += value; + remove => _popupHostChangedHandler -= value; + } + /// /// Opens the popup. /// @@ -479,6 +488,8 @@ namespace Avalonia.Controls.Primitives } Opened?.Invoke(this, EventArgs.Empty); + + _popupHostChangedHandler?.Invoke(Host); } /// @@ -581,6 +592,8 @@ namespace Avalonia.Controls.Primitives _openState.Dispose(); _openState = null; + _popupHostChangedHandler?.Invoke(null); + using (BeginIgnoringIsOpen()) { IsOpen = false; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 9667751e54..4b957c2382 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; using System.Collections.Specialized; using System.Reactive; using System.Reactive.Linq; using Avalonia.Controls; -using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.LogicalTree; using Avalonia.Media; @@ -18,11 +16,11 @@ namespace Avalonia.Diagnostics.ViewModels private string _classes; private bool _isExpanded; - protected TreeNode(IVisual visual, TreeNode? parent) + protected TreeNode(IVisual visual, TreeNode? parent, string? customName = null) { _classes = string.Empty; Parent = parent; - Type = visual.GetType().Name; + Type = customName ?? visual.GetType().Name; Visual = visual; FontWeight = IsRoot ? FontWeight.Bold : FontWeight.Normal; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index 6d803a1a7b..f03f16d18f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -1,7 +1,7 @@ using System; +using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Security.Cryptography.X509Certificates; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; @@ -13,18 +13,15 @@ namespace Avalonia.Diagnostics.ViewModels { internal class VisualTreeNode : TreeNode { - public VisualTreeNode(IVisual visual, TreeNode? parent) - : base(visual, parent) + public VisualTreeNode(IVisual visual, TreeNode? parent, string? customName = null) + : base(visual, parent, customName) { Children = new VisualTreeNodeCollection(this, visual); - if (Visual is IStyleable styleable) - { - IsInTemplate = styleable.TemplatedParent != null; - } + if (Visual is IStyleable styleable) IsInTemplate = styleable.TemplatedParent != null; } - public bool IsInTemplate { get; private set; } + public bool IsInTemplate { get; } public override TreeNodeCollection Children { get; } @@ -51,20 +48,30 @@ namespace Avalonia.Diagnostics.ViewModels _subscriptions.Dispose(); } - private static IObservable? GetHostedPopupRootObservable(IVisual visual) + private static IObservable? GetHostedPopupRootObservable(IVisual visual) { - static IObservable GetPopupHostObservable(IPopupHostProvider popupHostProvider) + static IObservable GetPopupHostObservable( + IPopupHostProvider popupHostProvider, + string? providerName = null) { return Observable.FromEvent( x => popupHostProvider.PopupHostChanged += x, x => popupHostProvider.PopupHostChanged -= x) .StartWith(popupHostProvider.PopupHost) - .Select(x => x is IControl c ? c : null); + .Select(popupHost => + { + if (popupHost is IControl control) + return new PopupRoot( + control, + providerName != null ? $"{providerName} ({control.GetType().Name})" : null); + + return (PopupRoot?)null; + }); } return visual switch { - Popup p => p.GetObservable(Popup.ChildProperty), + Popup p => GetPopupHostObservable(p), Control c => Observable.CombineLatest( c.GetObservable(Control.ContextFlyoutProperty), c.GetObservable(Control.ContextMenuProperty), @@ -73,18 +80,20 @@ namespace Avalonia.Diagnostics.ViewModels (ContextFlyout, ContextMenu, AttachedFlyout, ToolTip) => { if (ContextMenu != null) - { //Note: ContextMenus are special since all the items are added as visual children. //So we don't need to go via Popup - return Observable.Return(ContextMenu); - } + return Observable.Return(new PopupRoot(ContextMenu)); - if ((ContextFlyout ?? (IPopupHostProvider?) AttachedFlyout ?? ToolTip) is { } popupHostProvider) - { - return GetPopupHostObservable(popupHostProvider); - } + if (ContextFlyout != null) + return GetPopupHostObservable(ContextFlyout, "ContextFlyout"); + + if (AttachedFlyout != null) + return GetPopupHostObservable(AttachedFlyout, "AttachedFlyout"); + + if (ToolTip != null) + return GetPopupHostObservable(ToolTip, "ToolTip"); - return Observable.Return(null); + return Observable.Return(null); }) .Switch(), _ => null @@ -101,18 +110,21 @@ namespace Avalonia.Diagnostics.ViewModels _subscriptions.Add( popupRootObservable - .Subscribe(root => + .Subscribe(popupRoot => { - if (root != null) + if (popupRoot != null) { - childNode = new VisualTreeNode(root, Owner); + childNode = new VisualTreeNode( + popupRoot.Value.Root, + Owner, + popupRoot.Value.CustomName); nodes.Add(childNode); } else if (childNode != null) { nodes.Remove(childNode); - } + }s })); } @@ -122,6 +134,18 @@ namespace Avalonia.Diagnostics.ViewModels (i, item) => nodes.RemoveAt(i), () => nodes.Clear())); } + + private struct PopupRoot + { + public PopupRoot(IControl root, string? customName = null) + { + Root = root; + CustomName = customName; + } + + public IControl Root { get; } + public string? CustomName { get; } + } } } } From dfc5669507d6910c510a4a7619f377e0b02a5100 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 15 Jul 2021 16:20:22 +0200 Subject: [PATCH 25/73] Typo --- .../Diagnostics/ViewModels/VisualTreeNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index f03f16d18f..5524d06f1b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -124,7 +124,7 @@ namespace Avalonia.Diagnostics.ViewModels else if (childNode != null) { nodes.Remove(childNode); - }s + } })); } From 650ae63bd641868decb2bce426a21aa88ad94496 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 15 Jul 2021 17:46:00 +0200 Subject: [PATCH 26/73] make "ctrl+shift+click" work --- src/Avalonia.Controls/ContextMenu.cs | 17 ++++++- .../Diagnostics/ViewModels/VisualTreeNode.cs | 4 +- .../Diagnostics/Views/MainWindow.xaml.cs | 46 +++++++++++++++---- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index ead5b0d9f3..0c8fc31df1 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; - +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; @@ -21,7 +21,7 @@ namespace Avalonia.Controls /// /// A control context menu. /// - public class ContextMenu : MenuBase, ISetterValue + public class ContextMenu : MenuBase, ISetterValue, IPopupHostProvider { /// /// Defines the property. @@ -82,6 +82,7 @@ namespace Avalonia.Controls private Popup? _popup; private List? _attachedControls; private IInputElement? _previousFocus; + private Action? _popupHostChangedHandler; /// /// Initializes a new instance of the class. @@ -304,6 +305,14 @@ namespace Avalonia.Controls } } + IPopupHost? IPopupHostProvider.PopupHost => _popup?.Host; + + event Action? IPopupHostProvider.PopupHostChanged + { + add => _popupHostChangedHandler += value; + remove => _popupHostChangedHandler -= value; + } + protected override IItemContainerGenerator CreateItemContainerGenerator() { return new MenuItemContainerGenerator(this); @@ -364,6 +373,8 @@ namespace Avalonia.Controls { _previousFocus = FocusManager.Instance?.Current; Focus(); + + _popupHostChangedHandler?.Invoke(_popup!.Host); } private void PopupClosing(object sender, CancelEventArgs e) @@ -397,6 +408,8 @@ namespace Avalonia.Controls RoutedEvent = MenuClosedEvent, Source = this, }); + + _popupHostChangedHandler?.Invoke(null); } private void PopupKeyUp(object sender, KeyEventArgs e) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index 5524d06f1b..6a430897ba 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Collections; @@ -18,7 +17,8 @@ namespace Avalonia.Diagnostics.ViewModels { Children = new VisualTreeNodeCollection(this, visual); - if (Visual is IStyleable styleable) IsInTemplate = styleable.TemplatedParent != null; + if (Visual is IStyleable styleable) + IsInTemplate = styleable.TemplatedParent != null; } public bool IsInTemplate { get; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 9d37b5acdc..26e197d790 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using Avalonia.Controls; +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.Diagnostics.ViewModels; using Avalonia.Input; @@ -109,6 +111,37 @@ namespace Avalonia.Diagnostics.Views .FirstOrDefault(); } + private static IEnumerable GetPopupRoots(IVisual root) + { + foreach (var control in root.GetVisualDescendants().OfType()) + { + if (control is Popup { Host: PopupRoot r0 }) + { + yield return r0; + } + + if (control.GetValue(ContextFlyoutProperty) is IPopupHostProvider { PopupHost: PopupRoot r1 }) + { + yield return r1; + } + + if (control.GetValue(FlyoutBase.AttachedFlyoutProperty) is IPopupHostProvider { PopupHost: PopupRoot r2 }) + { + yield return r2; + } + + if (control.GetValue(ToolTipDiagnostics.ToolTipProperty) is IPopupHostProvider { PopupHost: PopupRoot r3 }) + { + yield return r3; + } + + if (control.GetValue(ContextMenuProperty) is IPopupHostProvider { PopupHost: PopupRoot r4 }) + { + yield return r4; + } + } + } + private void RawKeyDown(RawKeyEventArgs e) { var vm = (MainViewModel?)DataContext; @@ -123,16 +156,13 @@ namespace Avalonia.Diagnostics.Views { IControl? control = null; - foreach (var popup in Root.GetVisualDescendants().OfType()) + foreach (var popupRoot in GetPopupRoots(Root)) { - if (popup.Host?.HostedVisualTreeRoot is PopupRoot popupRoot) - { - control = GetHoveredControl(popupRoot); + control = GetHoveredControl(popupRoot); - if (control != null) - { - break; - } + if (control != null) + { + break; } } From 838626d763a9e446844244636c3cc51d55cbd946 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 16 Jul 2021 10:33:51 +0200 Subject: [PATCH 27/73] remove c#9 features, clean up --- .../Diagnostics/Views/MainWindow.xaml.cs | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 26e197d790..cfb9e2ead9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -111,35 +111,33 @@ namespace Avalonia.Diagnostics.Views .FirstOrDefault(); } - private static IEnumerable GetPopupRoots(IVisual root) + private static List GetPopupRoots(IVisual root) { - foreach (var control in root.GetVisualDescendants().OfType()) - { - if (control is Popup { Host: PopupRoot r0 }) - { - yield return r0; - } - - if (control.GetValue(ContextFlyoutProperty) is IPopupHostProvider { PopupHost: PopupRoot r1 }) - { - yield return r1; - } + var popupRoots = new List(); - if (control.GetValue(FlyoutBase.AttachedFlyoutProperty) is IPopupHostProvider { PopupHost: PopupRoot r2 }) + void ProcessProperty(IControl control, AvaloniaProperty property) + { + if (control.GetValue(property) is IPopupHostProvider popupProvider + && popupProvider.PopupHost is PopupRoot popupRoot) { - yield return r2; + popupRoots.Add(popupRoot); } + } - if (control.GetValue(ToolTipDiagnostics.ToolTipProperty) is IPopupHostProvider { PopupHost: PopupRoot r3 }) + foreach (var control in root.GetVisualDescendants().OfType()) + { + if (control is Popup p && p.Host is PopupRoot popupRoot) { - yield return r3; + popupRoots.Add(popupRoot); } - if (control.GetValue(ContextMenuProperty) is IPopupHostProvider { PopupHost: PopupRoot r4 }) - { - yield return r4; - } + ProcessProperty(control, ContextFlyoutProperty); + ProcessProperty(control, ContextMenuProperty); + ProcessProperty(control, FlyoutBase.AttachedFlyoutProperty); + ProcessProperty(control, ToolTipDiagnostics.ToolTipProperty); } + + return popupRoots; } private void RawKeyDown(RawKeyEventArgs e) From f589965b2df0f391d752b3ae0f11f615aa1fb672 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 16 Jul 2021 16:47:32 +0200 Subject: [PATCH 28/73] OSX: Fix showing window with no specified size. A Window without a `Width`/`Height` specified was not getting shown on OSX since 1f8b90925771c64e54afa1a264938de0fd5e3c70. Set the content view in the constructor to fix this. --- native/Avalonia.Native/src/OSX/window.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 7df9b76425..3c1a370195 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -52,6 +52,7 @@ public: [Window setBackingType:NSBackingStoreBuffered]; [Window setOpaque:false]; + [Window setContentView: StandardContainer]; } virtual HRESULT ObtainNSWindowHandle(void** ret) override @@ -124,7 +125,6 @@ public: SetPosition(lastPositionSet); UpdateStyle(); - [Window setContentView: StandardContainer]; [Window setTitle:_lastTitle]; if(ShouldTakeFocusOnShow() && activate) From 9b0287bfd6e397f4150820defa0edf598214167a Mon Sep 17 00:00:00 2001 From: mat1jaczyyy Date: Sat, 17 Jul 2021 04:19:31 +0200 Subject: [PATCH 29/73] Fix existing and add missing osx-specific key shortcuts --- src/Avalonia.Native/AvaloniaNativePlatform.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 82a845ffc1..6ff5294d4a 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -109,10 +109,16 @@ namespace Avalonia.Native .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) - .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta)) + .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt)) .Bind().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind().ToConstant(new AvaloniaNativeDragSource(_factory)); + var hotkeys = AvaloniaLocator.Current.GetService(); + hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); + hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); + hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + if (_options.UseGpu) { try From 2387e0bb5af5fa7448847bf11fd5157a0f111fcd Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Wed, 21 Jul 2021 18:34:14 +0200 Subject: [PATCH 30/73] Add option to freez popups (Alt + Ctrl + F) --- .../Diagnostics/ViewModels/MainViewModel.cs | 7 +++++++ .../Diagnostics/Views/MainWindow.xaml.cs | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 07ae222a9c..d0a4ad38c5 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -21,6 +21,7 @@ namespace Avalonia.Diagnostics.ViewModels private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; + private bool _freezePopups; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -40,6 +41,12 @@ namespace Avalonia.Diagnostics.ViewModels Console = new ConsoleViewModel(UpdateConsoleContext); } + public bool FreezePopups + { + get => _freezePopups; + set => RaiseAndSetIfChanged(ref _freezePopups, value); + } + public bool ShouldVisualizeMarginPadding { get => _shouldVisualizeMarginPadding; diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index cfb9e2ead9..810a630f46 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; +using System.Reactive.Subjects; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; @@ -173,6 +174,22 @@ namespace Avalonia.Diagnostics.Views break; } + + case RawInputModifiers.Control | RawInputModifiers.Alt when e.Key == Key.F: + { + vm.FreezePopups = !vm.FreezePopups; + + foreach (var popupRoot in GetPopupRoots(Root)) + { + if (popupRoot.Parent is Popup popup) + { + popup.IsLightDismissEnabled = !vm.FreezePopups; + } + } + + break; + } + case RawInputModifiers.Alt when e.Key == Key.S || e.Key == Key.D: { vm.EnableSnapshotStyles(e.Key == Key.S); From df2079454079e43a59ca3b8032ac5837a8cf8fb6 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 22 Jul 2021 12:01:20 +0200 Subject: [PATCH 31/73] Set LightDimissEnabled via SetValue() --- .../Diagnostics/Views/MainWindow.xaml.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 810a630f46..1791672940 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -1,15 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; +using Avalonia.Data; using Avalonia.Diagnostics.ViewModels; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Markup.Xaml; +using Avalonia.Media; using Avalonia.Styling; using Avalonia.VisualTree; @@ -18,6 +21,7 @@ namespace Avalonia.Diagnostics.Views internal class MainWindow : Window, IStyleHost { private readonly IDisposable _keySubscription; + private readonly Dictionary _frozenPopupStates; private TopLevel? _root; public MainWindow() @@ -28,6 +32,8 @@ namespace Avalonia.Diagnostics.Views .OfType() .Subscribe(RawKeyDown); + _frozenPopupStates = new Dictionary(); + EventHandler? lh = default; lh = (s, e) => { @@ -183,7 +189,25 @@ namespace Avalonia.Diagnostics.Views { if (popupRoot.Parent is Popup popup) { - popup.IsLightDismissEnabled = !vm.FreezePopups; + if (vm.FreezePopups) + { + var lightDismissEnabledState = popup.SetValue( + Popup.IsLightDismissEnabledProperty, + !vm.FreezePopups, + BindingPriority.Animation); + + if (lightDismissEnabledState != null) + { + _frozenPopupStates[popup] = lightDismissEnabledState; + } + } + else + { + if (_frozenPopupStates.TryGetValue(popup, out var state)) + { + state.Dispose(); + } + } } } From cabe6daa3f6f13d0991655465f6d286029eb1e27 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 23 Jul 2021 10:57:19 +0200 Subject: [PATCH 32/73] Review changes --- .../Diagnostics/Views/MainWindow.xaml.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 1791672940..a85fe6c0c4 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -86,6 +86,13 @@ namespace Avalonia.Diagnostics.Views base.OnClosed(e); _keySubscription.Dispose(); + foreach (var state in _frozenPopupStates) + { + state.Value.Dispose(); + } + + _frozenPopupStates.Clear(); + if (_root != null) { _root.Closed -= RootClosed; @@ -203,9 +210,11 @@ namespace Avalonia.Diagnostics.Views } else { - if (_frozenPopupStates.TryGetValue(popup, out var state)) + //TODO Use Dictionary.Remove(Key, out Value) in netstandard 2.1 + if (_frozenPopupStates.ContainsKey(popup)) { - state.Dispose(); + _frozenPopupStates[popup].Dispose(); + _frozenPopupStates.Remove(popup); } } } From 57ffcb85f7df6967c802029303345a71d1afe9b6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Jul 2021 14:38:49 +0100 Subject: [PATCH 33/73] initial implementation of win32 shutdown cancelling. --- src/Windows/Avalonia.Win32/Win32Platform.cs | 23 +++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 84e61ca007..67f0246cdc 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -65,7 +65,7 @@ namespace Avalonia namespace Avalonia.Win32 { - class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader + class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; @@ -122,7 +122,8 @@ namespace Avalonia.Win32 }) .Bind().ToConstant(s_instance) .Bind().ToConstant(new NonPumpingSyncContext.HelperImpl()) - .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()); + .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()) + .Bind().ToConstant(s_instance); Win32GlManager.Initialize(); @@ -207,6 +208,8 @@ namespace Avalonia.Win32 public event Action Signaled; + public event EventHandler ShutdownRequested; + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -214,6 +217,22 @@ namespace Avalonia.Win32 { Signaled?.Invoke(null); } + + if(msg == (uint)WindowsMessage.WM_QUERYENDSESSION) + { + if (ShutdownRequested != null) + { + var e = new CancelEventArgs(); + + ShutdownRequested(this, e); + + if(e.Cancel) + { + return IntPtr.Zero; + } + } + } + return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } From 851baa6537859ea6df8d774911003c97aa1e1bf8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Jul 2021 14:39:00 +0100 Subject: [PATCH 34/73] sandbox demo of shutdown cancel. --- samples/Sandbox/App.axaml.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/samples/Sandbox/App.axaml.cs b/samples/Sandbox/App.axaml.cs index 7eb8345784..c61ff5f0d5 100644 --- a/samples/Sandbox/App.axaml.cs +++ b/samples/Sandbox/App.axaml.cs @@ -16,7 +16,14 @@ namespace Sandbox if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) { desktopLifetime.MainWindow = new MainWindow(); + + desktopLifetime.ShutdownRequested += DesktopLifetime_ShutdownRequested; } } + + private void DesktopLifetime_ShutdownRequested(object sender, System.ComponentModel.CancelEventArgs e) + { + e.Cancel = true; + } } } From 6c3ba87cab3ffa9aedfffe6fedbbd8dc790443d2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Jul 2021 17:29:06 +0100 Subject: [PATCH 35/73] Revert "sandbox demo of shutdown cancel." This reverts commit 851baa6537859ea6df8d774911003c97aa1e1bf8. --- samples/Sandbox/App.axaml.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/samples/Sandbox/App.axaml.cs b/samples/Sandbox/App.axaml.cs index c61ff5f0d5..7eb8345784 100644 --- a/samples/Sandbox/App.axaml.cs +++ b/samples/Sandbox/App.axaml.cs @@ -16,14 +16,7 @@ namespace Sandbox if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) { desktopLifetime.MainWindow = new MainWindow(); - - desktopLifetime.ShutdownRequested += DesktopLifetime_ShutdownRequested; } } - - private void DesktopLifetime_ShutdownRequested(object sender, System.ComponentModel.CancelEventArgs e) - { - e.Cancel = true; - } } } From d0fd73729a4c7a2dfa7915537bcfc2aeb69cde0d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Jul 2021 17:55:41 +0100 Subject: [PATCH 36/73] clarify functionality on various platforms in event comments. --- .../IClassicDesktopStyleApplicationLifetime.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index ecf8a0358f..de6ee150db 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -37,13 +37,20 @@ namespace Avalonia.Controls.ApplicationLifetimes IReadOnlyList Windows { get; } /// - /// Raised by the platform when a shutdown is requested. + /// Raised by the platform when an application shutdown is requested. /// /// - /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. This event - /// provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application + /// Application Shutdown can be requested for various reasons like OS shutdown. + /// + /// On Windows this will be called when an OS Session (logout or shutdown) terminates. Cancelling the eventargs will + /// block OS shutdown. + /// + /// On OSX this has the same behavior as on Windows and in addition: + /// This event is raised via the Quit menu or right-clicking on the application icon and selecting Quit. + /// + /// This event provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application /// will try to close each non-owned open window, invoking the event on each and allowing - /// each window to cancel the shutdown. + /// each window to cancel the shutdown of the application. Windows cannot however prevent OS shutdown. /// event EventHandler ShutdownRequested; } From de3fdbeaf151deb6473aac9e5f3d2aa268e36925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 28 Jul 2021 20:55:13 +0200 Subject: [PATCH 37/73] Add ClipGeometry and OpacityMask properties to DrawingGroup --- src/Avalonia.Visuals/Media/DrawingGroup.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Avalonia.Visuals/Media/DrawingGroup.cs b/src/Avalonia.Visuals/Media/DrawingGroup.cs index e581c8c553..eeb6318ebd 100644 --- a/src/Avalonia.Visuals/Media/DrawingGroup.cs +++ b/src/Avalonia.Visuals/Media/DrawingGroup.cs @@ -12,6 +12,12 @@ namespace Avalonia.Media public static readonly StyledProperty TransformProperty = AvaloniaProperty.Register(nameof(Transform)); + public static readonly StyledProperty ClipGeometryProperty = + AvaloniaProperty.Register(nameof(ClipGeometry)); + + public static readonly StyledProperty OpacityMaskProperty = + AvaloniaProperty.Register(nameof(OpacityMask)); + public double Opacity { get => GetValue(OpacityProperty); @@ -24,6 +30,18 @@ namespace Avalonia.Media set => SetValue(TransformProperty, value); } + public Geometry ClipGeometry + { + get => GetValue(ClipGeometryProperty); + set => SetValue(ClipGeometryProperty, value); + } + + public IBrush OpacityMask + { + get => GetValue(OpacityMaskProperty); + set => SetValue(OpacityMaskProperty, value); + } + [Content] public AvaloniaList Children { get; } = new AvaloniaList(); @@ -31,6 +49,8 @@ namespace Avalonia.Media { using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity)) using (context.PushOpacity(Opacity)) + using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default(DrawingContext.PushedState)) + using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default(DrawingContext.PushedState)) { foreach (var drawing in Children) { From f1c87b4043db8996a866c89876a58e9c98a434c4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 29 Jul 2021 10:43:41 +0200 Subject: [PATCH 38/73] Added KeyboardNavigationMode.Local. --- src/Avalonia.Input/KeyboardNavigationMode.cs | 7 ++++++- src/Avalonia.Input/Navigation/TabNavigation.cs | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Input/KeyboardNavigationMode.cs b/src/Avalonia.Input/KeyboardNavigationMode.cs index 41e778bf49..e01ebf0330 100644 --- a/src/Avalonia.Input/KeyboardNavigationMode.cs +++ b/src/Avalonia.Input/KeyboardNavigationMode.cs @@ -36,5 +36,10 @@ namespace Avalonia.Input /// The container's children will not be focused when using the tab key. /// None, + + /// + /// TabIndexes are considered on local subtree only inside this container + /// + Local, } -} \ No newline at end of file +} diff --git a/src/Avalonia.Input/Navigation/TabNavigation.cs b/src/Avalonia.Input/Navigation/TabNavigation.cs index 12842e4f40..ed7df67bf2 100644 --- a/src/Avalonia.Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Input/Navigation/TabNavigation.cs @@ -152,6 +152,9 @@ namespace Avalonia.Input.Navigation // Look for element with the same TabIndex before the current element while ((nextTabElement = GetPrevTabInGroup(nextTabElement, container, tabbingType)) != null) { + if (nextTabElement == container && tabbingType == KeyboardNavigationMode.Local) + break; + // At this point nextTabElement is TabStop or TabGroup // In case it is a TabStop only return the element if (IsTabStop(nextTabElement) && !IsGroup(nextTabElement)) From c9458e5be833237ea3865cd30b4185445af5bb5a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 29 Jul 2021 14:23:27 +0100 Subject: [PATCH 39/73] use a ShutdownRequestedEventArgs class to future proof the event args. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 6 +++--- .../ClassicDesktopStyleApplicationLifetime.cs | 4 ++-- .../IClassicDesktopStyleApplicationLifetime.cs | 2 +- .../ShutdownRequestedCancelEventArgs.cs | 9 +++++++++ .../Platform/IPlatformLifetimeEventsImpl.cs | 3 ++- src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs | 5 +++-- src/Windows/Avalonia.Win32/Win32Platform.cs | 8 +++----- .../DesktopStyleApplicationLifetimeTests.cs | 2 +- 8 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedCancelEventArgs.cs diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index ea62a8d843..5614b30304 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -30,9 +30,9 @@ MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownV MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 79780dbd0b..38e4a10033 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls.ApplicationLifetimes public event EventHandler Startup; /// - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; /// public event EventHandler Exit; @@ -134,7 +134,7 @@ namespace Avalonia.Controls.ApplicationLifetimes _activeLifetime = null; } - private void OnShutdownRequested(object sender, CancelEventArgs e) + private void OnShutdownRequested(object sender, ShutdownRequestedCancelEventArgs e) { ShutdownRequested?.Invoke(this, e); diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index de6ee150db..722c275df8 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -52,6 +52,6 @@ namespace Avalonia.Controls.ApplicationLifetimes /// will try to close each non-owned open window, invoking the event on each and allowing /// each window to cancel the shutdown of the application. Windows cannot however prevent OS shutdown. /// - event EventHandler ShutdownRequested; + event EventHandler ShutdownRequested; } } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedCancelEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedCancelEventArgs.cs new file mode 100644 index 0000000000..3dd609f0d9 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedCancelEventArgs.cs @@ -0,0 +1,9 @@ +using System.ComponentModel; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + public class ShutdownRequestedCancelEventArgs : CancelEventArgs + { + + } +} diff --git a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs index 8e660777e9..e41362f13c 100644 --- a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs +++ b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Platform { @@ -11,6 +12,6 @@ namespace Avalonia.Platform /// /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. /// - event EventHandler ShutdownRequested; + event EventHandler ShutdownRequested; } } diff --git a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs index 77c0794d04..99bc857597 100644 --- a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Native.Interop; using Avalonia.Platform; @@ -7,7 +8,7 @@ namespace Avalonia.Native { internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl { - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) { @@ -17,7 +18,7 @@ namespace Avalonia.Native public int TryShutdown() { if (ShutdownRequested is null) return 1; - var e = new CancelEventArgs(); + var e = new ShutdownRequestedCancelEventArgs(); ShutdownRequested(this, e); return (!e.Cancel).AsComBool(); } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 67f0246cdc..d9b42d79ef 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -6,18 +6,16 @@ using System.IO; using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; -using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.OpenGL; -using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; using Avalonia.Utilities; -using Avalonia.Win32; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -208,7 +206,7 @@ namespace Avalonia.Win32 public event Action Signaled; - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) @@ -222,7 +220,7 @@ namespace Avalonia.Win32 { if (ShutdownRequested != null) { - var e = new CancelEventArgs(); + var e = new ShutdownRequestedCancelEventArgs(); ShutdownRequested(this, e); diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index 38713834c3..17002c4958 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests ++raised; }; - lifetimeEvents.Raise(x => x.ShutdownRequested += null, new CancelEventArgs()); + lifetimeEvents.Raise(x => x.ShutdownRequested += null, new ShutdownRequestedCancelEventArgs()); Assert.Equal(1, raised); Assert.Equal(new[] { window }, lifetime.Windows); From 2a4830bf5b401535d92449ea66eb39dbc0e3c44a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 29 Jul 2021 14:59:38 +0100 Subject: [PATCH 40/73] fix tests. --- tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 6f7e7bc8e7..ba01f3db40 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -230,7 +230,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var window = PreparedWindow(); @@ -268,7 +268,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(c.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Never); - popupImpl.Verify(x => x.Show(true), Times.Exactly(1)); + popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1)); } } @@ -277,7 +277,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show(true)).Verifiable(); + popupImpl.Setup(x => x.Show(true, false)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var window = PreparedWindow(); @@ -306,7 +306,7 @@ namespace Avalonia.Controls.UnitTests Assert.False(c.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Exactly(1)); - popupImpl.Verify(x => x.Show(true), Times.Exactly(1)); + popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1)); } } From 0ddc444d2c5e7c1fdb1df460f1cf66ede39f8d1a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 29 Jul 2021 15:07:31 +0100 Subject: [PATCH 41/73] Rename eventargs class. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 6 +++--- .../ClassicDesktopStyleApplicationLifetime.cs | 4 ++-- .../IClassicDesktopStyleApplicationLifetime.cs | 2 +- ...stedCancelEventArgs.cs => ShutdownRequestedEventArgs.cs} | 2 +- .../Platform/IPlatformLifetimeEventsImpl.cs | 2 +- src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs | 4 ++-- src/Windows/Avalonia.Win32/Win32Platform.cs | 4 ++-- .../DesktopStyleApplicationLifetimeTests.cs | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) rename src/Avalonia.Controls/ApplicationLifetimes/{ShutdownRequestedCancelEventArgs.cs => ShutdownRequestedEventArgs.cs} (59%) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 5614b30304..188623c320 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -30,9 +30,9 @@ MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownV MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 38e4a10033..2a42d99ac5 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls.ApplicationLifetimes public event EventHandler Startup; /// - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; /// public event EventHandler Exit; @@ -134,7 +134,7 @@ namespace Avalonia.Controls.ApplicationLifetimes _activeLifetime = null; } - private void OnShutdownRequested(object sender, ShutdownRequestedCancelEventArgs e) + private void OnShutdownRequested(object sender, ShutdownRequestedEventArgs e) { ShutdownRequested?.Invoke(this, e); diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 722c275df8..a70d5dd2f1 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -52,6 +52,6 @@ namespace Avalonia.Controls.ApplicationLifetimes /// will try to close each non-owned open window, invoking the event on each and allowing /// each window to cancel the shutdown of the application. Windows cannot however prevent OS shutdown. /// - event EventHandler ShutdownRequested; + event EventHandler ShutdownRequested; } } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedCancelEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs similarity index 59% rename from src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedCancelEventArgs.cs rename to src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs index 3dd609f0d9..62bc3a8904 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedCancelEventArgs.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs @@ -2,7 +2,7 @@ namespace Avalonia.Controls.ApplicationLifetimes { - public class ShutdownRequestedCancelEventArgs : CancelEventArgs + public class ShutdownRequestedEventArgs : CancelEventArgs { } diff --git a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs index e41362f13c..4cd6640453 100644 --- a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs +++ b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs @@ -12,6 +12,6 @@ namespace Avalonia.Platform /// /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. /// - event EventHandler ShutdownRequested; + event EventHandler ShutdownRequested; } } diff --git a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs index 99bc857597..8084e06d28 100644 --- a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs @@ -8,7 +8,7 @@ namespace Avalonia.Native { internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl { - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) { @@ -18,7 +18,7 @@ namespace Avalonia.Native public int TryShutdown() { if (ShutdownRequested is null) return 1; - var e = new ShutdownRequestedCancelEventArgs(); + var e = new ShutdownRequestedEventArgs(); ShutdownRequested(this, e); return (!e.Cancel).AsComBool(); } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index d9b42d79ef..2265ed2322 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -206,7 +206,7 @@ namespace Avalonia.Win32 public event Action Signaled; - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) @@ -220,7 +220,7 @@ namespace Avalonia.Win32 { if (ShutdownRequested != null) { - var e = new ShutdownRequestedCancelEventArgs(); + var e = new ShutdownRequestedEventArgs(); ShutdownRequested(this, e); diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index 17002c4958..f7a3bdea1c 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests ++raised; }; - lifetimeEvents.Raise(x => x.ShutdownRequested += null, new ShutdownRequestedCancelEventArgs()); + lifetimeEvents.Raise(x => x.ShutdownRequested += null, new ShutdownRequestedEventArgs()); Assert.Equal(1, raised); Assert.Equal(new[] { window }, lifetime.Windows); From 4d8d31dfe94cde993c0786ec9a1260fa5bb4611e Mon Sep 17 00:00:00 2001 From: GMIKE Date: Fri, 30 Jul 2021 00:09:35 +0300 Subject: [PATCH 42/73] Properties of PointerEventArgs in TappedEventArgs (#6322) * Properties of PointerEventArgs in TappedEventArgs * remove InputModifiers and Device properties * move properties * remove whitespace --- src/Avalonia.Input/TappedEventArgs.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Avalonia.Input/TappedEventArgs.cs b/src/Avalonia.Input/TappedEventArgs.cs index 02add509cd..daaab70632 100644 --- a/src/Avalonia.Input/TappedEventArgs.cs +++ b/src/Avalonia.Input/TappedEventArgs.cs @@ -13,6 +13,10 @@ namespace Avalonia.Input this.lastPointerEventArgs = lastPointerEventArgs; } + public IPointer Pointer => lastPointerEventArgs.Pointer; + public KeyModifiers KeyModifiers => lastPointerEventArgs.KeyModifiers; + public ulong Timestamp => lastPointerEventArgs.Timestamp; + public Point GetPosition(IVisual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo); } } From 08566c36e2435e1b861d91d592c8ca796c42e254 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 30 Jul 2021 14:36:13 +0200 Subject: [PATCH 43/73] Add in obsolete HandleResized for ApiCompat. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 3 +-- src/Avalonia.Controls/Window.cs | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index a1cf9a445c..026ee970c3 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -31,7 +31,6 @@ MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownV MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Controls.TopLevel.HandleResized(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'protected void Avalonia.Controls.Window.HandleResized(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Controls.WindowBase.HandleResized(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. @@ -58,4 +57,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. -Total Issues: 59 +Total Issues: 58 diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 2acfa545b9..ac3dace35e 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -949,6 +949,9 @@ namespace Avalonia.Controls Owner = null; } + [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] + protected void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); + /// protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason) { From 59e18e3517bc1d2884ba0f87b18427823815560c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 30 Jul 2021 14:40:07 +0200 Subject: [PATCH 44/73] Add obsolete HandleResized to TopLevel/WindowBase too. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 4 +--- src/Avalonia.Controls/TopLevel.cs | 3 +++ src/Avalonia.Controls/Window.cs | 2 +- src/Avalonia.Controls/WindowBase.cs | 3 +++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 026ee970c3..fac5923db5 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -29,9 +29,7 @@ MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChang MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'protected void Avalonia.Controls.TopLevel.HandleResized(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'protected void Avalonia.Controls.WindowBase.HandleResized(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. @@ -57,4 +55,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. -Total Issues: 58 +Total Issues: 56 diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index c5e7a7204f..116e276a3a 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -376,6 +376,9 @@ namespace Avalonia.Controls LayoutManager?.Dispose(); } + [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] + protected virtual void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); + /// /// Handles a resize notification from . /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ac3dace35e..0ca28ca196 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -950,7 +950,7 @@ namespace Avalonia.Controls } [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] - protected void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); + protected sealed override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); /// protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason) diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 5a06b908e5..7cfc5f8be8 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -201,6 +201,9 @@ namespace Avalonia.Controls } } + [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] + protected override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); + /// /// Handles a resize notification from . /// From c285a0e6a80faab1388d4dafcb9002a2f164f3ec Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 31 Jul 2021 17:27:22 -0400 Subject: [PATCH 45/73] Fix DataGrid wheel scroll calculation --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 83f13fe199..3887bb3380 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls private const double DATAGRID_minimumColumnHeaderHeight = 4; internal const double DATAGRID_maximumStarColumnWidth = 10000; internal const double DATAGRID_minimumStarColumnWidth = 0.001; - private const double DATAGRID_mouseWheelDelta = 72.0; + private const double DATAGRID_mouseWheelDelta = 50.0; private const double DATAGRID_maxHeadersThickness = 32768; private const double DATAGRID_defaultRowHeight = 22; @@ -2217,20 +2217,23 @@ namespace Avalonia.Controls if (IsEnabled && !e.Handled && DisplayData.NumDisplayedScrollingElements > 0) { double scrollHeight = 0; - if (e.Delta.Y > 0) + var delta = DATAGRID_mouseWheelDelta * e.Delta.Y; + var deltaAbs = Math.Abs(delta); + + if (delta > 0) { - scrollHeight = Math.Max(-_verticalOffset, -DATAGRID_mouseWheelDelta); + scrollHeight = Math.Max(-_verticalOffset, -deltaAbs); } - else if (e.Delta.Y < 0) + else if (delta < 0) { if (_vScrollBar != null && VerticalScrollBarVisibility == ScrollBarVisibility.Visible) { - scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), DATAGRID_mouseWheelDelta); + scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), deltaAbs); } else { double maximum = EdgedRowsHeightCalculated - CellsHeight; - scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), DATAGRID_mouseWheelDelta); + scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), deltaAbs); } } if (scrollHeight != 0) From ca2acc951612ad7ba536d2399f60de5a053a4c84 Mon Sep 17 00:00:00 2001 From: mat1jaczyyy Date: Sun, 1 Aug 2021 03:19:23 +0200 Subject: [PATCH 46/73] WindowImpl: Don't change z-order when Position is set --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index f20dae3e50..0b7bd13082 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -451,7 +451,7 @@ namespace Avalonia.Win32 value.Y, 0, 0, - SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE); + SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOZORDER); } } From 98a436481064952a4176f190820cacadcc47db16 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 1 Aug 2021 01:16:38 -0400 Subject: [PATCH 47/73] Handle horizontal wheel scrolling in DataGrid --- .../ControlCatalog/Pages/DataGridPage.xaml | 2 +- src/Avalonia.Controls.DataGrid/DataGrid.cs | 57 +++++++++++++++---- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index 820c1324e3..340b3376f5 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -28,7 +28,7 @@ DockPanel.Dock="Top"/> - + diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 3887bb3380..63e6950ae8 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -25,6 +25,7 @@ using System.ComponentModel.DataAnnotations; using Avalonia.Controls.Utils; using Avalonia.Layout; using Avalonia.Controls.Metadata; +using Avalonia.Input.GestureRecognizers; namespace Avalonia.Controls { @@ -2214,35 +2215,71 @@ namespace Avalonia.Controls /// PointerWheelEventArgs protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { - if (IsEnabled && !e.Handled && DisplayData.NumDisplayedScrollingElements > 0) + e.Handled = e.Handled || UpdateScroll(e.Delta * DATAGRID_mouseWheelDelta); + } + + internal bool UpdateScroll(Vector delta) + { + if (IsEnabled && DisplayData.NumDisplayedScrollingElements > 0) { - double scrollHeight = 0; - var delta = DATAGRID_mouseWheelDelta * e.Delta.Y; - var deltaAbs = Math.Abs(delta); + var handled = false; + var scrollHeight = 0d; - if (delta > 0) + // Vertical scroll handling + if (delta.Y > 0) { - scrollHeight = Math.Max(-_verticalOffset, -deltaAbs); + scrollHeight = Math.Max(-_verticalOffset, -delta.Y); } - else if (delta < 0) + else if (delta.Y < 0) { if (_vScrollBar != null && VerticalScrollBarVisibility == ScrollBarVisibility.Visible) { - scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), deltaAbs); + scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), -delta.Y); } else { double maximum = EdgedRowsHeightCalculated - CellsHeight; - scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), deltaAbs); + scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), -delta.Y); } } + if (scrollHeight != 0) { DisplayData.PendingVerticalScrollHeight = scrollHeight; + handled = true; + } + + // Horizontal scroll handling + if (delta.X != 0) + { + var originalHorizontalOffset = HorizontalOffset; + var horizontalOffset = originalHorizontalOffset - delta.X; + var widthNotVisible = Math.Max(0, ColumnsInternal.VisibleEdgedColumnsWidth - CellsWidth); + + if (horizontalOffset < 0) + { + horizontalOffset = 0; + } + if (horizontalOffset > widthNotVisible) + { + horizontalOffset = widthNotVisible; + } + + if (horizontalOffset != originalHorizontalOffset) + { + HorizontalOffset = horizontalOffset; + handled = true; + } + } + + if (handled) + { InvalidateRowsMeasure(invalidateIndividualElements: false); - e.Handled = true; + return true; } } + + return false; } /// From 7e3b6ecff5dc187c0b4a6c535673556ad8d1b168 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 1 Aug 2021 01:16:55 -0400 Subject: [PATCH 48/73] Handle touch scrolling in DataGrid --- src/Avalonia.Controls.DataGrid/DataGridCell.cs | 2 +- .../Primitives/DataGridRowsPresenter.cs | 13 +++++++++++++ src/Avalonia.Controls.DataGrid/Themes/Default.xaml | 6 +++++- src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml | 6 +++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index 445dc541a7..47bf7b906c 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -173,7 +173,7 @@ namespace Avalonia.Controls } if (OwningRow != null) { - e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); + OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); OwningGrid.UpdatedStateOnMouseLeftButtonDown = true; } } diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs index 0d19f4c479..308ebc69d4 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs @@ -5,6 +5,9 @@ using System; using System.Diagnostics; + +using Avalonia.Input; +using Avalonia.Input.GestureRecognizers; using Avalonia.Layout; using Avalonia.Media; @@ -16,6 +19,11 @@ namespace Avalonia.Controls.Primitives /// public sealed class DataGridRowsPresenter : Panel { + public DataGridRowsPresenter() + { + AddHandler(Gestures.ScrollGestureEvent, OnScrollGesture); + } + internal DataGrid OwningGrid { get; @@ -176,6 +184,11 @@ namespace Avalonia.Controls.Primitives return new Size(totalCellsWidth + headerWidth, totalHeight); } + private void OnScrollGesture(object sender, ScrollGestureEventArgs e) + { + e.Handled = e.Handled || OwningGrid.UpdateScroll(-e.Delta); + } + #if DEBUG internal void PrintChildren() { diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml index 09d19c8e43..ca0873e183 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml @@ -247,7 +247,11 @@ - + + + + + diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index f2dbf42196..8ccf717d94 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -601,7 +601,11 @@ + Grid.ColumnSpan="3"> + + + + Date: Sun, 1 Aug 2021 02:57:57 -0400 Subject: [PATCH 49/73] Handle DataGridCell_PointerPressed if not touch --- src/Avalonia.Controls.DataGrid/DataGridCell.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index 241d543ec3..7dda936317 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -173,7 +173,15 @@ namespace Avalonia.Controls } if (OwningRow != null) { - OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); + var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); + + // Do not handle PointerPressed with touch, + // so we can start scroll gesture on the same event. + if (e.Pointer.Type != PointerType.Touch) + { + e.Handled = handled; + } + OwningGrid.UpdatedStateOnMouseLeftButtonDown = true; } } From 64922a34a937757380700199fcb555f8f39f6e90 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 1 Aug 2021 21:45:07 -0400 Subject: [PATCH 50/73] Add CornerRadius to TemplatedControl and use it where possible --- samples/ControlCatalog/SideBar.xaml | 1 - samples/RenderDemo/SideBar.xaml | 1 - .../Flyouts/FlyoutPresenter.cs | 9 ----- .../Primitives/TemplatedControl.cs | 40 ++++++++++++++----- .../AutoCompleteBox.xaml | 1 + src/Avalonia.Themes.Default/Button.xaml | 3 +- .../ButtonSpinner.xaml | 2 + src/Avalonia.Themes.Default/Calendar.xaml | 3 +- .../CalendarDatePicker.xaml | 3 +- src/Avalonia.Themes.Default/CalendarItem.xaml | 5 ++- src/Avalonia.Themes.Default/Carousel.xaml | 3 +- src/Avalonia.Themes.Default/CheckBox.xaml | 1 + src/Avalonia.Themes.Default/ComboBox.xaml | 3 +- src/Avalonia.Themes.Default/ComboBoxItem.xaml | 1 + .../ContentControl.xaml | 3 +- src/Avalonia.Themes.Default/ContextMenu.xaml | 1 + .../DataValidationErrors.xaml | 1 + src/Avalonia.Themes.Default/DatePicker.xaml | 3 ++ src/Avalonia.Themes.Default/Expander.xaml | 5 ++- src/Avalonia.Themes.Default/GridSplitter.xaml | 1 + src/Avalonia.Themes.Default/ItemsControl.xaml | 1 + src/Avalonia.Themes.Default/Label.xaml | 1 + src/Avalonia.Themes.Default/ListBox.xaml | 3 +- src/Avalonia.Themes.Default/ListBoxItem.xaml | 1 + src/Avalonia.Themes.Default/Menu.xaml | 3 +- src/Avalonia.Themes.Default/MenuItem.xaml | 6 ++- .../NotificationCard.xaml | 1 + .../NumericUpDown.xaml | 2 +- src/Avalonia.Themes.Default/ProgressBar.xaml | 2 +- src/Avalonia.Themes.Default/RepeatButton.xaml | 1 + src/Avalonia.Themes.Default/Separator.xaml | 1 + src/Avalonia.Themes.Default/TabControl.xaml | 2 +- src/Avalonia.Themes.Default/TabItem.xaml | 2 +- src/Avalonia.Themes.Default/TabStripItem.xaml | 3 +- src/Avalonia.Themes.Default/TextBox.xaml | 3 +- src/Avalonia.Themes.Default/TimePicker.xaml | 3 ++ src/Avalonia.Themes.Default/ToggleButton.xaml | 3 +- src/Avalonia.Themes.Default/ToolTip.xaml | 3 +- src/Avalonia.Themes.Default/TreeView.xaml | 3 +- src/Avalonia.Themes.Default/TreeViewItem.xaml | 1 + src/Avalonia.Themes.Default/UserControl.xaml | 1 + .../Controls/AutoCompleteBox.xaml | 4 +- .../Controls/Button.xaml | 6 +-- .../Controls/ButtonSpinner.xaml | 3 +- .../Controls/Calendar.xaml | 10 ++++- .../Controls/CalendarDatePicker.xaml | 2 + .../Controls/CalendarItem.xaml | 8 +++- .../Controls/Carousel.xaml | 3 +- .../Controls/CheckBox.xaml | 13 ++---- .../Controls/ComboBox.xaml | 13 ++---- .../Controls/ComboBoxItem.xaml | 1 + .../Controls/ContentControl.xaml | 3 +- .../Controls/ContextMenu.xaml | 3 +- .../Controls/DataValidationErrors.xaml | 2 + .../Controls/DatePicker.xaml | 16 ++++++-- .../Controls/Expander.xaml | 29 +++++++------- .../Controls/GridSplitter.xaml | 1 + .../Controls/ItemsControl.xaml | 1 + .../Controls/Label.xaml | 3 +- .../Controls/ListBox.xaml | 8 ++-- .../Controls/ListBoxItem.xaml | 1 + src/Avalonia.Themes.Fluent/Controls/Menu.xaml | 1 + .../Controls/MenuItem.xaml | 6 ++- .../Controls/NotificationCard.xaml | 6 ++- .../Controls/NumericUpDown.xaml | 2 + .../Controls/ProgressBar.xaml | 3 +- .../Controls/RadioButton.xaml | 11 ++--- .../Controls/RepeatButton.xaml | 2 + .../Controls/Separator.xaml | 1 + .../Controls/Slider.xaml | 4 +- .../Controls/TabControl.xaml | 4 +- .../Controls/TabItem.xaml | 1 + .../Controls/TabStrip.xaml | 14 +++++-- .../Controls/TabStripItem.xaml | 1 + .../Controls/TextBox.xaml | 6 +-- .../Controls/TimePicker.xaml | 9 +++-- .../Controls/ToggleButton.xaml | 2 + .../Controls/ToolTip.xaml | 3 +- .../Controls/TreeView.xaml | 3 +- .../Controls/TreeViewItem.xaml | 1 + .../Controls/UserControl.xaml | 1 + 81 files changed, 223 insertions(+), 124 deletions(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 7c911e91e9..2b5215a3fe 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -16,7 +16,6 @@ diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml index fd23067f61..b82a7b0514 100644 --- a/samples/RenderDemo/SideBar.xaml +++ b/samples/RenderDemo/SideBar.xaml @@ -7,7 +7,6 @@ diff --git a/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs b/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs index 10f97794d7..0f257224dd 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs @@ -6,15 +6,6 @@ namespace Avalonia.Controls { public class FlyoutPresenter : ContentControl { - public static readonly StyledProperty CornerRadiusProperty = - Border.CornerRadiusProperty.AddOwner(); - - public CornerRadius CornerRadius - { - get => GetValue(CornerRadiusProperty); - set => SetValue(CornerRadiusProperty, value); - } - protected override void OnKeyDown(KeyEventArgs e) { if (e.Key == Key.Escape) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 9c73ff2411..59975b072d 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -5,7 +5,8 @@ using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Styling; -using Avalonia.VisualTree; + +#nullable enable namespace Avalonia.Controls.Primitives { @@ -17,13 +18,13 @@ namespace Avalonia.Controls.Primitives /// /// Defines the property. /// - public static readonly StyledProperty BackgroundProperty = + public static readonly StyledProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(); /// /// Defines the property. /// - public static readonly StyledProperty BorderBrushProperty = + public static readonly StyledProperty BorderBrushProperty = Border.BorderBrushProperty.AddOwner(); /// @@ -32,6 +33,12 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty BorderThicknessProperty = Border.BorderThicknessProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty CornerRadiusProperty = + Border.CornerRadiusProperty.AddOwner(); + /// /// Defines the property. /// @@ -59,7 +66,7 @@ namespace Avalonia.Controls.Primitives /// /// Defines the property. /// - public static readonly StyledProperty ForegroundProperty = + public static readonly StyledProperty ForegroundProperty = TextBlock.ForegroundProperty.AddOwner(); /// @@ -71,8 +78,8 @@ namespace Avalonia.Controls.Primitives /// /// Defines the property. /// - public static readonly StyledProperty TemplateProperty = - AvaloniaProperty.Register(nameof(Template)); + public static readonly StyledProperty TemplateProperty = + AvaloniaProperty.Register(nameof(Template)); /// /// Defines the IsTemplateFocusTarget attached property. @@ -88,7 +95,7 @@ namespace Avalonia.Controls.Primitives "TemplateApplied", RoutingStrategies.Direct); - private IControlTemplate _appliedTemplate; + private IControlTemplate? _appliedTemplate; /// /// Initializes static members of the class. @@ -111,7 +118,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets the brush used to draw the control's background. /// - public IBrush Background + public IBrush? Background { get { return GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value); } @@ -120,7 +127,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets the brush used to draw the control's border. /// - public IBrush BorderBrush + public IBrush? BorderBrush { get { return GetValue(BorderBrushProperty); } set { SetValue(BorderBrushProperty, value); } @@ -135,6 +142,15 @@ namespace Avalonia.Controls.Primitives set { SetValue(BorderThicknessProperty, value); } } + /// + /// Gets or sets the radius of the border rounded corners. + /// + public CornerRadius CornerRadius + { + get { return GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + /// /// Gets or sets the font family used to draw the control's text. /// @@ -174,7 +190,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets the brush used to draw the control's text and other foreground elements. /// - public IBrush Foreground + public IBrush? Foreground { get { return GetValue(ForegroundProperty); } set { SetValue(ForegroundProperty, value); } @@ -192,7 +208,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets the template that defines the control's appearance. /// - public IControlTemplate Template + public IControlTemplate? Template { get { return GetValue(TemplateProperty); } set { SetValue(TemplateProperty, value); } @@ -265,7 +281,9 @@ namespace Avalonia.Controls.Primitives var e = new TemplateAppliedEventArgs(nameScope); OnApplyTemplate(e); +#pragma warning disable CS0618 // Type or member is obsolete OnTemplateApplied(e); +#pragma warning restore CS0618 // Type or member is obsolete RaiseEvent(e); } diff --git a/src/Avalonia.Themes.Default/AutoCompleteBox.xaml b/src/Avalonia.Themes.Default/AutoCompleteBox.xaml index 66d0f17ede..fe4cd48e72 100644 --- a/src/Avalonia.Themes.Default/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Default/AutoCompleteBox.xaml @@ -11,6 +11,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Padding="{TemplateBinding Padding}" Watermark="{TemplateBinding Watermark}" DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" /> diff --git a/src/Avalonia.Themes.Default/Button.xaml b/src/Avalonia.Themes.Default/Button.xaml index 698ddec2a8..81d96aaa14 100644 --- a/src/Avalonia.Themes.Default/Button.xaml +++ b/src/Avalonia.Themes.Default/Button.xaml @@ -13,6 +13,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Padding="{TemplateBinding Padding}" @@ -31,4 +32,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/ButtonSpinner.xaml b/src/Avalonia.Themes.Default/ButtonSpinner.xaml index 89fbb9d64d..ce2b85d2b5 100644 --- a/src/Avalonia.Themes.Default/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Default/ButtonSpinner.xaml @@ -47,6 +47,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"> @@ -73,6 +74,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"> diff --git a/src/Avalonia.Themes.Default/Calendar.xaml b/src/Avalonia.Themes.Default/Calendar.xaml index 6bbee4ef17..4b67aa232b 100644 --- a/src/Avalonia.Themes.Default/Calendar.xaml +++ b/src/Avalonia.Themes.Default/Calendar.xaml @@ -22,10 +22,11 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" HeaderBackground="{TemplateBinding HeaderBackground}"/> - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/CalendarDatePicker.xaml b/src/Avalonia.Themes.Default/CalendarDatePicker.xaml index aab7d06c46..57b77f70ea 100644 --- a/src/Avalonia.Themes.Default/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Default/CalendarDatePicker.xaml @@ -88,7 +88,8 @@ \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/ContextMenu.xaml b/src/Avalonia.Themes.Default/ContextMenu.xaml index 9b84253c8a..0df4866184 100644 --- a/src/Avalonia.Themes.Default/ContextMenu.xaml +++ b/src/Avalonia.Themes.Default/ContextMenu.xaml @@ -9,6 +9,7 @@ diff --git a/src/Avalonia.Themes.Default/DatePicker.xaml b/src/Avalonia.Themes.Default/DatePicker.xaml index da878c88e2..c6c117138d 100644 --- a/src/Avalonia.Themes.Default/DatePicker.xaml +++ b/src/Avalonia.Themes.Default/DatePicker.xaml @@ -134,6 +134,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" IsEnabled="{TemplateBinding IsEnabled}" MinWidth="{DynamicResource DatePickerThemeMinWidth}" MaxWidth="{DynamicResource DatePickerThemeMaxWidth}" @@ -148,6 +149,7 @@ BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Content="{TemplateBinding Content}" TextBlock.Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="Stretch" @@ -242,6 +244,7 @@ diff --git a/src/Avalonia.Themes.Default/Expander.xaml b/src/Avalonia.Themes.Default/Expander.xaml index 08d8b4c995..5e0958c54c 100644 --- a/src/Avalonia.Themes.Default/Expander.xaml +++ b/src/Avalonia.Themes.Default/Expander.xaml @@ -10,7 +10,10 @@ \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 4bfae4c223..18bf79ce6c 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -14,7 +14,8 @@ + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}"> @@ -96,7 +97,8 @@ + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}"> diff --git a/src/Avalonia.Themes.Default/NumericUpDown.xaml b/src/Avalonia.Themes.Default/NumericUpDown.xaml index 025e822404..6740be69bb 100644 --- a/src/Avalonia.Themes.Default/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Default/NumericUpDown.xaml @@ -9,6 +9,7 @@ - + diff --git a/src/Avalonia.Themes.Default/RepeatButton.xaml b/src/Avalonia.Themes.Default/RepeatButton.xaml index 702e4e6ebd..a9a03c8ed5 100644 --- a/src/Avalonia.Themes.Default/RepeatButton.xaml +++ b/src/Avalonia.Themes.Default/RepeatButton.xaml @@ -20,6 +20,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Padding="{TemplateBinding Padding}" diff --git a/src/Avalonia.Themes.Default/Separator.xaml b/src/Avalonia.Themes.Default/Separator.xaml index cf0db16ee6..6a318d2e85 100644 --- a/src/Avalonia.Themes.Default/Separator.xaml +++ b/src/Avalonia.Themes.Default/Separator.xaml @@ -6,6 +6,7 @@ diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index ed2e67df28..afb5010baa 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -3,9 +3,9 @@ diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml index 6e344ce58e..c7748299a0 100644 --- a/src/Avalonia.Themes.Default/TabItem.xaml +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -12,11 +12,11 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" - Margin="{TemplateBinding Margin}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/TabStripItem.xaml b/src/Avalonia.Themes.Default/TabStripItem.xaml index 28c4c68a3d..61eecc0395 100644 --- a/src/Avalonia.Themes.Default/TabStripItem.xaml +++ b/src/Avalonia.Themes.Default/TabStripItem.xaml @@ -9,6 +9,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" @@ -20,4 +21,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 12df4b6213..9471beaaeb 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -30,7 +30,8 @@ + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}"> diff --git a/src/Avalonia.Themes.Default/TimePicker.xaml b/src/Avalonia.Themes.Default/TimePicker.xaml index c76f900cfe..a58fd62a99 100644 --- a/src/Avalonia.Themes.Default/TimePicker.xaml +++ b/src/Avalonia.Themes.Default/TimePicker.xaml @@ -58,6 +58,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" IsEnabled="{TemplateBinding IsEnabled}" MinWidth="{DynamicResource TimePickerThemeMinWidth}" MaxWidth="{DynamicResource TimePickerThemeMaxWidth}" @@ -71,6 +72,7 @@ BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Content="{TemplateBinding Content}" TextBlock.Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="Stretch" @@ -178,6 +180,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Padding="{DynamicResource DateTimeFlyoutBorderPadding}" MaxHeight="398"> diff --git a/src/Avalonia.Themes.Default/ToggleButton.xaml b/src/Avalonia.Themes.Default/ToggleButton.xaml index 9e05c38eef..ffebd4f63d 100644 --- a/src/Avalonia.Themes.Default/ToggleButton.xaml +++ b/src/Avalonia.Themes.Default/ToggleButton.xaml @@ -13,6 +13,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Padding="{TemplateBinding Padding}" @@ -35,4 +36,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/ToolTip.xaml b/src/Avalonia.Themes.Default/ToolTip.xaml index 1fc0202dd3..35c1dceb8d 100644 --- a/src/Avalonia.Themes.Default/ToolTip.xaml +++ b/src/Avalonia.Themes.Default/ToolTip.xaml @@ -9,9 +9,10 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Padding="{TemplateBinding Padding}"/> - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/TreeView.xaml b/src/Avalonia.Themes.Default/TreeView.xaml index 026bed5899..990d5d0823 100644 --- a/src/Avalonia.Themes.Default/TreeView.xaml +++ b/src/Avalonia.Themes.Default/TreeView.xaml @@ -8,7 +8,8 @@ + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}"> - + + @@ -36,6 +37,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" FontWeight="{TemplateBinding FontWeight}" diff --git a/src/Avalonia.Themes.Fluent/Controls/Button.xaml b/src/Avalonia.Themes.Fluent/Controls/Button.xaml index 597f5d00ec..53d53ef127 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Button.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Button.xaml @@ -16,6 +16,7 @@ + @@ -29,6 +30,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}" @@ -93,8 +95,4 @@ - - diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml index 12b4845522..d228c37912 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml @@ -58,6 +58,7 @@ + @@ -69,7 +70,7 @@ - + + + + + + - - - - - - - - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml index 5110d70a80..df800b4a06 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml @@ -39,6 +39,7 @@ + @@ -55,7 +56,7 @@ MaxWidth="{TemplateBinding MaxWidth}" MinHeight="{TemplateBinding MinHeight}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" - CornerRadius="{DynamicResource OverlayCornerRadius}"> + CornerRadius="{TemplateBinding CornerRadius}"> @@ -88,6 +89,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" /> diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index 032fdd9ae4..3e4471cada 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -8,6 +8,12 @@ + + + + + + 0,0,0,4 40 @@ -50,6 +56,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index 3f70939953..d5d44e1270 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -1,23 +1,23 @@ - - + + Expanded content - + Expanded content - + Expanded content - + Expanded content @@ -51,6 +51,7 @@ + @@ -140,31 +141,31 @@ - - - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml index 1c52c6272c..3320fc9a41 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml @@ -37,6 +37,7 @@ + @@ -59,6 +60,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" IsEnabled="{TemplateBinding IsEnabled}" MinWidth="{DynamicResource TimePickerThemeMinWidth}" MaxWidth="{DynamicResource TimePickerThemeMaxWidth}" @@ -72,11 +74,11 @@ BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Content="{TemplateBinding Content}" TextBlock.Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="Stretch" - VerticalContentAlignment="Stretch" - CornerRadius="{DynamicResource ControlCornerRadius}" /> + VerticalContentAlignment="Stretch" /> @@ -176,13 +178,14 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml index dd8e51e4e5..b1d07059b8 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml @@ -18,6 +18,7 @@ + @@ -29,6 +30,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}" diff --git a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml index f7a1ebbc6b..debdfb2772 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml @@ -49,6 +49,7 @@ + @@ -61,7 +62,7 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" Padding="{TemplateBinding Padding}" - CornerRadius="{DynamicResource OverlayCornerRadius}"> + CornerRadius="{TemplateBinding CornerRadius}"> + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{TemplateBinding CornerRadius}"> Date: Mon, 2 Aug 2021 10:38:40 +0300 Subject: [PATCH 51/73] add comments --- .../AvaloniaNativePlatformExtensions.cs | 35 ++++++++++++++- src/Avalonia.X11/X11Platform.cs | 43 ++++++++++++++++++- src/Windows/Avalonia.Win32/Win32Platform.cs | 35 ++++++++++++++- 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 76cb7a8057..51d3556dfd 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -26,19 +26,52 @@ namespace Avalonia } } + /// + /// OSX backend options. + /// public class AvaloniaNativePlatformOptions { + /// + /// Deferred renderer would be used on Windows when set to true. Immediate renderer would be used when set to false. The default value is true. + /// + /// + /// Avalonia has two different renderers - Immediate and Deferred. + /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. + /// public bool UseDeferredRendering { get; set; } = true; + + /// + /// Determines whether to use GPU for rendering in your project. The default value is true. + /// public bool UseGpu { get; set; } = true; + + /// + /// Embeds popups to the window when set to true. The default value is false. + /// public bool OverlayPopups { get; set; } + + /// + /// This property should be used in case you want to build Avalonia OSX native part by yourself + /// and make your Avalonia app run with it. The default value is null. + /// public string AvaloniaNativeLibraryPath { get; set; } } // ReSharper disable once InconsistentNaming + /// + /// OSX front-end options. + /// public class MacOSPlatformOptions { + /// + /// Determines whether to show your application in the dock when it runs. The default value is true. + /// public bool ShowInDock { get; set; } = true; - + + /// + /// By default, Avalonia adds items like Quit, Hide to the OSX Application Menu. + /// You can prevent Avalonia from adding those items to the OSX Application Menu with this property. The default value is false. + /// public bool DisableDefaultApplicationMenuItems { get; set; } } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index a57bdbdf87..0eb716badd 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -162,14 +162,48 @@ namespace Avalonia.X11 namespace Avalonia { - + /// + /// Platform-specific options which apply to Linux. + /// public class X11PlatformOptions { + /// + /// Enables native Linux EGL when set to true. The default value is false. + /// public bool UseEGL { get; set; } + + /// + /// Determines whether to use GPU for rendering in your project. The default value is true. + /// public bool UseGpu { get; set; } = true; + + /// + /// Embeds popups to the window when set to true. The default value is false. + /// public bool OverlayPopups { get; set; } + + /// + /// Enables global KDE menu. The default value is false. + /// public bool UseDBusMenu { get; set; } + + /// + /// Deferred renderer would be used on Windows when set to true. Immediate renderer would be used when set to false. The default value is true. + /// + /// + /// Avalonia has two different renderers - Immediate and Deferred. + /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. + /// public bool UseDeferredRendering { get; set; } = true; + + /// + /// Determines whether to use IME. + /// IME would be enabled by default if user input languages contain one of the following languages: Mandarin Chinese, Japanese, Vietnamese, Korean. + /// + /// + /// Input method editor is a component that enables users to generate characters not natively available + /// on their input devices by using sequences of characters or mouse operations that are natively available on their input devices. + /// public bool? EnableIme { get; set; } public IList GlProfiles { get; set; } = new List @@ -190,6 +224,13 @@ namespace Avalonia "llvmpipe" }; public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication"; + + /// + /// Enables multitouch support. The default value is false. + /// + /// + /// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time. + /// public bool? EnableMultiTouch { get; set; } } public static class AvaloniaX11PlatformExtensions diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 10a6db0b57..cf46db69eb 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -35,15 +35,46 @@ namespace Avalonia } } + /// + /// Platform-specific options which apply to Windows. + /// public class Win32PlatformOptions { + /// + /// Deferred renderer would be used on Windows when set to true. Immediate renderer would be used when set to false. The default value is true. + /// + /// + /// Avalonia has two different renderers - Immediate and Deferred. + /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. + /// public bool UseDeferredRendering { get; set; } = true; - + + /// + /// Enables ANGLE for Windows. For every Windows which is above Win 7, the default is true,otherwise-false. + /// + /// + /// GPU would not be used for rendering if you would set that to false. + /// public bool? AllowEglInitialization { get; set; } - + + /// + /// Enables multitouch support. The default value is false. + /// + /// + /// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time. + /// public bool? EnableMultitouch { get; set; } + + /// + /// Embeds popups to the window when set to true. The default value is false. + /// public bool OverlayPopups { get; set; } + + /// + /// Avalonia would try to use native Widows OpenGL when set to true. The default value is false. + /// public bool UseWgl { get; set; } + public IList WglProfiles { get; set; } = new List { new GlVersion(GlProfileType.OpenGL, 4, 0), From 01b87381b7d56414e9e718132a0733aedf94fc62 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 3 Aug 2021 12:22:37 +0300 Subject: [PATCH 52/73] enable multitouch --- src/Avalonia.X11/X11Platform.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index a57bdbdf87..4561993279 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -190,7 +190,7 @@ namespace Avalonia "llvmpipe" }; public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication"; - public bool? EnableMultiTouch { get; set; } + public bool? EnableMultiTouch { get; set; } = true; } public static class AvaloniaX11PlatformExtensions { diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 10a6db0b57..8cfb910e4b 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -40,8 +40,8 @@ namespace Avalonia public bool UseDeferredRendering { get; set; } = true; public bool? AllowEglInitialization { get; set; } - - public bool? EnableMultitouch { get; set; } + + public bool? EnableMultitouch { get; set; } = true; public bool OverlayPopups { get; set; } public bool UseWgl { get; set; } public IList WglProfiles { get; set; } = new List From c2c97901118dee637bd84925af7119ed77f430ea Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 4 Aug 2021 12:21:56 +0300 Subject: [PATCH 53/73] update --- src/Avalonia.X11/X11Platform.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index f4a1079455..df1c542159 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -226,7 +226,7 @@ namespace Avalonia public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication"; /// - /// Enables multitouch support. The default value is false. + /// Enables multitouch support. The default value is true. /// /// /// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time. diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 86f348bd01..0240655761 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -58,7 +58,7 @@ namespace Avalonia public bool? AllowEglInitialization { get; set; } /// - /// Enables multitouch support. The default value is false. + /// Enables multitouch support. The default value is true. /// /// /// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time. From f283921e281077c74fd6c3b135e624b00b848293 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 4 Aug 2021 12:50:15 +0300 Subject: [PATCH 54/73] fix --- src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs | 4 ++-- src/Avalonia.X11/X11Platform.cs | 6 +++--- src/Windows/Avalonia.Win32/Win32Platform.cs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 51d3556dfd..eef765e7ec 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -32,10 +32,10 @@ namespace Avalonia public class AvaloniaNativePlatformOptions { /// - /// Deferred renderer would be used on Windows when set to true. Immediate renderer would be used when set to false. The default value is true. + /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true. /// /// - /// Avalonia has two different renderers - Immediate and Deferred. + /// Avalonia has two rendering modes: Immediate and Deferred rendering. /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. /// public bool UseDeferredRendering { get; set; } = true; diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index df1c542159..1f9c91aa2b 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -188,17 +188,17 @@ namespace Avalonia public bool UseDBusMenu { get; set; } /// - /// Deferred renderer would be used on Windows when set to true. Immediate renderer would be used when set to false. The default value is true. + /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true. /// /// - /// Avalonia has two different renderers - Immediate and Deferred. + /// Avalonia has two rendering modes: Immediate and Deferred rendering. /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. /// public bool UseDeferredRendering { get; set; } = true; /// /// Determines whether to use IME. - /// IME would be enabled by default if user input languages contain one of the following languages: Mandarin Chinese, Japanese, Vietnamese, Korean. + /// IME would be enabled by default if the current user input language is one of the following: Mandarin, Japanese, Vietnamese or Korean. /// /// /// Input method editor is a component that enables users to generate characters not natively available diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 0240655761..a881c45cd0 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -41,19 +41,19 @@ namespace Avalonia public class Win32PlatformOptions { /// - /// Deferred renderer would be used on Windows when set to true. Immediate renderer would be used when set to false. The default value is true. + /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true. /// /// - /// Avalonia has two different renderers - Immediate and Deferred. + /// Avalonia has two rendering modes: Immediate and Deferred rendering. /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. /// public bool UseDeferredRendering { get; set; } = true; /// - /// Enables ANGLE for Windows. For every Windows which is above Win 7, the default is true,otherwise-false. + /// Enables ANGLE for Windows. For every Windows version that is above Windows 7, the default is true otherwise it's false. /// /// - /// GPU would not be used for rendering if you would set that to false. + /// GPU rendering will not be enabled if this is set to false. /// public bool? AllowEglInitialization { get; set; } From 037047299791cee0338a17f241614300bdc96e85 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 4 Aug 2021 13:13:36 +0300 Subject: [PATCH 55/73] update --- src/Avalonia.X11/X11Platform.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 1f9c91aa2b..3a919c8814 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -183,7 +183,8 @@ namespace Avalonia public bool OverlayPopups { get; set; } /// - /// Enables global KDE menu. The default value is false. + /// Enables global menu support on Linux desktop environments where it's supported (e. g. XFCE and MATE with plugin, KDE, etc). + /// The default value is false. /// public bool UseDBusMenu { get; set; } From dbb8427e3a13580ac2cc53558e40d2d50ec21396 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Wed, 4 Aug 2021 15:01:14 +0200 Subject: [PATCH 56/73] Make event actually keydown --- src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index a85fe6c0c4..ea06c33e4d 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Reactive.Subjects; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; @@ -12,7 +10,6 @@ using Avalonia.Diagnostics.ViewModels; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Markup.Xaml; -using Avalonia.Media; using Avalonia.Styling; using Avalonia.VisualTree; @@ -30,6 +27,7 @@ namespace Avalonia.Diagnostics.Views _keySubscription = InputManager.Instance.Process .OfType() + .Where(x => x.Type == RawKeyEventType.KeyDown) .Subscribe(RawKeyDown); _frozenPopupStates = new Dictionary(); From 5fb8126d2dfbb55ed62772a718f1610276794961 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Wed, 4 Aug 2021 15:41:31 +0200 Subject: [PATCH 57/73] Add label for frozen popups to status bar --- .../Diagnostics/Views/MainView.xaml | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 8c4db33f91..6f2ac96a66 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -5,14 +5,14 @@ - + + IsEnabled="False" /> @@ -21,58 +21,68 @@ + IsEnabled="False" /> + IsEnabled="False" /> - + + IsEnabled="False" /> - + - - - + + + + Content="{Binding Content}" /> - + IsVisible="False" /> + + IsVisible="{Binding IsVisible}" /> + - - Hold Ctrl+Shift over a control to inspect. - - Focused: - - - Pointer Over: - - + + + Hold Ctrl+Shift over a control to inspect. + + Focused: + + + Pointer Over: + + + + + + From 7e6d59d6702e1c679c5cd77193bda8f8f3bd3812 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 5 Aug 2021 11:04:52 +0200 Subject: [PATCH 58/73] Respect CancellationToken in RunLoop. --- .../Platform/InternalPlatformThreadingInterface.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index cb1291410a..a5495fdfc9 100644 --- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -21,10 +21,12 @@ namespace Avalonia.Controls.Platform public void RunLoop(CancellationToken cancellationToken) { - while (true) + var handles = new[] { _signaled, cancellationToken.WaitHandle }; + + while (!cancellationToken.IsCancellationRequested) { Signaled?.Invoke(null); - _signaled.WaitOne(); + WaitHandle.WaitAny(handles); } } From dc404b545e534ea904911ca9647f312f77e7ed6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 6 Aug 2021 08:49:05 +0200 Subject: [PATCH 59/73] Fix skia OpacityMask push and pop methods --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2352b8b076..ea4c35d6e7 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -534,7 +534,11 @@ namespace Avalonia.Skia public void PushOpacityMask(IBrush mask, Rect bounds) { // TODO: This should be disposed - var paint = new SKPaint(); + var paint = new SKPaint() + { + IsAntialias = true, + Style = SKPaintStyle.StrokeAndFill + }; Canvas.SaveLayer(paint); _maskStack.Push(CreatePaint(paint, mask, bounds, true)); @@ -543,7 +547,14 @@ namespace Avalonia.Skia /// public void PopOpacityMask() { - using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn }) + using (var paint = new SKPaint + { + IsAntialias = true, + Style = SKPaintStyle.StrokeAndFill, + BlendMode = SKBlendMode.DstIn, + Color = new SKColor(0, 0, 0, 255), + ColorFilter = SKColorFilter.CreateLumaColor() + }) { Canvas.SaveLayer(paint); using (var paintWrapper = _maskStack.Pop()) From 39e7d362b2fffc02c88c306301951b844d3849d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 6 Aug 2021 09:07:18 +0200 Subject: [PATCH 60/73] Enable antialiasing for PushGeometryClip --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 +- src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2352b8b076..1f186396ab 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -506,7 +506,7 @@ namespace Avalonia.Skia public void PushGeometryClip(IGeometryImpl clip) { Canvas.Save(); - Canvas.ClipPath(((GeometryImpl)clip).EffectivePath); + Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true); } /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 9336c9a7bb..09e5b7c71a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -541,7 +541,8 @@ namespace Avalonia.Direct2D1.Media ContentBounds = PrimitiveExtensions.RectangleInfinite, MaskTransform = PrimitiveExtensions.Matrix3x2Identity, Opacity = 1, - GeometricMask = ((GeometryImpl)clip).Geometry + GeometricMask = ((GeometryImpl)clip).Geometry, + MaskAntialiasMode = AntialiasMode.PerPrimitive }; var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); _deviceContext.PushLayer(ref parameters, layer); From 5b9f06865e212be05e8ec5c2d1657029fe0f074a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 6 Aug 2021 20:10:03 +0200 Subject: [PATCH 61/73] Update expected test files --- .../CustomRender/GeometryClip.expected.png | Bin 2258 -> 4082 bytes .../Geometry_Clip_Clips_Path.expected.png | Bin 728 -> 660 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png b/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png index 535d411cc7a02d6f26a14b9ef1392a9a7cc928d6..e2604d28362214beb03612498936b8c90a420418 100644 GIT binary patch literal 4082 zcmYk92UJtdv%u+pdIt$cI!Z60H)#<9Qlx{40-+-%fFfYPpr6!$bV8MmbO_xjCQqRo$tK&=A3(X@7)9^_EYXPYTNoH;X_ z$Hj`@7PFag!N8LYtZc@qo8Mi=!rd)83#xrqg{Af^&Z21jxBcR_it$p{tt@j$SzS|* zt*))cg!o!J7mGGITCO*5qSH~z)Tdpo+=f96U`@smljDj>ARwOxe===}RJI<0*L!iO zH5LZ7%`@QDr$R)>5!c48%}Pn3wUF{b6jdcx-ssmW1k1G7)Q!7_*ftc>z4{x z1Jn~~BPM-uD{d{k2URq`jB0~os|EjH#z5pw*Rff)*zR*5>a;A%biAfn zefYDyQHh(_MN{_&%2!9yM^s}6TbIrB)-_z)E-9lNn8Jw;3qGc^zPQX8TS@3>>4NKB zLh~|uyc~tJ!%2b;FoC~6d6OIMpYo3)i8+hhvw{7UL%&ge<)0S@8_Wb`+n^?9>mT74 zInKFsraNk@O;}Z-x1EG11fY07q>@C&m9Y+N**atLI_7LoYEpd<# zO9tk)lLw9^v-eGgU1}smeep;Tw*h}QRK9@!v{OXZNsHzTZA5>E5uj~Q4U0D$@JCEF z91Y27y)RDzHjY&Y`~<5h2F$~z5L>!76EM)^SStht^gPlYhQhpYJbNEryzK*3*z~Z@ z*WoX}|JV(Y%l^r)1@LZW*_&l=+vju$mfMArM5my&zET|+HST1ZxgmBrB1?4@*|ltu zt0_EY9V09Ve4Qvy_r*!W1XMMa~m@ zq^<{PG)$O|7geP2ZJS8T2Z?9aGWaDE=*nV(yLx?(KdHs7hztM5zH0A8g`5bb?1=(W zlF&uZd_zAi?-q+k(=`{UbzneSk%C&mmMXJjE*H_4R_gdB5;GBG@6_pOzyi@)?1-5nC99_PSSlU&r#Bd~E64UYen*!Of`Ri;|xb8fdSA zF#-534HZE5nEh+0%(aoe3L(~=t~w!2%x!M>2p2Yg+Ed!cl0f)9(Y~{1IONswY@V_l zs+eXIc`m8cI}om;42=kon}jr>bfF3XaaXn?xGk&m7#_kE=6TrTGYoQ1@|8CG zisb^YoG|Wd1=y&I1?NhvJzgM&11+Tq!Vy+RI?jkU8%E$WRSfB~B6iqLY=UP^(o+)1 z=`;@*u^HJ{j@jl}qhAE{#*%N(LVk9JNhhR%tlRgo)Q0m27ij_%_8I|Mi|lbKTsr&m zNU!H4#F#@jtkeFUf79K~-tS0#rbw8j3};r?j+ff`U^%I*ffn0$_k-;VL%5r6WO%&v zy~SxeJ@}uP#I)9hfM(DVoa8Q4?pK-wbKpvtNx6L$3NT>MO8)b97h3DpX|a+r<0Cr# zWxLq}$nM`DR=)tl0sNbCC5z&8Fo%!1;`0MqniDCxn<@`Nx&oT2F=YsdU+bqNwGQ>t zKkh*GRQ2IQ{xJz5>72BWUq!6aee{5@^i`G6D}KSVSrFy?FxEX1a|tO^Ouu4-ofeF> zxNb~40IhZP^JFsAX%XjSF`S(|P4?>YYf@D*TS>vs^xPQ;WZ0Atdb*NV$j=$iUpd`U zibCSOEl1(X_5{+FKr@qW4et>#A!GirK~{6_5_#o54IMl3Pn7i{@WBf zhiaMp^zZLF6l7{&Txbq%VttD3v(^)<;$}~k+hi7*q@@LyF_BFMW1Z$u+!H_}L6PE+ zVE;-Mh~?c#A0si}eAql=oV%P6O;5E8jJ0r;y;3NQ4iN^uYDs`Bn7euywL9pE)wuJg zw2Tu~Tr_VkjVQ!458i@`9G5HX6mNEPrKREB6jTpbF1D~b9Jq_Z%h3}{f>MnEQBp-YBxZ}NDS8gQ52maME#D~HN}^cPrRt8M8(ICBts{CGOoE(hMeI%vbX)@^0job|sB#|XMuup1`kuTc_D zA{2D(g{0{>-^;BaLdf;a*h9B3pe)8}4MALkAAibb3d>_t9~gHPUm07WULsQc#x73>i8l9bO=O?fegh4? zW_#irUj^E7>htJ|?Tw#sNMSw72)en8&AZTNmS_0*YVqyxY4o{X(gNdTF*@Qtk&(=l zHMH>^g)RFa2ggN!M<#kb+AlI5bc-7BW|sJ(A3#@0rP@pX)GsIsSYM*{;Aq=?N?dqa zUT4A;|Mt}Tj^o!b1ITSl=R3DbiHf={gK#c(Y9i&2=8I9Q*}6YDe+h&!j`BdgJ(rs_ z)ooRckd754c6W*&#(%nZIVC_Ok^KYZy*Nt}aJ7`>J^UId0 zu3#xBg1ad;yCcoG zGyRjgAMRR^WVUM5V0UAhB=@8{?wYYkcb}$}3J+{J1lhkl8*mGx@J}7t#$}@AUr^j> zcf3(ul&gCn>)#1qv%bIHfD{d0L5PG>Mqe%m>kuOAofXWUT^W%55)W_h)BJe%dN7ye zgJB*;t3~Sqi6Ce#m$|#_QO2Hv?%ukG3qEfNxF&p&nS_o*sv}*`O*8?Yy_gl3y{bs6 z*tV{5>jQw{j#`?&hn=3LuS%vTb8Fi?A(!8(pViw3c6CmF7 zC6AgY{6oe6Vf$bDoUZ?B3wg{%67_5q(RsNAo0(bTUxqmF3xLm_t4ryKC_`I^TbJEMp<>&u=SE$?*acIJ9hv!IRiFN!Is|wi%jML4SnsDKoLd7~% z$7itcFlJ07;H$Y@PuXQdLMC@iVp#gN(@3lKmH7?R@O5Lo+-T&oW{C|lv^QgVw%8bb<(U9U->GsJ0|zw(XUYTAdRJ?P-n_)R&e$r~SV?kfLF|4-V^1kXiA WkKxzPlE_Isg~=@oaJ_-cv;P8IdBoQM literal 2258 zcmb_eYfuwc6wX6LQ$VW*7r{ zEgK_<3bJe%P)Y$&*&SYA>H9^}*|5uv0KgJ&mr8Imw3Y7U8XGWWBA zI0x`PJbC_#R1(Rh30`FFx?fh4NW&5qgwGLfT=n#72gal zto+<0Vcrm%|9-dPripA`)T-EFzXkd8%` z{Brn)qpl!s>DG!xMZTv%6{_6Jnyjg_GJ&jmZTxMXvXBcxJ(5-=7DcxB0N0+A=0M|7 z%LqWKHDYcZw+2C47Iv?@V|rR9$Xo6->q6GuJFx)e9hSSkDD9OSa+P{^+(E8^{yvWL zShK+QqzR=Dc-_PfLr6>FLmBwq3lgZ>Tbn~YlkW{eo!)m7;I9G^ErC{!OK(zohhF(m z=I-9mYux8|v5UpZIlul${bX0|UdoZ>o-=5BBpy1E(>iaJHtTK_ zRN;1WSCFx$vpAq7)kE&(m_Fm9(l$rEA%#`e0YO8ydxLtP`GL}Mi?sms=WbBCw1e%^ z`^*OBxyLgB(6FFX7wa=)f+uqRDjSlT8y$P`*vj`JpY{eSl`lW7Yuy z1df>!hIoc%0AVwBs1=vZR!12BxfekROwjotK$?nX+QYn81OePQhFGS_*pGsN=5XC? zjY0s>GnPtqBM@MbinctP$wj?nG(mB#c-u6@mxMe${=W0P>o!Uj{rTsjsfb?bPk$cx z&b}#Tv_)uQP~$8)FRWK$RTL;w_7qeO-1`IG#9RXhrX0GFq>pwqH(7= zh5CsdNi+^Or%XWOBY3kPv=N=R5Y)4M}ydxpS|5&PKNYl(}~b9%sdj(o5l-@$r@}A6!3$S#~?KAV|pO^ zO>qpY8TUf;v)J^d@wyzOtNp>&!p~MPgO9>&BH|bsO*j>~+aHfYf-_y@g7hoQQb#x7 zThRfNAU$4evW5f6Y-enbJ65QUM`}WfJ2f&eP-SZkdfh)l8abd!5`pf}-PN+UDd2hu zHvF6F#6$h%1G2%v>X0)+k81(XGlB!2{RLP=Bz2nYy#2xN!=00K)%L_t(|obB4XP3=GsfZ^Xc-O#{k z;sQqksXZog2`ruzd16>X|PFsNu_jeK9v^wP0Q1~3S1^FjPyRhTAa{4i+%jjbpG0WCzL3{f z0dz9sE>B)m#8(nUg=K#CWu8J zAG}nsg~`nSr&X|lJkX-=sGc=i^!-e-M2o(gSypJ#_cF}_E&5L8X`@Bo$0bd)=)1V3 zg%*7e*K{<{qVM3|RcH0pHZAnByRCr$Pm(fw=Knz5k!G=NzOD2#Rp%4-w7-_c+X4Pvv;~Ci= z&8vG~jMb=1S9Lci<0yZ+tGpGaupf4HKf4?MG+OG!-ho=>*v7+ zwk}rBhYf96ti263xK*+8cG&P1#kyr+0h6Dfx3D7WESBM+gsuERW} zZkT0D=zV39>55sngq~L>dG45nx9WLilIPFc+b~`(cJVEGU72Lry4-beNLnM*6Q&3Z^Kv_I0U}b6QMcwXaK+oKy2t zmdACew|~@=<91!@J@VvuU6+c;^TU>_A>!JAEm&GawF6tSq=;z?wrD94(H?Bs5@LLt zu!XmZ(e1*P-Xg}f4O=`{jBFpae2y3w16Dw)7!?OrLW&p@3s%HhF(MwUj5Xr(n6N_r z6Q9O~m9iv0iw!I0nfRo`%3 Date: Sat, 7 Aug 2021 13:12:09 +0200 Subject: [PATCH 62/73] Allow for controling delay of scrollbar hide/show. --- src/Avalonia.Controls/Primitives/ScrollBar.cs | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index d264ed76cc..d5d6af8bfa 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -57,6 +57,18 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty AllowAutoHideProperty = AvaloniaProperty.Register(nameof(AllowAutoHide), true); + /// + /// Defines the property. + /// + public static readonly StyledProperty HideDelayProperty = + AvaloniaProperty.Register(nameof(HideDelay), TimeSpan.FromSeconds(2)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ShowDelayProperty = + AvaloniaProperty.Register(nameof(ShowDelay), TimeSpan.FromSeconds(0.5)); + private Button _lineUpButton; private Button _lineDownButton; private Button _pageUpButton; @@ -126,6 +138,24 @@ namespace Avalonia.Controls.Primitives get => GetValue(AllowAutoHideProperty); set => SetValue(AllowAutoHideProperty, value); } + + /// + /// Gets a value that determines how long will be the hide delay after user stops interacting with the scrollbar. + /// + public TimeSpan HideDelay + { + get => GetValue(HideDelayProperty); + set => SetValue(HideDelayProperty, value); + } + + /// + /// Gets a value that determines how long will be the show delay when user starts interacting with the scrollbar. + /// + public TimeSpan ShowDelay + { + get => GetValue(ShowDelayProperty); + set => SetValue(ShowDelayProperty, value); + } public event EventHandler Scroll; @@ -296,12 +326,12 @@ namespace Avalonia.Controls.Primitives private void CollapseAfterDelay() { - InvokeAfterDelay(Collapse, TimeSpan.FromSeconds(2)); + InvokeAfterDelay(Collapse, HideDelay); } private void ExpandAfterDelay() { - InvokeAfterDelay(Expand, TimeSpan.FromMilliseconds(400)); + InvokeAfterDelay(Expand, ShowDelay); } private void Collapse() From da06a15d16d9fb0fbe12e970aceebedd0a7611ad Mon Sep 17 00:00:00 2001 From: Sergey Mikolaytis Date: Sun, 8 Aug 2021 02:15:21 +0300 Subject: [PATCH 63/73] [OverlayPopupHost] remove render white rect override --- src/Avalonia.Controls/Primitives/OverlayPopupHost.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index 762d8d37a6..403902f676 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -140,10 +140,5 @@ namespace Avalonia.Controls.Primitives return new OverlayPopupHost(overlayLayer); } - - public override void Render(DrawingContext context) - { - context.FillRectangle(Brushes.White, new Rect(default, Bounds.Size)); - } } } From ae222e25e6b27b0035421d2583c2b37a4036fff1 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 10 Aug 2021 11:30:19 +0200 Subject: [PATCH 64/73] Mark IVisualTreeHost [Obsolete] --- src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs b/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs index b63c49bd5a..01212b238d 100644 --- a/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs +++ b/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs @@ -1,8 +1,11 @@ +using System; + namespace Avalonia.VisualTree { /// /// Interface for controls that host their own separate visual tree, such as popups. /// + [Obsolete] public interface IVisualTreeHost { /// From d666a4263c580b21c7221a972ee34aab65c86c6a Mon Sep 17 00:00:00 2001 From: "Luis v.d.Eltz" Date: Tue, 10 Aug 2021 11:30:42 +0200 Subject: [PATCH 65/73] Update comment Co-authored-by: Steven Kirk --- src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs index 58174b1039..4dfe5eb174 100644 --- a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs +++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs @@ -3,7 +3,7 @@ namespace Avalonia.Controls.Diagnostics { /// - /// Helper class to provide some diagnostics insides into . + /// Helper class to provide diagnostics information for . /// public static class ToolTipDiagnostics { From 08849539a72ccb7163bee028fd70fea20aa13b67 Mon Sep 17 00:00:00 2001 From: "Luis v.d.Eltz" Date: Tue, 10 Aug 2021 11:31:39 +0200 Subject: [PATCH 66/73] Update comment Co-authored-by: Steven Kirk --- src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs index 4dfe5eb174..4acf2a217f 100644 --- a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs +++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs @@ -7,6 +7,9 @@ namespace Avalonia.Controls.Diagnostics /// public static class ToolTipDiagnostics { + /// + /// Provides access to the internal for use in DevTools. + /// public static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty; } } From 71ac5e3db8990c3b5a10bdf809dad8d68ceca4e9 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 10 Aug 2021 11:41:05 +0200 Subject: [PATCH 67/73] Only IPopupHost should be root visual --- src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 4b957c2382..94707ac189 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -54,8 +54,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private bool IsRoot => Visual is TopLevel || - Visual is Popup || + private bool IsRoot => Visual is TopLevel || Visual is ContextMenu || Visual is IPopupHost; From 346015d804e777a1ea2f32f2252841a9135845ae Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 11 Aug 2021 21:03:52 +0200 Subject: [PATCH 68/73] Call WindowStateChanged when setting WindowState via code. Fixes #6399 --- native/Avalonia.Native/src/OSX/window.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 9c6a0e6187..0a78558d27 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1204,6 +1204,7 @@ private: } _actualWindowState = _lastWindowState; + WindowEvents->WindowStateChanged(_actualWindowState); } From 24c8af1b626dbc2376cd67006f847b8464ef40b4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 11 Aug 2021 21:04:37 +0200 Subject: [PATCH 69/73] Manually restore parent windows before showing child. --- native/Avalonia.Native/src/OSX/window.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 0a78558d27..14fe60ab0b 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -713,6 +713,12 @@ private: if(cparent == nullptr) return E_INVALIDARG; + // If one tries to show a child window with a minimized parent window, then the parent window will be + // restored but MacOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive + // state. Detect this and explicitly restore the parent window ourselves to avoid this situation. + if (cparent->WindowState() == Minimized) + cparent->SetWindowState(Normal); + [cparent->Window addChildWindow:Window ordered:NSWindowAbove]; UpdateStyle(); From 964121312c29c43ef743f7a2d0076ec268696838 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Sat, 14 Aug 2021 13:53:49 +0300 Subject: [PATCH 70/73] fix --- build/MicroCom.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/MicroCom.targets b/build/MicroCom.targets index b48e377fd4..49d2cdce72 100644 --- a/build/MicroCom.targets +++ b/build/MicroCom.targets @@ -15,7 +15,8 @@ Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs" Outputs="%(AvnComIdl.OutputFile)"> - + From 29f4806cfdeb1e59c36686b3f9d8264c009cfd19 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaytis Date: Mon, 16 Aug 2021 09:01:07 +0300 Subject: [PATCH 71/73] [OSX] [Native] fix tab shortcuts in NativeMenu --- native/Avalonia.Native/src/OSX/KeyTransform.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/native/Avalonia.Native/src/OSX/KeyTransform.mm b/native/Avalonia.Native/src/OSX/KeyTransform.mm index 6b7d95b619..4817ad0ccf 100644 --- a/native/Avalonia.Native/src/OSX/KeyTransform.mm +++ b/native/Avalonia.Native/src/OSX/KeyTransform.mm @@ -222,6 +222,7 @@ std::map s_QwertyKeyMap = { 45, "n" }, { 46, "m" }, { 47, "." }, + { 48, "\t" }, { 49, " " }, { 50, "`" }, { 51, "" }, From 08dea6498ede1422991635994409ff6eb9df81bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 16 Aug 2021 19:46:08 +0200 Subject: [PATCH 72/73] Check for TextBox.Text null in AutoCompleteBox --- src/Avalonia.Controls/AutoCompleteBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index c656ba6f6c..5a6e78f441 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -2005,7 +2005,7 @@ namespace Avalonia.Controls // The TextBox.TextChanged event was not firing immediately and // was causing an immediate update, even with wrapping. If there is // a selection currently, no update should happen. - if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != TextBox.Text.Length) + if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != (TextBox.Text?.Length ?? 0)) { return; } @@ -2303,7 +2303,7 @@ namespace Avalonia.Controls { if (IsTextCompletionEnabled && TextBox != null && userInitiated) { - int currentLength = TextBox.Text.Length; + int currentLength = TextBox.Text?.Length ?? 0; int selectionStart = TextBoxSelectionStart; if (selectionStart == text.Length && selectionStart > _textSelectionStart) { From 834f3b01b8601790ed1e4b6bbe8b35fca47c81bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 16 Aug 2021 20:25:46 +0200 Subject: [PATCH 73/73] Add Not value converter to BoolConverters --- src/Avalonia.Base/Data/Converters/BoolConverters.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Avalonia.Base/Data/Converters/BoolConverters.cs b/src/Avalonia.Base/Data/Converters/BoolConverters.cs index 9329cdd6af..3985c5e32f 100644 --- a/src/Avalonia.Base/Data/Converters/BoolConverters.cs +++ b/src/Avalonia.Base/Data/Converters/BoolConverters.cs @@ -18,5 +18,11 @@ namespace Avalonia.Data.Converters /// public static readonly IMultiValueConverter Or = new FuncMultiValueConverter(x => x.Any(y => y)); + + /// + /// A value converter that returns true when input is false and false when input is true. + /// + public static readonly IValueConverter Not = + new FuncValueConverter(x => !x); } }