From ea5623df7e645936ff94fdd29543c259fb8e1142 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 15:27:26 -0400 Subject: [PATCH 1/9] Fix initial state/overlay & make TemplateSettings a readonly DirectProperty --- src/Avalonia.Controls/SplitView/SplitView.cs | 124 ++++++++++++------- 1 file changed, 76 insertions(+), 48 deletions(-) diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index 1099a40f08..a5c87f5843 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -94,8 +94,9 @@ namespace Avalonia.Controls /// /// Defines the property /// - public static readonly StyledProperty TemplateSettingsProperty = - AvaloniaProperty.Register(nameof(TemplateSettings)); + public static readonly DirectProperty TemplateSettingsProperty = + AvaloniaProperty.RegisterDirect(nameof(TemplateSettings), + x => x.TemplateSettings); /// /// Defines the event. @@ -131,12 +132,12 @@ namespace Avalonia.Controls private Panel? _pane; private IDisposable? _pointerDisposable; + private SplitViewTemplateSettings _templateSettings; + private string _lastDisplayModePseudoclass; + private string _lastPlacementPseudoclass; public SplitView() { - PseudoClasses.Add(":overlay"); - PseudoClasses.Add(":left"); - TemplateSettings = new SplitViewTemplateSettings(); } @@ -208,7 +209,7 @@ namespace Avalonia.Controls get => GetValue(PaneProperty); set => SetValue(PaneProperty, value); } - + /// /// Gets or sets the data template used to display the header content of the control. /// @@ -235,8 +236,8 @@ namespace Avalonia.Controls /// public SplitViewTemplateSettings TemplateSettings { - get => GetValue(TemplateSettingsProperty); - set => SetValue(TemplateSettingsProperty, value); + get => _templateSettings; + private set => SetAndRaise(TemplateSettingsProperty, ref _templateSettings, value); } /// @@ -291,7 +292,7 @@ namespace Avalonia.Controls { return true; } - + return result; } @@ -299,12 +300,20 @@ namespace Avalonia.Controls { base.OnApplyTemplate(e); _pane = e.NameScope.Find("PART_PaneRoot"); + + UpdateVisualStateForCompactPaneLength(CompactPaneLength); + UpdateVisualStateForDisplayMode(DisplayMode); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); + // :left and :right style triggers contain the template so we need to do this as + // soon as we're attached so the template applies. The other visual states can + // be updated after the template applies + UpdateVisualStateForPanePlacementProperty(PanePlacement); + var topLevel = this.VisualRoot; if (topLevel is Window window) { @@ -325,36 +334,11 @@ namespace Avalonia.Controls if (change.Property == CompactPaneLengthProperty) { - var newLen = change.GetNewValue(); - var displayMode = DisplayMode; - if (displayMode == SplitViewDisplayMode.CompactInline) - { - TemplateSettings.ClosedPaneWidth = newLen; - } - else if (displayMode == SplitViewDisplayMode.CompactOverlay) - { - TemplateSettings.ClosedPaneWidth = newLen; - TemplateSettings.PaneColumnGridLength = new GridLength(newLen, GridUnitType.Pixel); - } + UpdateVisualStateForCompactPaneLength(change.GetNewValue()); } else if (change.Property == DisplayModeProperty) { - var oldState = GetPseudoClass(change.GetOldValue()); - var newState = GetPseudoClass(change.GetNewValue()); - - PseudoClasses.Remove($":{oldState}"); - PseudoClasses.Add($":{newState}"); - - var (closedPaneWidth, paneColumnGridLength) = change.GetNewValue() switch - { - SplitViewDisplayMode.Overlay => (0, new GridLength(0, GridUnitType.Pixel)), - SplitViewDisplayMode.CompactOverlay => (CompactPaneLength, new GridLength(CompactPaneLength, GridUnitType.Pixel)), - SplitViewDisplayMode.Inline => (0, new GridLength(0, GridUnitType.Auto)), - SplitViewDisplayMode.CompactInline => (CompactPaneLength, new GridLength(0, GridUnitType.Auto)), - _ => throw new NotImplementedException(), - }; - TemplateSettings.ClosedPaneWidth = closedPaneWidth; - TemplateSettings.PaneColumnGridLength = paneColumnGridLength; + UpdateVisualStateForDisplayMode(change.GetNewValue()); } else if (change.Property == IsPaneOpenProperty) { @@ -389,10 +373,7 @@ namespace Avalonia.Controls } else if (change.Property == PanePlacementProperty) { - var oldState = GetPseudoClass(change.GetOldValue()); - var newState = GetPseudoClass(change.GetNewValue()); - PseudoClasses.Remove($":{oldState}"); - PseudoClasses.Add($":{newState}"); + UpdateVisualStateForPanePlacementProperty(change.GetNewValue()); } else if (change.Property == UseLightDismissOverlayModeProperty) { @@ -438,7 +419,7 @@ namespace Avalonia.Controls e.Handled = true; } } - + private bool ShouldClosePane() { return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay); @@ -471,14 +452,14 @@ namespace Avalonia.Controls { return mode switch { - SplitViewDisplayMode.Inline => "inline", - SplitViewDisplayMode.CompactInline => "compactinline", - SplitViewDisplayMode.Overlay => "overlay", - SplitViewDisplayMode.CompactOverlay => "compactoverlay", + SplitViewDisplayMode.Inline => ":inline", + SplitViewDisplayMode.CompactInline => ":compactinline", + SplitViewDisplayMode.Overlay => ":overlay", + SplitViewDisplayMode.CompactOverlay => ":compactoverlay", _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null) }; } - + /// /// Gets the appropriate PseudoClass for the given . /// @@ -486,8 +467,8 @@ namespace Avalonia.Controls { return placement switch { - SplitViewPanePlacement.Left => "left", - SplitViewPanePlacement.Right => "right", + SplitViewPanePlacement.Left => ":left", + SplitViewPanePlacement.Right => ":right", _ => throw new ArgumentOutOfRangeException(nameof(placement), placement, null) }; } @@ -519,6 +500,53 @@ namespace Avalonia.Controls return value; } + private void UpdateVisualStateForCompactPaneLength(double newLen) + { + var displayMode = DisplayMode; + if (displayMode == SplitViewDisplayMode.CompactInline) + { + TemplateSettings.ClosedPaneWidth = newLen; + } + else if (displayMode == SplitViewDisplayMode.CompactOverlay) + { + TemplateSettings.ClosedPaneWidth = newLen; + TemplateSettings.PaneColumnGridLength = new GridLength(newLen, GridUnitType.Pixel); + } + } + + private void UpdateVisualStateForDisplayMode(SplitViewDisplayMode newValue) + { + if (!string.IsNullOrEmpty(_lastDisplayModePseudoclass)) + { + PseudoClasses.Remove(_lastDisplayModePseudoclass); + } + + _lastDisplayModePseudoclass = GetPseudoClass(newValue); + PseudoClasses.Add(_lastDisplayModePseudoclass); + + var (closedPaneWidth, paneColumnGridLength) = newValue switch + { + SplitViewDisplayMode.Overlay => (0, new GridLength(0, GridUnitType.Pixel)), + SplitViewDisplayMode.CompactOverlay => (CompactPaneLength, new GridLength(CompactPaneLength, GridUnitType.Pixel)), + SplitViewDisplayMode.Inline => (0, new GridLength(0, GridUnitType.Auto)), + SplitViewDisplayMode.CompactInline => (CompactPaneLength, new GridLength(0, GridUnitType.Auto)), + _ => throw new NotImplementedException(), + }; + TemplateSettings.ClosedPaneWidth = closedPaneWidth; + TemplateSettings.PaneColumnGridLength = paneColumnGridLength; + } + + private void UpdateVisualStateForPanePlacementProperty(SplitViewPanePlacement newValue) + { + if (!string.IsNullOrEmpty(_lastPlacementPseudoclass)) + { + PseudoClasses.Remove(_lastPlacementPseudoclass); + } + + _lastPlacementPseudoclass = GetPseudoClass(newValue); + PseudoClasses.Add(_lastPlacementPseudoclass); + } + /// /// Coerces/validates the property value. /// From 878e18509e36f53f09256b1796b2f1cad6f02b52 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 15:33:03 -0400 Subject: [PATCH 2/9] Use const strings for pseudoclasses --- src/Avalonia.Controls/SplitView/SplitView.cs | 45 +++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index a5c87f5843..786a26ae68 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -15,18 +15,21 @@ namespace Avalonia.Controls /// A control with two views: A collapsible pane and an area for content /// [TemplatePart("PART_PaneRoot", typeof(Panel))] - [PseudoClasses(":open", ":closed")] - [PseudoClasses(":compactoverlay", ":compactinline", ":overlay", ":inline")] - [PseudoClasses(":left", ":right")] - [PseudoClasses(":lightdismiss")] + [PseudoClasses(pcOpen, pcClosed)] + [PseudoClasses(pcCompactOverlay, pcCompactInline, pcOverlay, pcInline)] + [PseudoClasses(pcLeft, pcRight)] + [PseudoClasses(pcLightDismiss)] public class SplitView : ContentControl { - /* - Pseudo classes & combos - :open / :closed - :compactoverlay :compactinline :overlay :inline - :left :right - */ + protected const string pcOpen = ":open"; + protected const string pcClosed = ":closed"; + protected const string pcCompactOverlay = ":compactoverlay"; + protected const string pcCompactInline = ":compactInline"; + protected const string pcOverlay = ":overlay"; + protected const string pcInline = ":inline"; + protected const string pcLeft = ":left"; + protected const string pcRight = ":right"; + protected const string pcLightDismiss = ":lightDismiss"; /// /// Defines the property @@ -346,15 +349,15 @@ namespace Avalonia.Controls if (isPaneOpen) { - PseudoClasses.Add(":open"); - PseudoClasses.Remove(":closed"); + PseudoClasses.Add(pcOpen); + PseudoClasses.Remove(pcClosed); OnPaneOpened(new RoutedEventArgs(PaneOpenedEvent, this)); } else { - PseudoClasses.Add(":closed"); - PseudoClasses.Remove(":open"); + PseudoClasses.Add(pcClosed); + PseudoClasses.Remove(pcOpen); OnPaneClosed(new RoutedEventArgs(PaneClosedEvent, this)); } @@ -378,7 +381,7 @@ namespace Avalonia.Controls else if (change.Property == UseLightDismissOverlayModeProperty) { var mode = change.GetNewValue(); - PseudoClasses.Set(":lightdismiss", mode); + PseudoClasses.Set(pcLightDismiss, mode); } } @@ -452,10 +455,10 @@ namespace Avalonia.Controls { return mode switch { - SplitViewDisplayMode.Inline => ":inline", - SplitViewDisplayMode.CompactInline => ":compactinline", - SplitViewDisplayMode.Overlay => ":overlay", - SplitViewDisplayMode.CompactOverlay => ":compactoverlay", + SplitViewDisplayMode.Inline => pcInline, + SplitViewDisplayMode.CompactInline => pcCompactInline, + SplitViewDisplayMode.Overlay => pcOverlay, + SplitViewDisplayMode.CompactOverlay => pcCompactOverlay, _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null) }; } @@ -467,8 +470,8 @@ namespace Avalonia.Controls { return placement switch { - SplitViewPanePlacement.Left => ":left", - SplitViewPanePlacement.Right => ":right", + SplitViewPanePlacement.Left => pcLeft, + SplitViewPanePlacement.Right => pcRight, _ => throw new ArgumentOutOfRangeException(nameof(placement), placement, null) }; } From 25825e4eabc8e7c1525b89511de94ab0eba5e561 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:57:55 -0400 Subject: [PATCH 3/9] Fix typo in pseudoclass --- src/Avalonia.Controls/SplitView/SplitView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index 786a26ae68..9fd316a9f7 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -24,7 +24,7 @@ namespace Avalonia.Controls protected const string pcOpen = ":open"; protected const string pcClosed = ":closed"; protected const string pcCompactOverlay = ":compactoverlay"; - protected const string pcCompactInline = ":compactInline"; + protected const string pcCompactInline = ":compactinline"; protected const string pcOverlay = ":overlay"; protected const string pcInline = ":inline"; protected const string pcLeft = ":left"; From 87a2bb6bda76d5370bae07fd62951971c4d2f70e Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:58:20 -0400 Subject: [PATCH 4/9] Improve light dismiss behavior --- src/Avalonia.Controls/SplitView/SplitView.cs | 70 +++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index 9fd316a9f7..b8a697e309 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -8,6 +8,7 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Metadata; +using Avalonia.Reactive; namespace Avalonia.Controls { @@ -144,10 +145,6 @@ namespace Avalonia.Controls TemplateSettings = new SplitViewTemplateSettings(); } - static SplitView() - { - } - /// /// Gets or sets the length of the pane when in /// or mode @@ -316,12 +313,6 @@ namespace Avalonia.Controls // soon as we're attached so the template applies. The other visual states can // be updated after the template applies UpdateVisualStateForPanePlacementProperty(PanePlacement); - - var topLevel = this.VisualRoot; - if (topLevel is Window window) - { - _pointerDisposable = window.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel); - } } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) @@ -385,21 +376,27 @@ namespace Avalonia.Controls } } - private void PointerPressedOutside(object? sender, PointerPressedEventArgs e) + protected override void OnKeyDown(KeyEventArgs e) { - if (!IsPaneOpen) + if (!e.Handled && e.Key == Key.Escape) { - return; + if (IsPaneOpen && IsInOverlayMode()) + { + SetCurrentValue(IsPaneOpenProperty, false); + } } - //If we click within the Pane, don't do anything - //Otherwise, ClosePane if open & using an overlay display mode - bool closePane = ShouldClosePane(); - if (!closePane) + base.OnKeyDown(e); + } + + private void PointerReleasedOutside(object? sender, PointerReleasedEventArgs e) + { + if (!IsPaneOpen || _pane == null) { return; } + var closePane = true; var src = e.Source as Visual; while (src != null) { @@ -416,6 +413,7 @@ namespace Avalonia.Controls src = src.VisualParent; } + if (closePane) { SetCurrentValue(IsPaneOpenProperty, false); @@ -423,7 +421,7 @@ namespace Avalonia.Controls } } - private bool ShouldClosePane() + private bool IsInOverlayMode() { return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay); } @@ -435,6 +433,7 @@ namespace Avalonia.Controls protected virtual void OnPaneOpened(RoutedEventArgs args) { + EnableLightDismiss(); RaiseEvent(args); } @@ -445,6 +444,8 @@ namespace Avalonia.Controls protected virtual void OnPaneClosed(RoutedEventArgs args) { + _pointerDisposable?.Dispose(); + _pointerDisposable = null; RaiseEvent(args); } @@ -550,6 +551,39 @@ namespace Avalonia.Controls PseudoClasses.Add(_lastPlacementPseudoclass); } + private void EnableLightDismiss() + { + if (_pane == null) + return; + + // If this returns false, we're not in Overlay or CompactOverlay DisplayMode + // and don't need the light dismiss behavior + if (!IsInOverlayMode()) + return; + + var topLevel = TopLevel.GetTopLevel(this); + if (topLevel != null) + { + _pointerDisposable = Disposable.Create(() => + { + topLevel.PointerReleased -= PointerReleasedOutside; + topLevel.BackRequested -= TopLevelBackRequested; + }); + + topLevel.PointerReleased += PointerReleasedOutside; + topLevel.BackRequested += TopLevelBackRequested; + } + } + + private void TopLevelBackRequested(object sender, RoutedEventArgs e) + { + if (!IsInOverlayMode()) + return; + + SetCurrentValue(IsPaneOpenProperty, false); + e.Handled = true; + } + /// /// Coerces/validates the property value. /// From ffdab9436563b24c4705b659571ebb45047f5015 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 18:27:17 -0400 Subject: [PATCH 5/9] Only need to update display mode with ApplyTemplate --- src/Avalonia.Controls/SplitView/SplitView.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index b8a697e309..72e8140d09 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -301,7 +301,6 @@ namespace Avalonia.Controls base.OnApplyTemplate(e); _pane = e.NameScope.Find("PART_PaneRoot"); - UpdateVisualStateForCompactPaneLength(CompactPaneLength); UpdateVisualStateForDisplayMode(DisplayMode); } From a3c8210f2ee7c0041ebfe678f1609958390766e6 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 18:27:29 -0400 Subject: [PATCH 6/9] Add new tests --- .../SplitViewTests.cs | 224 +++++++++++++++++- 1 file changed, 221 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/SplitViewTests.cs b/tests/Avalonia.Controls.UnitTests/SplitViewTests.cs index 03653ec42c..9b43c469ba 100644 --- a/tests/Avalonia.Controls.UnitTests/SplitViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/SplitViewTests.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; +using Avalonia.Input; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests @@ -62,5 +61,224 @@ namespace Avalonia.Controls.UnitTests Assert.True(splitView.IsPaneOpen); } + + [Fact] + public void SplitView_TemplateSettings_Are_Correct_For_Display_Modes() + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow); + var wnd = new Window + { + Width = 1280, + Height = 720 + }; + var splitView = new SplitView(); + wnd.Content = splitView; + wnd.Show(); + + var zeroGridLength = new GridLength(0); + var compactLength = splitView.CompactPaneLength; + var compactGridLength = new GridLength(compactLength); + + // Overlay is default DisplayMode + Assert.Equal(0, splitView.TemplateSettings.ClosedPaneWidth); + Assert.Equal(zeroGridLength, splitView.TemplateSettings.PaneColumnGridLength); + + splitView.DisplayMode = SplitViewDisplayMode.CompactOverlay; + Assert.Equal(compactLength, splitView.TemplateSettings.ClosedPaneWidth); + Assert.Equal(compactGridLength, splitView.TemplateSettings.PaneColumnGridLength); + + splitView.DisplayMode = SplitViewDisplayMode.Inline; + Assert.Equal(0, splitView.TemplateSettings.ClosedPaneWidth); + Assert.Equal(GridLength.Auto, splitView.TemplateSettings.PaneColumnGridLength); + + splitView.DisplayMode = SplitViewDisplayMode.CompactInline; + Assert.Equal(compactLength, splitView.TemplateSettings.ClosedPaneWidth); + Assert.Equal(GridLength.Auto, splitView.TemplateSettings.PaneColumnGridLength); + } + + [Fact] + public void SplitView_TemplateSettings_Update_With_CompactPaneLength() + { + var splitView = new SplitView(); + + // CompactInline: + // - ClosedPaneWidth = CompactPaneLength + // - PaneColumnGridLength = Auto + splitView.DisplayMode = SplitViewDisplayMode.CompactInline; + + var compactLength = splitView.CompactPaneLength; + + Assert.Equal(GridLength.Auto, splitView.TemplateSettings.PaneColumnGridLength); + Assert.Equal(compactLength, splitView.TemplateSettings.ClosedPaneWidth); + + splitView.CompactPaneLength = 100; + + Assert.Equal(GridLength.Auto, splitView.TemplateSettings.PaneColumnGridLength); + Assert.Equal(100, splitView.TemplateSettings.ClosedPaneWidth); + + // CompactOverlay: + // - ClosedPaneWidth = CompactPaneLength + // - PaneColumnGridLength = GridLength { CompactPaneLength, Pixel } + splitView.DisplayMode = SplitViewDisplayMode.CompactOverlay; + splitView.CompactPaneLength = 50; + + Assert.Equal(new GridLength(50), splitView.TemplateSettings.PaneColumnGridLength); + Assert.Equal(50, splitView.TemplateSettings.ClosedPaneWidth); + + // Value shouldn't change for these - changing the display mode will update + // the template settings with the right value + splitView.DisplayMode = SplitViewDisplayMode.Inline; + splitView.CompactPaneLength = 1; + + Assert.Equal(GridLength.Auto, splitView.TemplateSettings.PaneColumnGridLength); + Assert.Equal(0, splitView.TemplateSettings.ClosedPaneWidth); + + splitView.DisplayMode = SplitViewDisplayMode.Overlay; + splitView.CompactPaneLength = 2; + + Assert.Equal(new GridLength(0), splitView.TemplateSettings.PaneColumnGridLength); + Assert.Equal(0, splitView.TemplateSettings.ClosedPaneWidth); + } + + [Fact] + public void SplitView_Pointer_Closes_Pane_In_Overlay_Mode() + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow + .With(globalClock: new MockGlobalClock())); + var wnd = new Window + { + Width = 1280, + Height = 720 + }; + var splitView = new SplitView(); + wnd.Content = splitView; + wnd.Show(); + + splitView.IsPaneOpen = true; + + splitView.RaiseEvent(new PointerReleasedEventArgs(splitView, + null, wnd, new Point(1270, 30), 0, + new PointerPointProperties(), + KeyModifiers.None, + MouseButton.Left)); + + Assert.False(splitView.IsPaneOpen); + + // Inline shouldn't close the pane + splitView.DisplayMode = SplitViewDisplayMode.Inline; + splitView.IsPaneOpen = true; + + splitView.RaiseEvent(new PointerReleasedEventArgs(splitView, + null, wnd, new Point(1270, 30), 0, + new PointerPointProperties(), + KeyModifiers.None, + MouseButton.Left)); + + Assert.True(splitView.IsPaneOpen); + } + + [Fact] + public void SplitView_Pointer_Should_Not_Close_Pane_If_Over_Pane() + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow + .With(globalClock: new MockGlobalClock())); + var wnd = new Window + { + Width = 1280, + Height = 720 + }; + var clickBorder = new Border + { + Width = 100, + Height = 100, + HorizontalAlignment = Layout.HorizontalAlignment.Left, + VerticalAlignment = Layout.VerticalAlignment.Top + }; + var splitView = new SplitView + { + Pane = clickBorder + }; + wnd.Content = splitView; + wnd.Show(); + + splitView.IsPaneOpen = true; + + clickBorder.RaiseEvent(new PointerReleasedEventArgs(splitView, + null, wnd, new Point(5, 5), 0, + new PointerPointProperties(), + KeyModifiers.None, + MouseButton.Left)); + + Assert.True(splitView.IsPaneOpen); + } + + [Fact] + public void SplitView_Escape_Key_Closes_Light_Dismissable_Pane() + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow + .With(globalClock: new MockGlobalClock())); + var wnd = new Window + { + Width = 1280, + Height = 720 + }; + var button = new Button(); + var splitView = new SplitView + { + Pane = button + }; + wnd.Content = splitView; + wnd.Show(); + + splitView.IsPaneOpen = true; + + button.RaiseEvent(new KeyEventArgs + { + Key = Key.Escape, + RoutedEvent = InputElement.KeyDownEvent + }); + + Assert.False(splitView.IsPaneOpen); + + splitView.DisplayMode = SplitViewDisplayMode.Inline; + + splitView.IsPaneOpen = true; + + button.RaiseEvent(new KeyEventArgs + { + Key = Key.Escape, + RoutedEvent = InputElement.KeyDownEvent + }); + + Assert.True(splitView.IsPaneOpen); + } + + [Fact] + public void Top_Level_Back_Requested_Closes_Light_Dismissable_Pane() + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow + .With(globalClock: new MockGlobalClock())); + var wnd = new Window + { + Width = 1280, + Height = 720 + }; + var splitView = new SplitView(); + wnd.Content = splitView; + wnd.Show(); + + splitView.IsPaneOpen = true; + + wnd.RaiseEvent(new Interactivity.RoutedEventArgs(TopLevel.BackRequestedEvent)); + + Assert.False(splitView.IsPaneOpen); + + splitView.DisplayMode = SplitViewDisplayMode.Inline; + splitView.IsPaneOpen = true; + + wnd.RaiseEvent(new Interactivity.RoutedEventArgs(TopLevel.BackRequestedEvent)); + + Assert.True(splitView.IsPaneOpen); + } } } From acc8b9fe52a734af6810a2f411b1ab6aba391123 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 18:28:04 -0400 Subject: [PATCH 7/9] Handle KeyDown if pane close is triggered --- src/Avalonia.Controls/SplitView/SplitView.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index 72e8140d09..8501d518a7 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -382,6 +382,7 @@ namespace Avalonia.Controls if (IsPaneOpen && IsInOverlayMode()) { SetCurrentValue(IsPaneOpenProperty, false); + e.Handled = true; } } From b5be1ef9b0494c8e357037c39f20fee1e2a39fa6 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 18:38:57 -0400 Subject: [PATCH 8/9] Fix nullable stuff --- src/Avalonia.Controls/SplitView/SplitView.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index 8501d518a7..93b0ffe57f 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -136,9 +136,9 @@ namespace Avalonia.Controls private Panel? _pane; private IDisposable? _pointerDisposable; - private SplitViewTemplateSettings _templateSettings; - private string _lastDisplayModePseudoclass; - private string _lastPlacementPseudoclass; + private SplitViewTemplateSettings _templateSettings = null!; + private string? _lastDisplayModePseudoclass; + private string? _lastPlacementPseudoclass; public SplitView() { @@ -575,7 +575,7 @@ namespace Avalonia.Controls } } - private void TopLevelBackRequested(object sender, RoutedEventArgs e) + private void TopLevelBackRequested(object? sender, RoutedEventArgs e) { if (!IsInOverlayMode()) return; From ed8a4fa7d5b13e751b5c7f7c1f2c477ddba95420 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Fri, 17 Mar 2023 21:29:45 -0400 Subject: [PATCH 9/9] Just init TemplateSettings --- src/Avalonia.Controls/SplitView/SplitView.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs index 93b0ffe57f..8060ca9594 100644 --- a/src/Avalonia.Controls/SplitView/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -136,15 +136,10 @@ namespace Avalonia.Controls private Panel? _pane; private IDisposable? _pointerDisposable; - private SplitViewTemplateSettings _templateSettings = null!; + private SplitViewTemplateSettings _templateSettings = new SplitViewTemplateSettings(); private string? _lastDisplayModePseudoclass; private string? _lastPlacementPseudoclass; - public SplitView() - { - TemplateSettings = new SplitViewTemplateSettings(); - } - /// /// Gets or sets the length of the pane when in /// or mode