-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
[C] use ResourcesChanged to propagate Theme #21229
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,10 +8,38 @@ namespace Microsoft.Maui.Controls | |
{ | ||
class AppThemeBinding : BindingBase | ||
{ | ||
public const string AppThemeResource = "__MAUI_ApplicationTheme__"; | ||
class AppThemeProxy : Element | ||
{ | ||
public AppThemeProxy(Element parent, AppThemeBinding binding) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the proxy might prevent the AppThemeBinding to GC, but the AppThemeBinding doesn't hold a strong ref to the BO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like I was wrong |
||
{ | ||
_parent = parent; | ||
Binding = binding; | ||
this.SetDynamicResource(AppThemeProperty, AppThemeResource); | ||
((IElementDefinition)parent).AddResourcesChangedListener(OnParentResourcesChanged); | ||
} | ||
|
||
public static BindableProperty AppThemeProperty = BindableProperty.Create("AppTheme", typeof(AppTheme), typeof(AppThemeBinding), AppTheme.Unspecified, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will it be more expensive to have a separate proxy Element object with a single extra property vs just adding this to maybe the base Element? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can't add that to the base Element, as an element might need have multiple AppThemebindings, and then multiples proxies. moving that to the element will require some sort of event subscription, and that's what we're avoiding here |
||
propertyChanged: (bindable, oldValue, newValue) => ((AppThemeProxy)bindable).OnAppThemeChanged()); | ||
readonly Element _parent; | ||
|
||
public void OnAppThemeChanged() | ||
{ | ||
Binding.Apply(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about the performance impact, especially on CollectionView scrolling. Could we perhaps have an UITest with this example #18505 where we can display the minimum fps in a Label by scrolling and verifying that the value is greater than n (where n is value higher than before)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may need @jonathanpeppers to run this PR in a release app with profiling. Our UI tests have a lot of overhead and appium things running. |
||
} | ||
|
||
public AppThemeBinding Binding { get; } | ||
|
||
public void Unsubscribe() | ||
{ | ||
((IElementDefinition)_parent).RemoveResourcesChangedListener(OnParentResourcesChanged); | ||
} | ||
} | ||
|
||
WeakReference<BindableObject> _weakTarget; | ||
BindableProperty _targetProperty; | ||
bool _attached; | ||
SetterSpecificity specificity; | ||
AppThemeProxy _appThemeProxy; | ||
|
||
internal override BindingBase Clone() | ||
{ | ||
|
@@ -34,7 +62,6 @@ internal override void Apply(bool fromTarget) | |
{ | ||
base.Apply(fromTarget); | ||
ApplyCore(); | ||
SetAttached(true); | ||
} | ||
|
||
internal override void Apply(object context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged, SetterSpecificity specificity) | ||
|
@@ -44,12 +71,15 @@ internal override void Apply(object context, BindableObject bindObj, BindablePro | |
base.Apply(context, bindObj, targetProperty, fromBindingContextChanged, specificity); | ||
this.specificity = specificity; | ||
ApplyCore(false); | ||
SetAttached(true); | ||
_appThemeProxy = new AppThemeProxy(bindObj as Element, this); | ||
|
||
} | ||
|
||
internal override void Unapply(bool fromBindingContextChanged = false) | ||
{ | ||
SetAttached(false); | ||
_appThemeProxy?.Unsubscribe(); | ||
_appThemeProxy = null; | ||
|
||
base.Unapply(fromBindingContextChanged); | ||
_weakTarget = null; | ||
_targetProperty = null; | ||
|
@@ -62,7 +92,6 @@ void ApplyCore(bool dispatch = false) | |
{ | ||
if (_weakTarget == null || !_weakTarget.TryGetTarget(out var target)) | ||
{ | ||
SetAttached(false); | ||
return; | ||
} | ||
|
||
|
@@ -147,24 +176,5 @@ target is VisualElement ve && | |
_ => _isLightSet ? Light : Default, | ||
}; | ||
} | ||
|
||
void SetAttached(bool value) | ||
{ | ||
var app = Application.Current; | ||
if (app != null && _attached != value) | ||
{ | ||
if (value) | ||
{ | ||
// Going from false -> true | ||
app.RequestedThemeChanged += OnRequestedThemeChanged; | ||
} | ||
else | ||
{ | ||
// Going from true -> false | ||
app.RequestedThemeChanged -= OnRequestedThemeChanged; | ||
} | ||
_attached = value; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,6 +101,15 @@ public Page? MainPage | |
|
||
_singleWindowMainPage = value; | ||
|
||
if (value is not null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this fixes another bug. resources changed at application level weren't propagated to MainPage There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think if you are just using MainPage, then you get parented to a window during the creating of the UI - so once the app is ready, it calls CreateWindow and assigns the MainPage to the window. The Windows[0].Page = value; What you have observed might just be because MainPage really is a delayed construction and assigned. Now this is to say it may be fine what you are doing here, but will it result in a double listener registration once the window is created and the MainPage parented to the window? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe, but that doesn't work in unit tests |
||
{ | ||
OnParentResourcesChanged(this.GetMergedResources()); | ||
OnParentResourcesChanged([new KeyValuePair<string, object>(AppThemeBinding.AppThemeResource, _lastAppTheme)]); | ||
((IElementDefinition)this).AddResourcesChangedListener(value.OnParentResourcesChanged); | ||
} else if (MainPage is not null) | ||
{ | ||
((IElementDefinition)this).RemoveResourcesChangedListener(MainPage.OnParentResourcesChanged); | ||
} | ||
if (Windows.Count == 1) | ||
{ | ||
Windows[0].Page = value; | ||
|
@@ -243,6 +252,7 @@ void TriggerThemeChangedActual() | |
_themeChangedFiring = true; | ||
_lastAppTheme = newTheme; | ||
|
||
OnParentResourcesChanged([new KeyValuePair<string, object>(AppThemeBinding.AppThemeResource, newTheme)]); | ||
_weakEventManager.HandleEvent(this, new AppThemeChangedEventArgs(newTheme), nameof(RequestedThemeChanged)); | ||
} | ||
finally | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -827,10 +827,12 @@ private protected override void OnHandlerChangingCore(HandlerChangingEventArgs a | |
return; | ||
|
||
if (args.NewHandler == null) | ||
#pragma warning disable CS0618 // Type or member is obsolete | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well, it's not anymore in this version of the PR, but maybe it should |
||
Application.Current.RequestedThemeChanged -= OnRequestedThemeChanged; | ||
|
||
if (args.NewHandler != null && args.OldHandler == null) | ||
Application.Current.RequestedThemeChanged += OnRequestedThemeChanged; | ||
#pragma warning restore CS0618 // Type or member is obsolete | ||
} | ||
|
||
private void OnRequestedThemeChanged(object sender, AppThemeChangedEventArgs e) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ public void ThemeChangeUsingSetAppThemeColor() | |
{ | ||
Text = "Green on Light, Red on Dark" | ||
}; | ||
app.LoadPage(new ContentPage {Content = label}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. apptheme propagation REQUIRES that the control is parented, which is fine, as otherwise it wouldn't be on screen |
||
|
||
label.SetAppThemeColor(Label.TextColorProperty, Colors.Green, Colors.Red); | ||
Assert.Equal(Colors.Green, label.TextColor); | ||
|
@@ -46,11 +47,14 @@ public void ThemeChangeUsingSetAppThemeColor() | |
[Fact] | ||
public void ThemeChangeUsingSetAppTheme() | ||
{ | ||
|
||
var label = new Label | ||
{ | ||
Text = "Green on Light, Red on Dark" | ||
}; | ||
|
||
app.LoadPage(new ContentPage {Content = label}); | ||
|
||
label.SetAppTheme(Label.TextColorProperty, Colors.Green, Colors.Red); | ||
Assert.Equal(Colors.Green, label.TextColor); | ||
|
||
|
@@ -66,6 +70,7 @@ public void ThemeChangeUsingSetBinding() | |
{ | ||
Text = "Green on Light, Red on Dark" | ||
}; | ||
app.LoadPage(new ContentPage {Content = label}); | ||
|
||
label.SetBinding(Label.TextColorProperty, new AppThemeBinding { Light = Colors.Green, Dark = Colors.Red }); | ||
Assert.Equal(Colors.Green, label.TextColor); | ||
|
@@ -82,6 +87,7 @@ public void ThemeChangeUsingUserAppTheme() | |
{ | ||
Text = "Green on Light, Red on Dark" | ||
}; | ||
app.LoadPage(new ContentPage {Content = label}); | ||
|
||
label.SetAppThemeColor(Label.TextColorProperty, Colors.Green, Colors.Red); | ||
Assert.Equal(Colors.Green, label.TextColor); | ||
|
@@ -225,6 +231,7 @@ public void NullApplicationCurrentFallsBackToEssentials() | |
{ | ||
Text = "Green on Light, Red on Dark" | ||
}; | ||
app.LoadPage(new ContentPage {Content = label}); | ||
|
||
label.SetAppThemeColor(Label.TextColorProperty, Colors.Green, Colors.Red); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😮