11using System ;
22using System . Collections . Generic ;
3+ using System . Linq ;
34
45#if IOS || MACCATALYST
56using PlatformView = UIKit . UIView ;
@@ -15,13 +16,37 @@ namespace Microsoft.Maui
1516{
1617 public abstract class PropertyMapper : IPropertyMapper
1718 {
19+ #if ANDROID
20+ private static readonly HashSet < string > UpdatePropertiesSkipList = new ( StringComparer . Ordinal )
21+ {
22+ nameof ( IView . Visibility ) ,
23+ nameof ( IView . MinimumHeight ) ,
24+ nameof ( IView . MinimumWidth ) ,
25+ nameof ( IView . IsEnabled ) ,
26+ nameof ( IView . Opacity ) ,
27+ nameof ( IView . TranslationX ) ,
28+ nameof ( IView . TranslationY ) ,
29+ nameof ( IView . Scale ) ,
30+ nameof ( IView . ScaleX ) ,
31+ nameof ( IView . ScaleY ) ,
32+ nameof ( IView . Rotation ) ,
33+ nameof ( IView . RotationX ) ,
34+ nameof ( IView . RotationY ) ,
35+ nameof ( IView . AnchorX ) ,
36+ nameof ( IView . AnchorY ) ,
37+ } ;
38+ #endif
39+
40+ // TODO: Make this private in .NET10
1841 protected readonly Dictionary < string , Action < IElementHandler , IElement > > _mapper = new ( StringComparer . Ordinal ) ;
1942
2043 IPropertyMapper [ ] ? _chained ;
2144
22- // Keep a distinct list of the keys so we don't run any duplicate (overridden) updates more than once
23- // when we call UpdateProperties
24- HashSet < string > ? _updateKeys ;
45+ IReadOnlyDictionary < string , Action < IElementHandler , IElement > > ? _mergedMappers ;
46+ private protected IReadOnlyDictionary < string , Action < IElementHandler , IElement > > MergedMappers => _mergedMappers ?? SnapshotMappers ( ) . Mappers ;
47+
48+ IReadOnlyList < string > ? _mergedKeys ;
49+ IReadOnlyList < string > MergedKeys => _mergedKeys ?? SnapshotMappers ( ) . Keys ;
2550
2651 public PropertyMapper ( )
2752 {
@@ -35,29 +60,48 @@ public PropertyMapper(params IPropertyMapper[]? chained)
3560 protected virtual void SetPropertyCore ( string key , Action < IElementHandler , IElement > action )
3661 {
3762 _mapper [ key ] = action ;
63+
3864 ClearKeyCache ( ) ;
3965 }
4066
67+ // TODO: Remove in .NET10
4168 protected virtual void UpdatePropertyCore ( string key , IElementHandler viewHandler , IElement virtualView )
4269 {
4370 if ( ! viewHandler . CanInvokeMappers ( ) )
71+ {
4472 return ;
73+ }
74+
75+ TryUpdatePropertyCore ( key , viewHandler , virtualView ) ;
76+ }
4577
46- var action = GetProperty ( key ) ;
47- action ? . Invoke ( viewHandler , virtualView ) ;
78+ internal bool TryUpdatePropertyCore ( string key , IElementHandler viewHandler , IElement virtualView )
79+ {
80+ if ( MergedMappers . TryGetValue ( key , out var action ) )
81+ {
82+ action ( viewHandler , virtualView ) ;
83+ return true ;
84+ }
85+
86+ return false ;
4887 }
4988
5089 public virtual Action < IElementHandler , IElement > ? GetProperty ( string key )
5190 {
5291 if ( _mapper . TryGetValue ( key , out var action ) )
92+ {
5393 return action ;
54- else if ( Chained is not null )
94+ }
95+
96+ if ( Chained is not null )
5597 {
5698 foreach ( var ch in Chained )
5799 {
58100 var returnValue = ch . GetProperty ( key ) ;
59101 if ( returnValue != null )
102+ {
60103 return returnValue ;
104+ }
61105 }
62106 }
63107
@@ -66,20 +110,36 @@ protected virtual void UpdatePropertyCore(string key, IElementHandler viewHandle
66110
67111 public void UpdateProperty ( IElementHandler viewHandler , IElement ? virtualView , string property )
68112 {
69- if ( virtualView == null )
113+ if ( virtualView == null || ! viewHandler . CanInvokeMappers ( ) )
114+ {
70115 return ;
116+ }
71117
72- UpdatePropertyCore ( property , viewHandler , virtualView ) ;
118+ if ( MergedMappers . TryGetValue ( property , out var action ) )
119+ {
120+ action ( viewHandler , virtualView ) ;
121+ }
73122 }
74123
75124 public void UpdateProperties ( IElementHandler viewHandler , IElement ? virtualView )
76125 {
77- if ( virtualView == null )
126+ if ( virtualView == null || ! viewHandler . CanInvokeMappers ( ) )
127+ {
78128 return ;
129+ }
79130
80- foreach ( var key in UpdateKeys )
131+ foreach ( var mapper in MergedMappers )
81132 {
82- UpdatePropertyCore ( key , viewHandler , virtualView ) ;
133+ #if ANDROID
134+ // When doing the initial properties batch updates, ignore properties in the skip list.
135+ // These will be handled by ViewHandler.SetVirtualView() instead.
136+ if ( UpdatePropertiesSkipList . Contains ( mapper . Key ) )
137+ {
138+ continue ;
139+ }
140+ #endif
141+
142+ mapper . Value ( viewHandler , virtualView ) ;
83143 }
84144 }
85145
@@ -93,35 +153,55 @@ public IPropertyMapper[]? Chained
93153 }
94154 }
95155
96- private HashSet < string > PopulateKeys ( )
97- {
98- var keys = new HashSet < string > ( StringComparer . Ordinal ) ;
99- foreach ( var key in GetKeys ( ) )
100- {
101- keys . Add ( key ) ;
102- }
103- return keys ;
104- }
105-
156+ // TODO: Make private in .NET10 with a new name: ClearMergedMappers
106157 protected virtual void ClearKeyCache ( )
107158 {
108- _updateKeys = null ;
159+ _mergedMappers = null ;
160+ _mergedKeys = null ;
109161 }
110162
111- public virtual IReadOnlyCollection < string > UpdateKeys => _updateKeys ??= PopulateKeys ( ) ;
163+ // TODO: Remove in .NET10
164+ public virtual IReadOnlyCollection < string > UpdateKeys => MergedKeys ;
112165
113166 public virtual IEnumerable < string > GetKeys ( )
114167 {
115- foreach ( var key in _mapper . Keys )
116- yield return key ;
117-
168+ // We want to retain the initial order of the keys to avoid race conditions
169+ // when a property mapping is overridden by a new instance of property mapper.
170+ // As an example, the container view mapper should always run first.
171+ // Siblings mapper should not have keys intersection.
118172 if ( Chained is not null )
119173 {
120- foreach ( var chain in Chained )
121- foreach ( var key in chain . GetKeys ( ) )
174+ for ( int i = Chained . Length - 1 ; i >= 0 ; i -- )
175+ {
176+ foreach ( var key in Chained [ i ] . GetKeys ( ) )
177+ {
122178 yield return key ;
179+ }
180+ }
181+ }
182+
183+ // Enqueue keys from this mapper.
184+ foreach ( var mapper in _mapper )
185+ {
186+ yield return mapper . Key ;
123187 }
124188 }
189+
190+ private ( List < string > Keys , Dictionary < string , Action < IElementHandler , IElement > > Mappers ) SnapshotMappers ( )
191+ {
192+ var keys = GetKeys ( ) . Distinct ( ) . ToList ( ) ;
193+
194+ var mappers = new Dictionary < string , Action < IElementHandler , IElement > > ( keys . Count ) ;
195+ foreach ( var key in keys )
196+ {
197+ mappers [ key ] = GetProperty ( key ) ! ;
198+ }
199+
200+ _mergedKeys = keys ;
201+ _mergedMappers = mappers ;
202+
203+ return ( keys , mappers ) ;
204+ }
125205 }
126206
127207 public interface IPropertyMapper
@@ -169,12 +249,22 @@ public void Add(string key, Action<TViewHandler, TVirtualView> action) =>
169249 SetPropertyCore ( key , ( h , v ) =>
170250 {
171251 if ( v is TVirtualView vv )
252+ {
172253 action ? . Invoke ( ( TViewHandler ) h , vv ) ;
254+ }
173255 else if ( Chained != null )
174256 {
175257 foreach ( var chain in Chained )
176258 {
177- if ( chain . GetProperty ( key ) != null )
259+ // Try to leverage our internal method which uses merged mappers
260+ if ( chain is PropertyMapper propertyMapper )
261+ {
262+ if ( propertyMapper . TryUpdatePropertyCore ( key , h , v ) )
263+ {
264+ break ;
265+ }
266+ }
267+ else if ( chain . GetProperty ( key ) != null )
178268 {
179269 chain . UpdateProperty ( h , v , key ) ;
180270 break ;
0 commit comments