diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a3733a..3127c25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.7] - 2026-06-06 + +### Added + +- **DaisyLoading**: Added segmented battery loading variants for charging and emptying states. +- **DaisyLoading**: Added directional traffic-light loading variants for up, right, down, and left cycles. +- **Gallery App**: Added examples for the new battery and traffic-light loading variants, including `DaisyIndicator` marker scenarios. +- **Gallery App**: Improved the browser Loading page by routing it to a dedicated view that loads later animation groups as they near the viewport. + ## [2.0.6] - 2026-05-14 ### Fixed diff --git a/Directory.Build.props b/Directory.Build.props index 0819017..107b374 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,8 @@ - 2.0.6 - 2.0.6.0 - 2.0.6.0 + 2.0.7 + 2.0.7.0 + 2.0.7.0 $(MSBuildThisFileDirectory)bin\$(Configuration)\ $(MSBuildThisFileDirectory)obj\$(MSBuildProjectName)\ $(BaseOutputPath)$(MSBuildProjectName)\ diff --git a/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml b/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml index a5ed838..d46501f 100644 --- a/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml +++ b/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml @@ -451,6 +451,23 @@ + + + + + + + + + + + + + + + + + @@ -504,25 +521,7 @@ - - - - - - - - - - - - - - - - - - - + diff --git a/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml.cs b/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml.cs index eb634f6..36303ad 100644 --- a/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml.cs +++ b/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Avalonia; diff --git a/Flowery.NET.Gallery/Examples/LayoutExamples.axaml b/Flowery.NET.Gallery/Examples/LayoutExamples.axaml index 6ac5c8b..54122c9 100644 --- a/Flowery.NET.Gallery/Examples/LayoutExamples.axaml +++ b/Flowery.NET.Gallery/Examples/LayoutExamples.axaml @@ -174,6 +174,43 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET.Gallery/Examples/LoadingExamples.cs b/Flowery.NET.Gallery/Examples/LoadingExamples.cs new file mode 100644 index 0000000..63aeeb2 --- /dev/null +++ b/Flowery.NET.Gallery/Examples/LoadingExamples.cs @@ -0,0 +1,536 @@ +using System; +using System.Collections.Generic; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Threading; +using Avalonia.VisualTree; +using Flowery.Controls; +using Flowery.Services; + +namespace Flowery.NET.Gallery.Examples; + +public sealed class LoadingExamples : UserControl, IScrollableExample +{ + private const double LazyLoadMargin = 240; + + private readonly ScrollViewer _scrollViewer; + private readonly StackPanel _contentPanel; + private readonly List _lazySections = new(); + private readonly Queue _rowLoadQueue = new(); + private bool _isLoaded; + private bool _isCheckingLazySections; + private bool _isLoadingRows; + + public LoadingExamples() + { + _contentPanel = new StackPanel + { + Spacing = 12, + Margin = new Thickness(24, 0, 24, 32) + }; + + _contentPanel.Children.Add(new SectionHeader { SectionId = "loading", Title = "Loading" }); + _contentPanel.Children.Add(CreateClassicSection()); + _contentPanel.Children.Add(CreateTerminalSection()); + + AddLazySection( + "Matrix Colon Patterns", + "Colon-dot patterns with directional wave animations", + CreateMatrixRows()); + AddLazySection( + "Advanced Digital Variants", + "Dense digital progress indicators for technical workflows", + CreateDigitalRows()); + AddLazySection( + "Instrumentation Variants", + "Monitoring, terminal, and signal-style loaders", + CreateInstrumentationRows()); + AddLazySection( + "Business / Workflow Variants", + "Document, cloud, approval, battery, and traffic-light progress indicators", + CreateBusinessRows()); + AddLazySection( + "Win95 Retro Variants", + "Nostalgic Windows 95 style file operation animations", + CreateWin95Rows()); + + _scrollViewer = new ScrollViewer + { + Name = "MainScrollViewer", + HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, + Content = _contentPanel + }; + + Content = _scrollViewer; + + FloweryResponsive.SetIsEnabled(_scrollViewer, true); + FloweryResponsive.SetBaseMaxWidth(_scrollViewer, 430); + + Loaded += OnLoaded; + Unloaded += OnUnloaded; + _scrollViewer.ScrollChanged += OnScrollChanged; + } + + public void ScrollToSection(string sectionName) + { + _scrollViewer.Offset = new Vector(0, 0); + QueueLazySectionCheck(DispatcherPriority.Loaded); + } + + private void OnLoaded(object? sender, RoutedEventArgs e) + { + _isLoaded = true; + QueueLazySectionCheck(DispatcherPriority.Loaded); + } + + private void OnUnloaded(object? sender, RoutedEventArgs e) + { + _isLoaded = false; + } + + private void AddLazySection(string title, string description, LoadingRow[] rows) + { + var section = new LazyLoadingSection(title, description, rows); + _lazySections.Add(section); + _contentPanel.Children.Add(section); + } + + private void OnScrollChanged(object? sender, ScrollChangedEventArgs e) + { + QueueLazySectionCheck(DispatcherPriority.Background); + } + + private void QueueLazySectionCheck(DispatcherPriority priority) + { + if (!_isLoaded || _isCheckingLazySections) + return; + + _isCheckingLazySections = true; + Dispatcher.UIThread.Post(LoadNextVisibleLazySection, priority); + } + + private void LoadNextVisibleLazySection() + { + _isCheckingLazySections = false; + + if (!_isLoaded) + return; + + foreach (var section in _lazySections) + { + if (section.HasLoadedContent || !IsNearViewport(section)) + continue; + + section.BeginLoading(); + _rowLoadQueue.Enqueue(section); + QueueRowLoad(); + QueueLazySectionCheck(DispatcherPriority.Background); + return; + } + } + + private bool IsNearViewport(Control control) + { + if (_scrollViewer.Bounds.Height <= 0) + return false; + + var transform = control.TransformToVisual(_scrollViewer); + if (!transform.HasValue) + return false; + + var point = transform.Value.Transform(new Point(0, 0)); + return point.Y < _scrollViewer.Bounds.Height + LazyLoadMargin && + point.Y + control.Bounds.Height > -LazyLoadMargin; + } + + private void QueueRowLoad() + { + if (!_isLoaded || _isLoadingRows || _rowLoadQueue.Count == 0) + return; + + _isLoadingRows = true; + Dispatcher.UIThread.Post(LoadNextLazyRow, DispatcherPriority.Background); + } + + private void LoadNextLazyRow() + { + _isLoadingRows = false; + + if (!_isLoaded) + return; + + while (_rowLoadQueue.Count > 0) + { + var section = _rowLoadQueue.Peek(); + if (section.AddNextRow()) + { + QueueRowLoad(); + return; + } + + _rowLoadQueue.Dequeue(); + } + } + + private static Control CreateClassicSection() + { + return CreateSection( + "Classic DaisyUI Variants", + "Original loading animations from DaisyUI component library", + new LoadingRow("Spinner", DaisyLoadingVariant.Spinner, AllSizes), + new LoadingRow("Dots", DaisyLoadingVariant.Dots, AllSizes), + new LoadingRow("Ring", DaisyLoadingVariant.Ring, AllSizes), + new LoadingRow("Ball", DaisyLoadingVariant.Ball, AllSizes), + new LoadingRow("Bars", DaisyLoadingVariant.Bars, AllSizes), + new LoadingRow("Infinity", DaisyLoadingVariant.Infinity, AllSizes), + new LoadingRow("Color Variants", DaisyLoadingVariant.Spinner, MediumSize, AllColors)); + } + + private static Control CreateTerminalSection() + { + return CreateSection( + "Terminal-Style Variants", + "CLI-inspired animations reminiscent of npm, yarn, and terminal progress indicators", + new LoadingRow("Orbit (npm-style)", DaisyLoadingVariant.Orbit, AllSizes), + new LoadingRow("Snake (centipede)", DaisyLoadingVariant.Snake, AllSizes), + new LoadingRow("Pulse (breathing)", DaisyLoadingVariant.Pulse, AllSizes), + new LoadingRow("Wave", DaisyLoadingVariant.Wave, AllSizes), + new LoadingRow("Bounce (grid)", DaisyLoadingVariant.Bounce, AllSizes), + new LoadingRow("Terminal Colors", TerminalColorVariants)); + } + + private static LoadingRow[] CreateMatrixRows() + { + return + [ + new LoadingRow("Matrix (left to right)", DaisyLoadingVariant.Matrix, MediumLargeExtraLarge), + new LoadingRow("MatrixInward (center to edges)", DaisyLoadingVariant.MatrixInward, MediumLargeExtraLarge), + new LoadingRow("MatrixOutward (edges to center)", DaisyLoadingVariant.MatrixOutward, MediumLargeExtraLarge), + new LoadingRow("MatrixVertical (top to bottom)", DaisyLoadingVariant.MatrixVertical, MediumLargeExtraLarge) + ]; + } + + private static LoadingRow[] CreateDigitalRows() + { + return + [ + new LoadingRow("MatrixRain", DaisyLoadingVariant.MatrixRain, MediumLargeExtraLarge), + new LoadingRow("BitFlip", DaisyLoadingVariant.BitFlip, MediumLargeExtraLarge), + new LoadingRow("PacketBurst", DaisyLoadingVariant.PacketBurst, MediumLargeExtraLarge), + new LoadingRow("CometTrail", DaisyLoadingVariant.CometTrail, MediumLargeExtraLarge), + new LoadingRow("RippleMatrix", DaisyLoadingVariant.RippleMatrix, MediumLargeExtraLarge), + new LoadingRow("CountdownSpinner", DaisyLoadingVariant.CountdownSpinner, MediumLargeExtraLarge) + ]; + } + + private static LoadingRow[] CreateInstrumentationRows() + { + return + [ + new LoadingRow("Hourglass", DaisyLoadingVariant.Hourglass, MediumLargeExtraLarge), + new LoadingRow("SignalSweep", DaisyLoadingVariant.SignalSweep, MediumLargeExtraLarge), + new LoadingRow("Heartbeat", DaisyLoadingVariant.Heartbeat, MediumLargeExtraLarge), + new LoadingRow("TunnelZoom", DaisyLoadingVariant.TunnelZoom, MediumLargeExtraLarge), + new LoadingRow("GlitchReveal", DaisyLoadingVariant.GlitchReveal, MediumLargeExtraLarge), + new LoadingRow("CursorBlink", DaisyLoadingVariant.CursorBlink, MediumLargeExtraLarge) + ]; + } + + private static LoadingRow[] CreateBusinessRows() + { + return + [ + new LoadingRow("DocumentFlipOn (opening)", DaisyLoadingVariant.DocumentFlipOn, MediumLargeExtraLarge), + new LoadingRow("DocumentFlipOff (closing)", DaisyLoadingVariant.DocumentFlipOff, MediumLargeExtraLarge), + new LoadingRow("MailSend", DaisyLoadingVariant.MailSend, MediumLargeExtraLarge), + new LoadingRow("CloudUpload", DaisyLoadingVariant.CloudUpload, MediumLargeExtraLarge), + new LoadingRow("CloudDownload", DaisyLoadingVariant.CloudDownload, MediumLargeExtraLarge), + new LoadingRow("DocumentStamp (approval OK)", DaisyLoadingVariant.DocumentStamp, MediumLargeExtraLarge), + new LoadingRow("DocumentReject (rejection X)", DaisyLoadingVariant.DocumentReject, MediumLargeExtraLarge), + new LoadingRow("ChartPulse (analytics)", DaisyLoadingVariant.ChartPulse, MediumLargeExtraLarge), + new LoadingRow("CalendarTick (scheduling)", DaisyLoadingVariant.CalendarTick, MediumLargeExtraLarge), + new LoadingRow("ApprovalFlow (workflow)", DaisyLoadingVariant.ApprovalFlow, MediumLargeExtraLarge), + new LoadingRow("BriefcaseSpin (business)", DaisyLoadingVariant.BriefcaseSpin, MediumLargeExtraLarge), + new LoadingRow("Battery (charge/drain)", BatteryVariants), + new LoadingRow("TrafficLight (directional)", TrafficLightVariants) + ]; + } + + private static LoadingRow[] CreateWin95Rows() + { + return + [ + new LoadingRow("Win95FileCopy (flying papers)", DaisyLoadingVariant.Win95FileCopy, MediumLargeExtraLarge), + new LoadingRow("Win95Search (magnifying glass)", DaisyLoadingVariant.Win95Search, MediumLargeExtraLarge), + new LoadingRow("Win95Delete (to recycle bin)", DaisyLoadingVariant.Win95Delete, MediumLargeExtraLarge), + new LoadingRow("Win95EmptyRecycle (emptying bin)", DaisyLoadingVariant.Win95EmptyRecycle, MediumLargeExtraLarge) + ]; + } + + private static Border CreateSection(string title, string description, params LoadingRow[] rows) + { + var panel = new StackPanel { Spacing = 12 }; + panel.Children.Add(new TextBlock + { + Text = title, + FontWeight = FontWeight.Bold, + FontSize = 16, + Opacity = 0.95 + }); + panel.Children.Add(new TextBlock + { + Text = description, + Opacity = 0.6, + Margin = new Thickness(0, 0, 0, 8) + }); + + var rowPanel = new WrapPanel { Orientation = Orientation.Horizontal }; + FlowerySizeManager.SetIgnoreGlobalSize(rowPanel, true); + + foreach (var row in rows) + rowPanel.Children.Add(CreateRow(row)); + + panel.Children.Add(rowPanel); + + var border = new Border + { + CornerRadius = new CornerRadius(8), + Padding = new Thickness(16), + Margin = new Thickness(0, 8, 0, 0), + Child = panel + }; + + if (Application.Current?.TryFindResource("DaisyBase200Brush", out var resource) == true && resource is IBrush brush) + border.Background = brush; + + return border; + } + + private static StackPanel CreateRow(LoadingRow row) + { + var loadingPanel = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 16, + VerticalAlignment = VerticalAlignment.Center + }; + + foreach (var item in row.Items) + { + loadingPanel.Children.Add(new DaisyLoading + { + Variant = item.Variant, + Size = item.Size, + Color = item.Color + }); + } + + return new StackPanel + { + Spacing = 10, + Margin = new Thickness(0, 0, 40, 12), + Children = + { + new TextBlock + { + Text = row.Title, + FontWeight = FontWeight.SemiBold, + FontSize = 14, + Opacity = 0.8 + }, + loadingPanel + } + }; + } + + private static LoadingItem[] CreateItems(DaisyLoadingVariant variant, DaisySize[] sizes) + { + var items = new LoadingItem[sizes.Length]; + for (var index = 0; index < sizes.Length; index++) + items[index] = new LoadingItem(variant, sizes[index], DaisyColor.Default); + + return items; + } + + private static LoadingItem[] CreateItems(DaisyLoadingVariant variant, DaisySize[] sizes, DaisyColor[] colors) + { + var items = new LoadingItem[colors.Length]; + for (var index = 0; index < colors.Length; index++) + items[index] = new LoadingItem(variant, sizes[Math.Min(index, sizes.Length - 1)], colors[index]); + + return items; + } + + private static readonly DaisySize[] AllSizes = + [ + DaisySize.ExtraSmall, + DaisySize.Small, + DaisySize.Medium, + DaisySize.Large, + DaisySize.ExtraLarge + ]; + + private static readonly DaisySize[] MediumSize = [DaisySize.Medium]; + + private static readonly DaisySize[] MediumLargeExtraLarge = + [ + DaisySize.Medium, + DaisySize.Large, + DaisySize.ExtraLarge + ]; + + private static readonly DaisyColor[] AllColors = + [ + DaisyColor.Primary, + DaisyColor.Secondary, + DaisyColor.Accent, + DaisyColor.Info, + DaisyColor.Success, + DaisyColor.Warning, + DaisyColor.Error + ]; + + private static readonly LoadingItem[] TerminalColorVariants = + [ + new(DaisyLoadingVariant.Orbit, DaisySize.Large, DaisyColor.Primary), + new(DaisyLoadingVariant.Snake, DaisySize.Large, DaisyColor.Secondary), + new(DaisyLoadingVariant.Pulse, DaisySize.Large, DaisyColor.Accent), + new(DaisyLoadingVariant.Wave, DaisySize.Large, DaisyColor.Info), + new(DaisyLoadingVariant.Bounce, DaisySize.Large, DaisyColor.Success) + ]; + + private static readonly LoadingItem[] BatteryVariants = + [ + new(DaisyLoadingVariant.BatteryCharging, DaisySize.Medium, DaisyColor.Success), + new(DaisyLoadingVariant.BatteryEmptying, DaisySize.Large, DaisyColor.Warning), + new(DaisyLoadingVariant.BatteryCharging, DaisySize.ExtraLarge, DaisyColor.Primary) + ]; + + private static readonly LoadingItem[] TrafficLightVariants = + [ + new(DaisyLoadingVariant.TrafficLightUp, DaisySize.Medium, DaisyColor.Default), + new(DaisyLoadingVariant.TrafficLightRight, DaisySize.Large, DaisyColor.Default), + new(DaisyLoadingVariant.TrafficLightDown, DaisySize.Large, DaisyColor.Default), + new(DaisyLoadingVariant.TrafficLightLeft, DaisySize.ExtraLarge, DaisyColor.Default) + ]; + + private sealed record LoadingRow + { + public LoadingRow(string title, DaisyLoadingVariant variant, DaisySize[] sizes) + : this(title, CreateItems(variant, sizes)) + { + } + + public LoadingRow(string title, DaisyLoadingVariant variant, DaisySize[] sizes, DaisyColor[] colors) + : this(title, CreateItems(variant, sizes, colors)) + { + } + + public LoadingRow(string title, LoadingItem[] items) + { + Title = title; + Items = items; + } + + public string Title { get; } + public LoadingItem[] Items { get; } + } + + private readonly record struct LoadingItem(DaisyLoadingVariant Variant, DaisySize Size, DaisyColor Color); + + private sealed class LazyLoadingSection : Border + { + private readonly string _description; + private readonly LoadingRow[] _rows; + private readonly string _title; + private int _loadedRowCount; + private WrapPanel? _rowPanel; + + public LazyLoadingSection(string title, string description, LoadingRow[] rows) + { + _description = description; + _rows = rows; + _title = title; + + MinHeight = 190; + CornerRadius = new CornerRadius(8); + Padding = new Thickness(16); + Margin = new Thickness(0, 8, 0, 0); + Child = new StackPanel + { + Spacing = 8, + Children = + { + new TextBlock + { + Text = title, + FontWeight = FontWeight.Bold, + FontSize = 16, + Opacity = 0.95 + }, + new TextBlock + { + Text = description, + Opacity = 0.6 + }, + new TextBlock + { + Text = "Scroll to load these animations", + Opacity = 0.5 + } + } + }; + + if (Application.Current?.TryFindResource("DaisyBase200Brush", out var resource) == true && resource is IBrush brush) + Background = brush; + } + + public bool HasLoadedContent { get; set; } + + public void BeginLoading() + { + if (HasLoadedContent) + return; + + HasLoadedContent = true; + _rowPanel = new WrapPanel { Orientation = Orientation.Horizontal }; + FlowerySizeManager.SetIgnoreGlobalSize(_rowPanel, true); + + Child = new StackPanel + { + Spacing = 12, + Children = + { + new TextBlock + { + Text = _title, + FontWeight = FontWeight.Bold, + FontSize = 16, + Opacity = 0.95 + }, + new TextBlock + { + Text = _description, + Opacity = 0.6, + Margin = new Thickness(0, 0, 0, 8) + }, + _rowPanel + } + }; + } + + public bool AddNextRow() + { + if (_rowPanel == null || _loadedRowCount >= _rows.Length) + return false; + + _rowPanel.Children.Add(CreateRow(_rows[_loadedRowCount])); + _loadedRowCount++; + return _loadedRowCount < _rows.Length; + } + } +} diff --git a/Flowery.NET.Gallery/Examples/ProgressSection.cs b/Flowery.NET.Gallery/Examples/ProgressSection.cs new file mode 100644 index 0000000..0cc6395 --- /dev/null +++ b/Flowery.NET.Gallery/Examples/ProgressSection.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Threading; +using Avalonia.VisualTree; +using Flowery.Controls; +using Flowery.Services; + +namespace Flowery.NET.Gallery.Examples; + +// This section is intentionally implemented in C# instead of inline AXAML because it doubles as +// a small tutorial for embedding Flowery controls in a larger gallery page without paying the full +// construction cost up front. +// +// Pattern shown here: +// - Keep the section in its normal parent page so category navigation remains continuous. +// - Render a cheap placeholder immediately so layout and scrolling stay responsive. +// - Find the containing ScrollViewer when loaded and watch for the section entering the viewport. +// - Build real DaisyProgress controls only when the user is close to seeing them. +// - Add rows one dispatcher tick at a time so animations and input are not blocked by control creation. +public sealed class ProgressSection : UserControl +{ + private const double LazyLoadMargin = 240; + + private readonly StackPanel _contentPanel; + private readonly Queue> _rowFactories = new(); + private Border? _contentBorder; + private bool _hasStartedLoading; + private bool _isCheckingViewport; + private bool _isLoaded; + private bool _isLoadingRows; + private WrapPanel? _rowPanel; + private ScrollViewer? _scrollViewer; + + public ProgressSection() + { + _contentPanel = new StackPanel { Spacing = 12 }; + _contentPanel.Children.Add(new SectionHeader { SectionId = "progress", Title = "Progress (Linear)" }); + _contentPanel.Children.Add(CreatePlaceholder()); + + _rowFactories.Enqueue(CreateVariantsAndSizesRow); + _rowFactories.Enqueue(CreateInteractiveRow); + + Content = _contentPanel; + + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + private void OnLoaded(object? sender, RoutedEventArgs e) + { + _isLoaded = true; + _scrollViewer = this.GetVisualAncestors().OfType().FirstOrDefault(); + if (_scrollViewer != null) + _scrollViewer.ScrollChanged += OnScrollChanged; + + QueueViewportCheck(DispatcherPriority.Loaded); + } + + private void OnUnloaded(object? sender, RoutedEventArgs e) + { + _isLoaded = false; + if (_scrollViewer != null) + _scrollViewer.ScrollChanged -= OnScrollChanged; + + _scrollViewer = null; + } + + private void OnScrollChanged(object? sender, ScrollChangedEventArgs e) + { + QueueViewportCheck(DispatcherPriority.Background); + } + + private void QueueViewportCheck(DispatcherPriority priority) + { + if (!_isLoaded || _isCheckingViewport || _hasStartedLoading) + return; + + _isCheckingViewport = true; + Dispatcher.UIThread.Post(LoadIfNearViewport, priority); + } + + private void LoadIfNearViewport() + { + _isCheckingViewport = false; + + if (!_isLoaded || _hasStartedLoading || !IsNearViewport()) + return; + + BeginLoading(); + QueueRowLoad(); + } + + private bool IsNearViewport() + { + if (_scrollViewer == null || _scrollViewer.Bounds.Height <= 0) + return false; + + var transform = this.TransformToVisual(_scrollViewer); + if (!transform.HasValue) + return false; + + var point = transform.Value.Transform(new Point(0, 0)); + return point.Y < _scrollViewer.Bounds.Height + LazyLoadMargin && + point.Y + Bounds.Height > -LazyLoadMargin; + } + + private void BeginLoading() + { + _hasStartedLoading = true; + _rowPanel = new WrapPanel { Orientation = Orientation.Horizontal }; + FlowerySizeManager.SetIgnoreGlobalSize(_rowPanel, true); + + if (_contentBorder != null) + _contentBorder.Child = CreateContentPanel(_rowPanel); + } + + private void QueueRowLoad() + { + if (!_isLoaded || _isLoadingRows || _rowFactories.Count == 0) + return; + + _isLoadingRows = true; + Dispatcher.UIThread.Post(LoadNextRow, DispatcherPriority.Background); + } + + private void LoadNextRow() + { + _isLoadingRows = false; + + if (!_isLoaded || _rowPanel == null || _rowFactories.Count == 0) + return; + + var factory = _rowFactories.Dequeue(); + _rowPanel.Children.Add(factory()); + QueueRowLoad(); + } + + private Border CreatePlaceholder() + { + _contentBorder = CreateSectionBorder(new StackPanel + { + Spacing = 8, + Children = + { + new TextBlock + { + Text = "Core Progress", + FontWeight = FontWeight.Bold, + FontSize = 16, + Opacity = 0.95 + }, + new TextBlock + { + Text = "Common linear progress examples and an interactive value demo", + Opacity = 0.6 + }, + new TextBlock + { + Text = "Scroll to load these progress examples", + Opacity = 0.5 + } + } + }); + + return _contentBorder; + } + + private static StackPanel CreateContentPanel(Control content) + { + return new StackPanel + { + Spacing = 12, + Children = + { + new TextBlock + { + Text = "Core Progress", + FontWeight = FontWeight.Bold, + FontSize = 16, + Opacity = 0.95 + }, + new TextBlock + { + Text = "Common linear progress examples and an interactive value demo", + Opacity = 0.6, + Margin = new Thickness(0, 0, 0, 8) + }, + content + } + }; + } + + private static Border CreateSectionBorder(Control child) + { + var border = new Border + { + CornerRadius = new CornerRadius(8), + Padding = new Thickness(16), + Margin = new Thickness(0, 8, 0, 0), + MinHeight = 150, + Child = child + }; + + if (Application.Current?.TryFindResource("DaisyBase200Brush", out var resource) == true && resource is IBrush brush) + border.Background = brush; + + return border; + } + + private static StackPanel CreateVariantsAndSizesRow() + { + return new StackPanel + { + Spacing = 10, + Margin = new Thickness(0, 0, 40, 20), + Width = 200, + Children = + { + new TextBlock + { + Text = "Variants and sizes", + FontWeight = FontWeight.SemiBold, + FontSize = 14, + Opacity = 0.8 + }, + new DaisyProgress { Value = 40 }, + new DaisyProgress { Value = 70, Variant = DaisyProgressVariant.Primary }, + new DaisyProgress { Value = 100, Variant = DaisyProgressVariant.Accent, Size = DaisySize.Small }, + new DaisyProgress { IsIndeterminate = true, Variant = DaisyProgressVariant.Secondary } + } + }; + } + + private static StackPanel CreateInteractiveRow() + { + var slider = new Slider + { + Minimum = 0, + Maximum = 100, + Value = 40 + }; + + var valueText = new TextBlock { Opacity = 0.7 }; + valueText.Bind(TextBlock.TextProperty, new Binding("Value") + { + Source = slider, + StringFormat = "{0:0}%" + }); + FlowerySizeManager.SetResponsiveFont(valueText, ResponsiveFontTier.Secondary); + + var progress = new DaisyProgress(); + progress.Bind(RangeBase.ValueProperty, new Binding("Value") { Source = slider }); + + var primaryProgress = new DaisyProgress { Variant = DaisyProgressVariant.Primary }; + primaryProgress.Bind(RangeBase.ValueProperty, new Binding("Value") { Source = slider }); + + return new StackPanel + { + Spacing = 10, + Margin = new Thickness(0, 0, 40, 20), + Width = 260, + Children = + { + new TextBlock + { + Text = "Animated value updates", + FontWeight = FontWeight.SemiBold, + FontSize = 14, + Opacity = 0.8 + }, + slider, + valueText, + progress, + primaryProgress + } + }; + } +} diff --git a/Flowery.NET.Gallery/MainView.axaml.cs b/Flowery.NET.Gallery/MainView.axaml.cs index 67a012c..2481900 100644 --- a/Flowery.NET.Gallery/MainView.axaml.cs +++ b/Flowery.NET.Gallery/MainView.axaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -330,12 +330,12 @@ private void NavigateToCategory(string tabHeader, string? sectionId = null) if (CategoryTitleBar != null) CategoryTitleBar.IsVisible = tabHeader != "Sidebar_Home"; - if (_categoryControls.TryGetValue(tabHeader, out var factory)) + if (TryGetCategoryFactory(tabHeader, sectionId, out var cacheKey, out var factory)) { - if (!_categoryControlCache.TryGetValue(tabHeader, out var newContent)) + if (!_categoryControlCache.TryGetValue(cacheKey, out var newContent)) { newContent = factory(); - _categoryControlCache[tabHeader] = newContent; + _categoryControlCache[cacheKey] = newContent; } var contentChanged = !ReferenceEquals(_activeCategoryContent, newContent); @@ -386,6 +386,24 @@ private void NavigateToCategory(string tabHeader, string? sectionId = null) } } + private bool TryGetCategoryFactory( + string tabHeader, + string? sectionId, + out string cacheKey, + out Func factory) + { + if (string.Equals(tabHeader, "Sidebar_Feedback", StringComparison.OrdinalIgnoreCase) && + string.Equals(sectionId, "loading", StringComparison.OrdinalIgnoreCase)) + { + cacheKey = "Sidebar_Feedback:loading"; + factory = static () => new LoadingExamples(); + return true; + } + + cacheKey = tabHeader; + return _categoryControls.TryGetValue(tabHeader, out factory!); + } + private ScrollViewer? _currentScrollViewer; private void AttachScrollHandler(Control content) diff --git a/Flowery.NET/Controls/DaisyLoading.cs b/Flowery.NET/Controls/DaisyLoading.cs index f15d446..d2a6c00 100644 --- a/Flowery.NET/Controls/DaisyLoading.cs +++ b/Flowery.NET/Controls/DaisyLoading.cs @@ -1,4 +1,4 @@ -using System; +using System; using Avalonia; using Avalonia.Automation.Peers; using Avalonia.Controls.Primitives; @@ -90,6 +90,18 @@ public enum DaisyLoadingVariant ApprovalFlow, /// BriefcaseSpin animation - spinning briefcase BriefcaseSpin, + /// BatteryCharging animation - segmented battery filling from empty to full + BatteryCharging, + /// BatteryEmptying animation - segmented battery draining from full to empty + BatteryEmptying, + /// TrafficLightUp animation - vertical traffic light cycling upward + TrafficLightUp, + /// TrafficLightRight animation - traffic light cycling toward the right + TrafficLightRight, + /// TrafficLightDown animation - vertical traffic light cycling downward + TrafficLightDown, + /// TrafficLightLeft animation - traffic light cycling toward the left + TrafficLightLeft, // ==================== WIN95 RETRO VARIANTS ==================== /// Win95FileCopy animation - classic Windows 95 file copy with flying papers diff --git a/Flowery.NET/Themes/DaisyLoading/DaisyLoading.Business.axaml b/Flowery.NET/Themes/DaisyLoading/DaisyLoading.Business.axaml index 109ea9c..4ea7ee6 100644 --- a/Flowery.NET/Themes/DaisyLoading/DaisyLoading.Business.axaml +++ b/Flowery.NET/Themes/DaisyLoading/DaisyLoading.Business.axaml @@ -714,4 +714,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index dc5c849..c91e4aa 100644 --- a/README.md +++ b/README.md @@ -147,12 +147,12 @@ xmlns:controls="clr-namespace:Flowery.Controls;assembly=Flowery.NET" - **Indicator** (`DaisyIndicator`): Utility to place a badge on the corner of another element. - **Status Indicator** (`DaisyStatusIndicator`): Status indicator with **33 variants** including animation and glyph modes (battery, traffic lights, WiFi, cellular). Supports all theme colors and 5 sizes. -- **Loading** (`DaisyLoading`): Animated loading indicators with **42 variants** (extended from DaisyUI's original 6) across 6 categories: +- **Loading** (`DaisyLoading`): Animated loading indicators with **48 variants** (extended from DaisyUI's original 6) across 6 categories: - *Classic*: Spinner, Dots, Ring, Ball, Bars, Infinity - *Terminal-inspired*: Orbit, Snake, Pulse, Wave, Bounce - *Matrix/Colon-dot*: Matrix, MatrixInward, MatrixOutward, MatrixVertical - *Special effects*: MatrixRain, Hourglass, SignalSweep, BitFlip, PacketBurst, CometTrail, Heartbeat, TunnelZoom, GlitchReveal, RippleMatrix, CursorBlink, CountdownSpinner - - *Business*: DocumentFlipOn, DocumentFlipOff, MailSend, CloudUpload, CloudDownload, DocumentStamp, DocumentReject, ChartPulse, CalendarTick, ApprovalFlow, BriefcaseSpin + - *Business*: DocumentFlipOn, DocumentFlipOff, MailSend, CloudUpload, CloudDownload, DocumentStamp, DocumentReject, ChartPulse, CalendarTick, ApprovalFlow, BriefcaseSpin, BatteryCharging, BatteryEmptying, TrafficLightUp, TrafficLightRight, TrafficLightDown, TrafficLightLeft - *Win95 retro*: Win95FileCopy, Win95Delete, Win95Search, Win95EmptyRecycle - **Mask** (`DaisyMask`): Applies shapes (Squircle, Heart, Hexagon, etc.) to content. - **Mockup** (`DaisyMockup`): Frames for Code, Window, or Browser. diff --git a/llms-static/DaisyLoading.md b/llms-static/DaisyLoading.md index a755629..c56c679 100644 --- a/llms-static/DaisyLoading.md +++ b/llms-static/DaisyLoading.md @@ -3,7 +3,7 @@ # Overview -DaisyLoading provides animated loading indicators with **42 different animation styles**, **5 size options**, and **9 color variants**. The control includes standard DaisyUI animations, creative terminal-inspired variants, Matrix/retro variants, unique special effect variants, professional business-themed animations, and nostalgic Windows 95-style animations. All animations scale properly across all sizes using Viewbox-based rendering. +DaisyLoading provides animated loading indicators with **48 different animation styles**, **5 size options**, and **9 color variants**. The control includes standard DaisyUI animations, creative terminal-inspired variants, Matrix/retro variants, unique special effect variants, professional business-themed animations, and nostalgic Windows 95-style animations. All animations scale properly across all sizes using Viewbox-based rendering. ![Loading Animations](images/loading_animations.gif) @@ -75,6 +75,12 @@ Professional animations suitable for enterprise and productivity applications. | **CalendarTick** | Calendar with header and rings, featuring a checkmark that pops in. Scheduling/completion themed. | | **ApprovalFlow** | Three workflow nodes (circles) with inner dots that pulse in sequence. Workflow/process states. | | **BriefcaseSpin** | Briefcase with handle that wobbles side-to-side with a subtle bounce. Business/work themed. | +| **BatteryCharging** | Segmented battery icon that fills from empty to full, useful for device/power states. | +| **BatteryEmptying** | Segmented battery icon that drains from full to empty, useful for discharge or remaining-power states. | +| **TrafficLightUp** | Vertical traffic light that cycles from green to yellow to red. | +| **TrafficLightRight** | Rotated traffic light that cycles toward the right. | +| **TrafficLightDown** | Vertical traffic light that cycles from red to yellow to green. | +| **TrafficLightLeft** | Rotated traffic light that cycles toward the left. | ### Windows 95 Retro Variants