15 changed files with 595 additions and 57 deletions
@ -0,0 +1,18 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Data.Converters; |
||||
|
|
||||
|
namespace ControlCatalog.Converter; |
||||
|
|
||||
|
public class MathSubtractConverter : IValueConverter |
||||
|
{ |
||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
||||
|
{ |
||||
|
return (double)value - (double)parameter; |
||||
|
} |
||||
|
|
||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
||||
|
{ |
||||
|
throw new NotSupportedException(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
xmlns:vm="using:ControlCatalog.ViewModels" |
||||
|
xmlns:converter="clr-namespace:ControlCatalog.Converter" |
||||
|
xmlns:system="using:System" |
||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
|
x:DataType="vm:TransitioningContentControlPageViewModel" |
||||
|
x:CompileBindings="True" |
||||
|
x:Class="ControlCatalog.Pages.TransitioningContentControlPage"> |
||||
|
|
||||
|
<UserControl.DataContext> |
||||
|
<vm:TransitioningContentControlPageViewModel /> |
||||
|
</UserControl.DataContext> |
||||
|
|
||||
|
<UserControl.Styles> |
||||
|
<Style Selector="HeaderedContentControl"> |
||||
|
<Setter Property="Template"> |
||||
|
<Setter.Value> |
||||
|
<ControlTemplate> |
||||
|
<Grid> |
||||
|
<Grid.ColumnDefinitions> |
||||
|
<ColumnDefinition Width="Auto" SharedSizeGroup="HeaderCol" /> |
||||
|
<ColumnDefinition Width="*" /> |
||||
|
</Grid.ColumnDefinitions> |
||||
|
<ContentPresenter Content="{TemplateBinding Header}" |
||||
|
Grid.Column="0" |
||||
|
VerticalAlignment="Center" /> |
||||
|
<ContentPresenter Content="{TemplateBinding Content}" |
||||
|
Grid.Column="1" |
||||
|
VerticalAlignment="Center" /> |
||||
|
</Grid> |
||||
|
</ControlTemplate> |
||||
|
</Setter.Value> |
||||
|
</Setter> |
||||
|
</Style> |
||||
|
</UserControl.Styles> |
||||
|
|
||||
|
<UserControl.Resources> |
||||
|
<converter:MathSubtractConverter x:Key="MathSubtractConverter" /> |
||||
|
<system:Double x:Key="TopMargin">8</system:Double> |
||||
|
</UserControl.Resources> |
||||
|
|
||||
|
<DockPanel LastChildFill="True" |
||||
|
Height="{Binding Path=Bounds.Height, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}, Converter={StaticResource MathSubtractConverter},ConverterParameter={StaticResource TopMargin}}"> |
||||
|
|
||||
|
<TextBlock DockPanel.Dock="Top" Classes="h2">The TransitioningContentControl control allows you to show a page transition whenever the Content changes.</TextBlock> |
||||
|
|
||||
|
<ExperimentalAcrylicBorder DockPanel.Dock="Bottom" Margin="10" CornerRadius="5" > |
||||
|
<ExperimentalAcrylicBorder.Material> |
||||
|
<ExperimentalAcrylicMaterial BackgroundSource="Digger" TintColor="White" /> |
||||
|
</ExperimentalAcrylicBorder.Material> |
||||
|
|
||||
|
<StackPanel Margin="5" Spacing="5" Grid.IsSharedSizeScope="True"> |
||||
|
<HeaderedContentControl Header="Select a transition"> |
||||
|
<ComboBox Items="{Binding PageTransitions}" SelectedItem="{Binding SelectedTransition}" /> |
||||
|
</HeaderedContentControl> |
||||
|
<HeaderedContentControl Header="Duration"> |
||||
|
<NumericUpDown Value="{Binding Duration}" Increment="250" Minimum="100" /> |
||||
|
</HeaderedContentControl> |
||||
|
<HeaderedContentControl Header="Clip to Bounds"> |
||||
|
<ToggleSwitch IsChecked="{Binding ClipToBounds}" /> |
||||
|
</HeaderedContentControl> |
||||
|
</StackPanel> |
||||
|
</ExperimentalAcrylicBorder> |
||||
|
|
||||
|
<Button DockPanel.Dock="Left" Command="{Binding PrevImage}" Content="<" /> |
||||
|
<Button DockPanel.Dock="Right" Command="{Binding NextImage}" Content=">" /> |
||||
|
|
||||
|
<Border ClipToBounds="{Binding ClipToBounds}" Margin="5"> |
||||
|
<TransitioningContentControl Content="{Binding SelectedImage}" |
||||
|
PageTransition="{Binding SelectedTransition.Transition}" > |
||||
|
<TransitioningContentControl.ContentTemplate> |
||||
|
<DataTemplate DataType="Bitmap"> |
||||
|
<Image Source="{Binding}" /> |
||||
|
</DataTemplate> |
||||
|
</TransitioningContentControl.ContentTemplate> |
||||
|
</TransitioningContentControl> |
||||
|
</Border> |
||||
|
</DockPanel> |
||||
|
</UserControl> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages |
||||
|
{ |
||||
|
public partial class TransitioningContentControlPage : UserControl |
||||
|
{ |
||||
|
public TransitioningContentControlPage() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,309 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Animation; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Styling; |
||||
|
using Avalonia.VisualTree; |
||||
|
using MiniMvvm; |
||||
|
|
||||
|
namespace ControlCatalog.ViewModels |
||||
|
{ |
||||
|
public class TransitioningContentControlPageViewModel : ViewModelBase |
||||
|
{ |
||||
|
public TransitioningContentControlPageViewModel() |
||||
|
{ |
||||
|
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>(); |
||||
|
|
||||
|
var images = new string[] |
||||
|
{ |
||||
|
"delicate-arch-896885_640.jpg", |
||||
|
"hirsch-899118_640.jpg", |
||||
|
"maple-leaf-888807_640.jpg" |
||||
|
}; |
||||
|
|
||||
|
foreach (var image in images) |
||||
|
{ |
||||
|
var path = $"avares://ControlCatalog/Assets/{image}"; |
||||
|
Images.Add(new Bitmap(assetLoader.Open(new Uri(path)))); |
||||
|
} |
||||
|
|
||||
|
SetupTransitions(); |
||||
|
|
||||
|
SelectedTransition = PageTransitions[1]; |
||||
|
SelectedImage = Images[0]; |
||||
|
} |
||||
|
|
||||
|
public List<PageTransition> PageTransitions { get; } = new List<PageTransition>(); |
||||
|
|
||||
|
public List<Bitmap> Images { get; } = new List<Bitmap>(); |
||||
|
|
||||
|
|
||||
|
private Bitmap? _SelectedImage; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or Sets the selected image
|
||||
|
/// </summary>
|
||||
|
public Bitmap? SelectedImage |
||||
|
{ |
||||
|
get { return _SelectedImage; } |
||||
|
set { this.RaiseAndSetIfChanged(ref _SelectedImage, value); } |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private PageTransition _SelectedTransition; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the transition to play
|
||||
|
/// </summary>
|
||||
|
public PageTransition SelectedTransition |
||||
|
{ |
||||
|
get { return _SelectedTransition; } |
||||
|
set { this.RaiseAndSetIfChanged(ref _SelectedTransition, value); } |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
private bool _ClipToBounds; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets if the content should be clipped to bounds
|
||||
|
/// </summary>
|
||||
|
public bool ClipToBounds |
||||
|
{ |
||||
|
get { return _ClipToBounds; } |
||||
|
set { this.RaiseAndSetIfChanged(ref _ClipToBounds, value); } |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private int _Duration = 500; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or Sets the duration
|
||||
|
/// </summary>
|
||||
|
public int Duration |
||||
|
{ |
||||
|
get { return _Duration; } |
||||
|
set |
||||
|
{ |
||||
|
this.RaiseAndSetIfChanged(ref _Duration , value); |
||||
|
SetupTransitions(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SetupTransitions() |
||||
|
{ |
||||
|
if (PageTransitions.Count == 0) |
||||
|
{ |
||||
|
PageTransitions.AddRange(new[] |
||||
|
{ |
||||
|
new PageTransition("None"), |
||||
|
new PageTransition("CrossFade"), |
||||
|
new PageTransition("Slide horizontally"), |
||||
|
new PageTransition("Slide vertically"), |
||||
|
new PageTransition("Composite"), |
||||
|
new PageTransition("Custom") |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
PageTransitions[1].Transition = new CrossFade(TimeSpan.FromMilliseconds(Duration)); |
||||
|
PageTransitions[2].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Horizontal); |
||||
|
PageTransitions[3].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Vertical); |
||||
|
|
||||
|
var compositeTransition = new CompositePageTransition(); |
||||
|
compositeTransition.PageTransitions.Add(PageTransitions[1].Transition); |
||||
|
compositeTransition.PageTransitions.Add(PageTransitions[2].Transition); |
||||
|
compositeTransition.PageTransitions.Add(PageTransitions[3].Transition); |
||||
|
PageTransitions[4].Transition = compositeTransition; |
||||
|
|
||||
|
PageTransitions[5].Transition = new CustomTransition(TimeSpan.FromMilliseconds(Duration)); |
||||
|
} |
||||
|
|
||||
|
public void NextImage() |
||||
|
{ |
||||
|
var index = Images.IndexOf(SelectedImage) + 1; |
||||
|
|
||||
|
if (index >= Images.Count) |
||||
|
{ |
||||
|
index = 0; |
||||
|
} |
||||
|
|
||||
|
SelectedImage = Images[index]; |
||||
|
} |
||||
|
|
||||
|
public void PrevImage() |
||||
|
{ |
||||
|
var index = Images.IndexOf(SelectedImage) - 1; |
||||
|
|
||||
|
if (index < 0) |
||||
|
{ |
||||
|
index = Images.Count-1; |
||||
|
} |
||||
|
|
||||
|
SelectedImage = Images[index]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class PageTransition : ViewModelBase |
||||
|
{ |
||||
|
public PageTransition(string displayTitle) |
||||
|
{ |
||||
|
DisplayTitle = displayTitle; |
||||
|
} |
||||
|
|
||||
|
public string DisplayTitle { get; } |
||||
|
|
||||
|
|
||||
|
private IPageTransition _Transition; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the transition
|
||||
|
/// </summary>
|
||||
|
public IPageTransition Transition |
||||
|
{ |
||||
|
get { return _Transition; } |
||||
|
set { this.RaiseAndSetIfChanged(ref _Transition, value); } |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return DisplayTitle; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public class CustomTransition : IPageTransition |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CustomTransition"/> class.
|
||||
|
/// </summary>
|
||||
|
public CustomTransition() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CustomTransition"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="duration">The duration of the animation.</param>
|
||||
|
public CustomTransition(TimeSpan duration) |
||||
|
{ |
||||
|
Duration = duration; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the duration of the animation.
|
||||
|
/// </summary>
|
||||
|
public TimeSpan Duration { get; set; } |
||||
|
|
||||
|
public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
if (cancellationToken.IsCancellationRequested) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var tasks = new List<Task>(); |
||||
|
var parent = GetVisualParent(from, to); |
||||
|
var scaleProperty = ScaleTransform.ScaleYProperty; |
||||
|
|
||||
|
if (from != null) |
||||
|
{ |
||||
|
var animation = new Animation |
||||
|
{ |
||||
|
FillMode = FillMode.Forward, |
||||
|
Children = |
||||
|
{ |
||||
|
new KeyFrame |
||||
|
{ |
||||
|
Setters = { new Setter { Property = scaleProperty, Value = 1d } }, |
||||
|
Cue = new Cue(0d) |
||||
|
}, |
||||
|
new KeyFrame |
||||
|
{ |
||||
|
Setters = |
||||
|
{ |
||||
|
new Setter |
||||
|
{ |
||||
|
Property = scaleProperty, |
||||
|
Value = 0d |
||||
|
} |
||||
|
}, |
||||
|
Cue = new Cue(1d) |
||||
|
} |
||||
|
}, |
||||
|
Duration = Duration |
||||
|
}; |
||||
|
tasks.Add(animation.RunAsync(from, null, cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
if (to != null) |
||||
|
{ |
||||
|
to.IsVisible = true; |
||||
|
var animation = new Animation |
||||
|
{ |
||||
|
FillMode = FillMode.Forward, |
||||
|
Children = |
||||
|
{ |
||||
|
new KeyFrame |
||||
|
{ |
||||
|
Setters = |
||||
|
{ |
||||
|
new Setter |
||||
|
{ |
||||
|
Property = scaleProperty, |
||||
|
Value = 0d |
||||
|
} |
||||
|
}, |
||||
|
Cue = new Cue(0d) |
||||
|
}, |
||||
|
new KeyFrame |
||||
|
{ |
||||
|
Setters = { new Setter { Property = scaleProperty, Value = 1d } }, |
||||
|
Cue = new Cue(1d) |
||||
|
} |
||||
|
}, |
||||
|
Duration = Duration |
||||
|
}; |
||||
|
tasks.Add(animation.RunAsync(to, null, cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
await Task.WhenAll(tasks); |
||||
|
|
||||
|
if (from != null && !cancellationToken.IsCancellationRequested) |
||||
|
{ |
||||
|
from.IsVisible = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the common visual parent of the two control.
|
||||
|
/// </summary>
|
||||
|
/// <param name="from">The from control.</param>
|
||||
|
/// <param name="to">The to control.</param>
|
||||
|
/// <returns>The common parent.</returns>
|
||||
|
/// <exception cref="ArgumentException">
|
||||
|
/// The two controls do not share a common parent.
|
||||
|
/// </exception>
|
||||
|
/// <remarks>
|
||||
|
/// Any one of the parameters may be null, but not both.
|
||||
|
/// </remarks>
|
||||
|
private static IVisual GetVisualParent(IVisual? from, IVisual? to) |
||||
|
{ |
||||
|
var p1 = (from ?? to)!.VisualParent; |
||||
|
var p2 = (to ?? from)!.VisualParent; |
||||
|
|
||||
|
if (p1 != null && p2 != null && p1 != p2) |
||||
|
{ |
||||
|
throw new ArgumentException("Controls for PageSlide must have same parent."); |
||||
|
} |
||||
|
|
||||
|
return p1 ?? throw new InvalidOperationException("Cannot determine visual parent."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Markup.Xaml.MarkupExtensions; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Diagnostics.ViewModels |
||||
|
{ |
||||
|
internal class BindingSetterViewModel : SetterViewModel |
||||
|
{ |
||||
|
public BindingSetterViewModel(AvaloniaProperty property, object? value) : base(property, value) |
||||
|
{ |
||||
|
switch (value) |
||||
|
{ |
||||
|
case Binding binding: |
||||
|
Path = binding.Path; |
||||
|
Tint = Brushes.CornflowerBlue; |
||||
|
|
||||
|
break; |
||||
|
case CompiledBindingExtension binding: |
||||
|
Path = binding.Path.ToString(); |
||||
|
Tint = Brushes.DarkGreen; |
||||
|
|
||||
|
break; |
||||
|
case TemplateBinding binding: |
||||
|
if (binding.Property is AvaloniaProperty templateProperty) |
||||
|
{ |
||||
|
Path = $"{templateProperty.OwnerType.Name}.{templateProperty.Name}"; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Path = "Unassigned"; |
||||
|
} |
||||
|
|
||||
|
Tint = Brushes.OrangeRed; |
||||
|
|
||||
|
break; |
||||
|
default: |
||||
|
throw new ArgumentException("Invalid binding type", nameof(value)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IBrush Tint { get; } |
||||
|
|
||||
|
public string Path { get; } |
||||
|
|
||||
|
public override void CopyValue() |
||||
|
{ |
||||
|
CopyToClipboard(Path); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue