using System.Diagnostics.CodeAnalysis; using Connected.Annotations; using Connected.Extensions; using Connected.Utilities; using Microsoft.AspNetCore.Components; namespace Connected.Components; public partial class Carousel : BindableItemsControlBase, IAsyncDisposable { protected string Classname => new CssBuilder("mud-carousel") .AddClass($"mud-carousel-{(BulletsColor ?? _currentColor).ToDescriptionString()}") .AddClass(Class) .Build(); protected string NavigationButtonsClassName => new CssBuilder() .AddClass($"align-self-{ConvertPosition(ArrowsPosition).ToDescriptionString()}", !(NavigationButtonsClass ?? "").Contains("align-self-")) .AddClass("mud-carousel-elements-rtl", RightToLeft) .AddClass(NavigationButtonsClass) .Build(); protected string BulletsButtonsClassName => new CssBuilder() .AddClass(BulletsClass) .Build(); private Timer _timer; private bool _autoCycle = true; private ThemeColor _currentColor = ThemeColor.Inherit; private TimeSpan _cycleTimeout = TimeSpan.FromSeconds(5); private void TimerElapsed(object stateInfo) => InvokeAsync(async () => await TimerTickAsync()); private static Position ConvertPosition(Position position) { return position switch { Position.Top => Position.Start, Position.Start => Position.Start, Position.Bottom => Position.End, Position.End => Position.End, _ => position }; } [CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; } /// /// Gets or Sets if 'Next' and 'Previous' arrows must be visible /// [Parameter] [Category(CategoryTypes.Carousel.Behavior)] public bool ShowArrows { get; set; } = true; /// /// Sets the position of the arrows. By default, the position is the Center position /// [Parameter] [Category(CategoryTypes.Carousel.Appearance)] public Position ArrowsPosition { get; set; } = Position.Center; /// /// Gets or Sets if bar with Bullets must be visible /// [Category(CategoryTypes.Carousel.Behavior)] [Parameter] public bool ShowBullets { get; set; } = true; /// /// Sets the position of the bullets. By default, the position is the Bottom position /// [Category(CategoryTypes.Carousel.Appearance)] [Parameter] public Position BulletsPosition { get; set; } = Position.Bottom; /// /// Gets or Sets the Bullets color. /// If not set, the color is determined based on the property of the active child. /// [Category(CategoryTypes.Carousel.Appearance)] [Parameter] public ThemeColor? BulletsColor { get; set; } /// /// Gets or Sets if bottom bar with Delimiters must be visible. /// Deprecated, use ShowBullets instead. /// [Category(CategoryTypes.Carousel.Behavior)] [Obsolete($"Use {nameof(ShowBullets)} instead", false)] [ExcludeFromCodeCoverage] [Parameter] public bool ShowDelimiters { get => ShowBullets; set => ShowBullets = value; } /// /// Gets or Sets the Delimiters color. /// If not set, the color is determined based on the property of the active child. /// Deprecated, use BulletsColor instead. /// [Obsolete($"Use {nameof(BulletsColor)} instead", false)] [Category(CategoryTypes.Carousel.Appearance)] [ExcludeFromCodeCoverage] [Parameter] public ThemeColor? DelimitersColor { get => BulletsColor; set => BulletsColor = value; } /// /// Gets or Sets automatic cycle on item collection. /// [Parameter] [Category(CategoryTypes.Carousel.Behavior)] public bool AutoCycle { get => _autoCycle; set { _autoCycle = value; if (_autoCycle) InvokeAsync(async () => await ResetTimerAsync()); else InvokeAsync(async () => await StopTimerAsync()); } } /// /// Gets or Sets the Auto Cycle time /// [Parameter] [Category(CategoryTypes.Carousel.Behavior)] public TimeSpan AutoCycleTime { get => _cycleTimeout; set { _cycleTimeout = value; if (_autoCycle == true) InvokeAsync(async () => await ResetTimerAsync()); else InvokeAsync(async () => await StopTimerAsync()); } } /// /// Gets or Sets custom class(es) for 'Next' and 'Previous' arrows /// [Parameter] [Category(CategoryTypes.Carousel.Appearance)] public string NavigationButtonsClass { get; set; } /// /// Gets or Sets custom class(es) for Bullets buttons /// [Category(CategoryTypes.Carousel.Appearance)] [Parameter] public string BulletsClass { get; set; } /// /// Gets or Sets custom class(es) for Delimiters buttons. /// Deprecated, use BulletsClass instead. /// [Category(CategoryTypes.Carousel.Appearance)] [Obsolete($"Use {nameof(BulletsClass)} instead", false)] [ExcludeFromCodeCoverage] [Parameter] public string DelimitersClass { get => BulletsClass; set => BulletsClass = value; } /// /// Custom previous navigation icon. /// [Parameter] [Category(CategoryTypes.Carousel.Appearance)] public string PreviousIcon { get; set; } = Icons.Material.Filled.NavigateBefore; /// /// Custom selected bullet icon. /// [Parameter] [Category(CategoryTypes.Carousel.Appearance)] public string CheckedIcon { get; set; } = Icons.Material.Filled.RadioButtonChecked; /// /// Custom unselected bullet icon. /// [Parameter] [Category(CategoryTypes.Carousel.Appearance)] public string UncheckedIcon { get; set; } = Icons.Material.Filled.RadioButtonUnchecked; /// /// Custom next navigation icon. /// [Parameter] [Category(CategoryTypes.Carousel.Appearance)] public string NextIcon { get; set; } = Icons.Material.Filled.NavigateNext; /// /// Gets or Sets the Template for the Left Arrow /// [Parameter] [Category(CategoryTypes.Carousel.Appearance)] public RenderFragment NextButtonTemplate { get; set; } /// /// Gets or Sets the Template for the Right Arrow /// [Parameter] [Category(CategoryTypes.Carousel.Appearance)] public RenderFragment PreviousButtonTemplate { get; set; } /// /// Gets or Sets the Template for Bullets /// [Category(CategoryTypes.Carousel.Appearance)] [Parameter] public RenderFragment BulletTemplate { get; set; } /// /// Gets or Sets if swipe gestures are allowed for touch devices. /// [Category(CategoryTypes.Carousel.Behavior)] [Parameter] public bool EnableSwipeGesture { get; set; } = true; /// /// Gets or Sets the Template for Delimiters. /// Deprecated, use BulletsTemplate instead. /// [Category(CategoryTypes.Carousel.Appearance)] [Obsolete($"Use {nameof(BulletTemplate)} instead", false)] [ExcludeFromCodeCoverage] [Parameter] public RenderFragment DelimiterTemplate { get => BulletTemplate; set => BulletTemplate = value; } /// /// Called when selected Index changed on base class /// protected override void SelectionChanged() { InvokeAsync(async () => await ResetTimerAsync()); _currentColor = SelectedContainer?.Color ?? ThemeColor.Inherit; } //When an item is added, it automatically checks the color public override void AddItem(CarouselItem item) { Items.Add(item); if (Items.Count - 1 == SelectedIndex) { _currentColor = item.Color; StateHasChanged(); } } /// /// Provides Selection changes by horizontal swipe gesture /// private void OnSwipe(SwipeDirection direction) { if (!EnableSwipeGesture) { return; } switch (direction) { case SwipeDirection.LeftToRight: if (RightToLeft) Next(); else Previous(); break; case SwipeDirection.RightToLeft: if (RightToLeft) Previous(); else Next(); break; } } /// /// Immediately starts the AutoCycle timer /// private ValueTask StartTimerAsync() { if (AutoCycle) _timer?.Change(AutoCycleTime, TimeSpan.Zero); return ValueTask.CompletedTask; } /// /// Immediately stops the AutoCycle timer /// private ValueTask StopTimerAsync() { _timer?.Change(Timeout.Infinite, Timeout.Infinite); return ValueTask.CompletedTask; } /// /// Stops and restart the AutoCycle timer /// private async ValueTask ResetTimerAsync() { await StopTimerAsync(); await StartTimerAsync(); } /// /// Changes the SelectedIndex to a next one (or restart on 0) /// private async ValueTask TimerTickAsync() { await InvokeAsync(Next); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); if (firstRender) { _timer = new Timer(TimerElapsed, null, AutoCycle ? AutoCycleTime : Timeout.InfiniteTimeSpan, AutoCycleTime); } } public async ValueTask DisposeAsync() { await DisposeAsync(true); GC.SuppressFinalize(this); } protected virtual async ValueTask DisposeAsync(bool disposing) { if (disposing) { await StopTimerAsync(); var timer = _timer; if (timer != null) { _timer = null; await timer.DisposeAsync(); } } } }