Skip to content

Commit 791605e

Browse files
authored
Added AvaloniaUI platform (#22)
1 parent 92a5f3b commit 791605e

File tree

198 files changed

+8539
-222
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

198 files changed

+8539
-222
lines changed

SecureFolderFS.AvaloniaUI/.gitignore

Lines changed: 454 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Threading.Tasks;
2+
using Avalonia.Animation;
3+
using Avalonia.Collections;
4+
using SecureFolderFS.Shared.Extensions;
5+
6+
namespace SecureFolderFS.AvaloniaUI.Animations
7+
{
8+
internal class AnimationExtended : Animation
9+
{
10+
/// <summary>
11+
/// Gets or sets the animated control.
12+
/// </summary>
13+
public required Animatable Target { get; set; }
14+
15+
/// <summary>
16+
/// Gets or sets the setters of the first frame in the animation.
17+
/// </summary>
18+
/// <remarks>This property won't take effect if <see cref="AnimationExtended.Children"/> is not empty.</remarks>
19+
public AvaloniaList<IAnimationSetter> From { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets the setters of the last frame in the animation.
23+
/// </summary>
24+
/// <remarks>This property won't take effect if <see cref="AnimationExtended.Children"/> is not empty.</remarks>
25+
public AvaloniaList<IAnimationSetter> To { get; set; }
26+
27+
public AnimationExtended()
28+
{
29+
From = new();
30+
To = new();
31+
}
32+
33+
public Task RunAnimationAsync()
34+
{
35+
if (Children.IsEmpty() && (!From.IsEmpty() || !To.IsEmpty()))
36+
{
37+
if (!From.IsEmpty())
38+
{
39+
var from = new KeyFrame { Cue = new(0) };
40+
from.Setters.AddRange(From);
41+
Children.Add(from);
42+
}
43+
44+
if (!To.IsEmpty())
45+
{
46+
var to = new KeyFrame { Cue = new(1) };
47+
to.Setters.AddRange(To);
48+
Children.Add(to);
49+
}
50+
}
51+
52+
return RunAsync(Target, null);
53+
}
54+
}
55+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using Avalonia;
4+
using Avalonia.Collections;
5+
using Avalonia.Controls;
6+
using Avalonia.Interactivity;
7+
using Avalonia.Metadata;
8+
using SecureFolderFS.AvaloniaUI.Animations.Transitions;
9+
10+
namespace SecureFolderFS.AvaloniaUI.Animations
11+
{
12+
/// <summary>
13+
/// A collection of animations.
14+
/// </summary>
15+
internal sealed class Storyboard : Control
16+
{
17+
public Storyboard()
18+
{
19+
Animations = new();
20+
}
21+
22+
public Task RunAnimationsAsync()
23+
{
24+
return Task.WhenAll(Animations.Select(x => x.RunAnimationAsync()));
25+
}
26+
27+
// This method exists to make it possible to run the transition using Interactions
28+
public void RunAnimations(object? sender, RoutedEventArgs e)
29+
{
30+
RunAnimationsAsync();
31+
}
32+
33+
public static readonly StyledProperty<AvaloniaList<TransitionBase>> AnimationsProperty =
34+
AvaloniaProperty.Register<Storyboard, AvaloniaList<TransitionBase>>(nameof(Animations));
35+
36+
[Content]
37+
public AvaloniaList<TransitionBase> Animations
38+
{
39+
get => GetValue(AnimationsProperty);
40+
set => SetValue(AnimationsProperty, value);
41+
}
42+
43+
public static readonly AttachedProperty<AvaloniaList<Storyboard>> StoryboardsProperty =
44+
AvaloniaProperty.RegisterAttached<AnimationExtended, IAvaloniaObject, AvaloniaList<Storyboard>>("Storyboards");
45+
46+
public static void SetStoryboards(IAvaloniaObject obj, AvaloniaList<Storyboard> value)
47+
{
48+
obj.SetValue(StoryboardsProperty, value);
49+
}
50+
51+
public static AvaloniaList<Storyboard> GetStoryboards(IAvaloniaObject obj)
52+
{
53+
var value = (AvaloniaList<Storyboard>?)obj.GetValue(StoryboardsProperty);
54+
if (value is null)
55+
{
56+
value = new();
57+
obj.SetValue(StoryboardsProperty, value);
58+
}
59+
60+
return value;
61+
}
62+
}
63+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Avalonia;
4+
using Avalonia.Styling;
5+
using Avalonia.VisualTree;
6+
7+
namespace SecureFolderFS.AvaloniaUI.Animations.Transitions
8+
{
9+
internal sealed class FadeTransition : TransitionBase
10+
{
11+
public required AnimationMode Mode { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets whether to update visibility of the target before and after running the animation.
15+
/// </summary>
16+
public bool UpdateVisibility { get; set; }
17+
18+
/// <exception cref="ArgumentException">Target doesn't implement IVisual</exception>
19+
protected override async Task RunAnimationAsync(IVisual target)
20+
{
21+
var animation = GetBaseAnimation();
22+
animation.From = new(new Setter(Visual.OpacityProperty, Mode == AnimationMode.In ? 0d : 1d));
23+
animation.To = new(new Setter(Visual.OpacityProperty, Mode == AnimationMode.In ? 1d : 0d));
24+
25+
if (UpdateVisibility && Mode == AnimationMode.In)
26+
target.IsVisible = true;
27+
28+
await animation.RunAnimationAsync();
29+
30+
if (UpdateVisibility && Mode == AnimationMode.Out)
31+
target.IsVisible = false;
32+
}
33+
34+
public enum AnimationMode
35+
{
36+
In,
37+
Out
38+
}
39+
}
40+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Avalonia;
4+
using Avalonia.Animation;
5+
using Avalonia.Animation.Easings;
6+
using Avalonia.Controls;
7+
using Avalonia.Media;
8+
using Avalonia.VisualTree;
9+
10+
namespace SecureFolderFS.AvaloniaUI.Animations.Transitions
11+
{
12+
/// <remarks>This transition is not affected by duration.</remarks>
13+
internal sealed class InfoBarTransition : TransitionBase
14+
{
15+
public required Mode AnimationMode { get; set; }
16+
17+
protected override Task RunAnimationAsync(IVisual target)
18+
{
19+
if (ContentBelow is not IVisual contentBelow)
20+
throw new ArgumentException("ContentBelow must implement IVisual.");
21+
22+
TransitionBase infoBarOpacityTransition;
23+
TransitionBase infoBarTranslateTransition;
24+
TransitionBase contentBelowTranslateTransition;
25+
26+
if (AnimationMode == Mode.Show)
27+
{
28+
GetTransform<TranslateTransform>(contentBelow).Y = -target.Bounds.Height;
29+
infoBarOpacityTransition = new FadeTransition
30+
{
31+
Target = Target,
32+
Duration = TimeSpan.FromMilliseconds(600),
33+
Easing = new QuinticEaseOut(),
34+
Mode = FadeTransition.AnimationMode.In
35+
};
36+
infoBarTranslateTransition = new TranslateTransition
37+
{
38+
Target = Target,
39+
Duration = TimeSpan.FromMilliseconds(400),
40+
Easing = new QuinticEaseOut(),
41+
From = new(0, -target.Bounds.Height)
42+
};
43+
contentBelowTranslateTransition = new TranslateTransition
44+
{
45+
Target = ContentBelow,
46+
Duration = TimeSpan.FromMilliseconds(400),
47+
Easing = new QuinticEaseOut(),
48+
From = new(0, -target.Bounds.Height)
49+
};
50+
}
51+
else
52+
{
53+
infoBarOpacityTransition = new FadeTransition
54+
{
55+
Target = Target,
56+
Duration = TimeSpan.FromMilliseconds(AnimationMode == Mode.QuickHide ? 200 : 600),
57+
Easing = new QuinticEaseOut(),
58+
Mode = FadeTransition.AnimationMode.Out
59+
};
60+
infoBarTranslateTransition = new TranslateTransition
61+
{
62+
Target = Target,
63+
Duration = TimeSpan.FromMilliseconds(AnimationMode == Mode.QuickHide ? 200 : 400),
64+
Easing = new QuinticEaseOut(),
65+
To = new(0, -target.Bounds.Height)
66+
};
67+
contentBelowTranslateTransition = new TranslateTransition()
68+
{
69+
Target = ContentBelow,
70+
Duration = TimeSpan.FromMilliseconds(AnimationMode == Mode.QuickHide ? 200 : 400),
71+
Easing = new QuinticEaseOut(),
72+
To = new(0, -target.Bounds.Height)
73+
};
74+
}
75+
76+
return Task.WhenAll
77+
(
78+
infoBarOpacityTransition.RunAnimationAsync(),
79+
infoBarTranslateTransition.RunAnimationAsync(),
80+
contentBelowTranslateTransition.RunAnimationAsync()
81+
);
82+
}
83+
84+
public static readonly StyledProperty<Animatable> ContentBelowProperty =
85+
AvaloniaProperty.Register<InfoBarTransition, Animatable>(nameof(ContentBelow));
86+
87+
[ResolveByName]
88+
public Animatable ContentBelow
89+
{
90+
get => GetValue(ContentBelowProperty);
91+
set => SetValue(ContentBelowProperty, value);
92+
}
93+
94+
public enum Mode
95+
{
96+
Show,
97+
Hide,
98+
QuickHide
99+
}
100+
}
101+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace SecureFolderFS.AvaloniaUI.Animations.Transitions.NavigationTransitions
4+
{
5+
internal sealed class EntranceNavigationTransition : SlideNavigationTransition
6+
{
7+
[SetsRequiredMembers]
8+
public EntranceNavigationTransition()
9+
: base(Side.Bottom, BigOffset)
10+
{
11+
}
12+
}
13+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Threading.Tasks;
4+
using Avalonia.Animation;
5+
using Avalonia.Animation.Easings;
6+
using Avalonia.Collections;
7+
using Avalonia.Media;
8+
using Avalonia.Styling;
9+
using Avalonia.VisualTree;
10+
11+
namespace SecureFolderFS.AvaloniaUI.Animations.Transitions.NavigationTransitions
12+
{
13+
internal class SlideNavigationTransition : TransitionBase
14+
{
15+
public const double BigOffset = 200d;
16+
public const double SmallOffset = 100d;
17+
18+
/// <summary>
19+
/// Gets or sets the side to slide from.
20+
/// </summary>
21+
public required Side From { get; init; }
22+
23+
public required double Offset { get; init; }
24+
25+
[SetsRequiredMembers]
26+
public SlideNavigationTransition(Side from, double offset)
27+
{
28+
Duration = TimeSpan.Parse("0:0:0:0.3");
29+
Easing = new CubicEaseOut();
30+
From = from;
31+
Offset = offset;
32+
}
33+
34+
protected override Task RunAnimationAsync(IVisual target)
35+
{
36+
var translateTransform = GetTransform<TranslateTransform>(target);
37+
AvaloniaList<IAnimationSetter> fromSetters = null!;
38+
39+
switch (From)
40+
{
41+
case Side.Left:
42+
translateTransform.X = -Offset;
43+
fromSetters = new(new Setter(TranslateTransform.XProperty, -Offset));
44+
break;
45+
46+
case Side.Right:
47+
translateTransform.X = Offset;
48+
fromSetters = new(new Setter(TranslateTransform.XProperty, Offset));
49+
break;
50+
51+
case Side.Top:
52+
translateTransform.Y = -Offset;
53+
fromSetters = new(new Setter(TranslateTransform.YProperty, -Offset));
54+
break;
55+
56+
case Side.Bottom:
57+
translateTransform.Y = Offset;
58+
fromSetters = new(new Setter(TranslateTransform.YProperty, Offset));
59+
break;
60+
}
61+
62+
var animation = GetBaseAnimation();
63+
animation.From = fromSetters;
64+
animation.To = From switch
65+
{
66+
Side.Left or Side.Right => new(new Setter(TranslateTransform.XProperty, 0d)),
67+
Side.Top or Side.Bottom => new(new Setter(TranslateTransform.YProperty, 0d))
68+
};
69+
70+
return animation.RunAnimationAsync();
71+
}
72+
73+
public enum Side
74+
{
75+
Left,
76+
Right,
77+
Top,
78+
Bottom
79+
}
80+
}
81+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Threading.Tasks;
4+
using Avalonia.VisualTree;
5+
6+
namespace SecureFolderFS.AvaloniaUI.Animations.Transitions.NavigationTransitions
7+
{
8+
/// <summary>
9+
/// Specifies that animations are suppressed during navigation.
10+
/// </summary>
11+
internal sealed class SuppressNavigationTransition : TransitionBase
12+
{
13+
[SetsRequiredMembers]
14+
public SuppressNavigationTransition()
15+
{
16+
Duration = TimeSpan.Zero;
17+
}
18+
19+
protected override Task RunAnimationAsync(IVisual target)
20+
{
21+
return Task.CompletedTask;
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)