70 changed files with 1907 additions and 3080 deletions
@ -0,0 +1,35 @@ |
|||||
|
<UserControl |
||||
|
xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="RenderDemo.Pages.SpringAnimationsPage" |
||||
|
MaxWidth="600"> |
||||
|
<UserControl.Resources> |
||||
|
<SpringEasing x:Key="SpringEasing" Mass="1" Stiffness="2000" Damping="20" InitialVelocity="0" /> |
||||
|
</UserControl.Resources> |
||||
|
<UserControl.Styles> |
||||
|
<Style Selector="Border.spring"> |
||||
|
<Style.Animations> |
||||
|
<Animation Duration="0:0:0.900" |
||||
|
IterationCount="Infinite" |
||||
|
PlaybackDirection="Normal" |
||||
|
Easing="{DynamicResource SpringEasing}"> |
||||
|
<KeyFrame Cue="0%" KeySpline=""> |
||||
|
<Setter Property="TranslateTransform.X" Value="-300"/> |
||||
|
</KeyFrame> |
||||
|
<KeyFrame Cue="100%"> |
||||
|
<Setter Property="TranslateTransform.X" Value="0" /> |
||||
|
</KeyFrame> |
||||
|
</Animation> |
||||
|
</Style.Animations> |
||||
|
</Style> |
||||
|
</UserControl.Styles> |
||||
|
<Grid> |
||||
|
<Border Classes="spring" Background="Red" Width="50" Height="50"> |
||||
|
<Border.RenderTransform> |
||||
|
<TransformGroup> |
||||
|
<TranslateTransform /> |
||||
|
</TransformGroup> |
||||
|
</Border.RenderTransform> |
||||
|
</Border> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,17 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace RenderDemo.Pages; |
||||
|
|
||||
|
public class SpringAnimationsPage : UserControl |
||||
|
{ |
||||
|
public SpringAnimationsPage() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
namespace Avalonia.Animation.Easings; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Eases a <see cref="double"/> value using a user-defined spring formula.
|
||||
|
/// </summary>
|
||||
|
public class SpringEasing : Easing |
||||
|
{ |
||||
|
private readonly Spring _internalSpring; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spring mass.
|
||||
|
/// </summary>
|
||||
|
public double Mass |
||||
|
{ |
||||
|
get => _internalSpring.Mass; |
||||
|
set |
||||
|
{ |
||||
|
_internalSpring.Mass = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spring stiffness.
|
||||
|
/// </summary>
|
||||
|
public double Stiffness |
||||
|
{ |
||||
|
get => _internalSpring.Stiffness; |
||||
|
set |
||||
|
{ |
||||
|
_internalSpring.Stiffness = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spring damping.
|
||||
|
/// </summary>
|
||||
|
public double Damping |
||||
|
{ |
||||
|
get => _internalSpring.Damping; |
||||
|
set |
||||
|
{ |
||||
|
_internalSpring.Damping = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spring initial velocity.
|
||||
|
/// </summary>
|
||||
|
public double InitialVelocity |
||||
|
{ |
||||
|
get => _internalSpring.InitialVelocity; |
||||
|
set |
||||
|
{ |
||||
|
_internalSpring.InitialVelocity = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public SpringEasing(double mass = 0d, double stiffness = 0d, double damping = 0d, double initialVelocity = 0d) |
||||
|
{ |
||||
|
_internalSpring = new Spring(); |
||||
|
|
||||
|
Mass = mass; |
||||
|
Stiffness = stiffness; |
||||
|
Damping = damping; |
||||
|
InitialVelocity = initialVelocity; |
||||
|
} |
||||
|
|
||||
|
public SpringEasing(Spring keySpline) |
||||
|
{ |
||||
|
_internalSpring = keySpline; |
||||
|
} |
||||
|
|
||||
|
public SpringEasing() |
||||
|
{ |
||||
|
_internalSpring = new Spring(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override double Ease(double progress) => _internalSpring.GetSpringProgress(progress); |
||||
|
} |
||||
@ -0,0 +1,143 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using System.Globalization; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Animation; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines how an animation is used based on spring formula.
|
||||
|
/// </summary>
|
||||
|
[TypeConverter(typeof(SpringTypeConverter))] |
||||
|
public class Spring |
||||
|
{ |
||||
|
private SpringSolver _springSolver; |
||||
|
private double _mass; |
||||
|
private double _stiffness; |
||||
|
private double _damping; |
||||
|
private double _initialVelocity; |
||||
|
private bool _isDirty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create a <see cref="Spring"/>.
|
||||
|
/// </summary>
|
||||
|
public Spring() |
||||
|
{ |
||||
|
_mass = 0.0; |
||||
|
_stiffness = 0.0; |
||||
|
_damping = 0.0; |
||||
|
_initialVelocity = 0.0; |
||||
|
_isDirty = true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create a <see cref="Spring"/> with the given parameters.
|
||||
|
/// </summary>
|
||||
|
/// <param name="mass">The spring mass.</param>
|
||||
|
/// <param name="stiffness">The spring stiffness.</param>
|
||||
|
/// <param name="damping">The spring damping.</param>
|
||||
|
/// <param name="initialVelocity">The spring initial velocity.</param>
|
||||
|
public Spring(double mass, double stiffness, double damping, double initialVelocity) |
||||
|
{ |
||||
|
_mass = mass; |
||||
|
_stiffness = stiffness; |
||||
|
_damping = damping; |
||||
|
_initialVelocity = initialVelocity; |
||||
|
_isDirty = true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Parse a <see cref="Spring"/> from a string. The string needs to contain 4 values in it.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">string with 4 values in it</param>
|
||||
|
/// <param name="culture">culture of the string</param>
|
||||
|
/// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
|
||||
|
/// <returns>A <see cref="Spring"/> with the appropriate values set</returns>
|
||||
|
public static Spring Parse(string value, CultureInfo? culture) |
||||
|
{ |
||||
|
if (culture is null) |
||||
|
{ |
||||
|
culture = CultureInfo.InvariantCulture; |
||||
|
} |
||||
|
|
||||
|
using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid Spring string: \"{value}\"."); |
||||
|
return new Spring(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spring mass.
|
||||
|
/// </summary>
|
||||
|
public double Mass |
||||
|
{ |
||||
|
get => _mass; |
||||
|
set |
||||
|
{ |
||||
|
_mass = value; |
||||
|
_isDirty = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spring stiffness.
|
||||
|
/// </summary>
|
||||
|
public double Stiffness |
||||
|
{ |
||||
|
get => _stiffness; |
||||
|
set |
||||
|
{ |
||||
|
_stiffness = value; |
||||
|
_isDirty = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spring damping.
|
||||
|
/// </summary>
|
||||
|
public double Damping |
||||
|
{ |
||||
|
get => _damping; |
||||
|
set |
||||
|
{ |
||||
|
_damping = value; |
||||
|
_isDirty = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The spring initial velocity.
|
||||
|
/// </summary>
|
||||
|
public double InitialVelocity |
||||
|
{ |
||||
|
get => _initialVelocity; |
||||
|
set |
||||
|
{ |
||||
|
_initialVelocity = value; |
||||
|
_isDirty = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates spring progress from a linear progress.
|
||||
|
/// </summary>
|
||||
|
/// <param name="linearProgress">the linear progress</param>
|
||||
|
/// <returns>The spring progress</returns>
|
||||
|
public double GetSpringProgress(double linearProgress) |
||||
|
{ |
||||
|
if (_isDirty) |
||||
|
{ |
||||
|
Build(); |
||||
|
} |
||||
|
|
||||
|
return _springSolver.Solve(linearProgress); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create cached spring solver.
|
||||
|
/// </summary>
|
||||
|
private void Build() |
||||
|
{ |
||||
|
_springSolver = new SpringSolver(_mass, _stiffness, _damping, _initialVelocity); |
||||
|
_isDirty = false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Avalonia.Animation; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts string values to <see cref="Spring"/> values.
|
||||
|
/// </summary>
|
||||
|
public class SpringTypeConverter : TypeConverter |
||||
|
{ |
||||
|
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) |
||||
|
{ |
||||
|
return sourceType == typeof(string); |
||||
|
} |
||||
|
|
||||
|
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) |
||||
|
{ |
||||
|
return Spring.Parse((string)value, CultureInfo.InvariantCulture); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Composition.Animations; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition; |
||||
|
|
||||
|
public partial class Compositor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Creates a new CompositionTarget
|
||||
|
/// </summary>
|
||||
|
/// <param name="renderTargetFactory">A factory method to create IRenderTarget to be called from the render thread</param>
|
||||
|
/// <returns></returns>
|
||||
|
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory) |
||||
|
{ |
||||
|
return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory)); |
||||
|
} |
||||
|
|
||||
|
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server)); |
||||
|
|
||||
|
public ExpressionAnimation CreateExpressionAnimation() => new ExpressionAnimation(this); |
||||
|
|
||||
|
public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this) |
||||
|
{ |
||||
|
Expression = expression |
||||
|
}; |
||||
|
|
||||
|
public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this); |
||||
|
|
||||
|
public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this); |
||||
|
} |
||||
@ -0,0 +1,121 @@ |
|||||
|
// Ported from:
|
||||
|
// https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/SpringSolver.h
|
||||
|
/* |
||||
|
* Copyright (C) 2016 Apple Inc. All rights reserved. |
||||
|
* |
||||
|
* Redistribution and use in source and binary forms, with or without |
||||
|
* modification, are permitted provided that the following conditions |
||||
|
* are met: |
||||
|
* 1. Redistributions of source code must retain the above copyright |
||||
|
* notice, this list of conditions and the following disclaimer. |
||||
|
* 2. Redistributions in binary form must reproduce the above copyright |
||||
|
* notice, this list of conditions and the following disclaimer in the |
||||
|
* documentation and/or other materials provided with the distribution. |
||||
|
* |
||||
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
||||
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
||||
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
||||
|
* THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Utilities; |
||||
|
|
||||
|
internal struct SpringSolver |
||||
|
{ |
||||
|
private double m_w0; |
||||
|
private double m_zeta; |
||||
|
private double m_wd; |
||||
|
private double m_A; |
||||
|
private double m_B; |
||||
|
|
||||
|
/// <summary>
|
||||
|
///
|
||||
|
/// </summary>
|
||||
|
/// <param name="period">The time period.</param>
|
||||
|
/// <param name="zeta">The damping ratio.</param>
|
||||
|
/// <param name="initialVelocity"></param>
|
||||
|
public SpringSolver(TimeSpan period, double zeta, double initialVelocity) |
||||
|
: this( |
||||
|
2 * Math.PI / period.TotalSeconds, |
||||
|
zeta, |
||||
|
initialVelocity) |
||||
|
{ |
||||
|
// T is time period [s]
|
||||
|
// T = (2*PI / sqrt(k)) * sqrt(m)
|
||||
|
|
||||
|
// ωn is natural frequency of the system [Hz] [1/s]
|
||||
|
// ωn = 2*PI / T
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
///
|
||||
|
/// </summary>
|
||||
|
/// <param name="m">The mass of the oscillating body.</param>
|
||||
|
/// <param name="k">The stiffness of the oscillated body (spring constant).</param>
|
||||
|
/// <param name="c">The actual damping.</param>
|
||||
|
/// <param name="initialVelocity">The initial velocity.</param>
|
||||
|
public SpringSolver(double m, double k, double c, double initialVelocity) |
||||
|
: this( |
||||
|
Math.Sqrt(k / m), // ωn
|
||||
|
c / (2 * Math.Sqrt(k * m)), // c / Cc
|
||||
|
initialVelocity) |
||||
|
{ |
||||
|
// ωn is natural frequency of the system [Hz] [1/s]
|
||||
|
// ωn = sqrt(k / m)
|
||||
|
|
||||
|
// Cc is critical damping coefficient
|
||||
|
// Cc = 2 * Sqrt(k * m)
|
||||
|
// Cc = 2 * m * wn
|
||||
|
// Cc = 2 * m * Sqrt(k / m)
|
||||
|
|
||||
|
// ζ is damping ratio (Greek letter zeta)
|
||||
|
// ζ = m_zeta = c / Cc
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
///
|
||||
|
/// </summary>
|
||||
|
/// <param name="ωn">The the natural frequency of the system [rad/s].</param>
|
||||
|
/// <param name="zeta">The damping ratio.</param>
|
||||
|
/// <param name="initialVelocity"></param>
|
||||
|
public SpringSolver(double ωn, double zeta, double initialVelocity) |
||||
|
{ |
||||
|
m_w0 = ωn; |
||||
|
m_zeta = zeta; |
||||
|
|
||||
|
if (m_zeta < 1) { |
||||
|
// Under-damped.
|
||||
|
m_wd = m_w0 * Math.Sqrt(1 - m_zeta * m_zeta); |
||||
|
m_A = 1; |
||||
|
m_B = (m_zeta * m_w0 + -initialVelocity) / m_wd; |
||||
|
} else { |
||||
|
// Critically damped (ignoring over-damped case for now).
|
||||
|
m_A = 1; |
||||
|
m_B = -initialVelocity + m_w0; |
||||
|
m_wd = 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public readonly double Solve(double t) |
||||
|
{ |
||||
|
if (m_zeta < 1) { |
||||
|
// Under-damped
|
||||
|
t = Math.Exp(-t * m_zeta * m_w0) * (m_A * Math.Cos(m_wd * t) + m_B * Math.Sin(m_wd * t)); |
||||
|
} else { |
||||
|
// Critically damped (ignoring over-damped case for now).
|
||||
|
t = (m_A + m_B * t) * Math.Exp(-t * m_w0); |
||||
|
} |
||||
|
|
||||
|
// Map range from [1..0] to [0..1].
|
||||
|
return 1 - t; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
// This file is imported from dotnet/runtime
|
||||
|
// Source Link: https://github.com/dotnet/runtime/blob/e63d21947e734db2da5093510a6636b5b7fb45b5/src/libraries/Common/src/System/Text/StringBuilderCache.cs
|
||||
|
// Commit: a9c5ead on Feb 10, 2021, https://github.com/dotnet/runtime/commit/a9c5eadd951dcba73167f72cc624eb790573663a
|
||||
|
//
|
||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Avalonia.Utilities; |
||||
|
|
||||
|
// <summary>Provide a cached reusable instance of stringbuilder per thread.</summary>
|
||||
|
internal static class StringBuilderCache |
||||
|
{ |
||||
|
// The value 360 was chosen in discussion with performance experts as a compromise between using
|
||||
|
// as little memory per thread as possible and still covering a large part of short-lived
|
||||
|
// StringBuilder creations on the startup path of VS designers.
|
||||
|
internal const int MaxBuilderSize = 360; |
||||
|
private const int DefaultCapacity = 16; // == StringBuilder.DefaultCapacity
|
||||
|
|
||||
|
// WARNING: We allow diagnostic tools to directly inspect this member (t_cachedInstance).
|
||||
|
// See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details.
|
||||
|
// Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools.
|
||||
|
// Get in touch with the diagnostics team if you have questions.
|
||||
|
[ThreadStatic] |
||||
|
private static StringBuilder? t_cachedInstance; |
||||
|
|
||||
|
/// <summary>Get a StringBuilder for the specified capacity.</summary>
|
||||
|
/// <remarks>If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.</remarks>
|
||||
|
public static StringBuilder Acquire(int capacity = DefaultCapacity) |
||||
|
{ |
||||
|
if (capacity <= MaxBuilderSize) |
||||
|
{ |
||||
|
StringBuilder? sb = t_cachedInstance; |
||||
|
if (sb != null) |
||||
|
{ |
||||
|
// Avoid stringbuilder block fragmentation by getting a new StringBuilder
|
||||
|
// when the requested size is larger than the current capacity
|
||||
|
if (capacity <= sb.Capacity) |
||||
|
{ |
||||
|
t_cachedInstance = null; |
||||
|
sb.Clear(); |
||||
|
return sb; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new StringBuilder(capacity); |
||||
|
} |
||||
|
|
||||
|
/// <summary>Place the specified builder in the cache if it is not too big.</summary>
|
||||
|
public static void Release(StringBuilder sb) |
||||
|
{ |
||||
|
if (sb.Capacity <= MaxBuilderSize) |
||||
|
{ |
||||
|
t_cachedInstance = sb; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>ToString() the stringbuilder, Release it to the cache, and return the resulting string.</summary>
|
||||
|
public static string GetStringAndRelease(StringBuilder sb) |
||||
|
{ |
||||
|
string result = sb.ToString(); |
||||
|
Release(sb); |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
import { build } from 'esbuild'; |
||||
|
import htmlPlugin from '@chialab/esbuild-plugin-html'; |
||||
|
import { compress } from 'esbuild-plugin-compress'; |
||||
|
|
||||
|
build({ |
||||
|
entryPoints: ['src/index.html'], |
||||
|
chunkNames: '[name]-[hash]', |
||||
|
logLevel: "warning", |
||||
|
outdir: "./build", |
||||
|
bundle: true, |
||||
|
treeShaking: true, |
||||
|
minify: true, |
||||
|
write: false, |
||||
|
format: "esm", |
||||
|
target: "es6", |
||||
|
platform: "browser", |
||||
|
sourcemap: "linked", |
||||
|
loader: {".ts": "tsx"}, |
||||
|
plugins: [ |
||||
|
htmlPlugin(), |
||||
|
compress({ |
||||
|
brotli: false, |
||||
|
gzip: true |
||||
|
}) |
||||
|
] |
||||
|
}) |
||||
|
.then(() => console.log("⚡ Done")) |
||||
|
.catch(() => process.exit(1)); |
||||
File diff suppressed because it is too large
@ -1,14 +1,15 @@ |
|||||
<!DOCTYPE html> |
<!DOCTYPE html> |
||||
<html> |
<html> |
||||
|
|
||||
<head> |
<head> |
||||
<meta charset="UTF-8"> |
<meta charset="UTF-8"> |
||||
<title>Avalonia XAML previewer web edition</title> |
<title>Avalonia XAML previewer web edition</title> |
||||
</head> |
</head> |
||||
|
|
||||
<body> |
<body> |
||||
<div id="app"> |
<div id="app"> |
||||
<center>Loading...</center> |
<center>Loading...</center> |
||||
</div> |
</div> |
||||
<noscript>JavaScript is required</noscript> |
<noscript>JavaScript is required</noscript> |
||||
|
<script src="index.tsx"></script> |
||||
</body> |
</body> |
||||
</html> |
</html> |
||||
@ -1,116 +0,0 @@ |
|||||
const webpack = require('webpack'); |
|
||||
const path = require('path'); |
|
||||
const LiveReloadPlugin = require('webpack-livereload-plugin'); |
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin'); |
|
||||
const CompressionPlugin = require('compression-webpack-plugin'); |
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); |
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); |
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); |
|
||||
const prod = process.env.NODE_ENV == 'production'; |
|
||||
|
|
||||
class Printer { |
|
||||
apply(compiler) { |
|
||||
compiler.hooks.afterEmit.tap("Printer", ()=> console.log("Build completed at " + new Date().toString())); |
|
||||
compiler.hooks.watchRun.tap("Printer", ()=> console.log("Watch triggered at " + new Date().toString())); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const config = { |
|
||||
stats: { |
|
||||
errorDetails: true |
|
||||
}, |
|
||||
entry: { |
|
||||
bundle: './src/index.tsx' |
|
||||
}, |
|
||||
output: { |
|
||||
path: path.resolve(__dirname, 'build'), |
|
||||
publicPath: '/', |
|
||||
filename: '[name].[chunkhash].js' |
|
||||
}, |
|
||||
performance: { hints: false }, |
|
||||
mode: prod ? "production" : "development", |
|
||||
module: { |
|
||||
rules: [ |
|
||||
{ |
|
||||
enforce: "pre", |
|
||||
test: /\.js$/, |
|
||||
loader: "source-map-loader", |
|
||||
exclude: [ |
|
||||
path.resolve(__dirname, 'node_modules/mobx-state-router') |
|
||||
] |
|
||||
}, |
|
||||
{ |
|
||||
"oneOf": [ |
|
||||
{ |
|
||||
test: /\.(ts|tsx)$/, |
|
||||
exclude: /node_modules/, |
|
||||
use: 'ts-loader' |
|
||||
}, |
|
||||
{ |
|
||||
test: /\.css$/, |
|
||||
use: [ |
|
||||
MiniCssExtractPlugin.loader, |
|
||||
'css-loader' |
|
||||
] |
|
||||
}, |
|
||||
{ |
|
||||
test: /\.(jpg|png)$/, |
|
||||
use: { |
|
||||
loader: "url-loader", |
|
||||
options: { |
|
||||
limit: 25000, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, |
|
||||
use: [{ |
|
||||
loader: 'file-loader', |
|
||||
options: { |
|
||||
name: '[name].[ext]', |
|
||||
outputPath: 'fonts/', // where the fonts will go
|
|
||||
} |
|
||||
}] |
|
||||
}, |
|
||||
{ |
|
||||
loader: require.resolve('file-loader'), |
|
||||
exclude: [/\.(js|jsx|mjs|tsx|ts)$/, /\.html$/, /\.json$/], |
|
||||
options: { |
|
||||
name: 'assets/[name].[hash:8].[ext]', |
|
||||
}, |
|
||||
}] |
|
||||
}, |
|
||||
|
|
||||
] |
|
||||
}, |
|
||||
devtool: "source-map", |
|
||||
resolve: { |
|
||||
modules: [path.resolve(__dirname, 'node_modules')], |
|
||||
plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json", logLevel: 'info' })], |
|
||||
extensions: ['.ts', '.tsx', '.js', '.json'], |
|
||||
alias: { |
|
||||
'src': path.resolve(__dirname, 'src') |
|
||||
} |
|
||||
}, |
|
||||
plugins: |
|
||||
[ |
|
||||
new Printer(), |
|
||||
new CleanWebpackPlugin({ |
|
||||
cleanAfterEveryBuildPatterns: ['dist'] |
|
||||
}), |
|
||||
new MiniCssExtractPlugin({ |
|
||||
filename: "[name].[chunkhash]h" + |
|
||||
".css", |
|
||||
chunkFilename: "[id].[chunkhash].css" |
|
||||
}), |
|
||||
new LiveReloadPlugin({appendScriptTag: !prod}), |
|
||||
new HtmlWebpackPlugin({ |
|
||||
template: path.resolve(__dirname, './src/index.html'), |
|
||||
filename: 'index.html' //relative to root of the application
|
|
||||
}), |
|
||||
new CompressionPlugin({ |
|
||||
test: /(\?.*)?$/i |
|
||||
}) |
|
||||
] |
|
||||
}; |
|
||||
module.exports = config; |
|
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
|
||||
|
namespace Avalonia.LinuxFramebuffer.Input.NullInput; |
||||
|
|
||||
|
public class NullInputBackend : IInputBackend |
||||
|
{ |
||||
|
public void Initialize(IScreenInfoProvider screen, Action<RawInputEventArgs> onInput) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void SetInputRoot(IInputRoot root) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
require("esbuild").build({ |
||||
|
entryPoints: [ |
||||
|
"./modules/Avalonia.ts", |
||||
|
"./modules/Storage.ts" |
||||
|
], |
||||
|
outdir: "../wwwroot", |
||||
|
bundle: true, |
||||
|
minify: true, |
||||
|
format: "esm", |
||||
|
target: "es2016", |
||||
|
platform: "browser", |
||||
|
sourcemap: "linked", |
||||
|
loader: {".ts": "ts"} |
||||
|
}) |
||||
|
.then(() => console.log("⚡ Done")) |
||||
|
.catch(() => process.exit(1)); |
||||
@ -0,0 +1,5 @@ |
|||||
|
export { DpiWatcher } from "./Avalonia/DpiWatcher" |
||||
|
export { InputHelper } from "./Avalonia/InputHelper" |
||||
|
export { NativeControlHost } from "./Avalonia/NativeControlHost" |
||||
|
export { SizeWatcher } from "./Avalonia/SizeWatcher" |
||||
|
export { SKHtmlCanvas } from "./Avalonia/SKHtmlCanvas" |
||||
@ -1,5 +0,0 @@ |
|||||
export { DpiWatcher } from "./DpiWatcher" |
|
||||
export { InputHelper } from "./InputHelper" |
|
||||
export { NativeControlHost } from "./NativeControlHost" |
|
||||
export { SizeWatcher } from "./SizeWatcher" |
|
||||
export { SKHtmlCanvas } from "./SKHtmlCanvas" |
|
||||
@ -0,0 +1 @@ |
|||||
|
export { StorageProvider } from "./Storage/StorageProvider" |
||||
@ -1,16 +1,13 @@ |
|||||
{ |
{ |
||||
"name": "avalonia.web", |
"name": "avalonia.web", |
||||
"scripts": { |
"scripts": { |
||||
"dist": "cross-env NODE_ENV=production webpack", |
"prebuild": "tsc -noEmit", |
||||
"build": "cross-env NODE_ENV=development webpack" |
"build": "node build.js" |
||||
}, |
}, |
||||
"devDependencies": { |
"devDependencies": { |
||||
"@types/emscripten": "^1.39.6", |
"@types/emscripten": "^1.39.6", |
||||
"@types/wicg-file-system-access": "^2020.9.5", |
"@types/wicg-file-system-access": "^2020.9.5", |
||||
"cross-env": "^7.0.3", |
|
||||
"ts-loader": "^9.3.1", |
|
||||
"typescript": "^4.7.4", |
"typescript": "^4.7.4", |
||||
"webpack": "^5.73.0", |
"esbuild": "^0.15.7" |
||||
"webpack-cli": "^4.10.0" |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,40 +0,0 @@ |
|||||
const path = require('path'); |
|
||||
const prod = process.env.NODE_ENV == 'production'; |
|
||||
|
|
||||
module.exports = { |
|
||||
mode: prod ? "production" : "development", |
|
||||
devtool: 'source-map', |
|
||||
target: ["web", "es2020"], |
|
||||
entry: { |
|
||||
avalonia: './modules/Avalonia/Avalonia.ts', |
|
||||
avaloniaStorage: { |
|
||||
import: './modules/Storage/StorageProvider.ts', |
|
||||
dependOn: 'avalonia', |
|
||||
} |
|
||||
}, |
|
||||
output: { |
|
||||
filename: '[name].js', |
|
||||
path: path.resolve(__dirname, '../wwwroot'), |
|
||||
library: { |
|
||||
type: 'module', |
|
||||
}, |
|
||||
}, |
|
||||
module: { |
|
||||
rules: [ |
|
||||
{ |
|
||||
test: /\.tsx?$/, |
|
||||
use: 'ts-loader', |
|
||||
exclude: /node_modules/, |
|
||||
}, |
|
||||
], |
|
||||
}, |
|
||||
resolve: { |
|
||||
extensions: ['.ts', '.js'], |
|
||||
}, |
|
||||
optimization: { |
|
||||
minimize: false |
|
||||
}, |
|
||||
experiments: { |
|
||||
outputModule: true, |
|
||||
} |
|
||||
}; |
|
||||
@ -0,0 +1,118 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Animation; |
||||
|
using Avalonia.Animation.Easings; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Styling; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Base.UnitTests.Animation; |
||||
|
|
||||
|
public class SpringTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData("1,2 3,4")] |
||||
|
public void Can_Parse_Spring_Via_TypeConverter(string input) |
||||
|
{ |
||||
|
var conv = new SpringTypeConverter(); |
||||
|
|
||||
|
var spring = (Spring)conv.ConvertFrom(input); |
||||
|
|
||||
|
Assert.Equal(1, spring.Mass); |
||||
|
Assert.Equal(2, spring.Stiffness); |
||||
|
Assert.Equal(3, spring.Damping); |
||||
|
Assert.Equal(4, spring.InitialVelocity); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("1,2F,3,4")] |
||||
|
[InlineData("Foo,Bar,Fee,Buzz")] |
||||
|
public void Can_Handle_Invalid_String_Via_TypeConverter(string input) |
||||
|
{ |
||||
|
var conv = new SpringTypeConverter(); |
||||
|
|
||||
|
Assert.ThrowsAny<Exception>(() => (Spring)conv.ConvertFrom(input)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SplineEasing_Can_Be_Mutated() |
||||
|
{ |
||||
|
var easing = new SpringEasing(1, 1, 1, 0); |
||||
|
|
||||
|
Assert.Equal(0, easing.Ease(0)); |
||||
|
Assert.Equal(0.34029984660829826, easing.Ease(1)); |
||||
|
|
||||
|
easing.Mass = 2; |
||||
|
easing.Stiffness = 2; |
||||
|
easing.Damping = 2; |
||||
|
easing.InitialVelocity = 1; |
||||
|
|
||||
|
Assert.NotEqual(0.05136985716812037, easing.Ease(0.5)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Check_SpringEasing_Handled_properly() |
||||
|
{ |
||||
|
var keyframe1 = new KeyFrame() |
||||
|
{ |
||||
|
Setters = |
||||
|
{ |
||||
|
new Setter(RotateTransform.AngleProperty, -2.5d), |
||||
|
}, |
||||
|
KeyTime = TimeSpan.FromSeconds(0) |
||||
|
}; |
||||
|
|
||||
|
var keyframe2 = new KeyFrame() |
||||
|
{ |
||||
|
Setters = |
||||
|
{ |
||||
|
new Setter(RotateTransform.AngleProperty, 2.5d), |
||||
|
}, |
||||
|
KeyTime = TimeSpan.FromSeconds(5) |
||||
|
}; |
||||
|
|
||||
|
var animation = new Avalonia.Animation.Animation() |
||||
|
{ |
||||
|
Duration = TimeSpan.FromSeconds(5), |
||||
|
Children = |
||||
|
{ |
||||
|
keyframe1, |
||||
|
keyframe2 |
||||
|
}, |
||||
|
IterationCount = new IterationCount(5), |
||||
|
PlaybackDirection = PlaybackDirection.Alternate, |
||||
|
Easing = new SpringEasing(1, 10, 1, 0) |
||||
|
}; |
||||
|
|
||||
|
var rotateTransform = new RotateTransform(-2.5); |
||||
|
var rect = new Rectangle() |
||||
|
{ |
||||
|
RenderTransform = rotateTransform |
||||
|
}; |
||||
|
|
||||
|
var clock = new TestClock(); |
||||
|
var animationRun = animation.RunAsync(rect, clock); |
||||
|
|
||||
|
clock.Step(TimeSpan.Zero); |
||||
|
Assert.Equal(rotateTransform.Angle, -2.5); |
||||
|
clock.Step(TimeSpan.FromSeconds(5)); |
||||
|
Assert.Equal(rotateTransform.Angle, 5.522828945000075); |
||||
|
|
||||
|
var tolerance = 0.01; |
||||
|
clock.Step(TimeSpan.Parse("00:00:10.0153932")); |
||||
|
var expected = -2.499763294237805; |
||||
|
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); |
||||
|
|
||||
|
clock.Step(TimeSpan.Parse("00:00:11.2655407")); |
||||
|
expected = -1.1011448950348934; |
||||
|
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); |
||||
|
|
||||
|
clock.Step(TimeSpan.Parse("00:00:12.6158773")); |
||||
|
expected = 2.1264981706749007; |
||||
|
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); |
||||
|
|
||||
|
clock.Step(TimeSpan.Parse("00:00:14.6495256")); |
||||
|
expected = 5.4337608446234782; |
||||
|
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue