-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Unify selection event handling for all SelectingItemsControl types plus TreeView
#19203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Unify selection event handling for all SelectingItemsControl types plus TreeView
#19203
Conversation
181ecb0 to
de20ad6
Compare
|
You can test this PR using the following package version. |
|
@MrJul this PR has had the "Needs API Review" label for some time now, and several more recent PRs have been API-approved in the meantime. Is there any ETA for when this one might come up for review? |
|
@TomEdwardsEnscape Sorry, we don't have any set order for the API review items. Sometimes simple ones that would take a few minutes of discussion take priority. For this one, I personally wanted to have a good grasp of it before doing any API review. The API changes, while important, are secondary in this specific case. Understanding and discussing the behavioral changes should be done first. Could you please update your branch to the latest master? |
de20ad6 to
376bc3d
Compare
|
You can test this PR using the following package version. |
376bc3d to
40e2d56
Compare
|
@MrJul the branch has been updated. |
|
You can test this PR using the following package version. |
|
API diff for review: Avalonia.Base (net6.0, net8.0, netstandard2.0) namespace Avalonia.Input
{
+ public interface IKeyModifiersEventArgs
+ {
+ KeyModifiers KeyModifiers { get; }
+ }
}Avalonia.Controls (net6.0, net8.0, netstandard2.0) namespace Avalonia.Controls
{
public class ComboBox : SelectingItemsControl
{
+ protected override InputSelectionTrigger EventSelectionTrigger(InputElement selectable, PointerEventArgs eventArgs);
+ public override bool UpdateSelectionFromEvent(Control container, RoutedEventArgs eventArgs);
}
public class ListBoxItem : ContentControl, ISelectable
{
+ protected override void OnKeyDown(KeyEventArgs e);
+ protected bool UpdateSelectionFromEvent(RoutedEventArgs e);
}
public class TabControl : SelectingItemsControl
{
- protected override void OnGotFocus(GotFocusEventArgs e);
- protected override void OnPointerPressed(PointerPressedEventArgs e);
- protected override void OnPointerReleased(PointerReleasedEventArgs e);
+ protected override InputSelectionTrigger EventSelectionTrigger(InputElement selectable, PointerEventArgs eventArgs);
+ public override bool UpdateSelectionFromEvent(Control container, RoutedEventArgs eventArgs);
}
public class TabItem : HeaderedContentControl, ISelectable
{
+ protected override void OnGotFocus(GotFocusEventArgs e);
+ protected override void OnPointerPressed(PointerPressedEventArgs e);
+ protected override void OnPointerReleased(PointerReleasedEventArgs e);
+ protected bool UpdateSelectionFromEvent(RoutedEventArgs e);
}
public class TreeView : ItemsControl, ICustomKeyboardNavigation
{
- protected override void OnPointerPressed(PointerPressedEventArgs e);
+ protected virtual InputSelectionTrigger EventSelectionTrigger(InputElement selectable, KeyEventArgs eventArgs);
+ protected virtual InputSelectionTrigger EventSelectionTrigger(InputElement selectable, PointerEventArgs eventArgs);
+ public virtual bool UpdateSelectionFromEvent(Control container, RoutedEventArgs eventArgs);
}
public class TreeViewItem : HeaderedItemsControl, ISelectable
{
+ protected override void OnPointerPressed(PointerPressedEventArgs e);
+ protected override void OnPointerReleased(PointerReleasedEventArgs e);
}
}
namespace Avalonia.Controls.Primitives
{
public class SelectingItemsControl : ItemsControl
{
+ protected virtual InputSelectionTrigger EventSelectionTrigger(InputElement selectable, KeyEventArgs eventArgs);
+ protected virtual InputSelectionTrigger EventSelectionTrigger(InputElement selectable, PointerEventArgs eventArgs);
+ public new static SelectingItemsControl? ItemsControlFromItemContainer(Control container);
+ public virtual bool UpdateSelectionFromEvent(Control container, RoutedEventArgs eventArgs);
}
public class TabStrip : SelectingItemsControl
{
- protected override void OnGotFocus(GotFocusEventArgs e);
- protected override void OnPointerPressed(PointerPressedEventArgs e);
+ protected override InputSelectionTrigger EventSelectionTrigger(InputElement selectable, PointerEventArgs e);
+ public override bool UpdateSelectionFromEvent(Control container, RoutedEventArgs eventArgs);
}
public class TabStripItem : ListBoxItem
{
+ protected override void OnGotFocus(GotFocusEventArgs e);
}
+ public enum InputSelectionTrigger
+ {
+ None = 0,
+ Press = 1,
+ Release = 2
+ }
+ public static class SelectionEventLogic
+ {
+ public static InputSelectionTrigger EventSelectionTrigger(InputElement selectable, KeyEventArgs eventArgs);
+ public static InputSelectionTrigger EventSelectionTrigger(InputElement selectable, PointerEventArgs eventArgs);
+ public static bool HasRangeSelectionModifier(InputElement selectable, RoutedEventArgs eventArgs);
+ public static bool HasToggleSelectionModifier(InputElement selectable, RoutedEventArgs eventArgs);
+ }
} |
|
During the API review meeting, we finally got to this one! While we like the overall idea of unifying touch behavior for v12, we feel like the API shape could be improved.
@TomEdwardsEnscape Do you think the We're going to revisit this later. |
40e2d56 to
6591007
Compare
| protected override bool EventSelectionTrigger(InputElement selectable, PointerEventArgs eventArgs) => | ||
| ItemSelectionEventTriggers.IsPointerEventWithinBounds(selectable, eventArgs) && | ||
| eventArgs is { Properties.PointerUpdateKind: PointerUpdateKind.LeftButtonReleased or PointerUpdateKind.RightButtonReleased } && | ||
| eventArgs.RoutedEvent == PointerReleasedEvent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the requested change to return boolean values instead of the InputSelectionTrigger enum. This method is where it gets ugly.
On mouse up we now only get "false" back from the base method, and we don't know why. To keep the control working I had to expose a new method, IsPointerEventWithinBounds, and then write entirely new logic here instead of calling the shared method and tweaking the return value.
|
You can test this PR using the following package version. |
…us TreeView - Controls can decide whether to select on press/release, or introduce their own logic - Container types handle events and can decide whether to forward them on to their owner - Corrected various cases where controls checked whether a button was held when the event occurred, rather than whether it triggered the event - Replaced various hardcoded modifier key checks with uses of PlatformHotkeyConfiguration - ListBox no longer cares if you swipe before releasing touch (unless that triggers a gesture) - TreeViewItem is now selected on touch/pen release
6591007 to
13bc9c1
Compare
|
You can test this PR using the following package version. |
This PR substantially refactors selection logic to achieve a consistent experience which adapts to user expectations on different input devices. Behaviour is unchanged for standard mouse input, but touch and pen input have both been altered to fix bugs and bring Avalonia into line with native UI behaviours on relevant devices.
What is the current behavior?
The current state of selection logic is outlined in #18902.
What is the updated/expected behavior with this PR?
User facing:
ListBoxno longer cares if you swipe before releasing touch, unless a gesture is triggered or you swipe off the item entirelyTreeViewItemis now selected on touch/pen releaseGestures.IsHoldWithMouseEnabledis true for a container, then mouse input selects on release instead of pressAPI:
ItemsControl. Previously some did this and others did not. This is of particular relevance toTreeViewItem, which can be nested.UpdateSelectionFromEventon theirItemsControl, a virtual method which contains shared selection logic for all containers of that control. This is where behaviour is customised.UpdateSelectionFromEventcallsEventSelectionTrigger, an overloaded virtual method which determines whether the state of the device calls for selection on press or release. Tweaking the return value is enough to implement correct behaviour for allSelectingItemsControltypes defined in Avalonia.TopLevel.PlatformSettings.HotkeyConfigurationis now used, with a fallback toApplication.Current.PlatformSettings.HotkeyConfigurationif null. This resolves a TODO comment I found in the codebase.How was the solution implemented (if it's not obvious)?
Because
TreeViewdoes not inherit fromSelectingItemsControl, I defined the standard selection logic inpublic staticmethods which both of these types call in their virtual methods.I moved event handling to container types because this is the sane way to ensure that the event actually came from a container. Previously all events that reached the
SelectingItemsControl/TreeViewwere handled, and the visual tree needed to be searched or hit tested to tell which container each one came from, if it came from one at all. This is no longer necessary.I originally aimed to have selection behaviour customisable via properties, but this proved impractical. It's instead achieved with method overrides.
Breaking changes
An
ItemsControlno longer sees the original events which cause its selection to change. This could disrupt existing custom event processing. To adapt to the new system, relevant code must be moved to an override of the newUpdateSelectionFromEventmethod(s). An example of this happening can be seen in the changes toComboBoxin this PR.The switch from selecting on touch press to touch release on some controls may break existing event handling.
Obsoletions / Deprecations
SelectingItemsControl.UpdateSelectionSelectingItemsControl.UpdateSelectionFromEventSourceCalls to either of these methods should be replaced with a call to
SelectingItemsControl.UpdateSelectionFromEvent.Fixed issues
Fixes #18902
Future work
TreeViewItem:pressedstyle to theAvalonia.Themes.SimpleTreeViewItem:pressedstyle fromAvalonia.Themes.Fluentdoesn't activate on iOS, but does on Mac and WindowsCalendar