diff --git a/src/AtomUI.Base/Controls/IWaveSpiritAwareControl.cs b/src/AtomUI.Base/Controls/IWaveSpiritAwareControl.cs
index ad5f76cb1..8e518847e 100644
--- a/src/AtomUI.Base/Controls/IWaveSpiritAwareControl.cs
+++ b/src/AtomUI.Base/Controls/IWaveSpiritAwareControl.cs
@@ -6,7 +6,6 @@ namespace AtomUI.Controls;
public interface IWaveSpiritAwareControl : IMotionAwareControl
{
bool IsWaveSpiritEnabled { get; }
- // Color WaveSpiritColor { get; }
}
public abstract class WaveSpiritAwareControlProperty : MotionAwareControlProperty
diff --git a/src/AtomUI.Controls/AtomUIOSSControlThemesProvider.axaml b/src/AtomUI.Controls/AtomUIOSSControlThemesProvider.axaml
index edf709ff3..453be35a8 100644
--- a/src/AtomUI.Controls/AtomUIOSSControlThemesProvider.axaml
+++ b/src/AtomUI.Controls/AtomUIOSSControlThemesProvider.axaml
@@ -17,6 +17,7 @@
+
diff --git a/src/AtomUI.Controls/Carousel/Carousel.cs b/src/AtomUI.Controls/Carousel/Carousel.cs
new file mode 100644
index 000000000..2b0d498d4
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/Carousel.cs
@@ -0,0 +1,226 @@
+using AtomUI.Controls.Themes;
+using AtomUI.Theme;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Animation.Easings;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+
+namespace AtomUI.Controls;
+
+public class Carousel : SelectingItemsControl,
+ IControlSharedTokenResourcesHost,
+ IMotionAwareControl
+{
+ #region 公共属性定义
+ public static readonly StyledProperty IsShowNavButtonsProperty =
+ AvaloniaProperty.Register(nameof(IsShowNavButtons));
+
+ public static readonly StyledProperty IsAutoPlayProperty =
+ AvaloniaProperty.Register(nameof(IsAutoPlay));
+
+ public static readonly StyledProperty AutoPlaySpeedProperty =
+ AvaloniaProperty.Register(nameof(AutoPlaySpeed), TimeSpan.FromMilliseconds(3000));
+
+ public static readonly StyledProperty PaginationPositionProperty =
+ AvaloniaProperty.Register(nameof(PaginationPosition), CarouselPaginationPosition.Bottom);
+
+ public static readonly StyledProperty IsShowPaginationProperty =
+ AvaloniaProperty.Register(nameof(IsShowPagination));
+
+ public static readonly StyledProperty IsDraggableProperty =
+ AvaloniaProperty.Register(nameof(IsDraggable));
+
+ public static readonly StyledProperty IsInfiniteProperty =
+ AvaloniaProperty.Register(nameof(IsInfinite));
+
+ public static readonly StyledProperty PageTransitionDurationProperty =
+ AvaloniaProperty.Register(nameof(PageTransitionDuration));
+
+ public static readonly StyledProperty PageInEasingProperty =
+ AvaloniaProperty.Register(nameof(PageInEasing));
+
+ public static readonly StyledProperty PageOutEasingProperty =
+ AvaloniaProperty.Register(nameof(PageOutEasing));
+
+ public static readonly StyledProperty TransitionEffectProperty =
+ AvaloniaProperty.Register(nameof(TransitionEffect));
+
+ public static readonly StyledProperty IsMotionEnabledProperty =
+ MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner();
+
+ public bool IsShowNavButtons
+ {
+ get => GetValue(IsShowNavButtonsProperty);
+ set => SetValue(IsShowNavButtonsProperty, value);
+ }
+
+ public bool IsAutoPlay
+ {
+ get => GetValue(IsAutoPlayProperty);
+ set => SetValue(IsAutoPlayProperty, value);
+ }
+
+ public TimeSpan AutoPlaySpeed
+ {
+ get => GetValue(AutoPlaySpeedProperty);
+ set => SetValue(AutoPlaySpeedProperty, value);
+ }
+
+ public CarouselPaginationPosition PaginationPosition
+ {
+ get => GetValue(PaginationPositionProperty);
+ set => SetValue(PaginationPositionProperty, value);
+ }
+
+ public bool IsShowPagination
+ {
+ get => GetValue(IsShowPaginationProperty);
+ set => SetValue(IsShowPaginationProperty, value);
+ }
+
+ public bool IsDraggable
+ {
+ get => GetValue(IsDraggableProperty);
+ set => SetValue(IsDraggableProperty, value);
+ }
+
+ public bool IsInfinite
+ {
+ get => GetValue(IsInfiniteProperty);
+ set => SetValue(IsInfiniteProperty, value);
+ }
+
+ public TimeSpan PageTransitionDuration
+ {
+ get => GetValue(PageTransitionDurationProperty);
+ set => SetValue(PageTransitionDurationProperty, value);
+ }
+
+ public Easing PageInEasing
+ {
+ get => GetValue(PageInEasingProperty);
+ set => SetValue(PageInEasingProperty, value);
+ }
+
+ public Easing PageOutEasing
+ {
+ get => GetValue(PageOutEasingProperty);
+ set => SetValue(PageOutEasingProperty, value);
+ }
+
+ public CarouselTransitionEffect TransitionEffect
+ {
+ get => GetValue(TransitionEffectProperty);
+ set => SetValue(TransitionEffectProperty, value);
+ }
+
+ public bool IsMotionEnabled
+ {
+ get => GetValue(IsMotionEnabledProperty);
+ set => SetValue(IsMotionEnabledProperty, value);
+ }
+ #endregion
+
+ #region 内部属性定义
+
+ internal static readonly StyledProperty PageTransitionProperty =
+ AvaloniaProperty.Register(nameof(PageTransition));
+
+ internal static readonly DirectProperty PreviousNavButtonVisibleProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(PreviousNavButtonVisible),
+ o => o.PreviousNavButtonVisible,
+ (o, v) => o.PreviousNavButtonVisible = v);
+
+ internal static readonly DirectProperty NextNavButtonVisibleProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(NextNavButtonVisible),
+ o => o.NextNavButtonVisible,
+ (o, v) => o.NextNavButtonVisible = v);
+
+ private static readonly FuncTemplate DefaultPanel =
+ new(() => new VirtualizingCarouselPanel());
+
+ public IPageTransition? PageTransition
+ {
+ get => GetValue(PageTransitionProperty);
+ set => SetValue(PageTransitionProperty, value);
+ }
+
+ private bool _previousNavButtonVisible;
+
+ internal bool PreviousNavButtonVisible
+ {
+ get => _previousNavButtonVisible;
+ set => SetAndRaise(PreviousNavButtonVisibleProperty, ref _previousNavButtonVisible, value);
+ }
+
+ private bool _nextNavButtonVisible;
+
+ internal bool NextNavButtonVisible
+ {
+ get => _nextNavButtonVisible;
+ set => SetAndRaise(NextNavButtonVisibleProperty, ref _nextNavButtonVisible, value);
+ }
+
+ Control IMotionAwareControl.PropertyBindTarget => this;
+ Control IControlSharedTokenResourcesHost.HostControl => this;
+ string IControlSharedTokenResourcesHost.TokenId => CarouselToken.ID;
+
+ #endregion
+
+ private IScrollable? _scroller;
+
+ static Carousel()
+ {
+ SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected);
+ ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
+ }
+
+ public void Next()
+ {
+ if (SelectedIndex < ItemCount - 1)
+ {
+ ++SelectedIndex;
+ }
+ }
+
+ public void Previous()
+ {
+ if (SelectedIndex > 0)
+ {
+ --SelectedIndex;
+ }
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ var result = base.ArrangeOverride(finalSize);
+
+ if (_scroller is not null)
+ {
+ _scroller.Offset = new(SelectedIndex, 0);
+ }
+
+ return result;
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ _scroller = e.NameScope.Find(CarouselThemeConstants.ScrollViewerPart);
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == SelectedIndexProperty && _scroller is not null)
+ {
+ var value = change.GetNewValue();
+ _scroller.Offset = new(value, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/CarouselEnums.cs b/src/AtomUI.Controls/Carousel/CarouselEnums.cs
new file mode 100644
index 000000000..0a5720c67
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/CarouselEnums.cs
@@ -0,0 +1,15 @@
+namespace AtomUI.Controls;
+
+public enum CarouselPaginationPosition
+{
+ Top,
+ Bottom,
+ Left,
+ Right
+}
+
+public enum CarouselTransitionEffect
+{
+ Scroll,
+ Fade
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/CarouselPage.cs b/src/AtomUI.Controls/Carousel/CarouselPage.cs
new file mode 100644
index 000000000..1437c7f84
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/CarouselPage.cs
@@ -0,0 +1,8 @@
+using Avalonia.Controls;
+
+namespace AtomUI.Controls;
+
+public class CarouselPage : ContentControl
+{
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/CarouselPageIndicator.cs b/src/AtomUI.Controls/Carousel/CarouselPageIndicator.cs
new file mode 100644
index 000000000..e45f4d84b
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/CarouselPageIndicator.cs
@@ -0,0 +1,8 @@
+using Avalonia.Controls.Primitives;
+
+namespace AtomUI.Controls;
+
+internal class CarouselPageIndicator : TemplatedControl
+{
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/CarouselPagination.cs b/src/AtomUI.Controls/Carousel/CarouselPagination.cs
new file mode 100644
index 000000000..460635d44
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/CarouselPagination.cs
@@ -0,0 +1,8 @@
+using Avalonia.Controls.Primitives;
+
+namespace AtomUI.Controls;
+
+internal class CarouselPagination : TemplatedControl
+{
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/CarouselToken.cs b/src/AtomUI.Controls/Carousel/CarouselToken.cs
new file mode 100644
index 000000000..8d5b088fe
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/CarouselToken.cs
@@ -0,0 +1,68 @@
+using AtomUI.Theme.TokenSystem;
+
+namespace AtomUI.Controls;
+
+[ControlDesignToken]
+internal class CarouselToken : AbstractControlDesignToken
+{
+ public const string ID = "Carousel";
+
+ public CarouselToken()
+ : base(ID)
+ {
+ }
+
+ ///
+ /// 指示点宽度
+ /// Width of indicator
+ ///
+ public double IndicatorWidth { get; set; }
+
+ ///
+ /// 指示点高度
+ /// Height of indicator
+ ///
+ public double IndicatorHeight { get; set; }
+
+ ///
+ /// 指示点之间的间距
+ /// gap between indicator
+ ///
+ public double IndicatorGap { get; set; }
+
+ ///
+ /// 指示点距离边缘的距离
+ /// dot offset to Carousel edge
+ ///
+ public double IndicatorOffset { get; set; }
+
+ ///
+ /// 激活态指示点宽度
+ /// Width of active indicator
+ ///
+ public double IndicatorActiveWidth { get; set; }
+
+ ///
+ /// 切换箭头大小
+ /// Size of arrows
+ ///
+ public double ArrowSize { get; set; }
+
+ ///
+ /// 切换箭头边距
+ /// arrows offset to Carousel edge
+ ///
+ public double ArrowOffset { get; set; }
+
+ public override void CalculateFromAlias()
+ {
+ base.CalculateFromAlias();
+ IndicatorActiveWidth = 24;
+ ArrowSize = 16;
+ ArrowOffset = SharedToken.SpacingXS;
+ IndicatorWidth = 16;
+ IndicatorHeight = 3;
+ IndicatorGap = SharedToken.SpacingXXS;
+ IndicatorOffset = 12;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/Themes/CarouselPageIndicatorTheme.axaml b/src/AtomUI.Controls/Carousel/Themes/CarouselPageIndicatorTheme.axaml
new file mode 100644
index 000000000..1bf7588f6
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/Themes/CarouselPageIndicatorTheme.axaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/Themes/CarouselPageTheme.axaml b/src/AtomUI.Controls/Carousel/Themes/CarouselPageTheme.axaml
new file mode 100644
index 000000000..a61a3a6e1
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/Themes/CarouselPageTheme.axaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/Themes/CarouselPageThemeConstants.cs b/src/AtomUI.Controls/Carousel/Themes/CarouselPageThemeConstants.cs
new file mode 100644
index 000000000..a5ffe25a0
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/Themes/CarouselPageThemeConstants.cs
@@ -0,0 +1,6 @@
+namespace AtomUI.Controls.Themes;
+
+internal static class CarouselThemeConstants
+{
+ public const string ScrollViewerPart = "PART_ScrollViewer";
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/Themes/CarouselPaginationTheme.axaml b/src/AtomUI.Controls/Carousel/Themes/CarouselPaginationTheme.axaml
new file mode 100644
index 000000000..fddf584be
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/Themes/CarouselPaginationTheme.axaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/Themes/CarouselTheme.axaml b/src/AtomUI.Controls/Carousel/Themes/CarouselTheme.axaml
new file mode 100644
index 000000000..b6fb2a7d8
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/Themes/CarouselTheme.axaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/Themes/CarouselThemes.axaml b/src/AtomUI.Controls/Carousel/Themes/CarouselThemes.axaml
new file mode 100644
index 000000000..31ef7efb2
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/Themes/CarouselThemes.axaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Carousel/VirtualizingCarouselPanel.cs b/src/AtomUI.Controls/Carousel/VirtualizingCarouselPanel.cs
new file mode 100644
index 000000000..fe711c11e
--- /dev/null
+++ b/src/AtomUI.Controls/Carousel/VirtualizingCarouselPanel.cs
@@ -0,0 +1,402 @@
+using System.Collections.Specialized;
+using System.Diagnostics;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+
+namespace AtomUI.Controls;
+
+internal class VirtualizingCarouselPanel: VirtualizingPanel, ILogicalScrollable
+{
+ private static readonly AttachedProperty