|
|
@@ -3,6 +3,7 @@ using System.Collections;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Collections.Specialized;
|
|
|
using System.ComponentModel;
|
|
|
+using System.Diagnostics;
|
|
|
using System.Linq;
|
|
|
using Avalonia.Animation;
|
|
|
using Avalonia.Collections;
|
|
|
@@ -11,6 +12,7 @@ using Avalonia.Data;
|
|
|
using Avalonia.Diagnostics;
|
|
|
using Avalonia.Logging;
|
|
|
using Avalonia.LogicalTree;
|
|
|
+using Avalonia.PropertyStore;
|
|
|
using Avalonia.Styling;
|
|
|
|
|
|
namespace Avalonia
|
|
|
@@ -69,10 +71,10 @@ namespace Avalonia
|
|
|
private IAvaloniaList<ILogical>? _logicalChildren;
|
|
|
private IResourceDictionary? _resources;
|
|
|
private Styles? _styles;
|
|
|
- private bool _styled;
|
|
|
+ private bool _stylesApplied;
|
|
|
+ private bool _themeApplied;
|
|
|
private ITemplatedControl? _templatedParent;
|
|
|
private bool _dataContextUpdating;
|
|
|
- private bool _hasPromotedTheme;
|
|
|
private ControlTheme? _implicitTheme;
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -141,7 +143,7 @@ namespace Avalonia
|
|
|
|
|
|
set
|
|
|
{
|
|
|
- if (_styled)
|
|
|
+ if (_stylesApplied)
|
|
|
{
|
|
|
throw new InvalidOperationException("Cannot set Name : styled element already styled.");
|
|
|
}
|
|
|
@@ -353,38 +355,33 @@ namespace Avalonia
|
|
|
/// </returns>
|
|
|
public bool ApplyStyling()
|
|
|
{
|
|
|
- if (_initCount == 0 && !_styled)
|
|
|
+ if (_initCount == 0 && (!_stylesApplied || !_themeApplied))
|
|
|
{
|
|
|
- var hasPromotedTheme = _hasPromotedTheme;
|
|
|
-
|
|
|
GetValueStore().BeginStyling();
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- ApplyControlTheme();
|
|
|
- ApplyStyles(this);
|
|
|
+ if (!_themeApplied)
|
|
|
+ {
|
|
|
+ ApplyControlTheme();
|
|
|
+ _themeApplied = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!_stylesApplied)
|
|
|
+ {
|
|
|
+ ApplyStyles(this);
|
|
|
+ _stylesApplied = true;
|
|
|
+ }
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
- _styled = true;
|
|
|
GetValueStore().EndStyling();
|
|
|
}
|
|
|
-
|
|
|
- if (hasPromotedTheme)
|
|
|
- {
|
|
|
- _hasPromotedTheme = false;
|
|
|
- ClearValue(ThemeProperty);
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- return _styled;
|
|
|
+ return _stylesApplied;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Detaches all styles from the element and queues a restyle.
|
|
|
- /// </summary>
|
|
|
- protected virtual void InvalidateStyles() => DetachStyles();
|
|
|
-
|
|
|
protected void InitializeIfNeeded()
|
|
|
{
|
|
|
if (_initCount == 0 && !IsInitialized)
|
|
|
@@ -506,17 +503,16 @@ namespace Avalonia
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- void IStyleable.DetachStyles() => DetachStyles();
|
|
|
-
|
|
|
void IStyleHost.StylesAdded(IReadOnlyList<IStyle> styles)
|
|
|
{
|
|
|
- InvalidateStylesOnThisAndDescendents();
|
|
|
+ if (HasSettersOrAnimations(styles))
|
|
|
+ InvalidateStyles(recurse: true);
|
|
|
}
|
|
|
|
|
|
void IStyleHost.StylesRemoved(IReadOnlyList<IStyle> styles)
|
|
|
{
|
|
|
- var allStyles = RecurseStyles(styles);
|
|
|
- DetachStylesFromThisAndDescendents(allStyles);
|
|
|
+ if (FlattenStyles(styles) is { } allStyles)
|
|
|
+ DetachStyles(allStyles);
|
|
|
}
|
|
|
|
|
|
protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
|
@@ -615,31 +611,25 @@ namespace Avalonia
|
|
|
|
|
|
if (change.Property == ThemeProperty)
|
|
|
{
|
|
|
- var (oldValue, newValue) = change.GetOldAndNewValue<ControlTheme?>();
|
|
|
-
|
|
|
- // Changing the theme detaches all styles, meaning that if the theme property was
|
|
|
- // set via a style, it will get cleared! To work around this, if the value was
|
|
|
- // applied at less than local value priority then promote the value to local value
|
|
|
- // priority until styling is re-applied.
|
|
|
- if (change.Priority > BindingPriority.LocalValue)
|
|
|
- {
|
|
|
- Theme = newValue;
|
|
|
- _hasPromotedTheme = true;
|
|
|
- }
|
|
|
- else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue)
|
|
|
- {
|
|
|
- _hasPromotedTheme = false;
|
|
|
- }
|
|
|
-
|
|
|
- InvalidateStyles();
|
|
|
-
|
|
|
- if (oldValue is not null)
|
|
|
- DetachControlThemeFromTemplateChildren(oldValue);
|
|
|
+ OnControlThemeChanged();
|
|
|
+ _themeApplied = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- internal virtual void DetachControlThemeFromTemplateChildren(ControlTheme theme)
|
|
|
+ private protected virtual void OnControlThemeChanged()
|
|
|
{
|
|
|
+ var values = GetValueStore();
|
|
|
+ values.BeginStyling();
|
|
|
+ try { values.RemoveFrames(FrameType.Theme); }
|
|
|
+ finally { values.EndStyling(); }
|
|
|
+ }
|
|
|
+
|
|
|
+ internal virtual void OnTemplatedParentControlThemeChanged()
|
|
|
+ {
|
|
|
+ var values = GetValueStore();
|
|
|
+ values.BeginStyling();
|
|
|
+ try { values.RemoveFrames(FrameType.TemplatedParentTheme); }
|
|
|
+ finally { values.EndStyling(); }
|
|
|
}
|
|
|
|
|
|
internal ControlTheme? GetEffectiveTheme()
|
|
|
@@ -667,6 +657,23 @@ namespace Avalonia
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
+ internal virtual void InvalidateStyles(bool recurse)
|
|
|
+ {
|
|
|
+ var values = GetValueStore();
|
|
|
+ values.BeginStyling();
|
|
|
+ try { values.RemoveFrames(FrameType.Style); }
|
|
|
+ finally { values.EndStyling(); }
|
|
|
+
|
|
|
+ _stylesApplied = false;
|
|
|
+
|
|
|
+ if (recurse && GetInheritanceChildren() is { } children)
|
|
|
+ {
|
|
|
+ var childCount = children.Count;
|
|
|
+ for (var i = 0; i < childCount; ++i)
|
|
|
+ (children[i] as StyledElement)?.InvalidateStyles(recurse);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted)
|
|
|
{
|
|
|
if (o is StyledElement element)
|
|
|
@@ -736,26 +743,28 @@ namespace Avalonia
|
|
|
var theme = GetEffectiveTheme();
|
|
|
|
|
|
if (theme is not null)
|
|
|
- ApplyControlTheme(theme);
|
|
|
+ ApplyControlTheme(theme, FrameType.Theme);
|
|
|
|
|
|
if (TemplatedParent is StyledElement styleableParent &&
|
|
|
styleableParent.GetEffectiveTheme() is { } parentTheme)
|
|
|
{
|
|
|
- ApplyControlTheme(parentTheme);
|
|
|
+ ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void ApplyControlTheme(ControlTheme theme)
|
|
|
+ private void ApplyControlTheme(ControlTheme theme, FrameType type)
|
|
|
{
|
|
|
+ Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme);
|
|
|
+
|
|
|
if (theme.BasedOn is ControlTheme basedOn)
|
|
|
- ApplyControlTheme(basedOn);
|
|
|
+ ApplyControlTheme(basedOn, type);
|
|
|
|
|
|
- theme.TryAttach(this, null);
|
|
|
+ theme.TryAttach(this, type);
|
|
|
|
|
|
if (theme.HasChildren)
|
|
|
{
|
|
|
foreach (var child in theme.Children)
|
|
|
- ApplyStyle(child, null);
|
|
|
+ ApplyStyle(child, null, type);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -768,17 +777,17 @@ namespace Avalonia
|
|
|
if (host.IsStylesInitialized)
|
|
|
{
|
|
|
foreach (var style in host.Styles)
|
|
|
- ApplyStyle(style, host);
|
|
|
+ ApplyStyle(style, host, FrameType.Style);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void ApplyStyle(IStyle style, IStyleHost? host)
|
|
|
+ private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type)
|
|
|
{
|
|
|
if (style is Style s)
|
|
|
- s.TryAttach(this, host);
|
|
|
+ s.TryAttach(this, host, type);
|
|
|
|
|
|
foreach (var child in style.Children)
|
|
|
- ApplyStyle(child, host);
|
|
|
+ ApplyStyle(child, host, type);
|
|
|
}
|
|
|
|
|
|
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
|
|
|
@@ -824,7 +833,7 @@ namespace Avalonia
|
|
|
{
|
|
|
_logicalRoot = null;
|
|
|
_implicitTheme = null;
|
|
|
- DetachStyles();
|
|
|
+ InvalidateStyles(recurse: false);
|
|
|
OnDetachedFromLogicalTree(e);
|
|
|
DetachedFromLogicalTree?.Invoke(this, e);
|
|
|
|
|
|
@@ -886,70 +895,81 @@ namespace Avalonia
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void DetachStyles(IReadOnlyList<StyleBase>? styles = null)
|
|
|
+ private void DetachStyles(IReadOnlyList<Style> styles)
|
|
|
{
|
|
|
- var valueStore = GetValueStore();
|
|
|
+ var values = GetValueStore();
|
|
|
+ values.BeginStyling();
|
|
|
+ try { values.RemoveFrames(styles); }
|
|
|
+ finally { values.EndStyling(); }
|
|
|
|
|
|
- valueStore.BeginStyling();
|
|
|
-
|
|
|
- for (var i = valueStore.Frames.Count - 1; i >= 0; --i)
|
|
|
+ if (_logicalChildren is not null)
|
|
|
{
|
|
|
- if (valueStore.Frames[i] is StyleInstance si &&
|
|
|
- (styles is null || styles.Contains(si.Source)))
|
|
|
+ var childCount = _logicalChildren.Count;
|
|
|
+
|
|
|
+ for (var i = 0; i < childCount; ++i)
|
|
|
{
|
|
|
- valueStore.RemoveFrame(si);
|
|
|
+ (_logicalChildren[i] as StyledElement)?.DetachStyles(styles);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- valueStore.EndStyling();
|
|
|
- _styled = false;
|
|
|
}
|
|
|
|
|
|
- private void InvalidateStylesOnThisAndDescendents()
|
|
|
+ private void NotifyResourcesChanged(
|
|
|
+ ResourcesChangedEventArgs? e = null,
|
|
|
+ bool propagate = true)
|
|
|
{
|
|
|
- InvalidateStyles();
|
|
|
-
|
|
|
- if (_logicalChildren is object)
|
|
|
+ if (ResourcesChanged is object)
|
|
|
{
|
|
|
- var childCount = _logicalChildren.Count;
|
|
|
+ e ??= ResourcesChangedEventArgs.Empty;
|
|
|
+ ResourcesChanged(this, e);
|
|
|
+ }
|
|
|
|
|
|
- for (var i = 0; i < childCount; ++i)
|
|
|
- {
|
|
|
- (_logicalChildren[i] as StyledElement)?.InvalidateStylesOnThisAndDescendents();
|
|
|
- }
|
|
|
+ if (propagate)
|
|
|
+ {
|
|
|
+ e ??= ResourcesChangedEventArgs.Empty;
|
|
|
+ NotifyChildResourcesChanged(e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void DetachStylesFromThisAndDescendents(IReadOnlyList<StyleBase> styles)
|
|
|
+ private static IReadOnlyList<Style>? FlattenStyles(IReadOnlyList<IStyle> styles)
|
|
|
{
|
|
|
- DetachStyles(styles);
|
|
|
+ List<Style>? result = null;
|
|
|
|
|
|
- if (_logicalChildren is object)
|
|
|
+ static void FlattenStyle(IStyle style, ref List<Style>? result)
|
|
|
{
|
|
|
- var childCount = _logicalChildren.Count;
|
|
|
+ if (style is Style s)
|
|
|
+ (result ??= new()).Add(s);
|
|
|
+ FlattenStyles(style.Children, ref result);
|
|
|
+ }
|
|
|
|
|
|
- for (var i = 0; i < childCount; ++i)
|
|
|
- {
|
|
|
- (_logicalChildren[i] as StyledElement)?.DetachStylesFromThisAndDescendents(styles);
|
|
|
- }
|
|
|
+ static void FlattenStyles(IReadOnlyList<IStyle> styles, ref List<Style>? result)
|
|
|
+ {
|
|
|
+ var count = styles.Count;
|
|
|
+ for (var i = 0; i < count; ++i)
|
|
|
+ FlattenStyle(styles[i], ref result);
|
|
|
}
|
|
|
+
|
|
|
+ FlattenStyles(styles, ref result);
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
- private void NotifyResourcesChanged(
|
|
|
- ResourcesChangedEventArgs? e = null,
|
|
|
- bool propagate = true)
|
|
|
+ private static bool HasSettersOrAnimations(IReadOnlyList<IStyle> styles)
|
|
|
{
|
|
|
- if (ResourcesChanged is object)
|
|
|
+ static bool StyleHasSettersOrAnimations(IStyle style)
|
|
|
{
|
|
|
- e ??= ResourcesChangedEventArgs.Empty;
|
|
|
- ResourcesChanged(this, e);
|
|
|
+ if (style is StyleBase s && s.HasSettersOrAnimations)
|
|
|
+ return true;
|
|
|
+ return HasSettersOrAnimations(style.Children);
|
|
|
}
|
|
|
|
|
|
- if (propagate)
|
|
|
+ var count = styles.Count;
|
|
|
+
|
|
|
+ for (var i = 0; i < count; ++i)
|
|
|
{
|
|
|
- e ??= ResourcesChangedEventArgs.Empty;
|
|
|
- NotifyChildResourcesChanged(e);
|
|
|
+ if (StyleHasSettersOrAnimations(styles[i]))
|
|
|
+ return true;
|
|
|
}
|
|
|
+
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
private static IReadOnlyList<StyleBase> RecurseStyles(IReadOnlyList<IStyle> styles)
|