diff --git a/readme.md b/readme.md index 9f16405726..3f4840fce2 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,7 @@ A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android. -![](docs/images/screen.png) +[![](docs/images/screen.png)](https://youtu.be/wHcB3sGLVYg) Desktop platforms: @@ -36,7 +36,7 @@ Try out the ControlCatalog to give it a quick demo. Avalonia is a multi-platform windowing toolkit - somewhat like WPF - that is intended to be multi- platform. It supports XAML, lookless controls and a flexible styling system, and runs on Windows -using Direct2D and other operating systems using Gtk & Cairo. +using Direct2D and other operating systems using Skia and OS-specific windowing backend (GTK, Cocoa, etc). ## Current Status diff --git a/samples/ControlCatalog/Assets/test_icon.ico b/samples/ControlCatalog/Assets/test_icon.ico index 9d1074ad13..da8d49ff9b 100644 Binary files a/samples/ControlCatalog/Assets/test_icon.ico and b/samples/ControlCatalog/Assets/test_icon.ico differ diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 340905a7f7..11ff531514 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -69,6 +69,9 @@ Designer + + Designer + Designer @@ -131,6 +134,9 @@ MenuPage.xaml + + ProgressBarPage.xaml + RadioButtonPage.xaml diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 6c432a90ac..0940316ce9 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -16,6 +16,7 @@ + diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml new file mode 100644 index 0000000000..bf40e68630 --- /dev/null +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml @@ -0,0 +1,24 @@ + + + ProgressBar + A progress bar control + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs b/samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs new file mode 100644 index 0000000000..56792b3908 --- /dev/null +++ b/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); + } + } +} diff --git a/src/Avalonia.Controls/Orientation.cs b/src/Avalonia.Controls/Orientation.cs new file mode 100644 index 0000000000..fe998c024a --- /dev/null +++ b/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 +{ + /// + /// Defines vertical or horizontal orientation. + /// + public enum Orientation + { + /// + /// Horizontal orientation. + /// + Horizontal, + + /// + /// Vertical orientation. + /// + Vertical, + } +} diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index 0f69e4e5bc..009e1d0ab8 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -30,7 +30,7 @@ namespace Avalonia.Controls.Primitives /// Defines the property. /// public static readonly StyledProperty OrientationProperty = - AvaloniaProperty.Register(nameof(Orientation)); + AvaloniaProperty.Register(nameof(Orientation), Orientation.Vertical); private Button _lineUpButton; private Button _lineDownButton; diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index a31a27ddfe..0ff3a78c1f 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/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 /// public class ProgressBar : RangeBase { + public static readonly StyledProperty IsIndeterminateProperty = + AvaloniaProperty.Register(nameof(IsIndeterminate)); + + public static readonly StyledProperty OrientationProperty = + AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal); + private Border _indicator; + private IndeterminateAnimation _indeterminateAnimation; static ProgressBar() { ValueProperty.Changed.AddClassHandler(x => x.ValueChanged); + + HorizontalAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Left); + VerticalAlignmentProperty.OverrideDefaultValue(VerticalAlignment.Top); + + IsIndeterminateProperty.Changed.AddClassHandler( + (p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); }); + OrientationProperty.Changed.AddClassHandler( + (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); } /// @@ -29,21 +60,123 @@ namespace Avalonia.Controls protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { _indicator = e.NameScope.Get("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; + private IDisposable _indeterminateBindSubscription; + private TimeSpan _startTime; + private bool _disposed; + + public bool Disposed => _disposed; + + private IndeterminateAnimation(ProgressBar progressBar) + { + _progressBar = new WeakReference(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; + } + } } } diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 26a755e5f1..0e12fb3283 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -6,22 +6,6 @@ using Avalonia.Input; namespace Avalonia.Controls { - /// - /// Defines vertical or horizontal orientation. - /// - public enum Orientation - { - /// - /// Vertical orientation. - /// - Vertical, - - /// - /// Horizontal orientation. - /// - Horizontal, - } - /// /// A panel which lays out its children horizontally or vertically. /// @@ -37,7 +21,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty OrientationProperty = - AvaloniaProperty.Register(nameof(Orientation)); + AvaloniaProperty.Register(nameof(Orientation), Orientation.Vertical); /// /// Initializes static members of the class. diff --git a/src/Avalonia.Themes.Default/ProgressBar.xaml b/src/Avalonia.Themes.Default/ProgressBar.xaml index 4acff26537..82f385e16b 100644 --- a/src/Avalonia.Themes.Default/ProgressBar.xaml +++ b/src/Avalonia.Themes.Default/ProgressBar.xaml @@ -1,8 +1,6 @@