Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(statetriggers): evaluate StateTriggers before the first layout cycle #19557

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
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;
using Microsoft.UI.Xaml.Media;
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;
Expand Down Expand Up @@ -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<VisualStateGroup>
{
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
}
42 changes: 42 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/Control/Control.crossruntime.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Linq;
using Uno.Extensions;
using Uno.UI.Xaml;

namespace Microsoft.UI.Xaml.Controls;

Expand All @@ -25,4 +27,44 @@
{
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)
// {

Check warning on line 38 in src/Uno.UI/UI/Xaml/Controls/Control/Control.crossruntime.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI/UI/Xaml/Controls/Control/Control.crossruntime.cs#L38

Remove this commented out code.
// // 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 */);
}
}
}
16 changes: 9 additions & 7 deletions src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,15 @@
{
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)

Check notice on line 219 in src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs#L219

Remove the unused local variable '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;

Expand Down
6 changes: 5 additions & 1 deletion src/Uno.UI/UI/Xaml/VisualStateGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}
Expand All @@ -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)
{
Expand Down
69 changes: 69 additions & 0 deletions src/Uno.UI/UI/Xaml/VisualStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,74 @@

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();

Check warning on line 307 in src/Uno.UI/UI/Xaml/VisualStateManager.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI/UI/Xaml/VisualStateManager.cs#L307

Remove this commented out code.
// 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<CVisualStateGroup*>(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<CFrameworkElement*>(pControl->GetFirstChildNoAddRef()), dataSource.get());
// IFC_RETURN(actuator.InitializeStateTriggers(pControl));

// Uno Specific
foreach (var visualStateGroup in GetVisualStateGroups(pControl))
{
visualStateGroup.OnOwnerElementLoaded(pControl, null);
visualStateGroup.RefreshStateTriggers(forceUpdate);
}
}
}
}
Loading