Skip to content

Commit 37fb5ff

Browse files
Merge branch 'master' into bugfix/timed-keyframe-animations
2 parents b57da01 + 842e2ff commit 37fb5ff

File tree

14 files changed

+568
-69
lines changed

14 files changed

+568
-69
lines changed

Microsoft.Toolkit.HighPerformance/Memory/ReadOnlySpan2D{T}.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -803,15 +803,15 @@ public ref T DangerousGetReferenceAt(int i, int j)
803803
/// </summary>
804804
/// <param name="row">The target row to map within the current instance.</param>
805805
/// <param name="column">The target column to map within the current instance.</param>
806-
/// <param name="width">The width to map within the current instance.</param>
807806
/// <param name="height">The height to map within the current instance.</param>
807+
/// <param name="width">The width to map within the current instance.</param>
808808
/// <exception cref="ArgumentException">
809809
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
810810
/// are negative or not within the bounds that are valid for the current instance.
811811
/// </exception>
812812
/// <returns>A new <see cref="ReadOnlySpan2D{T}"/> instance representing a slice of the current one.</returns>
813813
[Pure]
814-
public ReadOnlySpan2D<T> Slice(int row, int column, int width, int height)
814+
public ReadOnlySpan2D<T> Slice(int row, int column, int height, int width)
815815
{
816816
if ((uint)row >= Height)
817817
{
@@ -823,14 +823,14 @@ public ReadOnlySpan2D<T> Slice(int row, int column, int width, int height)
823823
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
824824
}
825825

826-
if ((uint)width > (this.width - column))
826+
if ((uint)height > (Height - row))
827827
{
828-
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
828+
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
829829
}
830830

831-
if ((uint)height > (Height - row))
831+
if ((uint)width > (this.width - column))
832832
{
833-
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
833+
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
834834
}
835835

836836
nint shift = ((nint)(uint)this.stride * (nint)(uint)row) + (nint)(uint)column;

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Mouse/MouseCursorPage.bind

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
Margin="50,100,20,20"
99
HorizontalAlignment="Left"
1010
VerticalAlignment="Top"
11-
ui:Mouse.Cursor="UniversalNo"
11+
ui:FrameworkElementExtensions.Cursor="UniversalNo"
1212
Background="DeepSkyBlue">
1313
<TextBlock Margin="4"
1414
HorizontalAlignment="Center"
@@ -21,7 +21,7 @@
2121
Margin="20"
2222
HorizontalAlignment="Left"
2323
VerticalAlignment="Top"
24-
ui:Mouse.Cursor="Wait"
24+
ui:FrameworkElementExtensions.Cursor="Wait"
2525
Background="Orange">
2626
<TextBlock Margin="4"
2727
HorizontalAlignment="Center"
@@ -37,7 +37,7 @@
3737
<Button Margin="20,290,20,20"
3838
HorizontalAlignment="Left"
3939
VerticalAlignment="Top"
40-
ui:Mouse.Cursor="Hand"
40+
ui:FrameworkElementExtensions.Cursor="Hand"
4141
Content="Button with Hand cursor, just like on web" />
4242
</Grid>
43-
</Page>
43+
</Page>

Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.cs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
#nullable enable
66

7+
using System;
78
using System.Collections.Generic;
89
using System.Diagnostics.Contracts;
10+
using System.Runtime.CompilerServices;
911
using System.Threading;
1012
using System.Threading.Tasks;
1113
using Windows.UI.Composition;
@@ -98,6 +100,152 @@ public void Start(UIElement element)
98100
}
99101
}
100102

103+
/// <summary>
104+
/// Starts the animations present in the current <see cref="AnimationBuilder"/> instance.
105+
/// </summary>
106+
/// <param name="element">The target <see cref="UIElement"/> to animate.</param>
107+
/// <param name="callback">The callback to invoke when the animation completes.</param>
108+
public void Start(UIElement element, Action callback)
109+
{
110+
// The point of this overload is to allow consumers to invoke a callback when an animation
111+
// completes, without having to create an async state machine. There are three different possible
112+
// scenarios to handle, and each can have a specialized code path to ensure the implementation
113+
// is as lean and efficient as possible. Specifically, for a given AnimationBuilder instance:
114+
// 1) There are only Composition animations
115+
// 2) There are only XAML animations
116+
// 3) There are both Composition and XAML animations
117+
// The implementation details of each of these paths is described below.
118+
if (this.compositionAnimationFactories.Count > 0)
119+
{
120+
if (this.xamlAnimationFactories.Count == 0)
121+
{
122+
// There are only Composition animations. In this case we can just use a Composition scoped batch,
123+
// capture the user-provided callback and invoke it directly when the batch completes. There is no
124+
// additional overhead here, since we would've had to create a closure regardless to be able to monitor
125+
// the completion of the animation (eg. to capture a TaskCompletionSource like we're doing below).
126+
static void Start(AnimationBuilder builder, UIElement element, Action callback)
127+
{
128+
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
129+
130+
Visual visual = ElementCompositionPreview.GetElementVisual(element);
131+
CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
132+
133+
batch.Completed += (_, _) => callback();
134+
135+
foreach (var factory in builder.compositionAnimationFactories)
136+
{
137+
var animation = factory.GetAnimation(visual, out var target);
138+
139+
if (target is null)
140+
{
141+
visual.StartAnimation(animation.Target, animation);
142+
}
143+
else
144+
{
145+
target.StartAnimation(animation.Target, animation);
146+
}
147+
}
148+
149+
batch.End();
150+
}
151+
152+
Start(this, element, callback);
153+
}
154+
else
155+
{
156+
// In this case we need to wait for both the Composition and XAML animation groups to complete. These two
157+
// groups use different APIs and can have a different duration, so we need to synchronize between them
158+
// without creating an async state machine (as that'd defeat the point of this separate overload).
159+
//
160+
// The code below relies on a mutable boxed counter that's shared across the two closures for the Completed
161+
// events for both the Composition scoped batch and the XAML Storyboard. The counter is initialized to 2, and
162+
// when each group completes, the counter is decremented (we don't need an interlocked decrement as the delegates
163+
// will already be invoked on the current DispatcherQueue instance, which acts as the synchronization context here.
164+
// The handlers for the Composition batch and the Storyboard will never execute concurrently). If the counter has
165+
// reached zero, it means that both groups have completed, so the user-provided callback is triggered, otherwise
166+
// the handler just does nothing. This ensures that the callback is executed exactly once when all the animation
167+
// complete, but without the need to create TaskCompletionSource-s and an async state machine to await for that.
168+
//
169+
// Note: we're using StrongBox<T> here because that exposes a mutable field of the type we need (int).
170+
// We can't just mutate a boxed int in-place with Unsafe.Unbox<T> as that's against the ECMA spec, since
171+
// that API uses the unbox IL opcode (§III.4.32) which returns a "controlled-mutability managed pointer"
172+
// (§III.1.8.1.2.2), which is not "verifier-assignable-to" (ie. directly assigning to it is not legal).
173+
static void Start(AnimationBuilder builder, UIElement element, Action callback)
174+
{
175+
StrongBox<int> counter = new(2);
176+
177+
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
178+
179+
Visual visual = ElementCompositionPreview.GetElementVisual(element);
180+
CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
181+
182+
batch.Completed += (_, _) =>
183+
{
184+
if (--counter.Value == 0)
185+
{
186+
callback();
187+
}
188+
};
189+
190+
foreach (var factory in builder.compositionAnimationFactories)
191+
{
192+
var animation = factory.GetAnimation(visual, out var target);
193+
194+
if (target is null)
195+
{
196+
visual.StartAnimation(animation.Target, animation);
197+
}
198+
else
199+
{
200+
target.StartAnimation(animation.Target, animation);
201+
}
202+
}
203+
204+
batch.End();
205+
206+
Storyboard storyboard = new();
207+
208+
foreach (var factory in builder.xamlAnimationFactories)
209+
{
210+
storyboard.Children.Add(factory.GetAnimation(element));
211+
}
212+
213+
storyboard.Completed += (_, _) =>
214+
{
215+
if (--counter.Value == 0)
216+
{
217+
callback();
218+
}
219+
};
220+
storyboard.Begin();
221+
}
222+
223+
Start(this, element, callback);
224+
}
225+
}
226+
else
227+
{
228+
// There are only XAML animations. This case is extremely similar to that where we only have Composition
229+
// animations, with the main difference being that the Completed event is directly exposed from the
230+
// Storyboard type, so we don't need a separate type to track the animation completion. The same
231+
// considerations regarding the closure to capture the provided callback apply here as well.
232+
static void Start(AnimationBuilder builder, UIElement element, Action callback)
233+
{
234+
Storyboard storyboard = new();
235+
236+
foreach (var factory in builder.xamlAnimationFactories)
237+
{
238+
storyboard.Children.Add(factory.GetAnimation(element));
239+
}
240+
241+
storyboard.Completed += (_, _) => callback();
242+
storyboard.Begin();
243+
}
244+
245+
Start(this, element, callback);
246+
}
247+
}
248+
101249
/// <summary>
102250
/// Starts the animations present in the current <see cref="AnimationBuilder"/> instance, and
103251
/// registers a given cancellation token to stop running animations before they complete.

Microsoft.Toolkit.Uwp.UI.Controls.Media/InfiniteCanvas/Commands/InfiniteCanvasCreateTextBoxCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal class InfiniteCanvasCreateTextBoxCommand : IInfiniteCanvasCommand
1212
private readonly List<IDrawable> _drawableList;
1313
private readonly TextDrawable _drawable;
1414

15-
public InfiniteCanvasCreateTextBoxCommand(List<IDrawable> drawableList, double x, double y, double width, double height, int textFontSize, string text, Color color, bool isBold, bool isItalic)
15+
public InfiniteCanvasCreateTextBoxCommand(List<IDrawable> drawableList, double x, double y, double width, double height, float textFontSize, string text, Color color, bool isBold, bool isItalic)
1616
{
1717
_drawable = new TextDrawable(
1818
x,

Microsoft.Toolkit.Uwp.UI.Controls.Media/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Commands.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ internal void ExecuteUpdateTextBoxFontSize(float newValue)
7979
ExecuteCommand(command);
8080
}
8181

82-
internal void ExecuteCreateTextBox(double x, double y, double width, double height, int textFontSize, string text, Color color, bool isBold, bool isItalic)
82+
internal void ExecuteCreateTextBox(double x, double y, double width, double height, float textFontSize, string text, Color color, bool isBold, bool isItalic)
8383
{
8484
var command = new InfiniteCanvasCreateTextBoxCommand(_drawableList, x, y, width, height, textFontSize, text, color, isBold, isItalic);
8585
ExecuteCommand(command);

Microsoft.Toolkit.Uwp.UI.Controls.Media/InfiniteCanvas/InfiniteCanvas.TextBox.cs

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,20 @@ public partial class InfiniteCanvas
2828
"Right",
2929
"Up",
3030
"Left",
31-
"Down"
31+
"Down",
32+
"Enter"
3233
};
3334

3435
private Point _lastInputPoint;
3536

3637
private TextDrawable SelectedTextDrawable => _drawingSurfaceRenderer.GetSelectedTextDrawable();
3738

38-
private int _lastValidTextFontSizeValue = DefaultFontValue;
39+
private float _textFontSize = DefaultFontValue;
3940

40-
private int TextFontSize
41+
private void SetFontSize(float newSize)
4142
{
42-
get
43-
{
44-
if (!string.IsNullOrWhiteSpace(_canvasTextBoxFontSizeTextBox.Text) &&
45-
Regex.IsMatch(_canvasTextBoxFontSizeTextBox.Text, "^[0-9]*$"))
46-
{
47-
var fontSize = int.Parse(_canvasTextBoxFontSizeTextBox.Text);
48-
_lastValidTextFontSizeValue = fontSize;
49-
}
50-
51-
return _lastValidTextFontSizeValue;
52-
}
43+
_textFontSize = newSize;
44+
_canvasTextBox.UpdateFontSize(newSize);
5345
}
5446

5547
private void InkScrollViewer_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
@@ -93,13 +85,34 @@ private void CanvasTextBoxItalicButton_Clicked(object sender, RoutedEventArgs e)
9385
}
9486
}
9587

96-
private void CanvasTextBoxFontSizeTextBox_TextChanged(object sender, TextChangedEventArgs e)
88+
private void CanvasComboBoxFontSizeTextBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
9789
{
98-
_canvasTextBox.UpdateFontSize(TextFontSize);
99-
if (SelectedTextDrawable != null)
90+
if (sender is ComboBox s
91+
&& s.SelectedItem is ComboBoxItem selectedItem
92+
&& selectedItem.Content is string selectedText
93+
&& float.TryParse(selectedText, out var sizeNumb))
10094
{
101-
_drawingSurfaceRenderer.ExecuteUpdateTextBoxFontSize(TextFontSize);
102-
ReDrawCanvas();
95+
SetFontSize(sizeNumb);
96+
97+
if (SelectedTextDrawable != null)
98+
{
99+
_drawingSurfaceRenderer.ExecuteUpdateTextBoxFontSize(sizeNumb);
100+
ReDrawCanvas();
101+
}
102+
}
103+
}
104+
105+
private void CanvasComboBoxFontSizeTextBox_TextSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
106+
{
107+
if (float.TryParse(args.Text, out var size))
108+
{
109+
SetFontSize(size);
110+
111+
if (SelectedTextDrawable != null)
112+
{
113+
_drawingSurfaceRenderer.ExecuteUpdateTextBoxFontSize(size);
114+
ReDrawCanvas();
115+
}
103116
}
104117
}
105118

@@ -147,20 +160,22 @@ private void CanvasTextBox_TextChanged(object sender, string text)
147160
ReDrawCanvas();
148161
return;
149162
}
163+
else
164+
{
165+
_drawingSurfaceRenderer.ExecuteCreateTextBox(
166+
_lastInputPoint.X,
167+
_lastInputPoint.Y,
168+
_canvasTextBox.GetEditZoneWidth(),
169+
_canvasTextBox.GetEditZoneHeight(),
170+
_textFontSize,
171+
text,
172+
_canvasTextBoxColorPicker.Color,
173+
_canvasTextBoxBoldButton.IsChecked ?? false,
174+
_canvasTextBoxItalicButton.IsChecked ?? false);
150175

151-
_drawingSurfaceRenderer.ExecuteCreateTextBox(
152-
_lastInputPoint.X,
153-
_lastInputPoint.Y,
154-
_canvasTextBox.GetEditZoneWidth(),
155-
_canvasTextBox.GetEditZoneHeight(),
156-
TextFontSize,
157-
text,
158-
_canvasTextBoxColorPicker.Color,
159-
_canvasTextBoxBoldButton.IsChecked ?? false,
160-
_canvasTextBoxItalicButton.IsChecked ?? false);
161-
162-
ReDrawCanvas();
163-
_drawingSurfaceRenderer.UpdateSelectedTextDrawable();
176+
ReDrawCanvas();
177+
_drawingSurfaceRenderer.UpdateSelectedTextDrawable();
178+
}
164179
}
165180

166181
private void InkScrollViewer_PointerPressed(object sender, PointerRoutedEventArgs e)
@@ -179,20 +194,17 @@ private void InkScrollViewer_PointerPressed(object sender, PointerRoutedEventArg
179194

180195
Canvas.SetLeft(_canvasTextBox, SelectedTextDrawable.Bounds.X);
181196
Canvas.SetTop(_canvasTextBox, SelectedTextDrawable.Bounds.Y);
182-
_canvasTextBox.UpdateFontSize(SelectedTextDrawable.FontSize);
183197
_canvasTextBox.UpdateFontStyle(SelectedTextDrawable.IsItalic);
184198
_canvasTextBox.UpdateFontWeight(SelectedTextDrawable.IsBold);
185199

186200
// Updating toolbar
187201
_canvasTextBoxColorPicker.Color = SelectedTextDrawable.TextColor;
188-
_canvasTextBoxFontSizeTextBox.Text = SelectedTextDrawable.FontSize.ToString();
189202
_canvasTextBoxBoldButton.IsChecked = SelectedTextDrawable.IsBold;
190203
_canvasTextBoxItalicButton.IsChecked = SelectedTextDrawable.IsItalic;
191204

192205
return;
193206
}
194207

195-
_canvasTextBox.UpdateFontSize(TextFontSize);
196208
_canvasTextBox.UpdateFontStyle(_canvasTextBoxItalicButton.IsChecked ?? false);
197209
_canvasTextBox.UpdateFontWeight(_canvasTextBoxBoldButton.IsChecked ?? false);
198210

@@ -210,7 +222,7 @@ private void ClearTextBoxValue()
210222
_canvasTextBox.Clear();
211223
}
212224

213-
private void CanvasTextBoxFontSizeTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
225+
private void CanvasComboBoxFontSizeTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
214226
{
215227
if (_allowedCommands.Contains(e.Key.ToString()))
216228
{

0 commit comments

Comments
 (0)