From eb684d505554e3401ac4d35a3ee1f096c1a90568 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 18 Feb 2025 13:58:41 +0200 Subject: [PATCH] fix(statetriggers): evaluate StateTriggers before the first layout cycle --- .../Controls/Control/Control.crossruntime.cs | 42 +++++++++++ .../FrameworkElement.Layout.crossruntime.cs | 16 +++-- src/Uno.UI/UI/Xaml/VisualStateGroup.cs | 6 +- src/Uno.UI/UI/Xaml/VisualStateManager.cs | 69 +++++++++++++++++++ 4 files changed, 125 insertions(+), 8 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/Control/Control.crossruntime.cs b/src/Uno.UI/UI/Xaml/Controls/Control/Control.crossruntime.cs index cc31110ce284..e6f18c64f855 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Control/Control.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Control/Control.crossruntime.cs @@ -1,4 +1,6 @@ using System.Linq; +using Uno.Extensions; +using Uno.UI.Xaml; namespace Microsoft.UI.Xaml.Controls; @@ -25,4 +27,44 @@ internal IFrameworkElement GetTemplateRoot() { return this.GetChildren()?.FirstOrDefault() as IFrameworkElement; } + + internal override void EnterImpl(EnterParams @params, int depth) + { + base.EnterImpl(@params, depth); + + if (@params.IsLive) + { + // if (SupportsBuiltInStyles() && !m_fIsBuiltInStyleApplied) + // { + // // When we apply the built-in style, we may resolve theme resources in doing so + // // that haven't yet been resolved - for example, if a property value references + // // a resource that then references another resource. + // // We need to make sure we're operating under the correct theme during that resolution. + // bool removeRequestedTheme = false; + // const auto theme = GetTheme(); + // const auto oldRequestedThemeForSubTree = GetRequestedThemeForSubTreeFromCore(); + // + // if (theme != Theming::Theme::None && Theming::GetBaseValue(theme) != oldRequestedThemeForSubTree) + // { + // SetRequestedThemeForSubTreeOnCore(theme); + // removeRequestedTheme = true; + // } + // + // auto themeGuard = wil::scope_exit([&] { + // if (removeRequestedTheme) + // { + // SetRequestedThemeForSubTreeOnCore(oldRequestedThemeForSubTree); + // } + // }); + // + // IFC_RETURN(ApplyBuiltInStyle()); + // } + + // Initialize StateTriggers at this time. We need to wait for this to enter a visual tree + // since we need for it to be part of the main visual tree to know which visual tree's + // qualifier context to use. We also need to force an update because our visual tree root + // may have changed since the last enter. + VisualStateManager.InitializeStateTriggers(this, true /* forceUpdate */); + } + } } diff --git a/src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs b/src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs index 44ad78967586..3d7f49c1f445 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs @@ -214,13 +214,15 @@ internal void InvokeApplyTemplate(out bool addedVisuals) { ApplyTemplate(out addedVisuals); - //if (auto visualTree = VisualTree::GetForElementNoRef(pControl)) - // { - // // Create VisualState StateTriggers and perform evaulation to determine initial state, - // // if we're in the visual tree (since we need it to get our qualifier context). - // // If we're not in the visual tree, we'll do this when we enter it. - // IFC(CVisualStateManager2::InitializeStateTriggers(this)); - //} + var pControl = this as Control; + + if (VisualTree.GetForElement(pControl) is { } visualTree) + { + // Create VisualState StateTriggers and perform evaulation to determine initial state, + // if we're in the visual tree (since we need it to get our qualifier context). + // If we're not in the visual tree, we'll do this when we enter it. + VisualStateManager.InitializeStateTriggers(pControl); + } //var control = this as Control; diff --git a/src/Uno.UI/UI/Xaml/VisualStateGroup.cs b/src/Uno.UI/UI/Xaml/VisualStateGroup.cs index 10e3a0a6d974..782c8bfde5c5 100644 --- a/src/Uno.UI/UI/Xaml/VisualStateGroup.cs +++ b/src/Uno.UI/UI/Xaml/VisualStateGroup.cs @@ -150,11 +150,15 @@ private void OnParentChanged(object instance, object key, DependencyObjectParent if (this.GetParent() is IFrameworkElement fe) { OnOwnerElementChanged(); +#if !__CROSSRUNTIME__ fe.Loaded += OnOwnerElementLoaded; +#endif fe.Unloaded += OnOwnerElementUnloaded; _parentLoadedDisposable.Disposable = Disposable.Create(() => { +#if !__CROSSRUNTIME__ fe.Loaded -= OnOwnerElementLoaded; +#endif fe.Unloaded -= OnOwnerElementUnloaded; }); } @@ -171,7 +175,7 @@ private void OnOwnerElementChanged() ExecuteOnTriggers(t => t.OnOwnerElementChanged()); } - private void OnOwnerElementLoaded(object sender, RoutedEventArgs args) + internal void OnOwnerElementLoaded(object sender, RoutedEventArgs args) { if (_pendingOnOwnerElementChanged) { diff --git a/src/Uno.UI/UI/Xaml/VisualStateManager.cs b/src/Uno.UI/UI/Xaml/VisualStateManager.cs index 2344b756ec8a..b9e2637254af 100644 --- a/src/Uno.UI/UI/Xaml/VisualStateManager.cs +++ b/src/Uno.UI/UI/Xaml/VisualStateManager.cs @@ -290,5 +290,74 @@ private static (VisualStateGroup, VisualState) GetValidGroupAndState(string stat return (null, null); } + + internal static void InitializeStateTriggers(DependencyObject pDO, bool forceUpdate = false) + { + if (pDO is not Control pControl) + { + return; + } + + var groupCollection = VisualStateManager.GetVisualStateGroups(pControl); + if (groupCollection is null) + { + return; + } + + // auto& groupCollectionMap = groupCollection->GetStateTriggerVariantMaps(); + // if (forceUpdate) + // { + // // Forcing an update, clear the map + // groupCollectionMap.clear(); + // + // // If we're forcing an update, we should also clear the state trigger variant maps in each group. + // for (auto group : *groupCollection) + // { + // static_cast(group)->m_pStateTriggerVariantMap = nullptr; + // } + // + // for (auto& groupContext : groupCollection->GetGroupContext()) + // { + // groupContext.GetStateTriggerVariantMap() = nullptr; + // } + // } + // + // if (!groupCollectionMap.empty()) + // { + // // If the map isn't clear, then we are already initialized or we weren't forced by diagnostics to update. + // // Adding triggers isn't something usually supported at runtime. + // return S_OK; + // } + // + // auto dataSource = CreateVisualStateManagerDataSource(groupCollection); + // auto dataSourceMap = dataSource->GetStateTriggerVariantMaps(); + // + // if (!dataSourceMap.empty()) + // { + // if (forceUpdate) + // { + // // Forcing an update, clear the map + // dataSourceMap.clear(); + // } + // + // if (!dataSourceMap.empty()) + // { + // // If the map isn't clear, then we are already initialized or we weren't forced by diagnostics to update. + // // Adding triggers isn't something usually supported at runtime. + // return S_OK; + // } + // } + // + // VisualStateManagerActuator actuator( + // static_cast(pControl->GetFirstChildNoAddRef()), dataSource.get()); + // IFC_RETURN(actuator.InitializeStateTriggers(pControl)); + + // Uno Specific + foreach (var visualStateGroup in GetVisualStateGroups(pControl)) + { + visualStateGroup.OnOwnerElementLoaded(pControl, null); + visualStateGroup.RefreshStateTriggers(forceUpdate); + } + } } }