Browse Source

Merge pull request #9625 from AvaloniaUI/fixes/9561-theme-reattach

Fix Theme Re-Attach and Animations in Styles Without Activators
pull/9464/head
Max Katz 4 years ago
committed by GitHub
parent
commit
2a5c1095cf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 67
      src/Avalonia.Base/StyledElement.cs
  2. 3
      src/Avalonia.Base/Styling/StyleInstance.cs
  3. 48
      tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs
  4. 51
      tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs

67
src/Avalonia.Base/StyledElement.cs

@ -81,6 +81,7 @@ namespace Avalonia
private Styles? _styles;
private bool _stylesApplied;
private bool _themeApplied;
private bool _templatedParentThemeApplied;
private AvaloniaObject? _templatedParent;
private bool _dataContextUpdating;
private ControlTheme? _implicitTheme;
@ -375,6 +376,12 @@ namespace Avalonia
_themeApplied = true;
}
if (!_templatedParentThemeApplied)
{
ApplyTemplatedParentControlTheme();
_templatedParentThemeApplied = true;
}
if (!_stylesApplied)
{
ApplyStyles(this);
@ -613,26 +620,38 @@ namespace Avalonia
base.OnPropertyChanged(change);
if (change.Property == ThemeProperty)
{
OnControlThemeChanged();
_themeApplied = false;
}
}
private protected virtual void OnControlThemeChanged()
{
var values = GetValueStore();
values.BeginStyling();
try { values.RemoveFrames(FrameType.Theme); }
finally { values.EndStyling(); }
try
{
values.RemoveFrames(FrameType.Theme);
}
finally
{
values.EndStyling();
_themeApplied = false;
}
}
internal virtual void OnTemplatedParentControlThemeChanged()
{
var values = GetValueStore();
values.BeginStyling();
try { values.RemoveFrames(FrameType.TemplatedParentTheme); }
finally { values.EndStyling(); }
try
{
values.RemoveFrames(FrameType.TemplatedParentTheme);
}
finally
{
values.EndStyling();
_templatedParentThemeApplied = false;
}
}
internal ControlTheme? GetEffectiveTheme()
@ -743,13 +762,13 @@ namespace Avalonia
private void ApplyControlTheme()
{
var theme = GetEffectiveTheme();
if (theme is not null)
if (GetEffectiveTheme() is { } theme)
ApplyControlTheme(theme, FrameType.Theme);
}
if (TemplatedParent is StyledElement styleableParent &&
styleableParent.GetEffectiveTheme() is { } parentTheme)
private void ApplyTemplatedParentControlTheme()
{
if ((TemplatedParent as StyledElement)?.GetEffectiveTheme() is { } parentTheme)
{
ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme);
}
@ -793,6 +812,28 @@ namespace Avalonia
ApplyStyle(child, host, type);
}
private void ReevaluateImplicitTheme()
{
// We only need to check if the theme has changed when Theme isn't set (i.e. when we
// have an implicit theme).
if (Theme is not null)
return;
// Refetch the implicit theme.
var oldImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme;
_implicitTheme = null;
GetEffectiveTheme();
var newImplicitTheme = _implicitTheme == s_invalidTheme ? null : _implicitTheme;
// If the implicit theme has changed, detach the existing theme.
if (newImplicitTheme != oldImplicitTheme)
{
OnControlThemeChanged();
_themeApplied = false;
}
}
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
if (this.GetLogicalParent() == null && !(this is ILogicalRoot))
@ -811,6 +852,7 @@ namespace Avalonia
{
_logicalRoot = e.Root;
ReevaluateImplicitTheme();
ApplyStyling();
NotifyResourcesChanged(propagate: false);
@ -835,7 +877,6 @@ namespace Avalonia
if (_logicalRoot != null)
{
_logicalRoot = null;
_implicitTheme = null;
InvalidateStyles(recurse: false);
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);

3
src/Avalonia.Base/Styling/StyleInstance.cs

@ -70,6 +70,9 @@ namespace Avalonia.Styling
_animationTrigger ??= new Subject<bool>();
foreach (var animation in _animations)
animation.Apply(animatable, null, _animationTrigger);
if (_activator is null)
_animationTrigger.OnNext(true);
}
}

48
tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs

@ -868,7 +868,53 @@ namespace Avalonia.Base.UnitTests.Styling
}
[Fact]
public void Animations_Should_Be_Activated_And_Deactivated()
public void Animations_Should_Be_Activated()
{
Style style = new Style(x => x.OfType<Class1>())
{
Animations =
{
new Avalonia.Animation.Animation
{
Duration = TimeSpan.FromSeconds(1),
Children =
{
new KeyFrame
{
Setters =
{
new Setter { Property = Class1.DoubleProperty, Value = 5.0 }
},
},
new KeyFrame
{
Setters =
{
new Setter { Property = Class1.DoubleProperty, Value = 10.0 }
},
Cue = new Cue(1d)
}
},
}
}
};
var clock = new TestClock();
var target = new Class1 { Clock = clock };
StyleHelpers.TryAttach(style, target);
Assert.Equal(0.0, target.Double);
clock.Step(TimeSpan.Zero);
Assert.Equal(5.0, target.Double);
clock.Step(TimeSpan.FromSeconds(0.5));
Assert.Equal(7.5, target.Double);
}
[Fact]
public void Animations_With_Trigger_Should_Be_Activated_And_Deactivated()
{
Style style = new Style(x => x.OfType<Class1>().Class("foo"))
{

51
tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs

@ -359,16 +359,49 @@ public class StyledElementTests_Theming
}
[Fact]
public void Implicit_Theme_Is_Cleared_When_Removed_From_Logical_Tree()
public void Implicit_Theme_Is_Not_Detached_When_Removed_From_Logical_Tree()
{
var target = CreateTarget();
var root = CreateRoot(target);
Assert.NotNull(target.GetEffectiveTheme());
Assert.Equal("theme", target.Tag);
root.Child = null;
Assert.Null(target.GetEffectiveTheme());
var border = Assert.IsType<Border>(target.VisualChild);
Assert.Equal("theme", target.Tag);
Assert.Equal("theme", border.Tag);
}
[Fact]
public void Can_Attach_Then_Reattach_To_Same_Logical_Tree()
{
var target = CreateTarget();
var root = CreateRoot(target);
Assert.Equal("theme", target.Tag);
root.Child = null;
root.Child = target;
Assert.Equal("theme", target.Tag);
}
[Fact]
public void Implicit_Theme_Is_Reevaluated_When_Removed_And_Added_To_Different_Logical_Tree()
{
var target = CreateTarget();
var root1 = CreateRoot(target, "theme1");
var root2 = CreateRoot(null, "theme2");
Assert.Equal("theme1", target.Tag);
root1.Child = null;
root2.Child = target;
var border = Assert.IsType<Border>(target.VisualChild);
Assert.Equal("theme2", target.Tag);
Assert.Equal("theme2", border.Tag);
}
[Fact]
@ -402,10 +435,10 @@ public class StyledElementTests_Theming
private static ThemedControl CreateTarget() => new ThemedControl();
private static TestRoot CreateRoot(Control child)
private static TestRoot CreateRoot(Control? child, string themeTag = "theme")
{
var result = new TestRoot();
result.Resources.Add(typeof(ThemedControl), CreateTheme());
result.Resources.Add(typeof(ThemedControl), CreateTheme(themeTag));
result.Child = child;
result.LayoutManager.ExecuteInitialLayoutPass();
return result;
@ -530,7 +563,7 @@ public class StyledElementTests_Theming
}
}
private static ControlTheme CreateTheme()
private static ControlTheme CreateTheme(string tag = "theme")
{
var template = new FuncControlTemplate<ThemedControl>((o, n) => new Border());
@ -539,7 +572,7 @@ public class StyledElementTests_Theming
TargetType = typeof(ThemedControl),
Setters =
{
new Setter(Control.TagProperty, "theme"),
new Setter(Control.TagProperty, tag),
new Setter(TemplatedControl.TemplateProperty, template),
new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(5)),
},
@ -550,7 +583,7 @@ public class StyledElementTests_Theming
Setters =
{
new Setter(Border.BackgroundProperty, Brushes.Red),
new Setter(Control.TagProperty, "theme"),
new Setter(Control.TagProperty, tag),
}
},
new Style(x => x.Nesting().Class("foo").Template().OfType<Border>())

Loading…
Cancel
Save