diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 870345e543..c0936356d2 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -513,6 +513,7 @@ private:
bool _fullScreenActive;
SystemDecorations _decorations;
AvnWindowState _lastWindowState;
+ AvnWindowState _actualWindowState;
bool _inSetWindowState;
NSRect _preZoomSize;
bool _transitioningWindowState;
@@ -539,6 +540,7 @@ private:
_transitioningWindowState = false;
_inSetWindowState = false;
_lastWindowState = Normal;
+ _actualWindowState = Normal;
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
@@ -633,7 +635,7 @@ private:
void WindowStateChanged () override
{
- if(!_inSetWindowState && !_transitioningWindowState)
+ if(_shown && !_inSetWindowState && !_transitioningWindowState)
{
AvnWindowState state;
GetWindowState(&state);
@@ -963,14 +965,14 @@ private:
{
@autoreleasepool
{
- if(_lastWindowState == state)
+ if(_actualWindowState == state)
{
return S_OK;
}
_inSetWindowState = true;
- auto currentState = _lastWindowState;
+ auto currentState = _actualWindowState;
_lastWindowState = state;
if(currentState == Normal)
@@ -1049,8 +1051,11 @@ private:
}
break;
}
+
+ _actualWindowState = _lastWindowState;
}
+
_inSetWindowState = false;
return S_OK;
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index 723351ae57..2446c0e1c9 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -17,7 +17,10 @@ namespace ControlCatalog
public MainWindow()
{
this.InitializeComponent();
- this.AttachDevTools();
+ this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
+ {
+ StartupScreenIndex = 1,
+ });
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;
diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index aa165d13f7..f37df56b73 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -36,6 +36,9 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml
new file mode 100644
index 0000000000..b386636cae
--- /dev/null
+++ b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs
new file mode 100644
index 0000000000..eed8ee29ce
--- /dev/null
+++ b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs
@@ -0,0 +1,27 @@
+using System.Reactive.Linq;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using RenderDemo.ViewModels;
+
+namespace RenderDemo.Pages
+{
+ public class CustomAnimatorPage : UserControl
+ {
+ public CustomAnimatorPage()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/RenderDemo/Pages/CustomStringAnimator.cs b/samples/RenderDemo/Pages/CustomStringAnimator.cs
new file mode 100644
index 0000000000..851a2d0187
--- /dev/null
+++ b/samples/RenderDemo/Pages/CustomStringAnimator.cs
@@ -0,0 +1,16 @@
+using Avalonia.Animation.Animators;
+
+namespace RenderDemo.Pages
+{
+ public class CustomStringAnimator : Animator
+ {
+ public override string Interpolate(double progress, string oldValue, string newValue)
+ {
+ if (newValue.Length == 0) return "";
+ var step = 1.0 / newValue.Length;
+ var length = (int)(progress / step);
+ var result = newValue.Substring(0, length + 1);
+ return result;
+ }
+ }
+}
diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml b/samples/RenderDemo/Pages/TransitionsPage.xaml
index f9f69fb341..1985074b0f 100644
--- a/samples/RenderDemo/Pages/TransitionsPage.xaml
+++ b/samples/RenderDemo/Pages/TransitionsPage.xaml
@@ -141,6 +141,39 @@
+
+
+
+
+
+
+
+
@@ -166,6 +199,9 @@
+
+
+
diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs
index c42153ec4f..daa4793ef0 100644
--- a/src/Avalonia.Animation/Animation.cs
+++ b/src/Avalonia.Animation/Animation.cs
@@ -194,6 +194,33 @@ namespace Avalonia.Animation
[Content]
public KeyFrames Children { get; } = new KeyFrames();
+ // Store values for the Animator attached properties for IAnimationSetter objects.
+ private static readonly Dictionary s_animators = new Dictionary();
+
+ ///
+ /// Gets the value of the Animator attached property for a setter.
+ ///
+ /// The animation setter.
+ /// The property animator type.
+ public static Type GetAnimator(IAnimationSetter setter)
+ {
+ if (s_animators.TryGetValue(setter, out var type))
+ {
+ return type;
+ }
+ return null;
+ }
+
+ ///
+ /// Sets the value of the Animator attached property for a setter.
+ ///
+ /// The animation setter.
+ /// The property animator value.
+ public static void SetAnimator(IAnimationSetter setter, Type value)
+ {
+ s_animators[setter] = value;
+ }
+
private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)>
{
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
@@ -248,7 +275,7 @@ namespace Avalonia.Animation
{
foreach (var setter in keyframe.Setters)
{
- var handler = GetAnimatorType(setter.Property);
+ var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property);
if (handler == null)
{
diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs
index 6f601a3e13..0e1882ce75 100644
--- a/src/Avalonia.Animation/AnimationInstance`1.cs
+++ b/src/Avalonia.Animation/AnimationInstance`1.cs
@@ -5,6 +5,7 @@ using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Data;
using Avalonia.Reactive;
+using JetBrains.Annotations;
namespace Avalonia.Animation
{
@@ -45,8 +46,9 @@ namespace Avalonia.Animation
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
- _neutralValue = (T)_targetControl.GetValue(_animator.Property);
+ control.PropertyChanged += ControlPropertyChanged;
+ UpdateNeutralValue();
FetchProperties();
}
@@ -216,5 +218,22 @@ namespace Avalonia.Animation
}
}
}
+
+ private void UpdateNeutralValue()
+ {
+ var property = _animator.Property;
+ var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue);
+
+ _neutralValue = baseValue != AvaloniaProperty.UnsetValue ?
+ (T)baseValue : (T)_targetControl.GetValue(property);
+ }
+
+ private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation)
+ {
+ UpdateNeutralValue();
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index 5506ce05d6..ff63e5644f 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -361,9 +361,9 @@ namespace Avalonia.Controls.Presenters
RenderInternal(context);
- if (selectionStart == selectionEnd)
+ if (selectionStart == selectionEnd && _caretBlink)
{
- var caretBrush = CaretBrush;
+ var caretBrush = CaretBrush?.ToImmutable();
if (caretBrush is null)
{
@@ -382,13 +382,10 @@ namespace Avalonia.Controls.Presenters
}
}
- if (_caretBlink)
- {
- var (p1, p2) = GetCaretPoints();
- context.DrawLine(
- new ImmutablePen(caretBrush, 1),
- p1, p2);
- }
+ var (p1, p2) = GetCaretPoints();
+ context.DrawLine(
+ new ImmutablePen(caretBrush, 1),
+ p1, p2);
}
}
diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index fabb38f87d..234960e87c 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -70,7 +70,7 @@ namespace Avalonia.Controls.Remote
public override void Render(DrawingContext context)
{
- if (_lastFrame != null)
+ if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
{
var fmt = (PixelFormat) _lastFrame.Format;
if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width ||
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
index 0e36c8f9cb..2a386f106e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
@@ -10,7 +10,8 @@ namespace Avalonia.Diagnostics
{
public static class DevTools
{
- private static readonly Dictionary s_open = new Dictionary();
+ private static readonly Dictionary s_open =
+ new Dictionary();
public static IDisposable Attach(TopLevel root, KeyGesture gesture)
{
@@ -52,6 +53,7 @@ namespace Avalonia.Diagnostics
Width = options.Size.Width,
Height = options.Size.Height,
};
+ window.SetOptions(options);
window.Closed += DevToolsClosed;
s_open.Add(root, window);
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
index f9978f3b7e..5336dca65b 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
@@ -22,5 +22,10 @@ namespace Avalonia.Diagnostics
/// Gets or sets the initial size of the DevTools window. The default value is 1280x720.
///
public Size Size { get; set; } = new Size(1280, 720);
+
+ ///
+ /// Get or set the startup screen index where the DevTools window will be displayed.
+ ///
+ public int? StartupScreenIndex { get; set; }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
index 63f68501a7..e4c4ca6115 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
@@ -26,9 +26,11 @@ namespace Avalonia.Diagnostics.ViewModels
public AvaloniaProperty Property { get; }
public override object Key => Property;
public override string Name { get; }
- public bool IsAttached => Property.IsAttached;
+ public override bool? IsAttached =>
+ Property.IsAttached;
- public string Priority => _priority;
+ public override string Priority =>
+ _priority;
public override string Type => _type;
@@ -69,7 +71,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (val != null)
{
RaiseAndSetIfChanged(ref _priority, val.Priority.ToString(), nameof(Priority));
- RaiseAndSetIfChanged(ref _group, IsAttached ? "Attached Properties" : "Properties", nameof(Group));
+ RaiseAndSetIfChanged(ref _group, IsAttached == true ? "Attached Properties" : "Properties", nameof(Group));
}
else
{
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
index 6b2dbb7bae..65626aeea5 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
@@ -49,6 +49,12 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
+ public override string Priority =>
+ string.Empty;
+
+ public override bool? IsAttached =>
+ default;
+
// [MemberNotNull(nameof(_type))]
public override void Update()
{
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
index 1f7205eb16..3790951b0c 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
private readonly IVisual _control;
private readonly IDictionary