A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

161 lines
6.1 KiB

// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
using Avalonia.Animation;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
namespace Avalonia.Controls
{
/// <summary>
/// A control used to indicate the progress of an operation.
/// </summary>
public class ProgressBar : RangeBase
{
public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
private Border _indicator;
private IndeterminateAnimation _indeterminateAnimation;
static ProgressBar()
{
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
}
public bool IsIndeterminate
{
get => GetValue(IsIndeterminateProperty);
set => SetValue(IsIndeterminateProperty, value);
}
public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
UpdateIndicator(finalSize);
return base.ArrangeOverride(finalSize);
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
_indicator = e.NameScope.Get<Border>("PART_Indicator");
UpdateIndicator(Bounds.Size);
UpdateIsIndeterminate(IsIndeterminate);
}
private void UpdateIndicator(Size bounds)
{
if (_indicator != null)
{
if (IsIndeterminate)
{
if (Orientation == Orientation.Horizontal)
_indicator.Width = bounds.Width / 5.0;
else
_indicator.Height = bounds.Height / 5.0;
}
else
{
double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum);
if (Orientation == Orientation.Horizontal)
_indicator.Width = bounds.Width * percent;
else
_indicator.Height = bounds.Height * percent;
}
}
}
private void UpdateIsIndeterminate(bool isIndeterminate)
{
if (isIndeterminate)
{
if (_indeterminateAnimation == null || _indeterminateAnimation.Disposed)
_indeterminateAnimation = IndeterminateAnimation.StartAnimation(this);
}
else
_indeterminateAnimation?.Dispose();
}
private void ValueChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateIndicator(Bounds.Size);
}
private class IndeterminateAnimation : IDisposable
{
private WeakReference<ProgressBar> _progressBar;
private IDisposable _indeterminateBindSubscription;
private TimeSpan _startTime;
private bool _disposed;
public bool Disposed => _disposed;
private IndeterminateAnimation(ProgressBar progressBar)
{
_progressBar = new WeakReference<ProgressBar>(progressBar);
_startTime = Animate.Stopwatch.Elapsed;
_indeterminateBindSubscription = Animate.Timer.TakeWhile(x => (x - _startTime).TotalSeconds <= 4.0)
.Select(GetAnimationRect)
.Finally(() => _startTime = Animate.Stopwatch.Elapsed)
.Repeat()
.Subscribe(AnimationTick);
}
public static IndeterminateAnimation StartAnimation(ProgressBar progressBar)
{
return new IndeterminateAnimation(progressBar);
}
private Rect GetAnimationRect(TimeSpan time)
{
if (_progressBar.TryGetTarget(out var progressBar))
{
if (progressBar.Orientation == Orientation.Horizontal)
return new Rect(-progressBar._indicator.Width - 5 + (time - _startTime).TotalSeconds / 4.0 * (progressBar.Bounds.Width + progressBar._indicator.Width + 10), 0, progressBar._indicator.Bounds.Width, progressBar._indicator.Bounds.Height);
else
return new Rect(0, progressBar.Bounds.Height + 5 - (time - _startTime).TotalSeconds / 4.0 * (progressBar.Bounds.Height + progressBar._indicator.Height + 10), progressBar._indicator.Bounds.Width, progressBar._indicator.Bounds.Height);
}
else
{
_indeterminateBindSubscription.Dispose();
return Rect.Empty;
}
}
private void AnimationTick(Rect rect)
{
if (_progressBar.TryGetTarget(out var progressBar))
progressBar._indicator.Arrange(rect);
else
_indeterminateBindSubscription.Dispose();
}
public void Dispose()
{
_indeterminateBindSubscription?.Dispose();
_disposed = true;
}
}
}
}