diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_VisualStateManager.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_VisualStateManager.cs index 4c6567b05172..9461d9dd7ba9 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_VisualStateManager.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_VisualStateManager.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Windows.Foundation; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; @@ -8,6 +10,7 @@ using Microsoft.UI.Xaml.Shapes; using Microsoft.VisualStudio.TestTools.UnitTesting; using MUXControlsTestApp.Utilities; +using SamplesApp.UITests; using Uno.Extensions; using Uno.UI.Extensions; using Uno.UI.RuntimeTests.Helpers; @@ -15,7 +18,7 @@ namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml; [TestClass] [RunsOnUIThread] -public class Given_VisualStateManager +public partial class Given_VisualStateManager { [TestMethod] public async Task When_Transition_Modifies_SubProperty_Of_Property_Set_By_Previous_State() @@ -79,4 +82,65 @@ public async Task SelectorItem_SelectedState() var states = VisualStateHelper.GetCurrentVisualStateName(container2).ToArray(); Assert.IsTrue(states.Contains("MultiSelectEnabled"), $"container2 is not in 'MultiSelectEnabled' state: states={states.JoinBy(",")}"); } + +#if HAS_UNO + [TestMethod] + [UnoWorkItem("https://github.com/unoplatform/uno/issues/19364")] + public async Task When_StateTriggers_Evaluated_Before_First_Layout() + { + MyUserControl uc = new MyUserControl(); + VisualStateManager.SetVisualStateGroups(uc, new List + { + new VisualStateGroup() + { + States = + { + new VisualState + { + Name = "MyVisualState1", + }, + new VisualState + { + Name = "MyVisualState2", + StateTriggers = + { + new AdaptiveTrigger() + { + MinWindowWidth = 1 + } + } + } + } + } + }); + + var contentControl = new ContentControl + { + Content = "0", + ContentTemplate = new DataTemplate(() => + { + return uc; + }) + }; + + await UITestHelper.Load(contentControl, control => control.IsLoaded); + Assert.AreEqual("MyVisualState2", uc.VisualStateOnFirstMeasure?.Name); + } + + private partial class MyUserControl : UserControl + { + private bool _firstMeasure = true; + public VisualState VisualStateOnFirstMeasure { get; set; } + + protected override Size MeasureOverride(Size availableSize) + { + if (_firstMeasure) + { + _firstMeasure = false; + VisualStateOnFirstMeasure = VisualStateManager.GetVisualStateGroups(this)[0].CurrentState; + } + return base.MeasureOverride(availableSize); + } + } +#endif } 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..eb385373291e 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,46 @@ internal IFrameworkElement GetTemplateRoot() { return this.GetChildren()?.FirstOrDefault() as IFrameworkElement; } + +#if !__NETSTD_REFERENCE__ + 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 */); + } + } +#endif } 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..524d468472bd 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 !UNO_HAS_ENHANCED_LIFECYCLE fe.Loaded += OnOwnerElementLoaded; +#endif fe.Unloaded += OnOwnerElementUnloaded; _parentLoadedDisposable.Disposable = Disposable.Create(() => { +#if !UNO_HAS_ENHANCED_LIFECYCLE 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); + } + } } }