1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Linq ;
3
4
4
5
#if IOS || MACCATALYST
5
6
using PlatformView = UIKit . UIView ;
@@ -15,13 +16,37 @@ namespace Microsoft.Maui
15
16
{
16
17
public abstract class PropertyMapper : IPropertyMapper
17
18
{
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
18
41
protected readonly Dictionary < string , Action < IElementHandler , IElement > > _mapper = new ( StringComparer . Ordinal ) ;
19
42
20
43
IPropertyMapper [ ] ? _chained ;
21
44
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 ;
25
50
26
51
public PropertyMapper ( )
27
52
{
@@ -35,29 +60,48 @@ public PropertyMapper(params IPropertyMapper[]? chained)
35
60
protected virtual void SetPropertyCore ( string key , Action < IElementHandler , IElement > action )
36
61
{
37
62
_mapper [ key ] = action ;
63
+
38
64
ClearKeyCache ( ) ;
39
65
}
40
66
67
+ // TODO: Remove in .NET10
41
68
protected virtual void UpdatePropertyCore ( string key , IElementHandler viewHandler , IElement virtualView )
42
69
{
43
70
if ( ! viewHandler . CanInvokeMappers ( ) )
71
+ {
44
72
return ;
73
+ }
74
+
75
+ TryUpdatePropertyCore ( key , viewHandler , virtualView ) ;
76
+ }
45
77
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 ;
48
87
}
49
88
50
89
public virtual Action < IElementHandler , IElement > ? GetProperty ( string key )
51
90
{
52
91
if ( _mapper . TryGetValue ( key , out var action ) )
92
+ {
53
93
return action ;
54
- else if ( Chained is not null )
94
+ }
95
+
96
+ if ( Chained is not null )
55
97
{
56
98
foreach ( var ch in Chained )
57
99
{
58
100
var returnValue = ch . GetProperty ( key ) ;
59
101
if ( returnValue != null )
102
+ {
60
103
return returnValue ;
104
+ }
61
105
}
62
106
}
63
107
@@ -66,20 +110,36 @@ protected virtual void UpdatePropertyCore(string key, IElementHandler viewHandle
66
110
67
111
public void UpdateProperty ( IElementHandler viewHandler , IElement ? virtualView , string property )
68
112
{
69
- if ( virtualView == null )
113
+ if ( virtualView == null || ! viewHandler . CanInvokeMappers ( ) )
114
+ {
70
115
return ;
116
+ }
71
117
72
- UpdatePropertyCore ( property , viewHandler , virtualView ) ;
118
+ if ( MergedMappers . TryGetValue ( property , out var action ) )
119
+ {
120
+ action ( viewHandler , virtualView ) ;
121
+ }
73
122
}
74
123
75
124
public void UpdateProperties ( IElementHandler viewHandler , IElement ? virtualView )
76
125
{
77
- if ( virtualView == null )
126
+ if ( virtualView == null || ! viewHandler . CanInvokeMappers ( ) )
127
+ {
78
128
return ;
129
+ }
79
130
80
- foreach ( var key in UpdateKeys )
131
+ foreach ( var mapper in MergedMappers )
81
132
{
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 ) ;
83
143
}
84
144
}
85
145
@@ -93,35 +153,55 @@ public IPropertyMapper[]? Chained
93
153
}
94
154
}
95
155
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
106
157
protected virtual void ClearKeyCache ( )
107
158
{
108
- _updateKeys = null ;
159
+ _mergedMappers = null ;
160
+ _mergedKeys = null ;
109
161
}
110
162
111
- public virtual IReadOnlyCollection < string > UpdateKeys => _updateKeys ??= PopulateKeys ( ) ;
163
+ // TODO: Remove in .NET10
164
+ public virtual IReadOnlyCollection < string > UpdateKeys => MergedKeys ;
112
165
113
166
public virtual IEnumerable < string > GetKeys ( )
114
167
{
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.
118
172
if ( Chained is not null )
119
173
{
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
+ {
122
178
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 ;
123
187
}
124
188
}
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
+ }
125
205
}
126
206
127
207
public interface IPropertyMapper
@@ -169,12 +249,22 @@ public void Add(string key, Action<TViewHandler, TVirtualView> action) =>
169
249
SetPropertyCore ( key , ( h , v ) =>
170
250
{
171
251
if ( v is TVirtualView vv )
252
+ {
172
253
action ? . Invoke ( ( TViewHandler ) h , vv ) ;
254
+ }
173
255
else if ( Chained != null )
174
256
{
175
257
foreach ( var chain in Chained )
176
258
{
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 )
178
268
{
179
269
chain . UpdateProperty ( h , v , key ) ;
180
270
break ;
0 commit comments