mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-28 14:20:47 +08:00
Add TimerStatistic And Statistic Controls
This commit is contained in:
@@ -11,6 +11,31 @@ public class StatisticViewModel : ReactiveObject, IRoutableViewModel
|
||||
|
||||
public string UrlPathSegment { get; } = ID.ToString();
|
||||
|
||||
|
||||
private DateTime _deadline;
|
||||
|
||||
public DateTime Deadline
|
||||
{
|
||||
get => _deadline;
|
||||
set => this.RaiseAndSetIfChanged(ref _deadline, value);
|
||||
}
|
||||
|
||||
private DateTime _tenSecondsLater;
|
||||
|
||||
public DateTime TenSecondsLater
|
||||
{
|
||||
get => _tenSecondsLater;
|
||||
set => this.RaiseAndSetIfChanged(ref _tenSecondsLater, value);
|
||||
}
|
||||
|
||||
private DateTime _before;
|
||||
|
||||
public DateTime Before
|
||||
{
|
||||
get => _before;
|
||||
set => this.RaiseAndSetIfChanged(ref _before, value);
|
||||
}
|
||||
|
||||
public StatisticViewModel(IScreen screen)
|
||||
{
|
||||
HostScreen = screen;
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<gallery:ShowCaseItem Title="Animated number" Description="Animated number with StatisticCountUp." IsOccupyEntireRow="True">
|
||||
<UniformGrid Columns="2" Rows="2">
|
||||
<atom:Statistic Header="Active Users" Value="100">
|
||||
<atom:Statistic Header="Active Users" Value="112893">
|
||||
<atom:Statistic.Content>
|
||||
<atom:StatisticCountUp EndValue="{Binding Value}"
|
||||
Precision="2"/>
|
||||
@@ -64,5 +64,16 @@
|
||||
</atom:Statistic>
|
||||
</UniformGrid>
|
||||
</gallery:ShowCaseItem>
|
||||
|
||||
<gallery:ShowCaseItem Title="Timer" Description="Timer component." IsOccupyEntireRow="True">
|
||||
<UniformGrid Columns="2" Rows="3" RowSpacing="10">
|
||||
<atom:TimerStatistic Value="{Binding Deadline}"/>
|
||||
<atom:TimerStatistic Header="Million Seconds" Value="{Binding Deadline}" Format="hh\:mm\:ss\.fff"/>
|
||||
<atom:TimerStatistic Header="Countdown" Value="{Binding TenSecondsLater}"/>
|
||||
<atom:TimerStatistic Header="Countup" Value="{Binding Before}"/>
|
||||
<atom:TimerStatistic Header="Day Level (Countdown)" Value="{Binding Deadline}" Format="d\ \天\ h\ \时\ m\ \分\ s\ \秒"/>
|
||||
<atom:TimerStatistic Header="Day Level (Countup)" Value="{Binding Before}" Format="d\ \天\ h\ \时\ m\ \分\ s\ \秒"/>
|
||||
</UniformGrid>
|
||||
</gallery:ShowCaseItem>
|
||||
</gallery:ShowCasePanel>
|
||||
</UserControl>
|
||||
@@ -8,7 +8,15 @@ public partial class StatisticShowCase : ReactiveUserControl<StatisticViewModel>
|
||||
{
|
||||
public StatisticShowCase()
|
||||
{
|
||||
this.WhenActivated(disposables => { });
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
if (DataContext is StatisticViewModel viewModel)
|
||||
{
|
||||
viewModel.Deadline = DateTime.Now.Add(TimeSpan.FromSeconds(60 * 60 * 24 * 2 + 30));
|
||||
viewModel.Before = DateTime.Now.Subtract(TimeSpan.FromSeconds(60 * 60 * 24 * 2 + 30));
|
||||
viewModel.TenSecondsLater = DateTime.Now.AddSeconds(10);
|
||||
}
|
||||
});
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,56 @@
|
||||
xmlns:atom="https://atomui.net"
|
||||
x:Class="AtomUI.Desktop.Controls.Themes.AbstractStatisticTheme"
|
||||
x:ClassModifier="internal">
|
||||
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="atom:AbstractStatistic">
|
||||
<Border Name="Frame"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<StackPanel Name="RootLayout">
|
||||
<ContentPresenter Name="HeaderPresenter"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
IsVisible="{TemplateBinding Header, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
<atom:Skeleton IsLoading="{TemplateBinding IsLoading}" IsActive="True" IsShowParagraph="False">
|
||||
<StackPanel Orientation="Horizontal" Name="ContentLayout">
|
||||
<ContentPresenter Name="ValuePrefixAddOn"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding ValuePrefixAddOn}"
|
||||
ContentTemplate="{TemplateBinding ValuePrefixAddOnTemplate}"
|
||||
IsVisible="{TemplateBinding ValuePrefixAddOn, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
<ContentPresenter Name="ContentPresenter"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
<ContentPresenter Name="ValueSuffixAddOn"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding ValueSuffixAddOn}"
|
||||
ContentTemplate="{TemplateBinding ValueSuffixAddOnTemplate}"
|
||||
IsVisible="{TemplateBinding ValueSuffixAddOn, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
</StackPanel>
|
||||
</atom:Skeleton>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^:is(atom|AbstractStatistic)">
|
||||
<Style Selector="^ /template/ ContentPresenter#HeaderPresenter">
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static atom:SharedTokenKey.ColorTextDescription}}"/>
|
||||
<Setter Property="FontSize" Value="{DynamicResource {x:Static atom:StatisticTokenKey.TitleFontSize}}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static atom:SharedTokenKey.ColorTextDescription}}" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource {x:Static atom:StatisticTokenKey.TitleFontSize}}" />
|
||||
</Style>
|
||||
|
||||
<Setter Property="ContentForeground" Value="{DynamicResource {x:Static atom:SharedTokenKey.ColorTextHeading}}"/>
|
||||
<Setter Property="ContentFontSize" Value="{DynamicResource {x:Static atom:StatisticTokenKey.ContentFontSize}}"/>
|
||||
|
||||
|
||||
<Setter Property="ContentForeground" Value="{DynamicResource {x:Static atom:SharedTokenKey.ColorTextHeading}}" />
|
||||
<Setter Property="ContentFontSize" Value="{DynamicResource {x:Static atom:StatisticTokenKey.ContentFontSize}}" />
|
||||
|
||||
<Style Selector="^ /template/ atom|Icon">
|
||||
<Setter Property="FillBrush" Value="{TemplateBinding ContentForeground}"/>
|
||||
<Setter Property="StrokeBrush" Value="{TemplateBinding ContentForeground}"/>
|
||||
<Setter Property="Width" Value="{DynamicResource {x:Static atom:StatisticTokenKey.ContentFontSize}}"/>
|
||||
<Setter Property="Height" Value="{DynamicResource {x:Static atom:StatisticTokenKey.ContentFontSize}}"/>
|
||||
<Setter Property="FillBrush" Value="{TemplateBinding ContentForeground}" />
|
||||
<Setter Property="StrokeBrush" Value="{TemplateBinding ContentForeground}" />
|
||||
<Setter Property="Width" Value="{DynamicResource {x:Static atom:StatisticTokenKey.ContentFontSize}}" />
|
||||
<Setter Property="Height" Value="{DynamicResource {x:Static atom:StatisticTokenKey.ContentFontSize}}" />
|
||||
</Style>
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
@@ -7,40 +7,6 @@
|
||||
<ControlTheme.BasedOn>
|
||||
<atom:AbstractStatisticTheme TargetType="atom:Statistic"/>
|
||||
</ControlTheme.BasedOn>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border Name="Frame"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<StackPanel Name="RootLayout">
|
||||
<ContentPresenter Name="HeaderPresenter"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"/>
|
||||
<atom:Skeleton IsLoading="{TemplateBinding IsLoading}" IsActive="True" IsShowParagraph="False">
|
||||
<StackPanel Orientation="Horizontal" Name="ContentLayout">
|
||||
<ContentPresenter Name="ValuePrefixAddOn"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding ValuePrefixAddOn}"
|
||||
ContentTemplate="{TemplateBinding ValuePrefixAddOnTemplate}"
|
||||
IsVisible="{TemplateBinding ValuePrefixAddOn, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
<ContentPresenter Name="ContentPresenter"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"/>
|
||||
<ContentPresenter Name="ValueSuffixAddOn"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding ValueSuffixAddOn}"
|
||||
ContentTemplate="{TemplateBinding ValueSuffixAddOnTemplate}"
|
||||
IsVisible="{TemplateBinding ValueSuffixAddOn, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
</StackPanel>
|
||||
</atom:Skeleton>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^ /template/ StackPanel#RootLayout">
|
||||
<Setter Property="Spacing" Value="{DynamicResource {x:Static atom:SharedTokenKey.SpacingXXS}}"/>
|
||||
|
||||
@@ -9,6 +9,36 @@
|
||||
</ControlTheme.BasedOn>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border Name="Frame"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<StackPanel Name="RootLayout">
|
||||
<ContentPresenter Name="HeaderPresenter"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
IsVisible="{TemplateBinding Header, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
<atom:Skeleton IsLoading="{TemplateBinding IsLoading}" IsActive="True" IsShowParagraph="False">
|
||||
<StackPanel Orientation="Horizontal" Name="ContentLayout">
|
||||
<ContentPresenter Name="ValuePrefixAddOn"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding ValuePrefixAddOn}"
|
||||
ContentTemplate="{TemplateBinding ValuePrefixAddOnTemplate}"
|
||||
IsVisible="{TemplateBinding ValuePrefixAddOn, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
<ContentPresenter Name="ContentPresenter"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding RemainingTimeText}"/>
|
||||
<ContentPresenter Name="ValueSuffixAddOn"
|
||||
Foreground="{TemplateBinding ContentForeground}"
|
||||
FontSize="{TemplateBinding ContentFontSize}"
|
||||
Content="{TemplateBinding ValueSuffixAddOn}"
|
||||
ContentTemplate="{TemplateBinding ValueSuffixAddOnTemplate}"
|
||||
IsVisible="{TemplateBinding ValueSuffixAddOn, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
</StackPanel>
|
||||
</atom:Skeleton>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace AtomUI.Desktop.Controls;
|
||||
|
||||
@@ -6,14 +7,163 @@ public class TimerStatistic : AbstractStatistic
|
||||
{
|
||||
#region 公共属性定义
|
||||
|
||||
public static readonly StyledProperty<double> ValueProperty =
|
||||
AvaloniaProperty.Register<AbstractStatistic, double>(nameof(Value));
|
||||
public static readonly StyledProperty<DateTime> ValueProperty =
|
||||
AvaloniaProperty.Register<AbstractStatistic, DateTime>(nameof(Value));
|
||||
|
||||
public double Value
|
||||
public static readonly StyledProperty<string?> FormatProperty =
|
||||
AvaloniaProperty.Register<AbstractStatistic, string?>(nameof(Format));
|
||||
|
||||
public static readonly StyledProperty<TimeSpan> RefreshDurationProperty =
|
||||
AvaloniaProperty.Register<AbstractStatistic, TimeSpan>(nameof(RefreshDuration), TimeSpan.FromMilliseconds(10));
|
||||
|
||||
public DateTime Value
|
||||
{
|
||||
get => GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public string? Format
|
||||
{
|
||||
get => GetValue(FormatProperty);
|
||||
set => SetValue(FormatProperty, value);
|
||||
}
|
||||
|
||||
public TimeSpan RefreshDuration
|
||||
{
|
||||
get => GetValue(RefreshDurationProperty);
|
||||
set => SetValue(RefreshDurationProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public Func<TimeSpan, string>? Formatter;
|
||||
#endregion
|
||||
|
||||
#region 公共事件定义
|
||||
|
||||
public event EventHandler? CountdownFinished;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部属性定义
|
||||
|
||||
internal static readonly DirectProperty<TimerStatistic, TimeSpan> RemainingTimeProperty =
|
||||
AvaloniaProperty.RegisterDirect<TimerStatistic, TimeSpan>(nameof(RemainingTime),
|
||||
o => o.RemainingTime,
|
||||
(o, v) => o.RemainingTime = v);
|
||||
|
||||
internal static readonly DirectProperty<TimerStatistic, string?> RemainingTimeTextProperty =
|
||||
AvaloniaProperty.RegisterDirect<TimerStatistic, string?>(nameof(RemainingTimeText),
|
||||
o => o.RemainingTimeText,
|
||||
(o, v) => o.RemainingTimeText = v);
|
||||
|
||||
private TimeSpan _remainingTime;
|
||||
|
||||
internal TimeSpan RemainingTime
|
||||
{
|
||||
get => _remainingTime;
|
||||
set => SetAndRaise(RemainingTimeProperty, ref _remainingTime, value);
|
||||
}
|
||||
|
||||
private string? _remainingTimeText;
|
||||
|
||||
internal string? RemainingTimeText
|
||||
{
|
||||
get => _remainingTimeText;
|
||||
set => SetAndRaise(RemainingTimeTextProperty, ref _remainingTimeText, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private DispatcherTimer? _timer;
|
||||
|
||||
static TimerStatistic()
|
||||
{
|
||||
AffectsMeasure<TimerStatistic>(ValueProperty, FormatProperty, RemainingTimeTextProperty);
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
_timer?.Start();
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_timer?.Stop();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == RemainingTimeProperty ||
|
||||
change.Property == FormatProperty)
|
||||
{
|
||||
GenerateRemainingTimeText();
|
||||
}
|
||||
|
||||
if (change.Property == ValueProperty ||
|
||||
change.Property == RefreshDurationProperty)
|
||||
{
|
||||
BuildTimer(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateRemainingTimeText()
|
||||
{
|
||||
string? formattedText = null;
|
||||
if (Formatter != null)
|
||||
{
|
||||
formattedText = Formatter(RemainingTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Format != null)
|
||||
{
|
||||
formattedText = RemainingTime.ToString(Format);
|
||||
}
|
||||
else if (Format == @"hh\:mm\:ss")
|
||||
{
|
||||
formattedText = $"{(int)RemainingTime.TotalHours:00}:{RemainingTime.Minutes:00}:{RemainingTime.Seconds:00}";
|
||||
}
|
||||
else if (Format == @"hh\:mm\:ss\.fff")
|
||||
{
|
||||
formattedText = $"{(int)RemainingTime.TotalHours:00}:{RemainingTime.Minutes:00}:{RemainingTime.Seconds:00}.{RemainingTime.Milliseconds:000}";
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedText = $"{(int)RemainingTime.TotalHours:00}:{RemainingTime.Minutes:00}:{RemainingTime.Seconds:00}";
|
||||
}
|
||||
}
|
||||
SetCurrentValue(RemainingTimeTextProperty, formattedText);
|
||||
}
|
||||
|
||||
private void BuildTimer(bool start)
|
||||
{
|
||||
_timer = new DispatcherTimer();
|
||||
_timer.Interval = RefreshDuration;
|
||||
_timer.Tick += HandleTickElapsed;
|
||||
if (start)
|
||||
{
|
||||
_timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTickElapsed(object? sender, EventArgs args)
|
||||
{
|
||||
if (Value > DateTime.Now)
|
||||
{
|
||||
RemainingTime = Value - DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemainingTime = DateTime.Now - Value;
|
||||
}
|
||||
if (RemainingTime <= TimeSpan.Zero)
|
||||
{
|
||||
RemainingTime = TimeSpan.Zero;
|
||||
_timer?.Stop();
|
||||
CountdownFinished?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user