diff --git a/src/AtomUI.Base/MotionScene/AnimationTargetPanel.cs b/src/AtomUI.Base/MotionScene/AnimationTargetPanel.cs new file mode 100644 index 0000000..353e0b3 --- /dev/null +++ b/src/AtomUI.Base/MotionScene/AnimationTargetPanel.cs @@ -0,0 +1,20 @@ +using Avalonia; +using Avalonia.Controls; + +namespace AtomUI.MotionScene; + +internal class AnimationTargetPanel : Panel +{ + public bool InAnimation { get; set; } + + private Size _cacheMeasureSize = default; + + protected override Size MeasureOverride(Size availableSize) + { + if (InAnimation && _cacheMeasureSize != default) { + return _cacheMeasureSize; + } + _cacheMeasureSize = base.MeasureOverride(availableSize); + return _cacheMeasureSize; + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Collapse/Collapse.cs b/src/AtomUI.Controls/Collapse/Collapse.cs index f1c3ccb..b001954 100644 --- a/src/AtomUI.Controls/Collapse/Collapse.cs +++ b/src/AtomUI.Controls/Collapse/Collapse.cs @@ -1,5 +1,4 @@ -using AtomUI.Controls.Utils; -using AtomUI.Data; +using AtomUI.Data; using AtomUI.Theme.Data; using AtomUI.Theme.Styling; using AtomUI.Utils; @@ -176,22 +175,12 @@ public class Collapse : SelectingItemsControl collapseItem.HeaderBorderThickness = new Thickness(0, 0, 0, headerBorderBottom); var contentBorderBottom = BorderThickness.Bottom; - if (index == ItemCount - 1 && collapseItem.IsSelected) { + if (index == ItemCount - 1 && (collapseItem.IsSelected || (!collapseItem.IsSelected && collapseItem.InAnimating))) { contentBorderBottom = 0d; } collapseItem.ContentBorderThickness = new Thickness(0, 0, 0, contentBorderBottom); } - protected override void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex) - { - base.ContainerIndexChangedOverride(container, oldIndex, newIndex); - } - - protected override void ClearContainerForItemOverride(Control element) - { - base.ClearContainerForItemOverride(element); - } - protected override void OnGotFocus(GotFocusEventArgs e) { base.OnGotFocus(e); @@ -199,7 +188,7 @@ public class Collapse : SelectingItemsControl if (e.NavigationMethod == NavigationMethod.Directional) { Control? containerFromEventSource = GetContainerFromEventSource(e.Source); if (containerFromEventSource is CollapseItem collapseItem) { - if (!collapseItem.IsAnimating) { + if (!collapseItem.InAnimating) { e.Handled = UpdateSelectionFromEventSource(e.Source); } } @@ -213,7 +202,7 @@ public class Collapse : SelectingItemsControl if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && e.Pointer.Type == PointerType.Mouse) { Control? containerFromEventSource = GetContainerFromEventSource(e.Source); if (containerFromEventSource is CollapseItem collapseItem) { - if (!collapseItem.IsAnimating) { + if (!collapseItem.InAnimating && collapseItem.IsPointInHeaderBounds(e.GetPosition(collapseItem))) { e.Handled = UpdateSelectionFromEventSource(e.Source); } } @@ -229,7 +218,7 @@ public class Collapse : SelectingItemsControl .Any(c => container == c || container.IsVisualAncestorOf(c))) { Control? containerFromEventSource = GetContainerFromEventSource(e.Source); if (containerFromEventSource is CollapseItem collapseItem) { - if (!collapseItem.IsAnimating) { + if (!collapseItem.InAnimating && collapseItem.IsPointInHeaderBounds(e.GetPosition(collapseItem))) { e.Handled = UpdateSelectionFromEventSource(e.Source); } } diff --git a/src/AtomUI.Controls/Collapse/CollapseItem.cs b/src/AtomUI.Controls/Collapse/CollapseItem.cs index 040abb9..8494d76 100644 --- a/src/AtomUI.Controls/Collapse/CollapseItem.cs +++ b/src/AtomUI.Controls/Collapse/CollapseItem.cs @@ -152,8 +152,10 @@ public class CollapseItem : HeaderedContentControl, ISelectable private bool _animating = false; private bool _enableAnimation = true; + private AnimationTargetPanel? _animationTarget; + private Border? _headerDecorator; - internal bool IsAnimating => _animating; + internal bool InAnimating => _animating; protected override AutomationPeer OnCreateAutomationPeer() => new ListItemAutomationPeer(this); @@ -179,6 +181,8 @@ public class CollapseItem : HeaderedContentControl, ISelectable protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); + _animationTarget = e.NameScope.Find(CollapseItemTheme.ContentAnimationTargetPart); + _headerDecorator = e.NameScope.Find(CollapseItemTheme.HeaderDecoratorPart); TokenResourceBinder.CreateTokenBinding(this, MotionDurationProperty, GlobalResourceKey.MotionDurationSlow); SetupIconButton(); _enableAnimation = false; @@ -213,24 +217,27 @@ public class CollapseItem : HeaderedContentControl, ISelectable private void ExpandItemContent() { - if (Presenter is null || _animating) { + if (_animationTarget is null || _animating) { return; } - - Presenter.IsVisible = true; + if (!_enableAnimation) { + _animationTarget.IsVisible = true; return; } - LayoutHelper.MeasureChild(Presenter, new Size(Bounds.Width, double.PositiveInfinity), new Thickness()); + _animationTarget.IsVisible = true; + LayoutHelper.MeasureChild(_animationTarget, new Size(Bounds.Width, double.PositiveInfinity), new Thickness()); _animating = true; var director = Director.Instance; var motion = new ExpandMotion(); motion.ConfigureOpacity(MotionDuration); motion.ConfigureHeight(MotionDuration); - var motionActor = new MotionActor(Presenter, motion); + var motionActor = new MotionActor(_animationTarget, motion); motionActor.DispatchInSceneLayer = false; + _animationTarget.InAnimation = true; motionActor.Completed += (sender, args) => { + _animationTarget.InAnimation = false; _animating = false; }; director?.Schedule(motionActor); @@ -238,26 +245,29 @@ public class CollapseItem : HeaderedContentControl, ISelectable private void CollapseItemContent() { - if (Presenter is null || _animating) { + if (_animationTarget is null || _animating) { return; } if (!_enableAnimation) { - Presenter.IsVisible = false; + _animationTarget.IsVisible = false; return; } _animating = true; - LayoutHelper.MeasureChild(Presenter, new Size(Bounds.Width, double.PositiveInfinity), new Thickness()); + + LayoutHelper.MeasureChild(_animationTarget, new Size(Bounds.Width, double.PositiveInfinity), new Thickness()); var director = Director.Instance; var motion = new CollapseMotion(); motion.ConfigureOpacity(MotionDuration); motion.ConfigureHeight(MotionDuration); - var motionActor = new MotionActor(Presenter, motion); + var motionActor = new MotionActor(_animationTarget!, motion); motionActor.DispatchInSceneLayer = false; + _animationTarget.InAnimation = true; motionActor.Completed += (sender, args) => { _animating = false; - Presenter.IsVisible = false; + _animationTarget.InAnimation = false; + _animationTarget.IsVisible = false; }; director?.Schedule(motionActor); } @@ -272,4 +282,13 @@ public class CollapseItem : HeaderedContentControl, ISelectable } UIStructureUtils.SetTemplateParent(ExpandIcon, this); } + + internal bool IsPointInHeaderBounds(Point position) + { + if (_headerDecorator is not null) { + return _headerDecorator.Bounds.Contains(position); + } + + return false; + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Collapse/CollapseItemTheme.cs b/src/AtomUI.Controls/Collapse/CollapseItemTheme.cs index da2fec7..80b4707 100644 --- a/src/AtomUI.Controls/Collapse/CollapseItemTheme.cs +++ b/src/AtomUI.Controls/Collapse/CollapseItemTheme.cs @@ -1,4 +1,5 @@ -using AtomUI.Theme; +using AtomUI.MotionScene; +using AtomUI.Theme; using AtomUI.Theme.Styling; using AtomUI.Theme.Utils; using AtomUI.Utils; @@ -17,11 +18,12 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class CollapseItemTheme : BaseControlTheme { - public const string MainLayoutPart = "PART_MainLayout"; - public const string ExpandButtonPart = "PART_ExpandButton"; - public const string HeaderPresenterPart = "PART_HeaderPresenter"; - public const string HeaderDecoratorPart = "PART_HeaderDecorator"; - public const string ContentPresenterPart = "PART_ContentPresenter"; + public const string MainLayoutPart = "PART_MainLayout"; + public const string ExpandButtonPart = "PART_ExpandButton"; + public const string HeaderPresenterPart = "PART_HeaderPresenter"; + public const string HeaderDecoratorPart = "PART_HeaderDecorator"; + public const string ContentPresenterPart = "PART_ContentPresenter"; + public const string ContentAnimationTargetPart = "PART_ContentAnimationTarget"; public CollapseItemTheme() : base(typeof(CollapseItem)) {} @@ -36,15 +38,21 @@ internal class CollapseItemTheme : BaseControlTheme }; BuildHeader(mainLayout, scope); + var animationPanel = new AnimationTargetPanel() + { + Name = ContentAnimationTargetPart + }; var contentPresenter = new ContentPresenter() { Name = ContentPresenterPart, }; + animationPanel.Children.Add(contentPresenter); TokenResourceBinder.CreateGlobalTokenBinding(contentPresenter, ContentPresenter.BorderBrushProperty, GlobalResourceKey.ColorBorder); CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, CollapseItem.ContentProperty); CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, CollapseItem.ContentTemplateProperty); CreateTemplateParentBinding(contentPresenter, ContentPresenter.BorderThicknessProperty, CollapseItem.ContentBorderThicknessProperty); - mainLayout.Children.Add(contentPresenter); + mainLayout.Children.Add(animationPanel); + animationPanel.RegisterInNameScope(scope); contentPresenter.RegisterInNameScope(scope); return mainLayout; }); @@ -56,6 +64,7 @@ internal class CollapseItemTheme : BaseControlTheme { Name = HeaderDecoratorPart }; + headerDecorator.RegisterInNameScope(scope); DockPanel.SetDock(headerDecorator, Dock.Top); TokenResourceBinder.CreateGlobalTokenBinding(headerDecorator, Border.BorderBrushProperty, GlobalResourceKey.ColorBorder);