diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index 2ad6a58d38d..668de5bca94 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -191,7 +191,7 @@ protected virtual void OnCollapsed(RoutedEventArgs eventArgs) /// /// Invoked just before the event. /// - protected virtual void OnCollapsing(RoutedEventArgs eventArgs) + protected virtual void OnCollapsing(CancelRoutedEventArgs eventArgs) { RaiseEvent(eventArgs); } @@ -207,7 +207,7 @@ protected virtual void OnExpanded(RoutedEventArgs eventArgs) /// /// Invoked just before the event. /// - protected virtual void OnExpanding(RoutedEventArgs eventArgs) + protected virtual void OnExpanding(CancelRoutedEventArgs eventArgs) { RaiseEvent(eventArgs); } diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs similarity index 56% rename from src/Avalonia.Controls/SplitView.cs rename to src/Avalonia.Controls/SplitView/SplitView.cs index 35b135e1521..1099a40f085 100644 --- a/src/Avalonia.Controls/SplitView.cs +++ b/src/Avalonia.Controls/SplitView/SplitView.cs @@ -1,77 +1,16 @@ -using Avalonia.Controls.Metadata; +using System; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Metadata; -using Avalonia.VisualTree; -using System; -using Avalonia.Reactive; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.LogicalTree; namespace Avalonia.Controls { - /// - /// Defines constants for how the SplitView Pane should display - /// - public enum SplitViewDisplayMode - { - /// - /// Pane is displayed next to content, and does not auto collapse - /// when tapped outside - /// - Inline, - /// - /// Pane is displayed next to content. When collapsed, pane is still - /// visible according to CompactPaneLength. Pane does not auto collapse - /// when tapped outside - /// - CompactInline, - /// - /// Pane is displayed above content. Pane collapses when tapped outside - /// - Overlay, - /// - /// Pane is displayed above content. When collapsed, pane is still - /// visible according to CompactPaneLength. Pane collapses when tapped outside - /// - CompactOverlay - } - - /// - /// Defines constants for where the Pane should appear - /// - public enum SplitViewPanePlacement - { - Left, - Right - } - - public class SplitViewTemplateSettings : AvaloniaObject - { - internal SplitViewTemplateSettings() { } - - public static readonly StyledProperty ClosedPaneWidthProperty = - AvaloniaProperty.Register(nameof(ClosedPaneWidth), 0d); - - public static readonly StyledProperty PaneColumnGridLengthProperty = - AvaloniaProperty.Register(nameof(PaneColumnGridLength)); - - public double ClosedPaneWidth - { - get => GetValue(ClosedPaneWidthProperty); - internal set => SetValue(ClosedPaneWidthProperty, value); - } - - public GridLength PaneColumnGridLength - { - get => GetValue(PaneColumnGridLengthProperty); - internal set => SetValue(PaneColumnGridLengthProperty, value); - } - } - /// /// A control with two views: A collapsible pane and an area for content /// @@ -93,26 +32,34 @@ Pseudo classes & combos /// Defines the property /// public static readonly StyledProperty CompactPaneLengthProperty = - AvaloniaProperty.Register(nameof(CompactPaneLength), defaultValue: 48); + AvaloniaProperty.Register( + nameof(CompactPaneLength), + defaultValue: 48); /// /// Defines the property /// public static readonly StyledProperty DisplayModeProperty = - AvaloniaProperty.Register(nameof(DisplayMode), defaultValue: SplitViewDisplayMode.Overlay); + AvaloniaProperty.Register( + nameof(DisplayMode), + defaultValue: SplitViewDisplayMode.Overlay); /// /// Defines the property /// - public static readonly DirectProperty IsPaneOpenProperty = - AvaloniaProperty.RegisterDirect(nameof(IsPaneOpen), - x => x.IsPaneOpen, (x, v) => x.IsPaneOpen = v); + public static readonly StyledProperty IsPaneOpenProperty = + AvaloniaProperty.Register( + nameof(IsPaneOpen), + defaultValue: false, + coerce: CoerceIsPaneOpen); /// /// Defines the property /// public static readonly StyledProperty OpenPaneLengthProperty = - AvaloniaProperty.Register(nameof(OpenPaneLength), defaultValue: 320); + AvaloniaProperty.Register( + nameof(OpenPaneLength), + defaultValue: 320); /// /// Defines the property @@ -150,7 +97,38 @@ Pseudo classes & combos public static readonly StyledProperty TemplateSettingsProperty = AvaloniaProperty.Register(nameof(TemplateSettings)); - private bool _isPaneOpen; + /// + /// Defines the event. + /// + public static readonly RoutedEvent PaneClosedEvent = + RoutedEvent.Register( + nameof(PaneClosed), + RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent PaneClosingEvent = + RoutedEvent.Register( + nameof(PaneClosing), + RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent PaneOpenedEvent = + RoutedEvent.Register( + nameof(PaneOpened), + RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent PaneOpeningEvent = + RoutedEvent.Register( + nameof(PaneOpening), + RoutingStrategies.Bubble); + private Panel? _pane; private IDisposable? _pointerDisposable; @@ -164,12 +142,6 @@ public SplitView() static SplitView() { - UseLightDismissOverlayModeProperty.Changed.AddClassHandler((x, v) => x.OnUseLightDismissChanged(v)); - CompactPaneLengthProperty.Changed.AddClassHandler((x, v) => x.OnCompactPaneLengthChanged(v)); - PanePlacementProperty.Changed.AddClassHandler((x, v) => x.OnPanePlacementChanged(v)); - DisplayModeProperty.Changed.AddClassHandler((x, v) => x.OnDisplayModeChanged(v)); - - PaneProperty.Changed.AddClassHandler((x, e) => x.PaneChanged(e)); } /// @@ -196,37 +168,8 @@ public SplitViewDisplayMode DisplayMode /// public bool IsPaneOpen { - get => _isPaneOpen; - set - { - if (value == _isPaneOpen) - { - return; - } - - if (value) - { - OnPaneOpening(this, EventArgs.Empty); - SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value); - - PseudoClasses.Add(":open"); - PseudoClasses.Remove(":closed"); - OnPaneOpened(this, EventArgs.Empty); - } - else - { - SplitViewPaneClosingEventArgs args = new SplitViewPaneClosingEventArgs(false); - OnPaneClosing(this, args); - if (!args.Cancel) - { - SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value); - - PseudoClasses.Add(":closed"); - PseudoClasses.Remove(":open"); - OnPaneClosed(this, EventArgs.Empty); - } - } - } + get => GetValue(IsPaneOpenProperty); + set => SetValue(IsPaneOpenProperty, value); } /// @@ -297,24 +240,48 @@ public SplitViewTemplateSettings TemplateSettings } /// - /// Fired when the pane is closed + /// Fired when the pane is closed. /// - public event EventHandler? PaneClosed; + public event EventHandler? PaneClosed + { + add => AddHandler(PaneClosedEvent, value); + remove => RemoveHandler(PaneClosedEvent, value); + } /// - /// Fired when the pane is closing + /// Fired when the pane is closing. /// - public event EventHandler? PaneClosing; + /// + /// The event args property may be set to true to cancel the event + /// and keep the pane open. + /// + public event EventHandler? PaneClosing + { + add => AddHandler(PaneClosingEvent, value); + remove => RemoveHandler(PaneClosingEvent, value); + } /// - /// Fired when the pane is opened + /// Fired when the pane is opened. /// - public event EventHandler? PaneOpened; + public event EventHandler? PaneOpened + { + add => AddHandler(PaneOpenedEvent, value); + remove => RemoveHandler(PaneOpenedEvent, value); + } /// - /// Fired when the pane is opening + /// Fired when the pane is opening. /// - public event EventHandler? PaneOpening; + /// + /// The event args property may be set to true to cancel the event + /// and keep the pane closed. + /// + public event EventHandler? PaneOpening + { + add => AddHandler(PaneOpeningEvent, value); + remove => RemoveHandler(PaneOpeningEvent, value); + } protected override bool RegisterContentPresenter(IContentPresenter presenter) { @@ -351,6 +318,89 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e _pointerDisposable?.Dispose(); } + /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + 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); + } + } + 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; + } + else if (change.Property == IsPaneOpenProperty) + { + bool isPaneOpen = change.GetNewValue(); + + if (isPaneOpen) + { + PseudoClasses.Add(":open"); + PseudoClasses.Remove(":closed"); + + OnPaneOpened(new RoutedEventArgs(PaneOpenedEvent, this)); + } + else + { + PseudoClasses.Add(":closed"); + PseudoClasses.Remove(":open"); + + OnPaneClosed(new RoutedEventArgs(PaneClosedEvent, this)); + } + } + else if (change.Property == PaneProperty) + { + if (change.OldValue is ILogical oldChild) + { + LogicalChildren.Remove(oldChild); + } + + if (change.NewValue is ILogical newChild) + { + LogicalChildren.Add(newChild); + } + } + else if (change.Property == PanePlacementProperty) + { + var oldState = GetPseudoClass(change.GetOldValue()); + var newState = GetPseudoClass(change.GetNewValue()); + PseudoClasses.Remove($":{oldState}"); + PseudoClasses.Add($":{newState}"); + } + else if (change.Property == UseLightDismissOverlayModeProperty) + { + var mode = change.GetNewValue(); + PseudoClasses.Set(":lightdismiss", mode); + } + } + private void PointerPressedOutside(object? sender, PointerPressedEventArgs e) { if (!IsPaneOpen) @@ -384,7 +434,7 @@ private void PointerPressedOutside(object? sender, PointerPressedEventArgs e) } if (closePane) { - IsPaneOpen = false; + SetCurrentValue(IsPaneOpenProperty, false); e.Handled = true; } } @@ -394,41 +444,29 @@ private bool ShouldClosePane() return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay); } - protected virtual void OnPaneOpening(SplitView sender, EventArgs args) + protected virtual void OnPaneOpening(CancelRoutedEventArgs args) { - PaneOpening?.Invoke(sender, args); + RaiseEvent(args); } - protected virtual void OnPaneOpened(SplitView sender, EventArgs args) + protected virtual void OnPaneOpened(RoutedEventArgs args) { - PaneOpened?.Invoke(sender, args); + RaiseEvent(args); } - protected virtual void OnPaneClosing(SplitView sender, SplitViewPaneClosingEventArgs args) + protected virtual void OnPaneClosing(CancelRoutedEventArgs args) { - PaneClosing?.Invoke(sender, args); + RaiseEvent(args); } - protected virtual void OnPaneClosed(SplitView sender, EventArgs args) + protected virtual void OnPaneClosed(RoutedEventArgs args) { - PaneClosed?.Invoke(sender, args); - } - - private void OnCompactPaneLengthChanged(AvaloniaPropertyChangedEventArgs e) - { - var newLen = (double)e.NewValue!; - 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); - } + RaiseEvent(args); } + /// + /// Gets the appropriate PseudoClass for the given . + /// private static string GetPseudoClass(SplitViewDisplayMode mode) { return mode switch @@ -441,6 +479,9 @@ private static string GetPseudoClass(SplitViewDisplayMode mode) }; } + /// + /// Gets the appropriate PseudoClass for the given . + /// private static string GetPseudoClass(SplitViewPanePlacement placement) { return placement switch @@ -451,51 +492,47 @@ private static string GetPseudoClass(SplitViewPanePlacement placement) }; } - private void OnPanePlacementChanged(AvaloniaPropertyChangedEventArgs e) - { - var oldState = GetPseudoClass(e.GetOldValue()); - var newState = GetPseudoClass(e.GetNewValue()); - PseudoClasses.Remove($":{oldState}"); - PseudoClasses.Add($":{newState}"); - } - - private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs e) + /// + /// Called when the property has to be coerced. + /// + /// The value to coerce. + protected virtual bool OnCoerceIsPaneOpen(bool value) { - var oldState = GetPseudoClass(e.GetOldValue()); - var newState = GetPseudoClass(e.GetNewValue()); + CancelRoutedEventArgs eventArgs; - PseudoClasses.Remove($":{oldState}"); - PseudoClasses.Add($":{newState}"); + if (value) + { + eventArgs = new CancelRoutedEventArgs(PaneOpeningEvent, this); + OnPaneOpening(eventArgs); + } + else + { + eventArgs = new CancelRoutedEventArgs(PaneClosingEvent, this); + OnPaneClosing(eventArgs); + } - var (closedPaneWidth, paneColumnGridLength) = e.GetNewValue() switch + if (eventArgs.Cancel) { - 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; - } + return !value; + } - private void OnUseLightDismissChanged(AvaloniaPropertyChangedEventArgs e) - { - var mode = (bool)e.NewValue!; - PseudoClasses.Set(":lightdismiss", mode); + return value; } - private void PaneChanged(AvaloniaPropertyChangedEventArgs e) + /// + /// Coerces/validates the property value. + /// + /// The instance. + /// The value to coerce. + /// The coerced/validated value. + private static bool CoerceIsPaneOpen(AvaloniaObject instance, bool value) { - if (e.OldValue is ILogical oldChild) + if (instance is SplitView splitView) { - LogicalChildren.Remove(oldChild); + return splitView.OnCoerceIsPaneOpen(value); } - if (e.NewValue is ILogical newChild) - { - LogicalChildren.Add(newChild); - } + return value; } } } diff --git a/src/Avalonia.Controls/SplitView/SplitViewDisplayMode.cs b/src/Avalonia.Controls/SplitView/SplitViewDisplayMode.cs new file mode 100644 index 00000000000..6333f96f863 --- /dev/null +++ b/src/Avalonia.Controls/SplitView/SplitViewDisplayMode.cs @@ -0,0 +1,29 @@ +namespace Avalonia.Controls +{ + /// + /// Defines constants for how the SplitView Pane should display + /// + public enum SplitViewDisplayMode + { + /// + /// Pane is displayed next to content, and does not auto collapse + /// when tapped outside + /// + Inline, + /// + /// Pane is displayed next to content. When collapsed, pane is still + /// visible according to CompactPaneLength. Pane does not auto collapse + /// when tapped outside + /// + CompactInline, + /// + /// Pane is displayed above content. Pane collapses when tapped outside + /// + Overlay, + /// + /// Pane is displayed above content. When collapsed, pane is still + /// visible according to CompactPaneLength. Pane collapses when tapped outside + /// + CompactOverlay + } +} diff --git a/src/Avalonia.Controls/SplitView/SplitViewPanePlacement.cs b/src/Avalonia.Controls/SplitView/SplitViewPanePlacement.cs new file mode 100644 index 00000000000..62c53871921 --- /dev/null +++ b/src/Avalonia.Controls/SplitView/SplitViewPanePlacement.cs @@ -0,0 +1,18 @@ +namespace Avalonia.Controls +{ + /// + /// Defines constants for where the Pane should appear + /// + public enum SplitViewPanePlacement + { + /// + /// The pane is shown to the left of content. + /// + Left, + + /// + /// The pane is shown to the right of content. + /// + Right + } +} diff --git a/src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs b/src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs new file mode 100644 index 00000000000..f2cbf559860 --- /dev/null +++ b/src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs @@ -0,0 +1,32 @@ +namespace Avalonia.Controls.Primitives +{ + /// + /// Provides calculated values for use with the 's control theme or template. + /// This class is NOT intended for general use. + /// + public class SplitViewTemplateSettings : AvaloniaObject + { + internal SplitViewTemplateSettings() { } + + public static readonly StyledProperty ClosedPaneWidthProperty = + AvaloniaProperty.Register(nameof(ClosedPaneWidth), + 0d); + + public static readonly StyledProperty PaneColumnGridLengthProperty = + AvaloniaProperty.Register( + nameof(PaneColumnGridLength)); + + public double ClosedPaneWidth + { + get => GetValue(ClosedPaneWidthProperty); + internal set => SetValue(ClosedPaneWidthProperty, value); + } + + public GridLength PaneColumnGridLength + { + get => GetValue(PaneColumnGridLengthProperty); + internal set => SetValue(PaneColumnGridLengthProperty, value); + } + } +} diff --git a/src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs b/src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs deleted file mode 100644 index 46fb2d161b3..00000000000 --- a/src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Avalonia.Controls -{ - public class SplitViewPaneClosingEventArgs : EventArgs - { - public bool Cancel { get; set; } - - public SplitViewPaneClosingEventArgs(bool cancel) - { - Cancel = cancel; - } - } -}