Browse Source

Merge pull request #1175 from jp2masa/progressbar

Added Orientation and IsIndeterminate properties to ProgressBar
Nikita Tsukanov 8 years ago
parent
commit
9d5a0be23e

+ 6 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -69,6 +69,9 @@
     <EmbeddedResource Include="Pages\MenuPage.xaml">
       <SubType>Designer</SubType>
     </EmbeddedResource>
+    <EmbeddedResource Include="Pages\ProgressBarPage.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
     <EmbeddedResource Include="Pages\RadioButtonPage.xaml">
       <SubType>Designer</SubType>
     </EmbeddedResource>
@@ -131,6 +134,9 @@
     <Compile Include="Pages\MenuPage.xaml.cs">
       <DependentUpon>MenuPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Pages\ProgressBarPage.xaml.cs">
+      <DependentUpon>ProgressBarPage.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Pages\RadioButtonPage.xaml.cs">
       <DependentUpon>RadioButtonPage.xaml</DependentUpon>
     </Compile>

+ 1 - 0
samples/ControlCatalog/MainView.xaml

@@ -16,6 +16,7 @@
     <TabItem Header="Image"><pages:ImagePage/></TabItem>
     <TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
     <TabItem Header="Menu"><pages:MenuPage/></TabItem>
+    <TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
     <TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
     <TabItem Header="Slider"><pages:SliderPage/></TabItem>
     <TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>

+ 24 - 0
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@@ -0,0 +1,24 @@
+<UserControl xmlns="https://github.com/avaloniaui">
+  <StackPanel Orientation="Vertical" Gap="4">
+    <TextBlock Classes="h1">ProgressBar</TextBlock>
+    <TextBlock Classes="h2">A progress bar control</TextBlock>
+
+    <StackPanel>
+      <StackPanel Orientation="Horizontal"
+                  Margin="0,16,0,0"
+                  HorizontalAlignment="Center"
+                  Gap="16">
+        <StackPanel Gap="16">
+          <ProgressBar Value="{Binding #hprogress.Value}" />
+          <ProgressBar IsIndeterminate="True"/>
+        </StackPanel>
+        <ProgressBar Value="{Binding #vprogress.Value}" Orientation="Vertical" />
+        <ProgressBar Orientation="Vertical" IsIndeterminate="True" />
+      </StackPanel>
+      <StackPanel Margin="16">
+        <Slider Name="hprogress" Maximum="100" Value="40"/>
+        <Slider Name="vprogress" Maximum="100" Value="60"/>
+      </StackPanel>
+    </StackPanel>
+  </StackPanel>
+</UserControl>

+ 18 - 0
samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs

@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public class ProgressBarPage : UserControl
+    {
+        public ProgressBarPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Controls/Orientation.cs

@@ -0,0 +1,21 @@
+// 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.
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines vertical or horizontal orientation.
+    /// </summary>
+    public enum Orientation
+    {
+        /// <summary>
+        /// Horizontal orientation.
+        /// </summary>
+        Horizontal,
+
+        /// <summary>
+        /// Vertical orientation.
+        /// </summary>
+        Vertical,
+    }
+}

+ 1 - 1
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -30,7 +30,7 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="Orientation"/> property.
         /// </summary>
         public static readonly StyledProperty<Orientation> OrientationProperty =
-            AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation));
+            AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation), Orientation.Vertical);
 
         private Button _lineUpButton;
         private Button _lineDownButton;

+ 136 - 3
src/Avalonia.Controls/ProgressBar.cs

@@ -1,8 +1,12 @@
 // 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.Controls.Templates;
+using Avalonia.Layout;
 
 namespace Avalonia.Controls
 {
@@ -11,11 +15,38 @@ namespace Avalonia.Controls
     /// </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()
         {
             ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
+
+            HorizontalAlignmentProperty.OverrideDefaultValue<ProgressBar>(HorizontalAlignment.Left);
+            VerticalAlignmentProperty.OverrideDefaultValue<ProgressBar>(VerticalAlignment.Top);
+
+            IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
+                (p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
+            OrientationProperty.Changed.AddClassHandler<ProgressBar>(
+                (p, e) => { if (p._indicator != null) p.UpdateOrientation((Orientation)e.NewValue); });
+        }
+
+        public bool IsIndeterminate
+        {
+            get => GetValue(IsIndeterminateProperty);
+            set => SetValue(IsIndeterminateProperty, value);
+        }
+
+        public Orientation Orientation
+        {
+            get => GetValue(OrientationProperty);
+            set => SetValue(OrientationProperty, value);
         }
 
         /// <inheritdoc/>
@@ -29,21 +60,123 @@ namespace Avalonia.Controls
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
             _indicator = e.NameScope.Get<Border>("PART_Indicator");
+
             UpdateIndicator(Bounds.Size);
+            UpdateOrientation(Orientation);
+            UpdateIsIndeterminate(IsIndeterminate);
         }
 
         private void UpdateIndicator(Size bounds)
         {
             if (_indicator != null)
             {
-                double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum);
-                _indicator.Width = bounds.Width * percent;
+                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 UpdateOrientation(Orientation orientation)
+        {
+            if (orientation == Orientation.Horizontal)
+            {
+                MinHeight = 14;
+                MinWidth = 200;
+
+                _indicator.HorizontalAlignment = HorizontalAlignment.Left;
+                _indicator.VerticalAlignment = VerticalAlignment.Stretch;
+            }
+            else
+            {
+                MinHeight = 200;
+                MinWidth = 14;
+
+                _indicator.HorizontalAlignment = HorizontalAlignment.Stretch;
+                _indicator.VerticalAlignment = VerticalAlignment.Bottom;
             }
         }
 
+        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;
+            }
+        }
     }
 }

+ 1 - 17
src/Avalonia.Controls/StackPanel.cs

@@ -6,22 +6,6 @@ using Avalonia.Input;
 
 namespace Avalonia.Controls
 {
-    /// <summary>
-    /// Defines vertical or horizontal orientation.
-    /// </summary>
-    public enum Orientation
-    {
-        /// <summary>
-        /// Vertical orientation.
-        /// </summary>
-        Vertical,
-
-        /// <summary>
-        /// Horizontal orientation.
-        /// </summary>
-        Horizontal,
-    }
-
     /// <summary>
     /// A panel which lays out its children horizontally or vertically.
     /// </summary>
@@ -37,7 +21,7 @@ namespace Avalonia.Controls
         /// Defines the <see cref="Orientation"/> property.
         /// </summary>
         public static readonly StyledProperty<Orientation> OrientationProperty =
-            AvaloniaProperty.Register<StackPanel, Orientation>(nameof(Orientation));
+            AvaloniaProperty.Register<StackPanel, Orientation>(nameof(Orientation), Orientation.Vertical);
 
         /// <summary>
         /// Initializes static members of the <see cref="StackPanel"/> class.

+ 1 - 4
src/Avalonia.Themes.Default/ProgressBar.xaml

@@ -1,8 +1,6 @@
 <Style xmlns="https://github.com/avaloniaui" Selector="ProgressBar">
   <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
   <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
-  <Setter Property="MinHeight" Value="14"/>
-  <Setter Property="MinWidth" Value="200"/>
   <Setter Property="Template">
     <ControlTemplate>
       <Border Background="{TemplateBinding Background}"
@@ -14,8 +12,7 @@
                   BorderBrush="{TemplateBinding Background}"/>
           <Border Name="PART_Indicator"
                   BorderThickness="1"
-                  Background="{TemplateBinding Foreground}"
-                  HorizontalAlignment="Left"/>
+                  Background="{TemplateBinding Foreground}"/>
         </Grid>
       </Border>
     </ControlTemplate>