From f7ba5af6d1fbcba6003bdc75dbb48e7ecfcd3044 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=EC=9C=A0=EB=A6=AC=EB=82=98/Common=20Platform=20Lab=28SR=29?= =?utf8?q?/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Mon, 23 Mar 2020 13:54:59 +0900 Subject: [PATCH] Synchronize the XSF with Xamarin.Form (#159) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * [Core] Removed null values from the DisplayActionSheet (#8445) fixes #4744 * Fixed IndicatorView issue resetting the template when data updates (#8709) * Fixed IndicatorView template resets when data updates * Fixed failing UITests * Undo unnecessary changes in the ControlGallery Android csproj * Removed unnecessary changes in the ControlGallery Android csproj * Fix wrong ControlGallery Android csproj * [Tizen] Implement DisplayPromptAsync for Tizen (#8820) fixes #6713 * Implement DisplayPromptAsync in Tizen * Update Xamarin.Forms.Platform.Tizen/Platform.cs Fix BackButtonPressed handler Co-Authored-By: campersau * Queue DisplayPromptAsync for display after pages loads; fixes #8526 (#8788) fixes #8526 * Custom/Embedded fonts (#6013) * Android fonts now work with shorter notation "Lobster-Regular.ttf#Lobster-Regular" now works as just "Lobster-Regular" * iOS Sample page now loads * UWP now uses simple font names * Fixed UWP Font loading! UWP now supports `PTM55FT#PTMono` * Added UWP Font Caching! * iOS now supports Custom fonts like `PTM55FT#PTMono` * Android now supports all the font formats! * Add new styles to show how they can work * Android fonts now work with shorter notation "Lobster-Regular.ttf#Lobster-Regular" now works as just "Lobster-Regular" * iOS Sample page now loads * UWP now uses simple font names * Fixed UWP Font loading! UWP now supports `PTM55FT#PTMono` * Added UWP Font Caching! * iOS now supports Custom fonts like `PTM55FT#PTMono` * Android now supports all the font formats! * Add new styles to show how they can work * Fixed iOS Font Loading * iOS now can load embedded resources! * Android can now load from embedded resources * Moved FontFile parsing to Xamarin.Forms.Core, Added Unit Tests! * IEmeddedFont now returns the path on success. * removed unused code * Embedded fonts now load in UWP * Fixed crash in fonts * changed the String.Contains to a string, instead of char to make VS windows happy * netStandard1.0 won't let me have nice things * grammer fixes :D * smidgen of cleanup * fix merge issues * Update Registrar.cs * [Tizen] Creates the GestureDetector on demand (#8874) * [Xaml] Throw XamlParseException in MarkupExpressionParser.GetNextPiece (#8447) * [XamlC] Support variance (#8535) * [Xaml[C]] Accept prefixed property names of markup extensions (#5186) * [XamlC] Dispose assembly resolver constructed by XamlCTask (#8397) An assembly resolver holds opened files, and leaks if it is not disposed. It is problematic if you run Xamarin.Forms.Xaml.UnitTests on Linux with the default configuration because it has a small limit for the number of opened files. It may cause stability issues also on other platforms, depending on the situation. * Typo Fix and change to nameof (#8942) fixes #8935 * Improve resiliency of Shell when removing/adding items (#9023) * Add IsVisible property to ShellItem/ShellSection/ShellContent * - fix formatting * - simplify interface * - fix local deploy * -fixes * - work * - check visibility after parent is set * - fix formatting * - embedding fix * - menu item not getting added to Visible Items - Process Items change on Visible Items * - fix incorrect cast * - fix casting * - fix route testing to not invlude not visible * - unsubscribe * - set shell variable * - remove IsVisible from BaseShellItem * - fix failing unit test * [Xaml] Create value from positional argument text of markup extension (#8980) The behavior introduced with this change conforms to the description of [MS-XAML-2009] page 80 and 81. fixes #6905 * [C,X] Resolve indexers on base class (#8968) - fixes #8936 this was regressed by #7836 * Resolve issues with Shell ToolbarItems not reacting to CanExecute Changes (#8889) fixes #8741 fixes #8327 * Fixing various toolbar item display issues * - fix csproj file * - added UI test * - fix possible breaks with Navigation Page * Fix for 8741: Assert that ToolbarItem is grayed-out (PR 8889) (#8892) * Assert that ToolbarItem is grayed-out * Check alpha values on Android * - fix toolbar item ordering * - toolbar tracker improvements * - remove whitespace * - just use an array from the get go * - primary Co-authored-by: Brian Runck * [Tizen] Fix back button issue in Shell navigation (#8982) * [Tizen] Fix back button issue in Shell navigation * [Tizen] Add navigation/back button click effect * Enhance LightweightPlatform (#8873) * [Tizen] Gif Animation Support (#9068) fixes #1704 * [core] perf improvements for Assembly attributes reflection (#8938) When profiling startup, I started looking into the number of calls retrieving custom attributes from assemblies: Method call summary Total(ms) Self(ms) Calls Method name 24 0 301 System.Reflection.RuntimeAssembly:GetCustomAttributes (System.Type,bool) I saw a pattern such as: object[] attributes = assembly.GetCustomAttributes(attrType, true); var handlerAttributes = new HandlerAttribute[attributes.Length]; Array.Copy(attributes, handlerAttributes, attributes.Length); RegisterRenderers(handlerAttributes); We can avoid the allocation and copying of the array completely here. We can simply cast `attributes` to `HandlerAttribute[]`. The other thing I saw was: string resolutionName = assembly.FullName; var resolutionNameAttribute = (ResolutionGroupNameAttribute)assembly.GetCustomAttribute(typeof(ResolutionGroupNameAttribute)); if (resolutionNameAttribute != null) resolutionName = resolutionNameAttribute.ShortName; This was happening even if the assembly had no `[assembly:Effect]`. I reordered the code to not look for `[assembly: ResolutionGroupName]` unless there was an `[assembly: Export]`. This reduced the calls to `GetCustomAttributes` to: Method call summary Total(ms) Self(ms) Calls Method name 21 0 251 System.Reflection.RuntimeAssembly:GetCustomAttributes (System.Type,bool) I also did a small amount of refactoring: * `ReflectionExtensions.GetCustomAttributesSafe` had a variable declaration that could be removed. * `ReflectionExtensions.GetCustomAttributesSafe` was a nice wrapper to avoid `#if` and also has a `try-catch` for the previewer. I used this in places where there was duplicated code. ~~ Results ~~ Before: Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms 12-17 13:08:57.119 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +588ms 12-17 13:09:00.852 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +583ms 12-17 13:09:04.602 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +573ms 12-17 13:09:08.388 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +581ms 12-17 13:09:12.137 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +576ms 12-17 13:09:15.887 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +583ms 12-17 13:09:19.621 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +578ms 12-17 13:09:23.388 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +588ms 12-17 13:09:27.123 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +587ms 12-17 13:09:30.892 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +571ms Average(ms): 580.8 Std Err(ms): 1.94250697124446 Std Dev(ms): 6.1427463998877 After: Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms Launching: com.xamarin.forms.helloforms 12-17 13:10:29.762 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +579ms 12-17 13:10:33.514 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +572ms 12-17 13:10:37.263 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +564ms 12-17 13:10:40.996 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +572ms 12-17 13:10:44.748 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +569ms 12-17 13:10:48.467 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +573ms 12-17 13:10:52.231 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +577ms 12-17 13:10:55.981 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +558ms 12-17 13:10:59.765 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +574ms 12-17 13:11:03.499 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +569ms Average(ms): 570.7 Std Err(ms): 1.94393644157644 Std Dev(ms): 6.1472667819844 I think this saves ~10ms on startup on Android, but this should help all platforms. These numbers were taken running on a Pixel 3 XL, a Blank Xamarin.Forms app template using Xamarin.Forms/master. Using: https://github.com/xamarin/Xamarin.Forms/pull/8867 * [core] improve Color & Clamp performance (#8884) * [core] improve Color & Clamp performance When profiling startup of a HelloForms app on a Pixel 3 XL, I noticed: Total(ms) Self(ms) Calls Method name 3 1 572 Xamarin.Forms.Internals.NumericExtensions:Clamp (double,double,double) This method is called multiple times for every `Color` created and ~142 are created on startup. This is why it shows up 572 times for an app with a single `Label`. I found there is a `Math.Clamp` implementation in corefx: https://github.com/dotnet/runtime/blob/6662a0f2fd05298af1f9b1b020fa526595f336f7/src/libraries/System.Private.CoreLib/src/System/Math.cs#L224-L225 The only difference is this version can throw an exception, so we could return the incoming value instead. That would be bad to change! If I rework `NumericExtensions` to use corefx's implementation it is a bit faster, I did a BenchmarkDotNet comparison running with Mono on macOS: Method | Mean | Error | StdDev | ------- |----------:|---------:|---------:| Clamp2 | 53.89 ns | 0.668 ns | 0.522 ns | Clamp1 | 61.84 ns | 1.270 ns | 2.289 ns | Color2 | 112.50 ns | 2.643 ns | 3.705 ns | Color1 | 129.03 ns | 2.603 ns | 4.760 ns | Maybe every `Color` is ~13% faster? Code for the benchmark is here: https://github.com/jonathanpeppers/Benchmarks/blob/clamp/Benchmarks/Clamp.cs Additionally, I reworked the list of default `Color` fields so that they call a new private constructor that does less math and avoids `Clamp` completely. We should still keep the original change, as it would help any cases where the `Color.To*` methods would be used in apps. I seem to be able to see a small difference in a Release build running on a Pixel 3 XL: Before: 12-18 13:04:27.154 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +606ms 12-18 13:04:30.851 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +589ms 12-18 13:04:34.601 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +587ms 12-18 13:04:38.352 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +575ms 12-18 13:04:42.084 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +583ms 12-18 13:04:45.802 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +573ms 12-18 13:04:49.566 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +592ms 12-18 13:04:53.284 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +583ms 12-18 13:04:57.015 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +594ms 12-18 13:05:00.715 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +581ms Average(ms): 586.3 Std Err(ms): 3.05886689260364 Std Dev(ms): 9.67298643990917 After: 12-18 13:08:16.677 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +593ms 12-18 13:08:20.377 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +577ms 12-18 13:08:24.107 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +583ms 12-18 13:08:27.827 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +576ms 12-18 13:08:31.574 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +586ms 12-18 13:08:35.324 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +584ms 12-18 13:08:39.056 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +587ms 12-18 13:08:42.773 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +586ms 12-18 13:08:46.523 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +584ms 12-18 13:08:50.256 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +587ms Average(ms): 584.3 Std Err(ms): 1.56382721409865 Std Dev(ms): 4.94525586350753 This change seems low risk and would help all platforms. * One last tweak byte -> int Doing some reading: https://stackoverflow.com/a/43158214/132442 It seems `int` performs even better than `byte`. I did another test run with just this change: Before: 12-18 13:37:23.347 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +576ms 12-18 13:37:27.079 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +575ms 12-18 13:37:30.828 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +581ms 12-18 13:37:34.578 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +588ms 12-18 13:37:38.296 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +572ms 12-18 13:37:42.046 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +579ms 12-18 13:37:45.781 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +576ms 12-18 13:37:49.526 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +586ms 12-18 13:37:53.276 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +586ms 12-18 13:37:57.009 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +582ms Average(ms): 580.1 Std Err(ms): 1.70912583243924 Std Dev(ms): 5.40473043833928 After: 12-18 13:35:38.745 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +572ms 12-18 13:35:42.459 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +572ms 12-18 13:35:46.209 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +581ms 12-18 13:35:49.974 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +581ms 12-18 13:35:53.724 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +574ms 12-18 13:35:57.474 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +580ms 12-18 13:36:01.207 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +573ms 12-18 13:36:04.957 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +568ms 12-18 13:36:08.707 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +566ms 12-18 13:36:12.407 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +565ms Average(ms): 573.2 Std Err(ms): 1.87853370714738 Std Dev(ms): 5.94044517598546 * Fixed the conflict of Registrar.cs * Detect when pages are popped from clicking on tab (#9086) * Fixed the conflict shell * send remove events (#9124) * [Tizen] Shell: FlyoutBackgroundImage, FlyoutBackgroundImageAspect (#8905) * [Tizen] Add BackgroundImage properties in NavigationView * [Tizen] Update INavigationView interface * [Tizen] Remove unused attribute and namespace * [Tizen] Update MaterialNavigationView fixes #4410 * Added IconColor property for managing navigation icon color (#5185) Co-authored-by: Samantha Houts * [android/ios] improve perf when not using Application.Properties (#8887) * [android/ios] improve perf when not using Application.Properties When profiling startup, I was seeing things like: Method call summary Total(ms) Self(ms) Calls Method name 52 0 3 System.IO.IsolatedStorage.IsolatedStorageFile:GetUserStoreForApplication () 52 0 3 (wrapper remoting-invoke-with-check) System.IO.IsolatedStorage.IsolatedStorageFile:PostInit () 51 0 3 System.IO.IsolatedStorage.IsolatedStorageFile:PostInit () 51 0 2 (wrapper remoting-invoke-with-check) System.IO.IsolatedStorage.IsolatedStorageFile:FileExists (string) 51 0 2 System.IO.IsolatedStorage.IsolatedStorageFile:FileExists (string) 49 0 4 System.IO.IsolatedStorage.IsolatedStorageFile:IsPathInStorage (string) This app has no `Properties` at all, but it seems to still be doing the file I/O for it. I found that a file was being written that contain a serialized empty dictionary: > adb shell run-as com.xamarin.forms.helloforms cat /data/user/0/com.xamarin.forms.helloforms/files/.config/.isolated-storage/PropertyStore.forms @▲ArrayOfKeyValueOfstringanyTyp9http://schemas.microsoft.com/2003/10/Serialization/Arrays ☺i)http://www.w3.org/2001/XMLSchema-instance☺ Any subsequent startup would parse this file. Two changes will help here: 1) For writes, use a single instance of: using (var store = IsolatedStorageFile.GetUserStoreForApplication()) It appears this call is expensive. 2) On writes, we don't need to write a file at all if the `Properties` dictionary is empty and no file is on disk. This way apps that don't use `Properties` at all won't load a file with an empty dictionary. ~~ Results ~~ Using a Release build of the Blank Forms app template on a Pixel 3 XL: Before: 12-13 14:01:42.826 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +606ms 12-13 14:01:46.541 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +588ms 12-13 14:01:50.325 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +597ms 12-13 14:01:54.088 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +600ms 12-13 14:01:57.855 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +579ms 12-13 14:02:01.619 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +594ms 12-13 14:02:05.371 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +605ms 12-13 14:02:09.106 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +597ms 12-13 14:02:12.869 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +600ms 12-13 14:02:16.635 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +601ms Average(ms): 596.7 Std Err(ms): 2.56493448042808 Std Dev(ms): 8.11103500725332 After: 12-13 13:59:46.260 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +600ms 12-13 13:59:49.993 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +579ms 12-13 13:59:53.739 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +587ms 12-13 13:59:57.506 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +592ms 12-13 14:00:01.255 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +586ms 12-13 14:00:05.007 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +580ms 12-13 14:00:08.841 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +581ms 12-13 14:00:12.587 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +591ms 12-13 14:00:16.320 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +585ms 12-13 14:00:20.052 1490 1517 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +577ms Average(ms): 585.8 Std Err(ms): 2.23507394856536 Std Dev(ms): 7.06792441637257 I think this change saves ~10ms on startup for apps that don't use `Application.Properties` at all. I made this change for Android & iOS, as they had similar implementations. * Moved the properties check earlier This way it won't even open the Stream on writes when there are no properties. * [tizen] include the same optimization As suggested by @rookiejava: https://github.com/xamarin/Xamarin.Forms/pull/8887#pullrequestreview-332378887 * [tizen] make implementation match Android exactly Context: https://github.com/xamarin/Xamarin.Forms/pull/8887#discussion_r359089505 As suggested by @rookiejava, we can make the implementation match other platforms exactly. I took the changes as-is, except matched the formatting in Android's `Deserializer.cs` so the two files are identical except for the namespace. I also had to use `Internals.Log`. * Add null check to GetIconColor (#9172) * [Tizen] Supports Custom/Embedded and System fonts (#9138) * Update GitInfo.txt * fix Profile FrameEnd call (#9117) * Fix SeachBarRenderer CreateNativeControl issue (#8946) * [Tizen] Supports Custom/Embedded and System fonts Co-authored-by: Samantha Houts Co-authored-by: Shane Neuville Co-authored-by: Rui Marinho Co-authored-by: TingtingAn * [Core] performance improvements around bindings (#9114) I made most of these changes through the benchmarks here: https://github.com/jonathanpeppers/Benchmarks/blob/6725a72844c5f18320a31223b6f37abd7b28ece4/Benchmarks/BindingBenchmarks.cs 1. `Clone()` was doing `new Binding()` but then using the property setters instead of the ctor parameters. `Clone()` seems to be called for `DataTemplate`, `ListView`, etc. 2. `BindingBase.ThrowIfApplied()` is called very frequently, so I added `MethodImplOptions.AggressiveInlining`. 3. `BindingBaseExtensions.GetRealizedMode` is called ~3 times per binding applied, added `MethodImplOptions.AggressiveInlining`. 4. In `BindingExpression` a `bool isLast` value was always calculated, even though it was only sometimes used inside an `if`. It can just do this check inside the `if`. 5. `object value = property.DefaultValue;` was defined prior to calling `TryGetValue`, which will always overwrite it. I just removed the call, since a Roslyn analyzer was showing me this. 6. `BindingExpression` was doing a `string.Split('.')` which always allocates a `char[]`. I defined the array as a `static` field instead. 7. `BindingExpression.GetPart` used `IEnumerable` and `yield return`. In the most common case, this function returned a single item and in the rare case two items. I removed this function and a `foreach` and moved logic inline within the `ParsePath` method. This avoids allocations around `foreach`, when it isn't needed. These are all small changes, but I was only able to see some difference after everything came together. If we need to split some of these up, we can do that. ~~ Results ~~ Benchmarks running on mono/macOS: BenchmarkDotNet=v0.11.3, OS=macOS Mojave 10.14.6 (18G95) [Darwin 18.7.0] Intel Core i7-6567U CPU 3.30GHz (Skylake), 1 CPU, 4 logical and 2 physical cores [Host] : Mono 6.6.0.155 (2019-08/296a9afdb24 Thu), 64bit DefaultJob : Mono 6.6.0.155 (2019-08/296a9afdb24 Thu), 64bit Method | Mean | Error | StdDev | Median | --------------- |------------:|-----------:|-----------:|------------:| CtorSingle2 | 355.9 ns | 2.600 ns | 2.432 ns | 356.1 ns | CtorSingle1 | 447.0 ns | 28.825 ns | 29.601 ns | 432.4 ns | CtorMultiple2 | 1,316.6 ns | 7.092 ns | 6.287 ns | 1,316.6 ns | CtorMultiple1 | 1,611.0 ns | 77.914 ns | 109.225 ns | 1,560.9 ns | Clone2 | 2,670.6 ns | 38.075 ns | 33.753 ns | 2,655.7 ns | Clone1 | 3,154.3 ns | 15.010 ns | 14.040 ns | 3,151.7 ns | ApplySingle2 | 8,065.5 ns | 87.537 ns | 77.599 ns | 8,071.3 ns | ApplySingle1 | 8,555.2 ns | 183.373 ns | 508.126 ns | 8,268.9 ns | ApplyMultiple2 | 28,933.1 ns | 252.589 ns | 236.272 ns | 28,937.1 ns | ApplyMultiple1 | 29,388.9 ns | 269.263 ns | 251.868 ns | 29,296.0 ns | This appears to save 0.5-1ms per binding applied. It is also somewhat concerning that this shows a complex binding takes ~30ms on mono? I will look into that further. Benchmarks running on Windows/.NET framework, allow us to see the memory usage as well (memory usage not implemented in BDN on mono): BenchmarkDotNet=v0.11.3, OS=Windows 10.0.18362 Intel Core i9-9900K CPU 3.60GHz, 1 CPU, 16 logical and 8 physical cores [Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.8.4075.0 DefaultJob : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.8.4075.0 Method | Mean | Error | StdDev | Allocated Memory/Op | --------------- |-----------:|-----------:|-----------:|--------------------:| CtorSingle2 | 122.6 ns | 0.5047 ns | 0.4721 ns | 369 B | CtorSingle1 | 156.3 ns | 0.5229 ns | 0.4636 ns | 421 B | CtorMultiple2 | 337.7 ns | 2.5286 ns | 2.3652 ns | 857 B | CtorMultiple1 | 435.4 ns | 1.0472 ns | 0.9796 ns | 1017 B | Clone2 | 667.0 ns | 2.5739 ns | 2.0095 ns | 1715 B | Clone1 | 890.6 ns | 9.1459 ns | 8.5551 ns | 2035 B | ApplySingle2 | 1,512.4 ns | 9.0686 ns | 7.0802 ns | 521 B | ApplySingle1 | 1,655.3 ns | 31.1960 ns | 35.9253 ns | 573 B | ApplyMultiple2 | 4,501.1 ns | 21.8014 ns | 20.3930 ns | 1394 B | ApplyMultiple1 | 4,510.7 ns | 25.4709 ns | 22.5793 ns | 1554 B | This looks like it saves ~160 bytes of allocations per binding applied. I also tested the Blank Forms app template after adding 100 `Label` with a single binding: Before: 01-13 16:23:09.406 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +746ms 01-13 16:23:13.103 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +716ms 01-13 16:23:16.888 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +732ms 01-13 16:23:20.656 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +728ms 01-13 16:23:24.390 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +726ms 01-13 16:23:28.192 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +723ms 01-13 16:23:31.910 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +725ms 01-13 16:23:35.660 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +726ms 01-13 16:23:39.410 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +728ms 01-13 16:23:43.126 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +724ms Average(ms): 727.4 Std Err(ms): 2.44585817704589 Std Dev(ms): 7.73448267321236 After: 01-13 16:26:21.557 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +732ms 01-13 16:26:25.294 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +716ms 01-13 16:26:29.042 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +736ms 01-13 16:26:32.760 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +721ms 01-13 16:26:36.490 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +722ms 01-13 16:26:40.223 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +722ms 01-13 16:26:43.957 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +724ms 01-13 16:26:47.726 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +718ms 01-13 16:26:51.476 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +722ms 01-13 16:26:55.224 1473 1503 I ActivityTaskManager: Displayed com.xamarin.forms.helloforms/crc6450e568c951913723.MainActivity: +724ms Average(ms): 723.7 Std Err(ms): 1.90933379888262 Std Dev(ms): 6.0378436180109 In this scenario the changes seems to save ~3.7ms to overall startup. The code for this project is here: https://github.com/jonathanpeppers/Xamarin.Forms/commit/a319b6709894379b5d08703f80238e5ef5a92ba0 I timed a Release build running on a Pixel 3 XL. * Fixed the rounding issue when the stepper value is incremented. (#7383) * Fixed the rounding issue when the stepper value is incremented and the increment value is small like say 0.5 per step. This is due to Double being used as the value so when it's unboxed from the binding object precision is lost. * Updated so that it's not using Math.Round as this will limit the number of decimal places in code, this version will use an internal counter to track the number of clicks the user makes and calculate a clean value using the counter times the increment. Tests updated as well to test for stepping away from and back to zero... * fixed issue with tests failing which was caused by the use of (int) causing a rounding down error... * Update Xamarin.Forms.Core/Stepper.cs Co-Authored-By: Gerald Versluis * Updated to use nameof() instead of strings in teh creation of the new BindableProperties... * Changed the Bindable StepperPositionProperty from being public to private so that there is not an API change with this PR as suggested by Sam Houts.Once this PR is accepted it may be an idea to revert this change so that the Positon Property can be controled and used as it's an int saves the need to work with doubles for calculating the position... Co-authored-by: Samantha Houts Co-authored-by: Gerald Versluis fixes #5168 * Fixed the build issue * Avoid NRE removing WeakReferences (and remove null ones) (#9195) fixes #9183 * [CSS] reapply Stylesheet on StyleClass changes (#9157) - fixes #2678 * [HR] complement the sourceinfo with assemblyname (#9294) --- .../ExpandMarkupsVisitor.cs | 96 ++++-- .../TypeReferenceExtensions.cs | 67 ++-- .../Xamarin.Forms.Build.Tasks/XamlCTask.cs | 346 +++++++++++---------- .../Xamarin.Forms.Core/ActionSheetArguments.cs | 3 +- Xamarin.Forms/Xamarin.Forms.Core/Binding.cs | 6 +- Xamarin.Forms/Xamarin.Forms.Core/BindingBase.cs | 1 + .../Xamarin.Forms.Core/BindingBaseExtensions.cs | 5 +- .../Xamarin.Forms.Core/BindingExpression.cs | 86 +++-- Xamarin.Forms/Xamarin.Forms.Core/Color.cs | 304 +++++++++--------- .../Xamarin.Forms.Core/DependencyService.cs | 24 +- .../Xamarin.Forms.Core/Element_StyleSheets.cs | 2 +- Xamarin.Forms/Xamarin.Forms.Core/EmbeddedFont.cs | 11 + .../Xamarin.Forms.Core/ExportFontAttribute.cs | 16 + Xamarin.Forms/Xamarin.Forms.Core/FontFile.cs | 108 +++++++ Xamarin.Forms/Xamarin.Forms.Core/FontRegistrar.cs | 71 +++++ .../Xamarin.Forms.Core/IEmbeddedFontLoader.cs | 8 + Xamarin.Forms/Xamarin.Forms.Core/IndicatorView.cs | 7 +- .../Interactivity/AttachedCollection.cs | 2 +- .../Xamarin.Forms.Core/Internals/ToolbarTracker.cs | 28 +- Xamarin.Forms/Xamarin.Forms.Core/MergedStyle.cs | 6 +- Xamarin.Forms/Xamarin.Forms.Core/NavigationPage.cs | 17 + .../Xamarin.Forms.Core/NumericExtensions.cs | 34 +- Xamarin.Forms/Xamarin.Forms.Core/Page.cs | 7 +- .../Xamarin.Forms.Core/ReflectionExtensions.cs | 7 +- Xamarin.Forms/Xamarin.Forms.Core/Registrar.cs | 47 +-- .../Shell/IShellContentController.cs | 5 +- .../Xamarin.Forms.Core/Shell/IShellController.cs | 6 + .../Shell/IShellItemController.cs | 8 +- .../Shell/IShellSectionController.cs | 20 +- .../Xamarin.Forms.Core/Shell/NavigableElement.cs | 17 +- .../Xamarin.Forms.Core/Shell/PresentationMode.cs | 16 + Xamarin.Forms/Xamarin.Forms.Core/Shell/Shell.cs | 208 +++++++++---- .../Xamarin.Forms.Core/Shell/ShellContent.cs | 34 +- .../Shell/ShellContentCollection.cs | 92 +++++- .../Xamarin.Forms.Core/Shell/ShellExtensions.cs | 15 +- .../Xamarin.Forms.Core/Shell/ShellItem.cs | 22 +- .../Shell/ShellItemCollection.cs | 132 +++++++- .../Xamarin.Forms.Core/Shell/ShellSection.cs | 282 +++++++++++++++-- .../Shell/ShellSectionCollection.cs | 117 ++++++- .../Xamarin.Forms.Core/Shell/ShellUriHandler.cs | 12 +- Xamarin.Forms/Xamarin.Forms.Core/StackLayout.cs | 10 +- Xamarin.Forms/Xamarin.Forms.Core/Stepper.cs | 21 +- .../Xamarin.Forms.Core/TabIndexExtensions.cs | 4 +- .../Xaml/Diagnostics/VisualDiagnostics.cs | 2 +- .../Shell/MaterialNavigationView.cs | 47 +++ .../Xamarin.Forms.Material.Tizen.csproj | 2 +- .../Xamarin.Forms.Platform.Tizen/Deserializer.cs | 115 ++++--- .../EmbeddedFontLoader.cs | 41 +++ .../Extensions/FontExtensions.cs | 50 +++ .../Extensions/ImageExtensions.cs | 27 ++ .../Xamarin.Forms.Platform.Tizen/Forms.cs | 10 +- .../LightweightPlatform.cs | 217 ++++++++++--- .../Xamarin.Forms.Platform.Tizen/Native/Image.cs | 31 -- .../Xamarin.Forms.Platform.Tizen/Platform.cs | 201 +----------- .../Xamarin.Forms.Platform.Tizen/PopupManager.cs | 295 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 2 + .../Renderers/ButtonRenderer.cs | 2 +- .../Renderers/DatePickerRenderer.cs | 2 +- .../Renderers/EditorRenderer.cs | 2 +- .../Renderers/EntryRenderer.cs | 2 +- .../Renderers/ImageRenderer.cs | 20 +- .../Renderers/LabelRenderer.cs | 2 +- .../Renderers/SearchBarRenderer.cs | 2 +- .../Renderers/TimePickerRenderer.cs | 2 +- .../Shell/INavigationView.cs | 6 +- .../Shell/NavigationView.cs | 79 ++++- .../Shell/ShellNavBar.cs | 37 ++- .../Shell/ShellRenderer.cs | 13 +- .../Shell/ShellSectionNavigation.cs | 2 +- .../StaticRegistrar.cs | 9 +- .../Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs | 6 +- .../Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs | 107 ++++--- .../Xamarin.Forms.Xaml/MarkupExpressionParser.cs | 91 +++--- .../Xamarin.Forms.Xaml/MarkupExtensionParser.cs | 11 +- Xamarin.Forms/Xamarin.Forms.Xaml/XamlLoader.cs | 4 +- Xamarin.Forms/Xamarin.Forms.Xaml/XamlParser.cs | 100 +++--- .../Xamarin.Forms.Xaml/XamlServiceProvider.cs | 7 + 77 files changed, 2725 insertions(+), 1151 deletions(-) mode change 100644 => 100755 Xamarin.Forms/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs mode change 100644 => 100755 Xamarin.Forms/Xamarin.Forms.Build.Tasks/XamlCTask.cs create mode 100644 Xamarin.Forms/Xamarin.Forms.Core/EmbeddedFont.cs create mode 100644 Xamarin.Forms/Xamarin.Forms.Core/ExportFontAttribute.cs create mode 100644 Xamarin.Forms/Xamarin.Forms.Core/FontFile.cs create mode 100644 Xamarin.Forms/Xamarin.Forms.Core/FontRegistrar.cs create mode 100644 Xamarin.Forms/Xamarin.Forms.Core/IEmbeddedFontLoader.cs create mode 100644 Xamarin.Forms/Xamarin.Forms.Core/Shell/PresentationMode.cs create mode 100755 Xamarin.Forms/Xamarin.Forms.Platform.Tizen/EmbeddedFontLoader.cs create mode 100755 Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Extensions/FontExtensions.cs mode change 100644 => 100755 Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Forms.cs create mode 100644 Xamarin.Forms/Xamarin.Forms.Platform.Tizen/PopupManager.cs diff --git a/Xamarin.Forms/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs b/Xamarin.Forms/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs index d8d7f95..b132775 100644 --- a/Xamarin.Forms/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs +++ b/Xamarin.Forms/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs @@ -127,27 +127,69 @@ namespace Xamarin.Forms.Build.Tasks if (split.Length > 2) throw new ArgumentException(); - string prefix, name; - if (split.Length == 2) { - prefix = split[0]; - name = split[1]; - } - else { - prefix = ""; - name = split[0]; - } + var (prefix, name) = ParseName(match); var namespaceuri = nsResolver.LookupNamespace(prefix) ?? ""; if (!string.IsNullOrEmpty(prefix) && string.IsNullOrEmpty(namespaceuri)) throw new XamlParseException($"Undeclared xmlns prefix '{prefix}'", xmlLineInfo); + + IList typeArguments = null; + var childnodes = new List<(XmlName, INode)>(); + var contentname = new XmlName(null, null); + + if (remaining.StartsWith("}", StringComparison.Ordinal)) + { + remaining = remaining.Substring(1); + } + else + { + Property parsed; + do + { + parsed = ParseProperty(serviceProvider, ref remaining); + + XmlName childname; + + if (parsed.name == null) + { + childname = contentname; + } + else + { + var (propertyPrefix, propertyName) = ParseName(parsed.name); + + childname = XamlParser.ParsePropertyName(new XmlName( + propertyPrefix == "" ? "" : nsResolver.LookupNamespace(propertyPrefix), + propertyName)); + + if (childname.NamespaceURI == null && childname.LocalName == null) + continue; + } + + if (childname == XmlName.xTypeArguments) + { + typeArguments = TypeArgumentsParser.ParseExpression(parsed.strValue, nsResolver, xmlLineInfo); + childnodes.Add((childname, new ValueNode(typeArguments, nsResolver))); + } + else + { + var childnode = parsed.value as INode ?? new ValueNode(parsed.strValue, nsResolver); + childnodes.Add((childname, childnode)); + } + } + while (!parsed.last); + } + //The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix. XmlType type; - try { - type = new XmlType(namespaceuri, name + "Extension", null); + try + { + type = new XmlType(namespaceuri, name + "Extension", typeArguments); type.GetTypeReference(contextProvider.Context.Module, null); } - catch (XamlParseException) { - type = new XmlType(namespaceuri, name, null); + catch (XamlParseException) + { + type = new XmlType(namespaceuri, name, typeArguments); } if (type == null) @@ -157,30 +199,18 @@ namespace Xamarin.Forms.Build.Tasks ? new ElementNode(type, "", nsResolver) : new ElementNode(type, "", nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition); - if (remaining.StartsWith("}", StringComparison.Ordinal)) { - remaining = remaining.Substring(1); - return _node; + foreach (var (childname, childnode) in childnodes) { + if (childname == contentname) { + //ContentProperty + _node.CollectionItems.Add(childnode); + } + else { + _node.Properties[childname] = childnode; + } } - string piece; - while ((piece = GetNextPiece(ref remaining, out var next)) != null) - HandleProperty(piece, serviceProvider, ref remaining, next != '='); - return _node; } - - protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider) - { - if (value == null && strValue == null) - throw new XamlParseException($"No value found for property '{prop}' in markup expression", serviceProvider); - var nsResolver = serviceProvider.GetService(typeof(IXmlNamespaceResolver)) as IXmlNamespaceResolver; - if (prop != null) { - var name = new XmlName(_node.NamespaceURI, prop); - _node.Properties[name] = value as INode ?? new ValueNode(strValue, nsResolver); - } - else //ContentProperty - _node.CollectionItems.Add(value as INode ?? new ValueNode(strValue, nsResolver)); - } } } } \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs b/Xamarin.Forms/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs old mode 100644 new mode 100755 index 93e6d2f..74df619 --- a/Xamarin.Forms/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs +++ b/Xamarin.Forms/Xamarin.Forms.Build.Tasks/TypeReferenceExtensions.cs @@ -182,29 +182,52 @@ namespace Xamarin.Forms.Build.Tasks public static bool InheritsFromOrImplements(this TypeReference typeRef, TypeReference baseClass) { - if (TypeRefComparer.Default.Equals(typeRef, baseClass)) - return true; - - if (typeRef.IsValueType) - return false; + if (typeRef is GenericInstanceType genericInstance) { + if (baseClass is GenericInstanceType genericInstanceBaseClass && + TypeRefComparer.Default.Equals(genericInstance.ElementType, genericInstanceBaseClass.ElementType)) { + foreach (var parameter in genericInstanceBaseClass.ElementType.ResolveCached().GenericParameters) { + var argument = genericInstance.GenericArguments[parameter.Position]; + var baseClassArgument = genericInstanceBaseClass.GenericArguments[parameter.Position]; + + if (parameter.IsCovariant) { + if (!argument.InheritsFromOrImplements(baseClassArgument)) + return false; + } else if (parameter.IsContravariant) { + if (!baseClassArgument.InheritsFromOrImplements(argument)) + return false; + } else if (!TypeRefComparer.Default.Equals(argument, baseClassArgument)) { + return false; + } + } - if (typeRef.IsArray) { - var array = (ArrayType)typeRef; - var arrayType = typeRef.ResolveCached(); - if (arrayInterfaces.Contains(baseClass.FullName)) return true; - if (array.IsVector && //generic interfaces are not implemented on multidimensional arrays - arrayGenericInterfaces.Contains(baseClass.ResolveCached().FullName) && - baseClass.IsGenericInstance && - TypeRefComparer.Default.Equals((baseClass as GenericInstanceType).GenericArguments[0], arrayType)) + } + } + else { + if (TypeRefComparer.Default.Equals(typeRef, baseClass)) return true; - return baseClass.FullName == "System.Object"; + + if (typeRef.IsArray) { + var array = (ArrayType)typeRef; + var arrayType = typeRef.ResolveCached(); + if (arrayInterfaces.Contains(baseClass.FullName)) + return true; + if (array.IsVector && //generic interfaces are not implemented on multidimensional arrays + arrayGenericInterfaces.Contains(baseClass.ResolveCached().FullName) && + baseClass.IsGenericInstance && + TypeRefComparer.Default.Equals((baseClass as GenericInstanceType).GenericArguments[0], arrayType)) + return true; + return baseClass.FullName == "System.Object"; + } } + if (typeRef.IsValueType) + return false; + if (typeRef.FullName == "System.Object") return false; var typeDef = typeRef.ResolveCached(); - if (typeDef.Interfaces.Any(ir => TypeRefComparer.Default.Equals(ir.InterfaceType.ResolveGenericParameters(typeRef), baseClass))) + if (typeDef.Interfaces.Any(ir => ir.InterfaceType.ResolveGenericParameters(typeRef).InheritsFromOrImplements(baseClass))) return true; if (typeDef.BaseType == null) return false; @@ -367,14 +390,22 @@ namespace Xamarin.Forms.Build.Tasks public static TypeReference ResolveGenericParameters(this TypeReference self, TypeReference declaringTypeReference) { var genericdeclType = declaringTypeReference as GenericInstanceType; - if (genericdeclType == null) + var genericParameterSelf = self as GenericParameter; + var genericself = self as GenericInstanceType; + + if (genericdeclType == null && genericParameterSelf == null && genericself == null) return self; - var genericParameterSelf = self as GenericParameter; + if (genericdeclType == null && genericParameterSelf!=null) + { + var typeDef = declaringTypeReference.Resolve(); + if (typeDef.BaseType == null || typeDef.BaseType.FullName == "System.Object") + return self; + return self.ResolveGenericParameters(typeDef.BaseType.ResolveGenericParameters(declaringTypeReference)); + } if (genericParameterSelf != null) return genericdeclType.GenericArguments[genericParameterSelf.Position]; - var genericself = self as GenericInstanceType; if (genericself != null) return genericself.ResolveGenericParameters(declaringTypeReference); diff --git a/Xamarin.Forms/Xamarin.Forms.Build.Tasks/XamlCTask.cs b/Xamarin.Forms/Xamarin.Forms.Build.Tasks/XamlCTask.cs old mode 100644 new mode 100755 index 3e7307f..fd0d584 --- a/Xamarin.Forms/Xamarin.Forms.Build.Tasks/XamlCTask.cs +++ b/Xamarin.Forms/Xamarin.Forms.Build.Tasks/XamlCTask.cs @@ -52,212 +52,214 @@ namespace Xamarin.Forms.Build.Tasks return true; } - var resolver = DefaultAssemblyResolver ?? new XamlCAssemblyResolver(); - if (resolver is XamlCAssemblyResolver xamlCResolver) { - if (!string.IsNullOrEmpty(DependencyPaths)) { - foreach (var dep in DependencyPaths.Split(';').Distinct()) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Adding searchpath {dep}"); - xamlCResolver.AddSearchDirectory(dep); + using (var fallbackResolver = DefaultAssemblyResolver == null ? new XamlCAssemblyResolver() : null) { + var resolver = DefaultAssemblyResolver ?? fallbackResolver; + if (resolver is XamlCAssemblyResolver xamlCResolver) { + if (!string.IsNullOrEmpty(DependencyPaths)) { + foreach (var dep in DependencyPaths.Split(';').Distinct()) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Adding searchpath {dep}"); + xamlCResolver.AddSearchDirectory(dep); + } } - } - if (!string.IsNullOrEmpty(ReferencePath)) { - var paths = ReferencePath.Replace("//", "/").Split(';').Distinct(); - foreach (var p in paths) { - var searchpath = Path.GetDirectoryName(p); - LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Adding searchpath {searchpath}"); - xamlCResolver.AddSearchDirectory(searchpath); + if (!string.IsNullOrEmpty(ReferencePath)) { + var paths = ReferencePath.Replace("//", "/").Split(';').Distinct(); + foreach (var p in paths) { + var searchpath = Path.GetDirectoryName(p); + LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Adding searchpath {searchpath}"); + xamlCResolver.AddSearchDirectory(searchpath); + } } } - } - else - LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Ignoring dependency and reference paths due to an unsupported resolver"); - - var debug = DebugSymbols || (!string.IsNullOrEmpty(DebugType) && DebugType.ToLowerInvariant() != "none"); - - var readerParameters = new ReaderParameters { - AssemblyResolver = resolver, - ReadWrite = !ValidateOnly, - ReadSymbols = debug && !ValidateOnly, // We don't need symbols for ValidateOnly, since we won't be writing - }; - - using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(Path.GetFullPath(Assembly),readerParameters)) { - CustomAttribute xamlcAttr; - if (assemblyDefinition.HasCustomAttributes && - (xamlcAttr = - assemblyDefinition.CustomAttributes.FirstOrDefault( - ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlCompilationAttribute")) != null) { - var options = (XamlCompilationOptions)xamlcAttr.ConstructorArguments[0].Value; - if ((options & XamlCompilationOptions.Skip) == XamlCompilationOptions.Skip) - skipassembly = true; - if ((options & XamlCompilationOptions.Compile) == XamlCompilationOptions.Compile) - skipassembly = false; - } + else + LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Ignoring dependency and reference paths due to an unsupported resolver"); + + var debug = DebugSymbols || (!string.IsNullOrEmpty(DebugType) && DebugType.ToLowerInvariant() != "none"); - foreach (var module in assemblyDefinition.Modules) { - var skipmodule = skipassembly; - if (module.HasCustomAttributes && + var readerParameters = new ReaderParameters { + AssemblyResolver = resolver, + ReadWrite = !ValidateOnly, + ReadSymbols = debug && !ValidateOnly, // We don't need symbols for ValidateOnly, since we won't be writing + }; + + using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(Path.GetFullPath(Assembly),readerParameters)) { + CustomAttribute xamlcAttr; + if (assemblyDefinition.HasCustomAttributes && (xamlcAttr = - module.CustomAttributes.FirstOrDefault( + assemblyDefinition.CustomAttributes.FirstOrDefault( ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlCompilationAttribute")) != null) { var options = (XamlCompilationOptions)xamlcAttr.ConstructorArguments[0].Value; if ((options & XamlCompilationOptions.Skip) == XamlCompilationOptions.Skip) - skipmodule = true; + skipassembly = true; if ((options & XamlCompilationOptions.Compile) == XamlCompilationOptions.Compile) - skipmodule = false; + skipassembly = false; } - LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Module: {module.Name}"); - var resourcesToPrune = new List(); - foreach (var resource in module.Resources.OfType()) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 4)}Resource: {resource.Name}"); - string classname; - if (!resource.IsXaml(module, out classname)) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}skipped."); - continue; - } - TypeDefinition typeDef = module.GetType(classname); - if (typeDef == null) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}no type found... skipped."); - continue; - } - var skiptype = skipmodule; - if (typeDef.HasCustomAttributes && + foreach (var module in assemblyDefinition.Modules) { + var skipmodule = skipassembly; + if (module.HasCustomAttributes && (xamlcAttr = - typeDef.CustomAttributes.FirstOrDefault( + module.CustomAttributes.FirstOrDefault( ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlCompilationAttribute")) != null) { var options = (XamlCompilationOptions)xamlcAttr.ConstructorArguments[0].Value; if ((options & XamlCompilationOptions.Skip) == XamlCompilationOptions.Skip) - skiptype = true; + skipmodule = true; if ((options & XamlCompilationOptions.Compile) == XamlCompilationOptions.Compile) - skiptype = false; + skipmodule = false; } - if (Type != null) - skiptype = !(Type == classname); + LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}Module: {module.Name}"); + var resourcesToPrune = new List(); + foreach (var resource in module.Resources.OfType()) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 4)}Resource: {resource.Name}"); + string classname; + if (!resource.IsXaml(module, out classname)) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}skipped."); + continue; + } + TypeDefinition typeDef = module.GetType(classname); + if (typeDef == null) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}no type found... skipped."); + continue; + } + var skiptype = skipmodule; + if (typeDef.HasCustomAttributes && + (xamlcAttr = + typeDef.CustomAttributes.FirstOrDefault( + ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlCompilationAttribute")) != null) { + var options = (XamlCompilationOptions)xamlcAttr.ConstructorArguments[0].Value; + if ((options & XamlCompilationOptions.Skip) == XamlCompilationOptions.Skip) + skiptype = true; + if ((options & XamlCompilationOptions.Compile) == XamlCompilationOptions.Compile) + skiptype = false; + } - if (skiptype && !ForceCompile) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}has XamlCompilationAttribute set to Skip and not Compile... skipped."); - continue; - } + if (Type != null) + skiptype = !(Type == classname); - var initComp = typeDef.Methods.FirstOrDefault(md => md.Name == "InitializeComponent"); - if (initComp == null) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}no InitializeComponent found... skipped."); - continue; - } + if (skiptype && !ForceCompile) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}has XamlCompilationAttribute set to Skip and not Compile... skipped."); + continue; + } - CustomAttribute xamlFilePathAttr; - var xamlFilePath = typeDef.HasCustomAttributes && (xamlFilePathAttr = typeDef.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlFilePathAttribute")) != null ? - (string)xamlFilePathAttr.ConstructorArguments[0].Value : - resource.Name; - - MethodDefinition initCompRuntime = null; - if (!ValidateOnly) - { - initCompRuntime = typeDef.Methods.FirstOrDefault(md => md.Name == "__InitComponentRuntime"); - if (initCompRuntime != null) - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}__InitComponentRuntime already exists... not creating"); - else - { - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Creating empty {typeDef.Name}.__InitComponentRuntime"); - initCompRuntime = new MethodDefinition("__InitComponentRuntime", initComp.Attributes, initComp.ReturnType); - initCompRuntime.Body.InitLocals = true; - LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Copying body of InitializeComponent to __InitComponentRuntime"); - initCompRuntime.Body = new MethodBody(initCompRuntime); - var iCRIl = initCompRuntime.Body.GetILProcessor(); - foreach (var instr in initComp.Body.Instructions) - iCRIl.Append(instr); - initComp.Body.Instructions.Clear(); - initComp.Body.GetILProcessor().Emit(OpCodes.Ret); - initComp.Body.InitLocals = true; - typeDef.Methods.Add(initCompRuntime); - LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); + var initComp = typeDef.Methods.FirstOrDefault(md => md.Name == "InitializeComponent"); + if (initComp == null) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}no InitializeComponent found... skipped."); + continue; } - } - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Parsing Xaml"); - var rootnode = ParseXaml(resource.GetResourceStream(), typeDef); - if (rootnode == null) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}failed."); - continue; - } - LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); - - hasCompiledXamlResources = true; - - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Replacing {0}.InitializeComponent ()"); - Exception e; - if (!TryCoreCompile(initComp, initCompRuntime, rootnode, xamlFilePath, out e)) { - success = false; - LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}failed."); - (thrownExceptions = thrownExceptions ?? new List()).Add(e); - if (e is XamlParseException xpe) - LoggingHelper.LogError(null, null, null, xamlFilePath, xpe.XmlInfo.LineNumber, xpe.XmlInfo.LinePosition, 0, 0, xpe.Message, xpe.HelpLink, xpe.Source); - else if (e is XmlException xe) - LoggingHelper.LogError(null, null, null, xamlFilePath, xe.LineNumber, xe.LinePosition, 0, 0, xe.Message, xe.HelpLink, xe.Source); - else - LoggingHelper.LogError(null, null, null, xamlFilePath, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source); - LoggingHelper.LogMessage(Low, e.StackTrace); - continue; - } - if (Type != null) - InitCompForType = initComp; + CustomAttribute xamlFilePathAttr; + var xamlFilePath = typeDef.HasCustomAttributes && (xamlFilePathAttr = typeDef.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "Xamarin.Forms.Xaml.XamlFilePathAttribute")) != null ? + (string)xamlFilePathAttr.ConstructorArguments[0].Value : + resource.Name; - LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); + MethodDefinition initCompRuntime = null; + if (!ValidateOnly) + { + initCompRuntime = typeDef.Methods.FirstOrDefault(md => md.Name == "__InitComponentRuntime"); + if (initCompRuntime != null) + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}__InitComponentRuntime already exists... not creating"); + else + { + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Creating empty {typeDef.Name}.__InitComponentRuntime"); + initCompRuntime = new MethodDefinition("__InitComponentRuntime", initComp.Attributes, initComp.ReturnType); + initCompRuntime.Body.InitLocals = true; + LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Copying body of InitializeComponent to __InitComponentRuntime"); + initCompRuntime.Body = new MethodBody(initCompRuntime); + var iCRIl = initCompRuntime.Body.GetILProcessor(); + foreach (var instr in initComp.Body.Instructions) + iCRIl.Append(instr); + initComp.Body.Instructions.Clear(); + initComp.Body.GetILProcessor().Emit(OpCodes.Ret); + initComp.Body.InitLocals = true; + typeDef.Methods.Add(initCompRuntime); + LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); + } + } + + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Parsing Xaml"); + var rootnode = ParseXaml(resource.GetResourceStream(), typeDef); + if (rootnode == null) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}failed."); + continue; + } + LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); - if (ValidateOnly) - continue; + hasCompiledXamlResources = true; + + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Replacing {0}.InitializeComponent ()"); + Exception e; + if (!TryCoreCompile(initComp, initCompRuntime, rootnode, xamlFilePath, out e)) { + success = false; + LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}failed."); + (thrownExceptions = thrownExceptions ?? new List()).Add(e); + if (e is XamlParseException xpe) + LoggingHelper.LogError(null, null, null, xamlFilePath, xpe.XmlInfo.LineNumber, xpe.XmlInfo.LinePosition, 0, 0, xpe.Message, xpe.HelpLink, xpe.Source); + else if (e is XmlException xe) + LoggingHelper.LogError(null, null, null, xamlFilePath, xe.LineNumber, xe.LinePosition, 0, 0, xe.Message, xe.HelpLink, xe.Source); + else + LoggingHelper.LogError(null, null, null, xamlFilePath, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source); + LoggingHelper.LogMessage(Low, e.StackTrace); + continue; + } + if (Type != null) + InitCompForType = initComp; - if (OptimizeIL) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Optimizing IL"); - initComp.Body.Optimize(); LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); - } + + if (ValidateOnly) + continue; + + if (OptimizeIL) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Optimizing IL"); + initComp.Body.Optimize(); + LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); + } #pragma warning disable 0618 - if (OutputGeneratedILAsCode) - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Decompiling option has been removed. Use a 3rd party decompiler to admire the beauty of the IL generated"); + if (OutputGeneratedILAsCode) + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Decompiling option has been removed. Use a 3rd party decompiler to admire the beauty of the IL generated"); #pragma warning restore 0618 - resourcesToPrune.Add(resource); + resourcesToPrune.Add(resource); + } + if (hasCompiledXamlResources) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 4)}Changing the module MVID"); + module.Mvid = Guid.NewGuid(); + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}done."); + } + if (!KeepXamlResources) { + if (resourcesToPrune.Any()) + LoggingHelper.LogMessage(Low, $"{new string(' ', 4)}Removing compiled xaml resources"); + foreach (var resource in resourcesToPrune) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Removing {resource.Name}"); + module.Resources.Remove(resource); + LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); + } + } } - if (hasCompiledXamlResources) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 4)}Changing the module MVID"); - module.Mvid = Guid.NewGuid(); - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}done."); + if (ValidateOnly) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 0)}ValidateOnly=True. Skipping writing assembly."); + return success; } - if (!KeepXamlResources) { - if (resourcesToPrune.Any()) - LoggingHelper.LogMessage(Low, $"{new string(' ', 4)}Removing compiled xaml resources"); - foreach (var resource in resourcesToPrune) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 6)}Removing {resource.Name}"); - module.Resources.Remove(resource); - LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}done."); - } + if (!hasCompiledXamlResources) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 0)}No compiled resources. Skipping writing assembly."); + return success; + } + + LoggingHelper.LogMessage(Low, $"{new string(' ', 0)}Writing the assembly"); + try { + assemblyDefinition.Write(new WriterParameters { + WriteSymbols = debug, + }); + LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}done."); + } catch (Exception e) { + LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}failed."); + LoggingHelper.LogErrorFromException(e); + (thrownExceptions = thrownExceptions ?? new List()).Add(e); + LoggingHelper.LogMessage(Low, e.StackTrace); + success = false; } - } - if (ValidateOnly) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 0)}ValidateOnly=True. Skipping writing assembly."); - return success; - } - if (!hasCompiledXamlResources) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 0)}No compiled resources. Skipping writing assembly."); - return success; - } - - LoggingHelper.LogMessage(Low, $"{new string(' ', 0)}Writing the assembly"); - try { - assemblyDefinition.Write(new WriterParameters { - WriteSymbols = debug, - }); - LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}done."); - } catch (Exception e) { - LoggingHelper.LogMessage(Low, $"{new string(' ', 2)}failed."); - LoggingHelper.LogErrorFromException(e); - (thrownExceptions = thrownExceptions ?? new List()).Add(e); - LoggingHelper.LogMessage(Low, e.StackTrace); - success = false; } } return success; diff --git a/Xamarin.Forms/Xamarin.Forms.Core/ActionSheetArguments.cs b/Xamarin.Forms/Xamarin.Forms.Core/ActionSheetArguments.cs index c3f7e8a..c459d18 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/ActionSheetArguments.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/ActionSheetArguments.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Threading.Tasks; namespace Xamarin.Forms.Internals @@ -12,7 +13,7 @@ namespace Xamarin.Forms.Internals Title = title; Cancel = cancel; Destruction = destruction; - Buttons = buttons; + Buttons = buttons?.Where(c => c != null); Result = new TaskCompletionSource(); } diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Binding.cs b/Xamarin.Forms/Xamarin.Forms.Core/Binding.cs index 3ba88ec..33ab46d 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Binding.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Binding.cs @@ -276,11 +276,7 @@ namespace Xamarin.Forms internal override BindingBase Clone() { - return new Binding(Path, Mode) { - Converter = Converter, - ConverterParameter = ConverterParameter, - StringFormat = StringFormat, - Source = Source, + return new Binding(Path, Mode, Converter, ConverterParameter, StringFormat, Source) { UpdateSourceEventName = UpdateSourceEventName, TargetNullValue = TargetNullValue, FallbackValue = FallbackValue, diff --git a/Xamarin.Forms/Xamarin.Forms.Core/BindingBase.cs b/Xamarin.Forms/Xamarin.Forms.Core/BindingBase.cs index 1b675c4..6f8cead 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/BindingBase.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/BindingBase.cs @@ -87,6 +87,7 @@ namespace Xamarin.Forms SynchronizedCollections.Add(collection, new CollectionSynchronizationContext(context, callback)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void ThrowIfApplied() { if (IsApplied) diff --git a/Xamarin.Forms/Xamarin.Forms.Core/BindingBaseExtensions.cs b/Xamarin.Forms/Xamarin.Forms.Core/BindingBaseExtensions.cs index 5da40f2..6998aa4 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/BindingBaseExtensions.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/BindingBaseExtensions.cs @@ -1,7 +1,10 @@ -namespace Xamarin.Forms +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms { static class BindingBaseExtensions { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static BindingMode GetRealizedMode(this BindingBase self, BindableProperty property) { return self.Mode != BindingMode.Default ? self.Mode : property.DefaultBindingMode; diff --git a/Xamarin.Forms/Xamarin.Forms.Core/BindingExpression.cs b/Xamarin.Forms/Xamarin.Forms.Core/BindingExpression.cs index 91c722a..166a2bc 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/BindingExpression.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/BindingExpression.cs @@ -13,6 +13,7 @@ namespace Xamarin.Forms internal class BindingExpression { internal const string PropertyNotFoundErrorMessage = "'{0}' property not found on '{1}', target property: '{2}.{3}'"; + static readonly char[] ExpressionSplit = new[] { '.' }; readonly List _parts = new List(); @@ -110,7 +111,6 @@ namespace Xamarin.Forms for (var i = 0; i < _parts.Count; i++) { part = _parts[i]; - bool isLast = i + 1 == _parts.Count; if (!part.IsSelf && current != null) { @@ -119,7 +119,7 @@ namespace Xamarin.Forms if (part.LastGetter == null || !part.LastGetter.DeclaringType.GetTypeInfo().IsAssignableFrom(currentType)) SetupPart(currentType, part); - if (!isLast) + if (i < _parts.Count - 1) part.TryGetValue(current, out current); } @@ -140,8 +140,7 @@ namespace Xamarin.Forms if (needsGetter) { - object value = property.DefaultValue; - if (part.TryGetValue(current, out value) || part.IsSelf) { + if (part.TryGetValue(current, out object value) || part.IsSelf) { value = Binding.GetSourceValue(value, property.ReturnType); } else @@ -185,38 +184,6 @@ namespace Xamarin.Forms } } - IEnumerable GetPart(string part) - { - part = part.Trim(); - if (part == string.Empty) - throw new FormatException("Path contains an empty part"); - - BindingExpressionPart indexer = null; - - int lbIndex = part.IndexOf('['); - if (lbIndex != -1) - { - int rbIndex = part.LastIndexOf(']'); - if (rbIndex == -1) - throw new FormatException("Indexer did not contain closing bracket"); - - int argLength = rbIndex - lbIndex - 1; - if (argLength == 0) - throw new FormatException("Indexer did not contain arguments"); - - string argString = part.Substring(lbIndex + 1, argLength); - indexer = new BindingExpressionPart(this, argString, true); - - part = part.Substring(0, lbIndex); - part = part.Trim(); - } - - if (part.Length > 0) - yield return new BindingExpressionPart(this, part); - if (indexer != null) - yield return indexer; - } - void ParsePath() { string p = Path.Trim(); @@ -232,14 +199,44 @@ namespace Xamarin.Forms p = p.Substring(1); } - string[] pathParts = p.Split('.'); + string[] pathParts = p.Split(ExpressionSplit); for (var i = 0; i < pathParts.Length; i++) { - foreach (BindingExpressionPart part in GetPart(pathParts[i])) + string part = pathParts[i].Trim(); + if (part == string.Empty) + throw new FormatException("Path contains an empty part"); + + BindingExpressionPart indexer = null; + + int lbIndex = part.IndexOf('['); + if (lbIndex != -1) { - last.NextPart = part; - _parts.Add(part); - last = part; + int rbIndex = part.LastIndexOf(']'); + if (rbIndex == -1) + throw new FormatException("Indexer did not contain closing bracket"); + + int argLength = rbIndex - lbIndex - 1; + if (argLength == 0) + throw new FormatException("Indexer did not contain arguments"); + + string argString = part.Substring(lbIndex + 1, argLength); + indexer = new BindingExpressionPart(this, argString, true); + + part = part.Substring(0, lbIndex); + part = part.Trim(); + } + if (part.Length > 0) + { + var next = new BindingExpressionPart(this, part); + last.NextPart = next; + _parts.Add(next); + last = next; + } + if (indexer != null) + { + last.NextPart = indexer; + _parts.Add(indexer); + last = indexer; } } } @@ -279,15 +276,16 @@ namespace Xamarin.Forms return pi; } + //defined on a base class ? + if (sourceType.BaseType is Type baseT && GetIndexer(baseT.GetTypeInfo(), indexerName, content) is PropertyInfo p) + return p; + //defined on an interface ? foreach (var face in sourceType.ImplementedInterfaces) { if (GetIndexer(face.GetTypeInfo(), indexerName, content) is PropertyInfo pi) return pi; } - //defined on a base class ? - if (sourceType.BaseType is Type baseT && GetIndexer(baseT.GetTypeInfo(), indexerName, content) is PropertyInfo p) - return p; return null; } diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Color.cs b/Xamarin.Forms/Xamarin.Forms.Core/Color.cs index c52ae6d..db0b04d 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Color.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Color.cs @@ -114,6 +114,26 @@ namespace Xamarin.Forms } } + Color(int r, int g, int b) + { + _mode = Mode.Rgb; + _r = r / 255f; + _g = g / 255f; + _b = b / 255f; + _a = 1; + ConvertToHsl(_r, _g, _b, _mode, out _hue, out _saturation, out _luminosity); + } + + Color(int r, int g, int b, int a) + { + _mode = Mode.Rgb; + _r = r / 255f; + _g = g / 255f; + _b = b / 255f; + _a = a / 255f; + ConvertToHsl(_r, _g, _b, _mode, out _hue, out _saturation, out _luminosity); + } + public Color(double r, double g, double b) : this(r, g, b, 1) { } @@ -418,150 +438,150 @@ namespace Xamarin.Forms #region Color Definitions // matches colors in WPF's System.Windows.Media.Colors - public static readonly Color AliceBlue = FromRgb(240, 248, 255); - public static readonly Color AntiqueWhite = FromRgb(250, 235, 215); - public static readonly Color Aqua = FromRgb(0, 255, 255); - public static readonly Color Aquamarine = FromRgb(127, 255, 212); - public static readonly Color Azure = FromRgb(240, 255, 255); - public static readonly Color Beige = FromRgb(245, 245, 220); - public static readonly Color Bisque = FromRgb(255, 228, 196); - public static readonly Color Black = FromRgb(0, 0, 0); - public static readonly Color BlanchedAlmond = FromRgb(255, 235, 205); - public static readonly Color Blue = FromRgb(0, 0, 255); - public static readonly Color BlueViolet = FromRgb(138, 43, 226); - public static readonly Color Brown = FromRgb(165, 42, 42); - public static readonly Color BurlyWood = FromRgb(222, 184, 135); - public static readonly Color CadetBlue = FromRgb(95, 158, 160); - public static readonly Color Chartreuse = FromRgb(127, 255, 0); - public static readonly Color Chocolate = FromRgb(210, 105, 30); - public static readonly Color Coral = FromRgb(255, 127, 80); - public static readonly Color CornflowerBlue = FromRgb(100, 149, 237); - public static readonly Color Cornsilk = FromRgb(255, 248, 220); - public static readonly Color Crimson = FromRgb(220, 20, 60); - public static readonly Color Cyan = FromRgb(0, 255, 255); - public static readonly Color DarkBlue = FromRgb(0, 0, 139); - public static readonly Color DarkCyan = FromRgb(0, 139, 139); - public static readonly Color DarkGoldenrod = FromRgb(184, 134, 11); - public static readonly Color DarkGray = FromRgb(169, 169, 169); - public static readonly Color DarkGreen = FromRgb(0, 100, 0); - public static readonly Color DarkKhaki = FromRgb(189, 183, 107); - public static readonly Color DarkMagenta = FromRgb(139, 0, 139); - public static readonly Color DarkOliveGreen = FromRgb(85, 107, 47); - public static readonly Color DarkOrange = FromRgb(255, 140, 0); - public static readonly Color DarkOrchid = FromRgb(153, 50, 204); - public static readonly Color DarkRed = FromRgb(139, 0, 0); - public static readonly Color DarkSalmon = FromRgb(233, 150, 122); - public static readonly Color DarkSeaGreen = FromRgb(143, 188, 143); - public static readonly Color DarkSlateBlue = FromRgb(72, 61, 139); - public static readonly Color DarkSlateGray = FromRgb(47, 79, 79); - public static readonly Color DarkTurquoise = FromRgb(0, 206, 209); - public static readonly Color DarkViolet = FromRgb(148, 0, 211); - public static readonly Color DeepPink = FromRgb(255, 20, 147); - public static readonly Color DeepSkyBlue = FromRgb(0, 191, 255); - public static readonly Color DimGray = FromRgb(105, 105, 105); - public static readonly Color DodgerBlue = FromRgb(30, 144, 255); - public static readonly Color Firebrick = FromRgb(178, 34, 34); - public static readonly Color FloralWhite = FromRgb(255, 250, 240); - public static readonly Color ForestGreen = FromRgb(34, 139, 34); - public static readonly Color Fuchsia = FromRgb(255, 0, 255); + public static readonly Color AliceBlue = new Color(240, 248, 255); + public static readonly Color AntiqueWhite = new Color(250, 235, 215); + public static readonly Color Aqua = new Color(0, 255, 255); + public static readonly Color Aquamarine = new Color(127, 255, 212); + public static readonly Color Azure = new Color(240, 255, 255); + public static readonly Color Beige = new Color(245, 245, 220); + public static readonly Color Bisque = new Color(255, 228, 196); + public static readonly Color Black = new Color(0, 0, 0); + public static readonly Color BlanchedAlmond = new Color(255, 235, 205); + public static readonly Color Blue = new Color(0, 0, 255); + public static readonly Color BlueViolet = new Color(138, 43, 226); + public static readonly Color Brown = new Color(165, 42, 42); + public static readonly Color BurlyWood = new Color(222, 184, 135); + public static readonly Color CadetBlue = new Color(95, 158, 160); + public static readonly Color Chartreuse = new Color(127, 255, 0); + public static readonly Color Chocolate = new Color(210, 105, 30); + public static readonly Color Coral = new Color(255, 127, 80); + public static readonly Color CornflowerBlue = new Color(100, 149, 237); + public static readonly Color Cornsilk = new Color(255, 248, 220); + public static readonly Color Crimson = new Color(220, 20, 60); + public static readonly Color Cyan = new Color(0, 255, 255); + public static readonly Color DarkBlue = new Color(0, 0, 139); + public static readonly Color DarkCyan = new Color(0, 139, 139); + public static readonly Color DarkGoldenrod = new Color(184, 134, 11); + public static readonly Color DarkGray = new Color(169, 169, 169); + public static readonly Color DarkGreen = new Color(0, 100, 0); + public static readonly Color DarkKhaki = new Color(189, 183, 107); + public static readonly Color DarkMagenta = new Color(139, 0, 139); + public static readonly Color DarkOliveGreen = new Color(85, 107, 47); + public static readonly Color DarkOrange = new Color(255, 140, 0); + public static readonly Color DarkOrchid = new Color(153, 50, 204); + public static readonly Color DarkRed = new Color(139, 0, 0); + public static readonly Color DarkSalmon = new Color(233, 150, 122); + public static readonly Color DarkSeaGreen = new Color(143, 188, 143); + public static readonly Color DarkSlateBlue = new Color(72, 61, 139); + public static readonly Color DarkSlateGray = new Color(47, 79, 79); + public static readonly Color DarkTurquoise = new Color(0, 206, 209); + public static readonly Color DarkViolet = new Color(148, 0, 211); + public static readonly Color DeepPink = new Color(255, 20, 147); + public static readonly Color DeepSkyBlue = new Color(0, 191, 255); + public static readonly Color DimGray = new Color(105, 105, 105); + public static readonly Color DodgerBlue = new Color(30, 144, 255); + public static readonly Color Firebrick = new Color(178, 34, 34); + public static readonly Color FloralWhite = new Color(255, 250, 240); + public static readonly Color ForestGreen = new Color(34, 139, 34); + public static readonly Color Fuchsia = new Color(255, 0, 255); [Obsolete("Fuschia is obsolete as of version 1.3.0. Please use Fuchsia instead.")] [EditorBrowsable(EditorBrowsableState.Never)] - public static readonly Color Fuschia = FromRgb(255, 0, 255); - public static readonly Color Gainsboro = FromRgb(220, 220, 220); - public static readonly Color GhostWhite = FromRgb(248, 248, 255); - public static readonly Color Gold = FromRgb(255, 215, 0); - public static readonly Color Goldenrod = FromRgb(218, 165, 32); - public static readonly Color Gray = FromRgb(128, 128, 128); - public static readonly Color Green = FromRgb(0, 128, 0); - public static readonly Color GreenYellow = FromRgb(173, 255, 47); - public static readonly Color Honeydew = FromRgb(240, 255, 240); - public static readonly Color HotPink = FromRgb(255, 105, 180); - public static readonly Color IndianRed = FromRgb(205, 92, 92); - public static readonly Color Indigo = FromRgb(75, 0, 130); - public static readonly Color Ivory = FromRgb(255, 255, 240); - public static readonly Color Khaki = FromRgb(240, 230, 140); - public static readonly Color Lavender = FromRgb(230, 230, 250); - public static readonly Color LavenderBlush = FromRgb(255, 240, 245); - public static readonly Color LawnGreen = FromRgb(124, 252, 0); - public static readonly Color LemonChiffon = FromRgb(255, 250, 205); - public static readonly Color LightBlue = FromRgb(173, 216, 230); - public static readonly Color LightCoral = FromRgb(240, 128, 128); - public static readonly Color LightCyan = FromRgb(224, 255, 255); - public static readonly Color LightGoldenrodYellow = FromRgb(250, 250, 210); - public static readonly Color LightGray = FromRgb(211, 211, 211); - public static readonly Color LightGreen = FromRgb(144, 238, 144); - public static readonly Color LightPink = FromRgb(255, 182, 193); - public static readonly Color LightSalmon = FromRgb(255, 160, 122); - public static readonly Color LightSeaGreen = FromRgb(32, 178, 170); - public static readonly Color LightSkyBlue = FromRgb(135, 206, 250); - public static readonly Color LightSlateGray = FromRgb(119, 136, 153); - public static readonly Color LightSteelBlue = FromRgb(176, 196, 222); - public static readonly Color LightYellow = FromRgb(255, 255, 224); - public static readonly Color Lime = FromRgb(0, 255, 0); - public static readonly Color LimeGreen = FromRgb(50, 205, 50); - public static readonly Color Linen = FromRgb(250, 240, 230); - public static readonly Color Magenta = FromRgb(255, 0, 255); - public static readonly Color Maroon = FromRgb(128, 0, 0); - public static readonly Color MediumAquamarine = FromRgb(102, 205, 170); - public static readonly Color MediumBlue = FromRgb(0, 0, 205); - public static readonly Color MediumOrchid = FromRgb(186, 85, 211); - public static readonly Color MediumPurple = FromRgb(147, 112, 219); - public static readonly Color MediumSeaGreen = FromRgb(60, 179, 113); - public static readonly Color MediumSlateBlue = FromRgb(123, 104, 238); - public static readonly Color MediumSpringGreen = FromRgb(0, 250, 154); - public static readonly Color MediumTurquoise = FromRgb(72, 209, 204); - public static readonly Color MediumVioletRed = FromRgb(199, 21, 133); - public static readonly Color MidnightBlue = FromRgb(25, 25, 112); - public static readonly Color MintCream = FromRgb(245, 255, 250); - public static readonly Color MistyRose = FromRgb(255, 228, 225); - public static readonly Color Moccasin = FromRgb(255, 228, 181); - public static readonly Color NavajoWhite = FromRgb(255, 222, 173); - public static readonly Color Navy = FromRgb(0, 0, 128); - public static readonly Color OldLace = FromRgb(253, 245, 230); - public static readonly Color Olive = FromRgb(128, 128, 0); - public static readonly Color OliveDrab = FromRgb(107, 142, 35); - public static readonly Color Orange = FromRgb(255, 165, 0); - public static readonly Color OrangeRed = FromRgb(255, 69, 0); - public static readonly Color Orchid = FromRgb(218, 112, 214); - public static readonly Color PaleGoldenrod = FromRgb(238, 232, 170); - public static readonly Color PaleGreen = FromRgb(152, 251, 152); - public static readonly Color PaleTurquoise = FromRgb(175, 238, 238); - public static readonly Color PaleVioletRed = FromRgb(219, 112, 147); - public static readonly Color PapayaWhip = FromRgb(255, 239, 213); - public static readonly Color PeachPuff = FromRgb(255, 218, 185); - public static readonly Color Peru = FromRgb(205, 133, 63); - public static readonly Color Pink = FromRgb(255, 192, 203); - public static readonly Color Plum = FromRgb(221, 160, 221); - public static readonly Color PowderBlue = FromRgb(176, 224, 230); - public static readonly Color Purple = FromRgb(128, 0, 128); - public static readonly Color Red = FromRgb(255, 0, 0); - public static readonly Color RosyBrown = FromRgb(188, 143, 143); - public static readonly Color RoyalBlue = FromRgb(65, 105, 225); - public static readonly Color SaddleBrown = FromRgb(139, 69, 19); - public static readonly Color Salmon = FromRgb(250, 128, 114); - public static readonly Color SandyBrown = FromRgb(244, 164, 96); - public static readonly Color SeaGreen = FromRgb(46, 139, 87); - public static readonly Color SeaShell = FromRgb(255, 245, 238); - public static readonly Color Sienna = FromRgb(160, 82, 45); - public static readonly Color Silver = FromRgb(192, 192, 192); - public static readonly Color SkyBlue = FromRgb(135, 206, 235); - public static readonly Color SlateBlue = FromRgb(106, 90, 205); - public static readonly Color SlateGray = FromRgb(112, 128, 144); - public static readonly Color Snow = FromRgb(255, 250, 250); - public static readonly Color SpringGreen = FromRgb(0, 255, 127); - public static readonly Color SteelBlue = FromRgb(70, 130, 180); - public static readonly Color Tan = FromRgb(210, 180, 140); - public static readonly Color Teal = FromRgb(0, 128, 128); - public static readonly Color Thistle = FromRgb(216, 191, 216); - public static readonly Color Tomato = FromRgb(255, 99, 71); - public static readonly Color Transparent = FromRgba(255, 255, 255, 0); - public static readonly Color Turquoise = FromRgb(64, 224, 208); - public static readonly Color Violet = FromRgb(238, 130, 238); - public static readonly Color Wheat = FromRgb(245, 222, 179); - public static readonly Color White = FromRgb(255, 255, 255); - public static readonly Color WhiteSmoke = FromRgb(245, 245, 245); - public static readonly Color Yellow = FromRgb(255, 255, 0); - public static readonly Color YellowGreen = FromRgb(154, 205, 50); + public static readonly Color Fuschia = new Color(255, 0, 255); + public static readonly Color Gainsboro = new Color(220, 220, 220); + public static readonly Color GhostWhite = new Color(248, 248, 255); + public static readonly Color Gold = new Color(255, 215, 0); + public static readonly Color Goldenrod = new Color(218, 165, 32); + public static readonly Color Gray = new Color(128, 128, 128); + public static readonly Color Green = new Color(0, 128, 0); + public static readonly Color GreenYellow = new Color(173, 255, 47); + public static readonly Color Honeydew = new Color(240, 255, 240); + public static readonly Color HotPink = new Color(255, 105, 180); + public static readonly Color IndianRed = new Color(205, 92, 92); + public static readonly Color Indigo = new Color(75, 0, 130); + public static readonly Color Ivory = new Color(255, 255, 240); + public static readonly Color Khaki = new Color(240, 230, 140); + public static readonly Color Lavender = new Color(230, 230, 250); + public static readonly Color LavenderBlush = new Color(255, 240, 245); + public static readonly Color LawnGreen = new Color(124, 252, 0); + public static readonly Color LemonChiffon = new Color(255, 250, 205); + public static readonly Color LightBlue = new Color(173, 216, 230); + public static readonly Color LightCoral = new Color(240, 128, 128); + public static readonly Color LightCyan = new Color(224, 255, 255); + public static readonly Color LightGoldenrodYellow = new Color(250, 250, 210); + public static readonly Color LightGray = new Color(211, 211, 211); + public static readonly Color LightGreen = new Color(144, 238, 144); + public static readonly Color LightPink = new Color(255, 182, 193); + public static readonly Color LightSalmon = new Color(255, 160, 122); + public static readonly Color LightSeaGreen = new Color(32, 178, 170); + public static readonly Color LightSkyBlue = new Color(135, 206, 250); + public static readonly Color LightSlateGray = new Color(119, 136, 153); + public static readonly Color LightSteelBlue = new Color(176, 196, 222); + public static readonly Color LightYellow = new Color(255, 255, 224); + public static readonly Color Lime = new Color(0, 255, 0); + public static readonly Color LimeGreen = new Color(50, 205, 50); + public static readonly Color Linen = new Color(250, 240, 230); + public static readonly Color Magenta = new Color(255, 0, 255); + public static readonly Color Maroon = new Color(128, 0, 0); + public static readonly Color MediumAquamarine = new Color(102, 205, 170); + public static readonly Color MediumBlue = new Color(0, 0, 205); + public static readonly Color MediumOrchid = new Color(186, 85, 211); + public static readonly Color MediumPurple = new Color(147, 112, 219); + public static readonly Color MediumSeaGreen = new Color(60, 179, 113); + public static readonly Color MediumSlateBlue = new Color(123, 104, 238); + public static readonly Color MediumSpringGreen = new Color(0, 250, 154); + public static readonly Color MediumTurquoise = new Color(72, 209, 204); + public static readonly Color MediumVioletRed = new Color(199, 21, 133); + public static readonly Color MidnightBlue = new Color(25, 25, 112); + public static readonly Color MintCream = new Color(245, 255, 250); + public static readonly Color MistyRose = new Color(255, 228, 225); + public static readonly Color Moccasin = new Color(255, 228, 181); + public static readonly Color NavajoWhite = new Color(255, 222, 173); + public static readonly Color Navy = new Color(0, 0, 128); + public static readonly Color OldLace = new Color(253, 245, 230); + public static readonly Color Olive = new Color(128, 128, 0); + public static readonly Color OliveDrab = new Color(107, 142, 35); + public static readonly Color Orange = new Color(255, 165, 0); + public static readonly Color OrangeRed = new Color(255, 69, 0); + public static readonly Color Orchid = new Color(218, 112, 214); + public static readonly Color PaleGoldenrod = new Color(238, 232, 170); + public static readonly Color PaleGreen = new Color(152, 251, 152); + public static readonly Color PaleTurquoise = new Color(175, 238, 238); + public static readonly Color PaleVioletRed = new Color(219, 112, 147); + public static readonly Color PapayaWhip = new Color(255, 239, 213); + public static readonly Color PeachPuff = new Color(255, 218, 185); + public static readonly Color Peru = new Color(205, 133, 63); + public static readonly Color Pink = new Color(255, 192, 203); + public static readonly Color Plum = new Color(221, 160, 221); + public static readonly Color PowderBlue = new Color(176, 224, 230); + public static readonly Color Purple = new Color(128, 0, 128); + public static readonly Color Red = new Color(255, 0, 0); + public static readonly Color RosyBrown = new Color(188, 143, 143); + public static readonly Color RoyalBlue = new Color(65, 105, 225); + public static readonly Color SaddleBrown = new Color(139, 69, 19); + public static readonly Color Salmon = new Color(250, 128, 114); + public static readonly Color SandyBrown = new Color(244, 164, 96); + public static readonly Color SeaGreen = new Color(46, 139, 87); + public static readonly Color SeaShell = new Color(255, 245, 238); + public static readonly Color Sienna = new Color(160, 82, 45); + public static readonly Color Silver = new Color(192, 192, 192); + public static readonly Color SkyBlue = new Color(135, 206, 235); + public static readonly Color SlateBlue = new Color(106, 90, 205); + public static readonly Color SlateGray = new Color(112, 128, 144); + public static readonly Color Snow = new Color(255, 250, 250); + public static readonly Color SpringGreen = new Color(0, 255, 127); + public static readonly Color SteelBlue = new Color(70, 130, 180); + public static readonly Color Tan = new Color(210, 180, 140); + public static readonly Color Teal = new Color(0, 128, 128); + public static readonly Color Thistle = new Color(216, 191, 216); + public static readonly Color Tomato = new Color(255, 99, 71); + public static readonly Color Transparent = new Color(255, 255, 255, 0); + public static readonly Color Turquoise = new Color(64, 224, 208); + public static readonly Color Violet = new Color(238, 130, 238); + public static readonly Color Wheat = new Color(245, 222, 179); + public static readonly Color White = new Color(255, 255, 255); + public static readonly Color WhiteSmoke = new Color(245, 245, 245); + public static readonly Color Yellow = new Color(255, 255, 0); + public static readonly Color YellowGreen = new Color(154, 205, 50); #endregion } diff --git a/Xamarin.Forms/Xamarin.Forms.Core/DependencyService.cs b/Xamarin.Forms/Xamarin.Forms.Core/DependencyService.cs index f246ca9..658162b 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/DependencyService.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/DependencyService.cs @@ -109,33 +109,15 @@ namespace Xamarin.Forms if (s_initialized) return; - Type targetAttrType = typeof(DependencyAttribute); - // Don't use LINQ for performance reasons // Naive implementation can easily take over a second to run foreach (Assembly assembly in assemblies) { - object[] attributes; - try - { -#if NETSTANDARD2_0 - attributes = assembly.GetCustomAttributes(targetAttrType, true); -#else - attributes = assembly.GetCustomAttributes(targetAttrType).ToArray(); -#endif - } - catch (System.IO.FileNotFoundException) - { - // Sometimes the previewer doesn't actually have everything required for these loads to work - Log.Warning(nameof(Registrar), "Could not load assembly: {0} for Attibute {1} | Some renderers may not be loaded", assembly.FullName, targetAttrType.FullName); - continue; - } - - var length = attributes.Length; - if (length == 0) + object[] attributes = assembly.GetCustomAttributesSafe(typeof(DependencyAttribute)); + if (attributes == null) continue; - for (int i = 0; i < length; i++) + for (int i = 0; i < attributes.Length; i++) { DependencyAttribute attribute = (DependencyAttribute)attributes[i]; if (!DependencyTypes.Contains(attribute.Implementor)) diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Element_StyleSheets.cs b/Xamarin.Forms/Xamarin.Forms.Core/Element_StyleSheets.cs index 705509f..690fb47 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Element_StyleSheets.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Element_StyleSheets.cs @@ -38,7 +38,7 @@ namespace Xamarin.Forms IStyleSelectable IStyleSelectable.Parent => Parent; //on parent set, or on parent stylesheet changed, reapply all - void ApplyStyleSheetsOnParentSet() + internal void ApplyStyleSheetsOnParentSet() { var parent = Parent; if (parent == null) diff --git a/Xamarin.Forms/Xamarin.Forms.Core/EmbeddedFont.cs b/Xamarin.Forms/Xamarin.Forms.Core/EmbeddedFont.cs new file mode 100644 index 0000000..5765bc7 --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Core/EmbeddedFont.cs @@ -0,0 +1,11 @@ +using System; +using System.IO; + +namespace Xamarin.Forms +{ + public class EmbeddedFont + { + public string FontName { get; set; } + public Stream ResourceStream { get; set; } + } +} diff --git a/Xamarin.Forms/Xamarin.Forms.Core/ExportFontAttribute.cs b/Xamarin.Forms/Xamarin.Forms.Core/ExportFontAttribute.cs new file mode 100644 index 0000000..14c0d8a --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Core/ExportFontAttribute.cs @@ -0,0 +1,16 @@ +using System; +namespace Xamarin.Forms +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class ExportFontAttribute : Attribute + { + public string Alias { get; set; } + public ExportFontAttribute(string fontFileName) + { + FontFileName = fontFileName; + } + + public string EmbeddedFontResourceId { get; set; } + public string FontFileName { get; } + } +} diff --git a/Xamarin.Forms/Xamarin.Forms.Core/FontFile.cs b/Xamarin.Forms/Xamarin.Forms.Core/FontFile.cs new file mode 100644 index 0000000..3211fa7 --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Core/FontFile.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms.Core +{ + public class FontFile + { + public string FileName { get; set; } + public string Extension { get; set; } + public string FileNameWithExtension(string extension) => $"{FileName}{extension}"; + public string FileNameWithExtension() => FileNameWithExtension(Extension); + public string PostScriptName { get; set; } + + public string GetPostScriptNameWithSpaces() => + string.Join(" ", GetFontName(PostScriptName)); + + public static readonly string[] Extensions = { + ".ttf", + ".otf", + }; + + public static FontFile FromString(string input) + { + var hashIndex = input.IndexOf("#", System.StringComparison.Ordinal); + //UWP names require Spaces. Sometimes people may use those, "CuteFont-Regular#Cute Font" should be "CuteFont-Regular#CuteFont" + var postScriptName = hashIndex > 0 ? input.Substring(hashIndex + 1).Replace(" ", "") : input; + //Get the fontFamily name; + var fontFamilyName = hashIndex > 0 ? input.Substring(0, hashIndex) : input; + + var foundExtension = Extensions. + FirstOrDefault(x => fontFamilyName.EndsWith(x, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrWhiteSpace(foundExtension)) + fontFamilyName = fontFamilyName.Substring(0, fontFamilyName.Length - foundExtension.Length); + + return new FontFile + { + FileName = fontFamilyName, + Extension = foundExtension, + PostScriptName = postScriptName, + }; + } + + + static IEnumerable GetFontName(string fontFamily) + { + if (fontFamily.Contains(" ")) + { + yield return fontFamily; + //We are done, they have spaces, they have it handled. + yield break; + } + string currentString = ""; + char lastCharacter = ' '; + var index = fontFamily.LastIndexOf("-", StringComparison.Ordinal); + bool multipleCaps = false; + var cleansedString = index > 0 ? fontFamily.Substring(0, index) : fontFamily; + foreach (var c in cleansedString) + { + //Always break on these characters + if (c == '_' || c == '-') + { + yield return currentString; + //Reset everything, + currentString = ""; + lastCharacter = ' '; + multipleCaps = false; + } + else + { + + if (char.IsUpper(c)) + { + //If the last character is lowercase, we are in a new CamelCase font + if (char.IsLower(lastCharacter)) + { + yield return currentString; + currentString = ""; + lastCharacter = ' '; + } + else if (char.IsUpper(lastCharacter)) + { + multipleCaps = true; + } + } + + //Detect multiple UpperCase letters so we can separate things like PTSansNarrow into "PT Sans Narrow" + else if (multipleCaps && currentString.Length > 1) + { + var last = currentString[currentString.Length - 1]; + yield return currentString.Substring(0, currentString.Length - 1); + //Reset everything so it doesnt do a space + multipleCaps = false; + lastCharacter = ' '; + currentString = last.ToString(); + } + + currentString += c; + lastCharacter = c; + } + } + //Send what is left! + if (!string.IsNullOrWhiteSpace(currentString)) + yield return currentString.Trim(); + } + } +} diff --git a/Xamarin.Forms/Xamarin.Forms.Core/FontRegistrar.cs b/Xamarin.Forms/Xamarin.Forms.Core/FontRegistrar.cs new file mode 100644 index 0000000..ca444dd --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Core/FontRegistrar.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Internals +{ + public static class FontRegistrar + { + internal static readonly Dictionary EmbeddedFonts = new Dictionary(); + + public static void Register(ExportFontAttribute fontAttribute, Assembly assembly) + { + EmbeddedFonts[fontAttribute.FontFileName] = (fontAttribute, assembly); + } + + //TODO: Investigate making this Async + public static (bool hasFont, string fontPath) HasFont(string font) + { + try + { + if (!EmbeddedFonts.TryGetValue(font, out var foundFont)) + { + return (false, null); + } + + var fontStream = GetEmbeddedResourceStream(foundFont.assembly, font); + + var type = Registrar.Registered.GetHandlerType(typeof(EmbeddedFont)); + var fontHandler = (IEmbeddedFontLoader)Activator.CreateInstance(type); + return fontHandler.LoadFont(new EmbeddedFont { FontName = font, ResourceStream = fontStream }); + + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + return (false, null); + } + + static Stream GetEmbeddedResourceStream(Assembly assembly, string resourceFileName) + { + var resourceNames = assembly.GetManifestResourceNames(); + + var resourcePaths = resourceNames + .Where(x => x.EndsWith(resourceFileName, StringComparison.CurrentCultureIgnoreCase)) + .ToArray(); + + if (!resourcePaths.Any()) + { + throw new Exception(string.Format("Resource ending with {0} not found.", resourceFileName)); + } + if (resourcePaths.Length > 1) + { + resourcePaths = resourcePaths.Where(x => IsFile(x, resourceFileName)).ToArray(); + } + + return assembly.GetManifestResourceStream(resourcePaths.FirstOrDefault()); + } + + static bool IsFile(string path, string file) + { + if (!path.EndsWith(file, StringComparison.Ordinal)) + return false; + return path.Replace(file, "").EndsWith(".", StringComparison.Ordinal); + } + } +} diff --git a/Xamarin.Forms/Xamarin.Forms.Core/IEmbeddedFontLoader.cs b/Xamarin.Forms/Xamarin.Forms.Core/IEmbeddedFontLoader.cs new file mode 100644 index 0000000..94f46aa --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Core/IEmbeddedFontLoader.cs @@ -0,0 +1,8 @@ +using System; +namespace Xamarin.Forms +{ + public interface IEmbeddedFontLoader + { + (bool success, string filePath) LoadFont(EmbeddedFont font); + } +} diff --git a/Xamarin.Forms/Xamarin.Forms.Core/IndicatorView.cs b/Xamarin.Forms/Xamarin.Forms.Core/IndicatorView.cs index 2bd441e..92d4add 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/IndicatorView.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/IndicatorView.cs @@ -1,7 +1,5 @@ using System.Collections; -using System.Collections.Generic; using System.Collections.Specialized; -using System.Runtime.CompilerServices; using Xamarin.Forms.Platform; namespace Xamarin.Forms @@ -21,8 +19,8 @@ namespace Xamarin.Forms public static readonly BindableProperty MaximumVisibleProperty = BindableProperty.Create(nameof(MaximumVisible), typeof(int), typeof(IndicatorView), int.MaxValue); - public static readonly BindableProperty IndicatorTemplateProperty = BindableProperty.Create(nameof(IndicatorTemplate), typeof(DataTemplate), typeof(IndicatorView), default(DataTemplate), propertyChanged: (bindable, oldValue, newValue) - => UpdateIndicatorLayout(((IndicatorView)bindable), newValue)); + public static readonly BindableProperty IndicatorTemplateProperty = BindableProperty.Create(nameof(IndicatorTemplate), typeof(DataTemplate), typeof(IndicatorView), default(DataTemplate), propertyChanging: (bindable, oldValue, newValue) + => UpdateIndicatorLayout((IndicatorView)bindable, newValue)); public static readonly BindableProperty HideSingleProperty = BindableProperty.Create(nameof(HideSingle), typeof(bool), typeof(IndicatorView), true); @@ -192,6 +190,5 @@ namespace Xamarin.Forms } Count = count; } - } } \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs b/Xamarin.Forms/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs index 1acb7a1..f4bf238 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Interactivity/AttachedCollection.cs @@ -135,7 +135,7 @@ namespace Xamarin.Forms return; } - _associatedObjects.RemoveAll(t => !t.IsAlive); + _associatedObjects.RemoveAll(t => t == null || !t.IsAlive); _cleanupThreshold = _associatedObjects.Count + CleanupTrigger; } } diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Internals/ToolbarTracker.cs b/Xamarin.Forms/Xamarin.Forms.Core/Internals/ToolbarTracker.cs index 4b95d37..3ad43c8 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Internals/ToolbarTracker.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Internals/ToolbarTracker.cs @@ -12,6 +12,11 @@ namespace Xamarin.Forms.Internals { int _masterDetails; Page _target; + ToolBarItemComparer _toolBarItemComparer; + public ToolbarTracker() + { + _toolBarItemComparer = new ToolBarItemComparer(); + } public IEnumerable AdditionalTargets { get; set; } @@ -44,12 +49,20 @@ namespace Xamarin.Forms.Internals get { if (Target == null) - return Enumerable.Empty(); - IEnumerable items = GetCurrentToolbarItems(Target); + return new ToolbarItem[0]; + + // I realize this is sorting on every single get but we don't have + // a mechanism in place currently to invalidate a stored version of this + + List returnValue = GetCurrentToolbarItems(Target); + if (AdditionalTargets != null) - items = items.Concat(AdditionalTargets.SelectMany(t => t.ToolbarItems)); + foreach(var item in AdditionalTargets) + foreach(var toolbarItem in item.ToolbarItems) + returnValue.Add(toolbarItem); - return items.OrderBy(ti => ti.Priority); + returnValue.Sort(_toolBarItemComparer); + return returnValue; } } @@ -58,7 +71,7 @@ namespace Xamarin.Forms.Internals void EmitCollectionChanged() => CollectionChanged?.Invoke(this, EventArgs.Empty); - IEnumerable GetCurrentToolbarItems(Page page) + List GetCurrentToolbarItems(Page page) { var result = new List(); result.AddRange(page.ToolbarItems); @@ -178,5 +191,10 @@ namespace Xamarin.Forms.Internals page.DescendantRemoved -= OnChildRemoved; page.PropertyChanged -= OnPropertyChanged; } + + class ToolBarItemComparer : IComparer + { + public int Compare(ToolbarItem x, ToolbarItem y) => x.Priority.CompareTo(y.Priority); + } } } \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Core/MergedStyle.cs b/Xamarin.Forms/Xamarin.Forms.Core/MergedStyle.cs index 25ee49c..c87ebf9 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/MergedStyle.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/MergedStyle.cs @@ -53,7 +53,7 @@ namespace Xamarin.Forms if (_styleClass == value) return; - if (_styleClass != null && _classStyles != null) + if (_styleClass != null && _classStyleProperties != null) foreach (var classStyleProperty in _classStyleProperties) Target.RemoveDynamicResource(classStyleProperty); @@ -67,6 +67,10 @@ namespace Xamarin.Forms _classStyleProperties.Add (classStyleProperty); Target.OnSetDynamicResource (classStyleProperty, Forms.Style.StyleClassPrefix + styleClass); } + + //reapply the css stylesheets + if (Target is Element targetelement) + targetelement.ApplyStyleSheetsOnParentSet(); } } } diff --git a/Xamarin.Forms/Xamarin.Forms.Core/NavigationPage.cs b/Xamarin.Forms/Xamarin.Forms.Core/NavigationPage.cs index 28a68cc..321ae47 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/NavigationPage.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/NavigationPage.cs @@ -32,6 +32,8 @@ namespace Xamarin.Forms [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty TitleIconProperty = TitleIconImageSourceProperty; + public static readonly BindableProperty IconColorProperty = BindableProperty.CreateAttached("IconColor", typeof(Color), typeof(NavigationPage), Color.Default); + public static readonly BindableProperty TitleViewProperty = BindableProperty.CreateAttached("TitleView", typeof(View), typeof(NavigationPage), null, propertyChanging: TitleViewPropertyChanging); static readonly BindablePropertyKey CurrentPagePropertyKey = BindableProperty.CreateReadOnly("CurrentPage", typeof(Page), typeof(NavigationPage), null); @@ -159,6 +161,16 @@ namespace Xamarin.Forms return (View)bindable.GetValue(TitleViewProperty); } + public static Color GetIconColor(BindableObject bindable) + { + if (bindable == null) + { + return Color.Default; + } + + return (Color)bindable.GetValue(IconColorProperty); + } + public Task PopAsync() { return PopAsync(true); @@ -278,6 +290,11 @@ namespace Xamarin.Forms bindable.SetValue(TitleViewProperty, value); } + public static void SetIconColor(BindableObject bindable, Color value) + { + bindable.SetValue(IconColorProperty, value); + } + protected override bool OnBackButtonPressed() { if (CurrentPage.SendBackButtonPressed()) diff --git a/Xamarin.Forms/Xamarin.Forms.Core/NumericExtensions.cs b/Xamarin.Forms/Xamarin.Forms.Core/NumericExtensions.cs index f5fb5de..2651ae9 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/NumericExtensions.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/NumericExtensions.cs @@ -1,20 +1,48 @@ using System; using System.ComponentModel; +using System.Runtime.CompilerServices; namespace Xamarin.Forms.Internals { [EditorBrowsable(EditorBrowsableState.Never)] public static class NumericExtensions { - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Clamp(this double self, double min, double max) { - return Math.Min(max, Math.Max(self, min)); + if (max < min) + { + return max; + } + else if (self < min) + { + return min; + } + else if (self > max) + { + return max; + } + + return self; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Clamp(this int self, int min, int max) { - return Math.Min(max, Math.Max(self, min)); + if (max < min) + { + return max; + } + else if (self < min) + { + return min; + } + else if (self > max) + { + return max; + } + + return self; } } } \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Page.cs b/Xamarin.Forms/Xamarin.Forms.Core/Page.cs index 2a4bff6..fd5f2b7 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Page.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Page.cs @@ -225,7 +225,12 @@ namespace Xamarin.Forms public Task DisplayPromptAsync(string title, string message, string accept = "OK", string cancel = "Cancel", string placeholder = null, int maxLength = -1, Keyboard keyboard = default(Keyboard), string initialValue = "") { var args = new PromptArguments(title, message, accept, cancel, placeholder, maxLength, keyboard, initialValue); - MessagingCenter.Send(this, PromptSignalName, args); + + if (IsPlatformEnabled) + MessagingCenter.Send(this, PromptSignalName, args); + else + _pendingActions.Add(() => MessagingCenter.Send(this, PromptSignalName, args)); + return args.Result.Task; } diff --git a/Xamarin.Forms/Xamarin.Forms.Core/ReflectionExtensions.cs b/Xamarin.Forms/Xamarin.Forms.Core/ReflectionExtensions.cs index fd47061..9ea553c 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/ReflectionExtensions.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/ReflectionExtensions.cs @@ -47,13 +47,12 @@ namespace Xamarin.Forms.Internals internal static object[] GetCustomAttributesSafe(this Assembly assembly, Type attrType) { - object[] attributes = null; try { #if NETSTANDARD2_0 - attributes = assembly.GetCustomAttributes(attrType, true); + return assembly.GetCustomAttributes(attrType, true); #else - attributes = assembly.GetCustomAttributes(attrType).ToArray(); + return assembly.GetCustomAttributes(attrType).ToArray(); #endif } catch (System.IO.FileNotFoundException) @@ -62,7 +61,7 @@ namespace Xamarin.Forms.Internals Log.Warning(nameof(Registrar), "Could not load assembly: {0} for Attribute {1} | Some renderers may not be loaded", assembly.FullName, attrType.FullName); } - return attributes; + return null; } public static Type[] GetExportedTypes(this Assembly assembly) diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Registrar.cs b/Xamarin.Forms/Xamarin.Forms.Core/Registrar.cs index 0fa4a32..dd8eafc 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Registrar.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Registrar.cs @@ -263,6 +263,7 @@ namespace Xamarin.Forms.Internals //typeof(ExportRendererAttribute); //typeof(ExportCellAttribute); //typeof(ExportImageSourceHandlerAttribute); + //TODO this is no longer used? public static void RegisterRenderers(HandlerAttribute[] attributes) { var length = attributes.Length; @@ -336,37 +337,39 @@ namespace Xamarin.Forms.Internals foreach (Type attrType in attrTypes) { - object[] attributes; - try - { -#if NETSTANDARD2_0 - attributes = assembly.GetCustomAttributes(attrType, true); -#else - attributes = assembly.GetCustomAttributes(attrType).ToArray(); -#endif - } - catch (System.IO.FileNotFoundException) - { - // Sometimes the previewer doesn't actually have everything required for these loads to work - Log.Warning(nameof(Registrar), "Could not load assembly: {0} for Attibute {1} | Some renderers may not be loaded", assembly.FullName, attrType.FullName); + object[] attributes = assembly.GetCustomAttributesSafe(attrType); + if (attributes == null || attributes.Length == 0) continue; + + var length = attributes.Length; + for (var i = 0; i < length; i++) + { + var a = attributes[i]; + var attribute = a as HandlerAttribute; + if(attribute == null && (a is ExportFontAttribute fa)) + { + FontRegistrar.Register(fa, assembly); + } + else + { + if (attribute.ShouldRegister()) + Registered.Register(attribute.HandlerType, attribute.TargetType, attribute.SupportedVisuals, attribute.Priority); + } } + } - var handlerAttributes = new HandlerAttribute[attributes.Length]; - Array.Copy(attributes, handlerAttributes, attributes.Length); - RegisterRenderers(handlerAttributes); + object[] effectAttributes = assembly.GetCustomAttributesSafe(typeof (ExportEffectAttribute)); + if (effectAttributes == null || effectAttributes.Length == 0) + { + Profile.FrameEnd(assemblyName); + continue; } string resolutionName = assembly.FullName; var resolutionNameAttribute = (ResolutionGroupNameAttribute)assembly.GetCustomAttribute(typeof(ResolutionGroupNameAttribute)); if (resolutionNameAttribute != null) resolutionName = resolutionNameAttribute.ShortName; - -#if NETSTANDARD2_0 - object[] effectAttributes = assembly.GetCustomAttributes(typeof(ExportEffectAttribute), true); -#else - object[] effectAttributes = assembly.GetCustomAttributes(typeof(ExportEffectAttribute)).ToArray(); -#endif + //NOTE: a simple cast to ExportEffectAttribute[] failed on UWP, hence the Array.Copy var typedEffectAttributes = new ExportEffectAttribute[effectAttributes.Length]; Array.Copy(effectAttributes, typedEffectAttributes, effectAttributes.Length); RegisterEffects(resolutionName, typedEffectAttributes); diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellContentController.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellContentController.cs index 6427f82..d7a6d8f 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellContentController.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellContentController.cs @@ -1,4 +1,6 @@ -namespace Xamarin.Forms +using System; + +namespace Xamarin.Forms { public interface IShellContentController : IElementController { @@ -7,5 +9,6 @@ void RecyclePage(Page page); Page Page { get; } + event EventHandler IsPageVisibleChanged; } } \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellController.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellController.cs index fcf8e0a..9ac3f42 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellController.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellController.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Threading.Tasks; namespace Xamarin.Forms @@ -43,5 +45,9 @@ namespace Xamarin.Forms bool RemoveFlyoutBehaviorObserver(IFlyoutBehaviorObserver observer); void UpdateCurrentState(ShellNavigationSource source); + + ReadOnlyCollection GetItems(); + + event NotifyCollectionChangedEventHandler ItemsCollectionChanged; } } \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellItemController.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellItemController.cs index f394943..d72a31a 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellItemController.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellItemController.cs @@ -1,11 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Collections.ObjectModel; +using System.Collections.Specialized; namespace Xamarin.Forms { public interface IShellItemController : IElementController { bool ProposeSection(ShellSection shellSection, bool setValue = true); + + ReadOnlyCollection GetItems(); + event NotifyCollectionChangedEventHandler ItemsCollectionChanged; } } \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellSectionController.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellSectionController.cs index 7841cce..1f05682 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellSectionController.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/IShellSectionController.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; +using System.Collections.ObjectModel; +using System.Collections.Specialized; using Xamarin.Forms.Internals; namespace Xamarin.Forms @@ -21,8 +24,23 @@ namespace Xamarin.Forms void SendInsetChanged(Thickness inset, double tabThickness); + void SendPopping(Task poppingCompleted); + void SendPoppingToRoot(Task finishedPopping); + + [Obsolete] + [EditorBrowsable(EditorBrowsableState.Never)] void SendPopped(); + + [Obsolete] + [EditorBrowsable(EditorBrowsableState.Never)] void SendPopping(Page page); + + [Obsolete] + [EditorBrowsable(EditorBrowsableState.Never)] void SendPopped(Page page); + + ReadOnlyCollection GetItems(); + + event NotifyCollectionChangedEventHandler ItemsCollectionChanged; } -} \ No newline at end of file +} diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/NavigableElement.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/NavigableElement.cs index cc5fcfc..0676fa0 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/NavigableElement.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/NavigableElement.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.ComponentModel; using Xamarin.Forms.Internals; -using Xamarin.Forms.PlatformConfiguration.TizenSpecific; namespace Xamarin.Forms { @@ -21,11 +20,7 @@ namespace Xamarin.Forms internal NavigableElement() { Navigation = new NavigationProxy(); - - if (OptionalFeatureValues.UseStyle) - { - _mergedStyle = new MergedStyle(GetType(), this); - } + _mergedStyle = new MergedStyle(GetType(), this); } public INavigation Navigation { @@ -35,10 +30,7 @@ namespace Xamarin.Forms public Style Style { get { return (Style)GetValue(StyleProperty); } - set { - if (OptionalFeatureValues.UseStyle) - SetValue(StyleProperty, value); - } + set { SetValue(StyleProperty, value); } } [TypeConverter(typeof(ListStringTypeConverter))] @@ -50,10 +42,7 @@ namespace Xamarin.Forms [TypeConverter(typeof(ListStringTypeConverter))] public IList @class { get { return _mergedStyle.StyleClass; } - set { - if (OptionalFeatureValues.UseStyle) - _mergedStyle.StyleClass = value; - } + set { _mergedStyle.StyleClass = value; } } [EditorBrowsable(EditorBrowsableState.Never)] diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/PresentationMode.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/PresentationMode.cs new file mode 100644 index 0000000..6ad3e7e --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/PresentationMode.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Xamarin.Forms +{ + [Flags] + public enum PresentationMode + { + NotAnimated = 1, + Animated = 1 << 1, + Modal = 1 << 2, + ModalAnimated = PresentationMode.Animated | PresentationMode.Modal, + ModalNotAnimated = PresentationMode.NotAnimated | PresentationMode.Modal + } +} diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/Shell.cs index 8327d6a..02a5ac4 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/Shell.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/Shell.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; @@ -25,6 +26,8 @@ namespace Xamarin.Forms SetInheritedBindingContext(newHandlerBehavior, bindable.BindingContext); } + public static readonly BindableProperty PresentationModeProperty = BindableProperty.CreateAttached("PresentationMode", typeof(PresentationMode), typeof(Shell), PresentationMode.Animated); + public static readonly BindableProperty FlyoutBehaviorProperty = BindableProperty.CreateAttached("FlyoutBehavior", typeof(FlyoutBehavior), typeof(Shell), FlyoutBehavior.Flyout, propertyChanged: OnFlyoutBehaviorChanged); @@ -69,6 +72,9 @@ namespace Xamarin.Forms public static BackButtonBehavior GetBackButtonBehavior(BindableObject obj) => (BackButtonBehavior)obj.GetValue(BackButtonBehaviorProperty); public static void SetBackButtonBehavior(BindableObject obj, BackButtonBehavior behavior) => obj.SetValue(BackButtonBehaviorProperty, behavior); + public static PresentationMode GetPresentationMode(BindableObject obj) => (PresentationMode)obj.GetValue(PresentationModeProperty); + public static void SetPresentationMode(BindableObject obj, PresentationMode presentationMode) => obj.SetValue(PresentationModeProperty, presentationMode); + public static FlyoutBehavior GetFlyoutBehavior(BindableObject obj) => (FlyoutBehavior)obj.GetValue(FlyoutBehaviorProperty); public static void SetFlyoutBehavior(BindableObject obj, FlyoutBehavior value) => obj.SetValue(FlyoutBehaviorProperty, value); @@ -204,6 +210,8 @@ namespace Xamarin.Forms View IShellController.FlyoutHeader => FlyoutHeaderView; + IShellController ShellController => this; + void IShellController.AddAppearanceObserver(IAppearanceObserver observer, Element pivot) { _appearanceObservers.Add((observer, pivot)); @@ -216,7 +224,7 @@ namespace Xamarin.Forms // We need to wait until the visible page has been created before we try to calculate // the flyout behavior - if(GetVisiblePage() != null) + if (GetVisiblePage() != null) observer.OnFlyoutBehaviorChanged(GetEffectiveFlyoutBehavior()); } @@ -268,7 +276,7 @@ namespace Xamarin.Forms } ShellNavigationState IShellController.GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, bool includeStack) - => GetNavigationState(shellItem, shellSection, shellContent, includeStack ? shellSection.Stack.ToList() : null); + => GetNavigationState(shellItem, shellSection, shellContent, includeStack ? shellSection.Stack.ToList() : null, includeStack ? shellSection.Navigation.ModalStack.ToList() : null); async void IShellController.OnFlyoutItemSelected(Element element) { @@ -309,17 +317,34 @@ namespace Xamarin.Forms shellSection = shellSection ?? shellItem.CurrentItem; shellContent = shellContent ?? shellSection?.CurrentItem; - var state = GetNavigationState(shellItem, shellSection, shellContent, null); + var state = GetNavigationState(shellItem, shellSection, shellContent, null, null); if (FlyoutIsPresented && FlyoutBehavior == FlyoutBehavior.Flyout) SetValueFromRenderer(FlyoutIsPresentedProperty, false); - await GoToAsync(state).ConfigureAwait(false); + if (shellSection == null) + shellItem.PropertyChanged += OnShellItemPropertyChanged; + else if (shellContent == null) + shellSection.PropertyChanged += OnShellItemPropertyChanged; + else + await GoToAsync(state).ConfigureAwait(false); + } + + void OnShellItemPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == CurrentItemProperty.PropertyName) + { + (sender as BindableObject).PropertyChanged -= OnShellItemPropertyChanged; + if (sender is ShellItem item) + ((IShellController)this).OnFlyoutItemSelected(item); + else if (sender is ShellSection section) + ((IShellController)this).OnFlyoutItemSelected(section.Parent); + } } bool IShellController.ProposeNavigation(ShellNavigationSource source, ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList stack, bool canCancel) { - var proposedState = GetNavigationState(shellItem, shellSection, shellContent, stack); + var proposedState = GetNavigationState(shellItem, shellSection, shellContent, stack, shellSection.Navigation.ModalStack); return ProposeNavigation(source, proposedState, canCancel); } @@ -346,12 +371,20 @@ namespace Xamarin.Forms var shellSection = shellItem?.CurrentItem; var shellContent = shellSection?.CurrentItem; var stack = shellSection?.Stack; - var result = GetNavigationState(shellItem, shellSection, shellContent, stack); + var modalStack = shellSection?.Navigation?.ModalStack; + var result = GetNavigationState(shellItem, shellSection, shellContent, stack, modalStack); SetValueFromRenderer(CurrentStatePropertyKey, result); OnNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source)); } + ReadOnlyCollection IShellController.GetItems() => ((ShellItemCollection)Items).VisibleItems; + + event NotifyCollectionChangedEventHandler IShellController.ItemsCollectionChanged + { + add { ((ShellItemCollection)Items).VisibleItemsChanged += value; } + remove { ((ShellItemCollection)Items).VisibleItemsChanged -= value; } + } public static Shell Current => Application.Current?.MainPage as Shell; @@ -385,12 +418,17 @@ namespace Xamarin.Forms return routes; } - public Task GoToAsync(ShellNavigationState state, bool animate = true) + public Task GoToAsync(ShellNavigationState state) + { + return GoToAsync(state, null, false); + } + + public Task GoToAsync(ShellNavigationState state, bool animate) { return GoToAsync(state, animate, false); } - internal async Task GoToAsync(ShellNavigationState state, bool animate, bool enableRelativeShellRoutes) + internal async Task GoToAsync(ShellNavigationState state, bool? animate, bool enableRelativeShellRoutes) { // FIXME: This should not be none, we need to compute the delta and set flags correctly var accept = ProposeNavigation(ShellNavigationSource.Unknown, state, true); @@ -408,11 +446,23 @@ namespace Xamarin.Forms var shellItem = navigationRequest.Request.Item; var shellSection = navigationRequest.Request.Section; + var currentShellSection = CurrentItem?.CurrentItem; + var nextActiveSection = shellSection ?? shellItem?.CurrentItem; + ShellContent shellContent = navigationRequest.Request.Content; + bool modalStackPreBuilt = false; + // If we're replacing the whole stack and there are global routes then build the navigation stack before setting the shell section visible + if (navigationRequest.Request.GlobalRoutes.Count > 0 && nextActiveSection != null && navigationRequest.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt) + { + modalStackPreBuilt = true; + await nextActiveSection.GoToAsync(navigationRequest, queryData, false); + } + if (shellItem != null) { ApplyQueryAttributes(shellItem, queryData, navigationRequest.Request.Section == null); + bool navigatedToNewShellElement = false; if (shellSection != null && shellContent != null) { @@ -420,6 +470,7 @@ namespace Xamarin.Forms if (shellSection.CurrentItem != shellContent) { shellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, shellContent); + navigatedToNewShellElement = true; } } @@ -429,20 +480,32 @@ namespace Xamarin.Forms if (shellItem.CurrentItem != shellSection) { shellItem.SetValueFromRenderer(ShellItem.CurrentItemProperty, shellSection); + navigatedToNewShellElement = true; } } if (CurrentItem != shellItem) { SetValueFromRenderer(CurrentItemProperty, shellItem); + navigatedToNewShellElement = true; } - if (navigationRequest.Request.GlobalRoutes.Count > 0) + if (!modalStackPreBuilt && currentShellSection?.Navigation.ModalStack.Count > 0) + { + // - navigating to new shell element so just pop everything + // - or route contains no global route requests + if (navigatedToNewShellElement || navigationRequest.Request.GlobalRoutes.Count == 0) + { + await currentShellSection.PopModalStackToPage(null, animate); + } + } + + if (navigationRequest.Request.GlobalRoutes.Count > 0 && navigationRequest.StackRequest != NavigationRequest.WhatToDoWithTheStack.ReplaceIt) { // TODO get rid of this hack and fix so if there's a stack the current page doesn't display Device.BeginInvokeOnMainThread(async () => { - await CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, false); + await CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate); }); } } @@ -472,9 +535,9 @@ namespace Xamarin.Forms //if the lastItem is implicitly wrapped, get the actual ShellContent if (isLastItem) { - if (element is ShellItem shellitem && shellitem.Items.FirstOrDefault() is ShellSection section) + if (element is IShellItemController shellitem && shellitem.GetItems().FirstOrDefault() is ShellSection section) element = section; - if (element is ShellSection shellsection && shellsection.Items.FirstOrDefault() is ShellContent content) + if (element is IShellSectionController shellsection && shellsection.GetItems().FirstOrDefault() is ShellContent content) element = content; if (element is ShellContent shellcontent && shellcontent.Content is Element e) element = e; @@ -501,7 +564,7 @@ namespace Xamarin.Forms element.SetValue(ShellContent.QueryAttributesProperty, query); } - ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList sectionStack) + ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList sectionStack, IReadOnlyList modalStack) { StringBuilder stateBuilder = new StringBuilder($"//"); Dictionary queryData = new Dictionary(); @@ -537,6 +600,28 @@ namespace Xamarin.Forms stateBuilder.Append("/"); } } + + if (modalStack != null && modalStack.Count > 0) + { + if (!stackAtRoot && sectionStack.Count > 0) + stateBuilder.Append("/"); + + for (int i = 0; i < modalStack.Count; i++) + { + var topPage = modalStack[i]; + + if(i > 0) + stateBuilder.Append("/"); + + stateBuilder.Append(Routing.GetRoute(topPage)); + + for (int j = 1; j < topPage.Navigation.NavigationStack.Count; j++) + { + stateBuilder.Append("/"); + stateBuilder.Append(Routing.GetRoute(topPage.Navigation.NavigationStack[j])); + } + } + } } } @@ -588,8 +673,42 @@ namespace Xamarin.Forms public Shell() { Navigation = new NavigationImpl(this); - ((INotifyCollectionChanged)Items).CollectionChanged += (s, e) => SendStructureChanged(); Route = Routing.GenerateImplicitRoute("shell"); + Initialize(); + } + + void Initialize() + { + if (CurrentItem != null) + SetCurrentItem(); + + ShellController.ItemsCollectionChanged += (s, e) => + { + SetCurrentItem(); + SendStructureChanged(); + }; + + void SetCurrentItem() + { + var shellItems = ShellController.GetItems(); + + if (CurrentItem != null && shellItems.Contains(CurrentItem)) + return; + + ShellItem shellItem = null; + + foreach (var item in shellItems) + { + if (item is ShellItem && ValidDefaultShellItem(item)) + { + shellItem = item; + break; + } + } + + if (shellItem != null) + ShellController.OnFlyoutItemSelected(shellItem); + } } public ScrollMode FlyoutVerticalScrollMode @@ -736,13 +855,13 @@ namespace Xamarin.Forms } } - foreach (var shellItem in Items) + foreach (var shellItem in ShellController.GetItems()) { if (shellItem.FlyoutDisplayOptions == FlyoutDisplayOptions.AsMultipleItems) { IncrementGroup(); - foreach (var shellSection in shellItem.Items) + foreach (var shellSection in (shellItem as IShellItemController).GetItems()) { if (shellSection.FlyoutDisplayOptions == FlyoutDisplayOptions.AsMultipleItems) { @@ -760,11 +879,11 @@ namespace Xamarin.Forms } else { - if(!(shellSection.Parent is TabBar)) + if (!(shellSection.Parent is TabBar)) currentGroup.Add(shellSection); // If we have only a single child we will also show the items menu items - if (shellSection.Items.Count == 1 && shellSection == shellItem.CurrentItem) + if ((shellSection as IShellSectionController).GetItems().Count == 1 && shellSection == shellItem.CurrentItem) { currentGroup.AddRange(shellSection.CurrentItem.MenuItems); } @@ -801,34 +920,6 @@ namespace Xamarin.Forms return false; } - protected override void OnChildAdded(Element child) - { - base.OnChildAdded(child); - - if (child is ShellItem shellItem && CurrentItem == null && ValidDefaultShellItem(child)) - { - ((IShellController)this).OnFlyoutItemSelected(shellItem); - } - } - - protected override void OnChildRemoved(Element child) - { - base.OnChildRemoved(child); - - if (child == CurrentItem) - { - for (var i = 0; i < Items.Count; i++) - { - var item = Items[i]; - if (ValidDefaultShellItem(item)) - { - ((IShellController)this).OnFlyoutItemSelected(item); - break; - } - } - } - } - bool ValidDefaultShellItem(Element child) => !(child is MenuShellItem); internal override IEnumerable ChildrenNotDrawnByThisElement @@ -877,8 +968,8 @@ namespace Xamarin.Forms var shell = (Shell)bindable; UpdateChecked(shell); - ((IShellController)shell).AppearanceChanged(shell, false); - ((IShellController)shell).UpdateCurrentState(ShellNavigationSource.ShellItemChanged); + shell.ShellController.AppearanceChanged(shell, false); + shell.ShellController.UpdateCurrentState(ShellNavigationSource.ShellItemChanged); } static void OnCurrentItemChanging(BindableObject bindable, object oldValue, object newValue) @@ -893,7 +984,7 @@ namespace Xamarin.Forms var shellSection = shellItem.CurrentItem; var shellContent = shellSection.CurrentItem; var stack = shellSection.Stack; - ((IShellController)shell).ProposeNavigation(ShellNavigationSource.ShellItemChanged, shellItem, shellSection, shellContent, stack, false); + shell.ShellController.ProposeNavigation(ShellNavigationSource.ShellItemChanged, shellItem, shellSection, shellContent, stack, false); } } @@ -909,7 +1000,7 @@ namespace Xamarin.Forms if (root is Shell shell) { ShellItem currentItem = shell.CurrentItem; - var items = shell.Items; + var items = shell.ShellController.GetItems(); var count = items.Count; for (int i = 0; i < count; i++) { @@ -920,7 +1011,7 @@ namespace Xamarin.Forms else if (root is ShellItem shellItem) { var currentItem = shellItem.CurrentItem; - var items = shellItem.Items; + var items = (shellItem as IShellItemController).GetItems(); var count = items.Count; for (int i = 0; i < count; i++) { @@ -931,7 +1022,7 @@ namespace Xamarin.Forms else if (root is ShellSection shellSection) { var currentItem = shellSection.CurrentItem; - var items = shellSection.Items; + var items = (shellSection as IShellSectionController).GetItems(); var count = items.Count; for (int i = 0; i < count; i++) { @@ -1167,8 +1258,13 @@ namespace Xamarin.Forms if (ModalStack.Count > 0) ModalStack[ModalStack.Count - 1].SendDisappearing(); - if (ModalStack.Count == 1) - _shell.CurrentItem.SendAppearing(); + if (!_shell.CurrentItem.CurrentItem.IsPoppingModalStack) + { + if (ModalStack.Count == 1) + _shell.CurrentItem.SendAppearing(); + else if (ModalStack.Count > 1) + ModalStack[ModalStack.Count - 2].SendAppearing(); + } return base.OnPopModal(animated); } @@ -1177,7 +1273,9 @@ namespace Xamarin.Forms if (ModalStack.Count == 0) _shell.CurrentItem.SendDisappearing(); - modal.SendAppearing(); + if(!_shell.CurrentItem.CurrentItem.IsPushingModalStack) + modal.SendAppearing(); + return base.OnPushModal(modal, animated); } } diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellContent.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellContent.cs index 60454fd..f27363f 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellContent.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellContent.cs @@ -9,6 +9,7 @@ using System.Linq; #endif using System.Reflection; +using System.Runtime.CompilerServices; using Xamarin.Forms.Internals; namespace Xamarin.Forms @@ -45,6 +46,9 @@ namespace Xamarin.Forms Page IShellContentController.Page => ContentCache; + EventHandler _isPageVisibleChanged; + event EventHandler IShellContentController.IsPageVisibleChanged { add => _isPageVisibleChanged += value; remove => _isPageVisibleChanged -= value; } + Page IShellContentController.GetOrCreateContent() { var template = ContentTemplate; @@ -125,13 +129,35 @@ namespace Xamarin.Forms protected override void OnChildAdded(Element child) { base.OnChildAdded(child); - if (child is Page page && IsVisibleContent) + if (child is Page page) { - SendAppearing(); - SendPageAppearing(page); + if (IsVisibleContent && page.IsVisible) + { + SendAppearing(); + SendPageAppearing(page); + } + + page.PropertyChanged += OnPagePropertyChanged; + _isPageVisibleChanged?.Invoke(this, EventArgs.Empty); } } + protected override void OnChildRemoved(Element child) + { + base.OnChildRemoved(child); + if (child is Page page) + { + page.PropertyChanged -= OnPagePropertyChanged; + } + } + + + void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Page.IsVisibleProperty.PropertyName) + _isPageVisibleChanged?.Invoke(this, EventArgs.Empty); + } + Page ContentCache { get => _contentCache; @@ -147,7 +173,7 @@ namespace Xamarin.Forms ((ShellSection)Parent).UpdateDisplayedPage(); } } - + public static implicit operator ShellContent(TemplatedPage page) { var shellContent = new ShellContent(); diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellContentCollection.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellContentCollection.cs index 75f335e..36f4a04 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellContentCollection.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellContentCollection.cs @@ -1,20 +1,95 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Linq; namespace Xamarin.Forms { internal sealed class ShellContentCollection : IList, INotifyCollectionChanged { + public event NotifyCollectionChangedEventHandler VisibleItemsChanged; ObservableCollection _inner = new ObservableCollection(); + ObservableCollection _visibleContents = new ObservableCollection(); - event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged + public ReadOnlyCollection VisibleItems { get; } + + public ShellContentCollection() { - add { ((INotifyCollectionChanged)_inner).CollectionChanged += value; } - remove { ((INotifyCollectionChanged)_inner).CollectionChanged -= value; } + _inner.CollectionChanged += InnerCollectionChanged; + VisibleItems = new ReadOnlyCollection(_visibleContents); + _visibleContents.CollectionChanged += (_, args) => + { + VisibleItemsChanged?.Invoke(VisibleItems, args); + }; + } + + void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (ShellContent element in e.NewItems) + { + if (element is IShellContentController controller) + controller.IsPageVisibleChanged += OnIsPageVisibleChanged; + CheckVisibility(element); + } + } + + if (e.OldItems != null) + { + Removing(e.OldItems); + } + + CollectionChanged?.Invoke(this, e); } + void Removing(IEnumerable items) + { + foreach (ShellContent element in items) + { + if (_visibleContents.Contains(element)) + _visibleContents.Remove(element); + + if (element is IShellContentController controller) + controller.IsPageVisibleChanged -= OnIsPageVisibleChanged; + } + } + + void OnIsPageVisibleChanged(object sender, EventArgs e) + { + CheckVisibility((ShellContent)sender); + } + + void CheckVisibility(ShellContent shellContent) + { + if (shellContent is IShellContentController controller) + { + // Assume incoming page will be visible + if (controller.Page == null) + { + if (!_visibleContents.Contains(shellContent)) + _visibleContents.Add(shellContent); + } + else if(controller.Page.IsVisible) + { + if (!_visibleContents.Contains(shellContent)) + _visibleContents.Add(shellContent); + } + else + { + _visibleContents.Remove(shellContent); + } + } + else if (_visibleContents.Contains(shellContent)) + { + _visibleContents.Remove(shellContent); + } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + public int Count => _inner.Count; public bool IsReadOnly => ((IList)_inner).IsReadOnly; @@ -27,7 +102,14 @@ namespace Xamarin.Forms public void Add(ShellContent item) => _inner.Add(item); - public void Clear() => _inner.Clear(); + public void Clear() + { + var list = _inner.ToList(); + Removing(_inner); + _inner.Clear(); + + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list)); + } public bool Contains(ShellContent item) => _inner.Contains(item); diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellExtensions.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellExtensions.cs index 37aaf93..8fb822e 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellExtensions.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellExtensions.cs @@ -20,11 +20,12 @@ namespace Xamarin.Forms.Core SearchForPart(shell, (p) => p.Route == route); - public static BaseShellItem SearchForPart(this Shell shell, Func searchBy) + public static BaseShellItem SearchForPart(this IShellController shell, Func searchBy) { - for (var i = 0; i < shell.Items.Count; i++) + var items = shell.GetItems(); + for (var i = 0; i < items.Count; i++) { - var result = SearchForPart(shell.Items[i], searchBy); + var result = SearchForPart(items[i], searchBy); if (result != null) return result; } @@ -40,16 +41,16 @@ namespace Xamarin.Forms.Core BaseShellItem baseShellItem = null; switch (part) { - case ShellItem item: - foreach (var section in item.Items) + case IShellItemController item: + foreach (var section in item.GetItems()) { baseShellItem = SearchForPart(section, searchBy); if (baseShellItem != null) return baseShellItem; } break; - case ShellSection section: - foreach (var content in section.Items) + case IShellSectionController section: + foreach (var content in section.GetItems()) { baseShellItem = SearchForPart(content, searchBy); if (baseShellItem != null) diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellItem.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellItem.cs index 2d46e7c..6570393 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellItem.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellItem.cs @@ -41,12 +41,14 @@ namespace Xamarin.Forms #region IShellItemController + IShellItemController ShellItemController => this; + internal Task GoToPart(NavigationRequest request, Dictionary queryData) { var shellSection = request.Request.Section; if (shellSection == null) - shellSection = Items[0]; + shellSection = ShellItemController.GetItems()[0]; Shell.ApplyQueryAttributes(shellSection, queryData, request.Request.Content == null); @@ -77,6 +79,14 @@ namespace Xamarin.Forms return accept; } + ReadOnlyCollection IShellItemController.GetItems() => ((ShellSectionCollection)Items).VisibleItems; + + event NotifyCollectionChangedEventHandler IShellItemController.ItemsCollectionChanged + { + add { ((ShellSectionCollection)Items).VisibleItemsChanged += value; } + remove { ((ShellSectionCollection)Items).VisibleItemsChanged -= value; } + } + #endregion IShellItemController #region IPropertyPropagationController @@ -99,7 +109,9 @@ namespace Xamarin.Forms public ShellItem() { - ((INotifyCollectionChanged)Items).CollectionChanged += ItemsCollectionChanged; + ShellItemController.ItemsCollectionChanged += (_, __) => SendStructureChanged(); + (Items as INotifyCollectionChanged).CollectionChanged += ItemsCollectionChanged; + _platformConfigurationRegistry = new Lazy>(() => new PlatformConfigurationRegistry(this)); } @@ -196,10 +208,10 @@ namespace Xamarin.Forms base.OnChildRemoved(child); if (CurrentItem == child) { - if (Items.Count == 0) + if (ShellItemController.GetItems().Count == 0) ClearValue(CurrentItemProperty); else - SetValueFromRenderer(CurrentItemProperty, Items[0]); + SetValueFromRenderer(CurrentItemProperty, ShellItemController.GetItems()[0]); } } @@ -237,8 +249,6 @@ namespace Xamarin.Forms foreach (Element element in e.OldItems) OnChildRemoved(element); } - - SendStructureChanged(); } internal override void SendAppearing() diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellItemCollection.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellItemCollection.cs index 6611768..dfd1cfc 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellItemCollection.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellItemCollection.cs @@ -1,20 +1,134 @@ using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Linq; namespace Xamarin.Forms { internal sealed class ShellItemCollection : IList, INotifyCollectionChanged { - event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged + public event NotifyCollectionChangedEventHandler VisibleItemsChanged; + + IList _inner; + ObservableCollection _visibleContents = new ObservableCollection(); + public ReadOnlyCollection VisibleItems { get; } + + public ShellItemCollection() { - add { ((INotifyCollectionChanged)Inner).CollectionChanged += value; } - remove { ((INotifyCollectionChanged)Inner).CollectionChanged -= value; } + VisibleItems = new ReadOnlyCollection(_visibleContents); + _visibleContents.CollectionChanged += (_, args) => + { + VisibleItemsChanged?.Invoke(VisibleItems, args); + }; } + public event NotifyCollectionChangedEventHandler CollectionChanged; + public int Count => Inner.Count; public bool IsReadOnly => ((IList)Inner).IsReadOnly; - internal IList Inner { get; set; } + internal IList Inner + { + get + { + return _inner; + } + set + { + _inner = value; + ((INotifyCollectionChanged)_inner).CollectionChanged += InnerCollectionChanged; + } + } + + void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (ShellItem element in e.NewItems) + { + if (element is IShellItemController controller) + controller.ItemsCollectionChanged += OnShellItemControllerItemsCollectionChanged; + + CheckVisibility(element); + } + } + + if (e.OldItems != null) + { + Removing(e.OldItems); + } + + CollectionChanged?.Invoke(this, e); + } + + + void Removing(IEnumerable items) + { + foreach (ShellItem element in items) + { + if (_visibleContents.Contains(element)) + _visibleContents.Remove(element); + + if (element is IShellSectionController controller) + controller.ItemsCollectionChanged -= OnShellItemControllerItemsCollectionChanged; + } + } + + void OnShellItemControllerItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + foreach (ShellSection section in (e.NewItems ?? e.OldItems ?? (IList)_inner)) + { + if (section.Parent == null) + section.ParentSet += OnParentSet; + else + CheckVisibility(section.Parent as ShellItem); + } + + void OnParentSet(object s, System.EventArgs __) + { + var shellSection = (ShellSection)s; + shellSection.ParentSet -= OnParentSet; + CheckVisibility(shellSection.Parent as ShellItem); + } + } + + void CheckVisibility(ShellItem shellItem) + { + if (IsShellItemVisible(shellItem)) + { + if (_visibleContents.Contains(shellItem)) + return; + + int visibleIndex = 0; + for (var i = 0; i < _inner.Count; i++) + { + var item = _inner[i]; + + if (!IsShellItemVisible(item)) + continue; + + if (item == shellItem) + { + _visibleContents.Insert(visibleIndex, shellItem); + break; + } + + visibleIndex++; + } + } + else if (_visibleContents.Contains(shellItem)) + { + _visibleContents.Remove(shellItem); + } + + bool IsShellItemVisible(ShellItem item) + { + return (item is IShellItemController itemController && itemController.GetItems().Count > 0) || + item is IMenuItemController; + } + } + + public ShellItem this[int index] { @@ -36,7 +150,7 @@ namespace Xamarin.Forms ) { int i = Count - 1; - if (i >= 0 && this[i] is TabBar && Routing.IsImplicit(this[i])) + if (i >= 0 && this[i] is TabBar && Routing.IsImplicit(this[i])) { this[i].Items.Add(item.Items[0]); return; @@ -46,7 +160,13 @@ namespace Xamarin.Forms Inner.Add(item); } - public void Clear() => Inner.Clear(); + public void Clear() + { + var list = Inner.ToList(); + Removing(Inner); + Inner.Clear(); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list)); + } public bool Contains(ShellItem item) => Inner.Contains(item); diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellSection.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellSection.cs index 761396f..607990e 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellSection.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellSection.cs @@ -29,6 +29,7 @@ namespace Xamarin.Forms #region IShellSectionController + IShellSectionController ShellSectionController => this; readonly List<(object Observer, Action Callback)> _displayedPageObservers = new List<(object Observer, Action Callback)>(); readonly List _observers = new List(); @@ -47,6 +48,14 @@ namespace Xamarin.Forms { get { + if (Navigation.ModalStack.Count > 0) + { + if (Navigation.ModalStack[Navigation.ModalStack.Count - 1] is NavigationPage np) + return np.Navigation.NavigationStack[np.Navigation.NavigationStack.Count - 1]; + + return Navigation.ModalStack[0]; + } + if (_navStack.Count > 1) return _navStack[_navStack.Count - 1]; return ((IShellContentController)CurrentItem)?.Page; @@ -72,7 +81,7 @@ namespace Xamarin.Forms ShellContent shellContent = request.Request.Content; if (shellContent == null) - shellContent = Items[0]; + shellContent = ShellSectionController.GetItems()[0]; if (request.Request.GlobalRoutes.Count > 0) { @@ -118,6 +127,45 @@ namespace Xamarin.Forms _lastTabThickness = tabThickness; } + async void IShellSectionController.SendPopping(Task poppingCompleted) + { + if (_navStack.Count <= 1) + throw new Exception("Nav Stack consistency error"); + + var page = _navStack[_navStack.Count - 1]; + + _navStack.Remove(page); + UpdateDisplayedPage(); + + await poppingCompleted; + + RemovePage(page); + SendUpdateCurrentState(ShellNavigationSource.Pop); + } + + async void IShellSectionController.SendPoppingToRoot(Task finishedPopping) + { + if (_navStack.Count <= 1) + throw new Exception("Nav Stack consistency error"); + + var oldStack = _navStack; + _navStack = new List { null }; + + for (int i = 1; i < oldStack.Count; i++) + oldStack[i].SendDisappearing(); + + UpdateDisplayedPage(); + await finishedPopping; + + for (int i = 1; i < oldStack.Count; i++) + RemovePage(oldStack[i]); + + SendUpdateCurrentState(ShellNavigationSource.PopToRoot); + } + + [Obsolete] + [EditorBrowsable(EditorBrowsableState.Never)] + void IShellSectionController.SendPopped() { if (_navStack.Count <= 1) @@ -131,6 +179,10 @@ namespace Xamarin.Forms SendUpdateCurrentState(ShellNavigationSource.Pop); } + ReadOnlyCollection IShellSectionController.GetItems() => ((ShellContentCollection)Items).VisibleItems; + + [Obsolete] + [EditorBrowsable(EditorBrowsableState.Never)] void IShellSectionController.SendPopping(Page page) { if (_navStack.Count <= 1) @@ -140,15 +192,24 @@ namespace Xamarin.Forms SendAppearanceChanged(); } + [Obsolete] + [EditorBrowsable(EditorBrowsableState.Never)] void IShellSectionController.SendPopped(Page page) { - if(_navStack.Contains(page)) + if (_navStack.Contains(page)) _navStack.Remove(page); RemovePage(page); SendUpdateCurrentState(ShellNavigationSource.Pop); } + + event NotifyCollectionChangedEventHandler IShellSectionController.ItemsCollectionChanged + { + add { ((ShellContentCollection)Items).VisibleItemsChanged += value; } + remove { ((ShellContentCollection)Items).VisibleItemsChanged -= value; } + } + #endregion IShellSectionController #region IPropertyPropagationController @@ -170,13 +231,15 @@ namespace Xamarin.Forms ReadOnlyCollection _logicalChildrenReadOnly; List _navStack = new List { null }; + internal bool IsPushingModalStack { get; private set; } + internal bool IsPoppingModalStack { get; private set; } public ShellSection() { - ((INotifyCollectionChanged)Items).CollectionChanged += ItemsCollectionChanged; + (Items as INotifyCollectionChanged).CollectionChanged += ItemsCollectionChanged; Navigation = new NavigationImpl(this); } - + public ShellContent CurrentItem { get { return (ShellContent)GetValue(CurrentItemProperty); } @@ -245,44 +308,163 @@ namespace Xamarin.Forms return (ShellSection)(ShellContent)page; } - internal async Task GoToAsync(NavigationRequest request, IDictionary queryData, bool animate) + internal async Task GoToAsync(NavigationRequest request, IDictionary queryData, bool? animate) { - List routes = request.Request.GlobalRoutes; - if (routes == null || routes.Count == 0) + List globalRoutes = request.Request.GlobalRoutes; + List navStack = null; + string route = String.Empty; + + if (globalRoutes == null || globalRoutes.Count == 0) { - await Navigation.PopToRootAsync(animate); + await Navigation.PopToRootAsync(animate ?? false); return; } - for (int i = 0; i < routes.Count; i++) + int whereToStartNavigation = 0; + + // Pop the stack down to where it no longer matches + if (request.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt) { - bool isLast = i == routes.Count - 1; - var route = routes[i]; - var navPage = _navStack.Count > i + 1 ? _navStack[i + 1] : null; - - if (navPage != null) + for (int i = 0; i < globalRoutes.Count; i++) { - if (Routing.GetRoute(navPage) == route) + whereToStartNavigation = i; + bool isLast = i == globalRoutes.Count - 1; + route = globalRoutes[i]; + + navStack = BuildFlattenedNavigationStack(new List(_navStack), Navigation?.ModalStack); + + // if the navStack count is one that means there is nothing pushed + if (navStack.Count == 1) + break; + + Page navPage = navStack.Count > i + 1 ? navStack[i + 1] : null; + + if (navPage != null) { - Shell.ApplyQueryAttributes(navPage, queryData, isLast); - continue; - } + // if the routes don't match then pop this route off the stack + int popCount = i + 1; - if (request.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt) - { - while (_navStack.Count > i + 1) + if (Routing.GetRoute(navPage) == route) { - await OnPopAsync(false); + // if the routes do match and this is the last in the loop + // pop everything after this route + popCount = i + 2; + whereToStartNavigation++; + Shell.ApplyQueryAttributes(navPage, queryData, isLast); + + // If we're not on the last loop of the stack then continue + // otherwise pop the rest of the stack + if(!isLast) + continue; } + + IsPoppingModalStack = true; + while (navStack.Count > popCount) + { + if (Navigation.ModalStack.Contains(navStack[navStack.Count - 1])) + { + await Navigation.PopModalAsync(false); + } + else if (Navigation.ModalStack.Count > 0) + { + await Navigation.ModalStack[Navigation.ModalStack.Count - 1].Navigation.PopAsync(false); + } + else + { + await OnPopAsync(false); + } + + navStack = BuildFlattenedNavigationStack(new List(_navStack), Navigation?.ModalStack); + } + IsPoppingModalStack = false; + + break; } + + var content = Routing.GetOrCreateContent(route) as Page; + if (content == null) + break; + + Shell.ApplyQueryAttributes(content, queryData, isLast); } + } + + List modalPageStacks = new List(); + List nonModalPageStacks = new List(); + if (Navigation?.ModalStack?.Count > 0) + modalPageStacks.AddRange(Navigation.ModalStack); + + // populate global routes and build modal stacks + for (int i = whereToStartNavigation; i < globalRoutes.Count; i++) + { + bool isLast = i == globalRoutes.Count - 1; + route = globalRoutes[i]; var content = Routing.GetOrCreateContent(route) as Page; if (content == null) break; + var isModal = (Shell.GetPresentationMode(content) & PresentationMode.Modal) == PresentationMode.Modal; + Shell.ApplyQueryAttributes(content, queryData, isLast); - await OnPushAsync(content, i == routes.Count - 1 && animate); + if (isModal) + { + modalPageStacks.Add(content); + } + else if (modalPageStacks.Count > 0) + { + if (modalPageStacks[modalPageStacks.Count - 1] is NavigationPage navigationPage) + await navigationPage.Navigation.PushAsync(content); + else + throw new InvalidOperationException($"Shell cannot push a page to the following type: {modalPageStacks[modalPageStacks.Count - 1]}. The visible modal page needs to be a NavigationPage"); + } + else + { + nonModalPageStacks.Add(content); + } + } + + for (int i = Navigation.ModalStack.Count; i < modalPageStacks.Count; i++) + { + bool isLast = i == modalPageStacks.Count - 1; + bool isAnimated = animate ?? (Shell.GetPresentationMode(modalPageStacks[i]) & PresentationMode.NotAnimated) != PresentationMode.NotAnimated; + IsPushingModalStack = !isLast; + await ((NavigationImpl)Navigation).PushModalAsync(modalPageStacks[i], isAnimated); + } + + for (int i = nonModalPageStacks.Count - 1; i >= 0; i--) + { + bool isLast = i == nonModalPageStacks.Count - 1; + + if(isLast) + { + bool isAnimated = animate ?? (Shell.GetPresentationMode(nonModalPageStacks[i]) & PresentationMode.NotAnimated) != PresentationMode.NotAnimated; + await OnPushAsync(nonModalPageStacks[i], isAnimated); + } + else + Navigation.InsertPageBefore(nonModalPageStacks[i], nonModalPageStacks[nonModalPageStacks.Count - 1]); + } + + if (Parent?.Parent is IShellController shell) + { + shell.UpdateCurrentState(ShellNavigationSource.ShellSectionChanged); + } + + List BuildFlattenedNavigationStack(List startingList, IReadOnlyList modalStack) + { + if (modalStack == null) + return startingList; + + for (int i = 0; i < modalStack.Count; i++) + { + startingList.Add(modalStack[i]); + for (int j = 1; j < modalStack[i].Navigation.NavigationStack.Count; j++) + { + startingList.Add(modalStack[i].Navigation.NavigationStack[j]); + } + } + + return startingList; } } @@ -307,12 +489,12 @@ namespace Xamarin.Forms else { IShellContentController currentItem = CurrentItem; - if (currentItem.Page != null) - DisplayedPage = currentItem.Page; + DisplayedPage = currentItem?.Page; } if (previousPage != DisplayedPage) { + previousPage?.SendDisappearing(); PresentedPageAppearing(); SendAppearanceChanged(); } @@ -321,10 +503,11 @@ namespace Xamarin.Forms protected override void OnChildAdded(Element child) { base.OnChildAdded(child); - if (CurrentItem == null && Items.Contains(child)) + if (CurrentItem == null && ((IShellSectionController)this).GetItems().Contains(child)) SetValueFromRenderer(CurrentItemProperty, child); - UpdateDisplayedPage(); + if(CurrentItem != null) + UpdateDisplayedPage(); } protected override void OnChildRemoved(Element child) @@ -332,7 +515,8 @@ namespace Xamarin.Forms base.OnChildRemoved(child); if (CurrentItem == child) { - if (Items.Count == 0) + var items = ShellSectionController.GetItems(); + if (items.Count == 0) ClearValue(CurrentItemProperty); else { @@ -340,7 +524,7 @@ namespace Xamarin.Forms Device.BeginInvokeOnMainThread(() => { if (CurrentItem == null) - SetValueFromRenderer(CurrentItemProperty, Items[0]); + SetValueFromRenderer(CurrentItemProperty, items[0]); }); } } @@ -497,6 +681,41 @@ namespace Xamarin.Forms return args.Task; } + internal async Task PopModalStackToPage(Page page, bool? animated) + { + try + { + IsPoppingModalStack = true; + int modalStackCount = Navigation.ModalStack.Count; + for (int i = 0; i < modalStackCount; i++) + { + var pageToPop = Navigation.ModalStack[Navigation.ModalStack.Count - 1]; + if (pageToPop == page) + break; + + // indicate that we are done popping down the stack to the modal page requested + // This is mainly used by life cycle events so they don't fire onappearing + if(page == null && Navigation.ModalStack.Count == 1) + { + IsPoppingModalStack = false; + } + else if(Navigation.ModalStack.Count > 1 && Navigation.ModalStack[Navigation.ModalStack.Count - 2] == page) + { + IsPoppingModalStack = false; + } + + bool isAnimated = animated ?? (Shell.GetPresentationMode(pageToPop) & PresentationMode.NotAnimated) != PresentationMode.NotAnimated; + await Navigation.PopModalAsync(isAnimated); + } + + ((IShellController)Shell).UpdateCurrentState(ShellNavigationSource.ShellSectionChanged); + } + finally + { + IsPoppingModalStack = false; + } + } + protected virtual void OnRemovePage(Page page) { if (!_navStack.Contains(page)) @@ -673,9 +892,6 @@ namespace Xamarin.Forms protected override Task OnPopModal(bool animated) { - if (ModalStack.Count > 0) - ModalStack[ModalStack.Count - 1].SendDisappearing(); - if(ModalStack.Count == 1) { _owner.PresentedPageAppearing(); @@ -683,6 +899,7 @@ namespace Xamarin.Forms return base.OnPopModal(animated); } + protected override Task OnPushModal(Page modal, bool animated) { if (ModalStack.Count == 0) @@ -690,7 +907,6 @@ namespace Xamarin.Forms _owner.PresentedPageDisappearing(); } - modal.SendAppearing(); return base.OnPushModal(modal, animated); } } diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellSectionCollection.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellSectionCollection.cs index b8de8a5..f2d7c87 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellSectionCollection.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellSectionCollection.cs @@ -1,20 +1,119 @@ using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Linq; namespace Xamarin.Forms { - internal sealed class ShellSectionCollection : IList, INotifyCollectionChanged + internal sealed class ShellSectionCollection : IList, INotifyCollectionChanged { - event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged + public event NotifyCollectionChangedEventHandler VisibleItemsChanged; + IList _inner; + ObservableCollection _visibleContents = new ObservableCollection(); + + public ShellSectionCollection() { - add { ((INotifyCollectionChanged)Inner).CollectionChanged += value; } - remove { ((INotifyCollectionChanged)Inner).CollectionChanged -= value; } + VisibleItems = new ReadOnlyCollection(_visibleContents); + _visibleContents.CollectionChanged += (_, args) => + { + VisibleItemsChanged?.Invoke(VisibleItems, args); + }; } + public ReadOnlyCollection VisibleItems { get; } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + public int Count => Inner.Count; public bool IsReadOnly => Inner.IsReadOnly; - internal IList Inner { get; set; } + internal IList Inner + { + get => _inner; + set + { + _inner = value; + ((INotifyCollectionChanged)_inner).CollectionChanged += InnerCollectionChanged; + } + } + + void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (ShellSection element in e.NewItems) + { + if (element is IShellSectionController controller) + controller.ItemsCollectionChanged += OnShellSectionControllerItemsCollectionChanged; + + CheckVisibility(element); + } + } + + if (e.OldItems != null) + { + Removing(e.OldItems); + } + + CollectionChanged?.Invoke(this, e); + } + + void Removing(IEnumerable items) + { + foreach (ShellSection element in items) + { + if (_visibleContents.Contains(element)) + _visibleContents.Remove(element); + + if (element is IShellSectionController controller) + controller.ItemsCollectionChanged -= OnShellSectionControllerItemsCollectionChanged; + } + } + + void OnShellSectionControllerItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + foreach (ShellContent content in (e.NewItems ?? e.OldItems ?? (IList)_inner)) + { + if(content.Parent == null) + content.ParentSet += OnParentSet; + else + CheckVisibility(content.Parent as ShellSection); + } + + void OnParentSet(object s, System.EventArgs __) + { + var shellContent = (ShellContent)s; + shellContent.ParentSet -= OnParentSet; + CheckVisibility(shellContent.Parent as ShellSection); + } + } + + void CheckVisibility(ShellSection section) + { + if (section is IShellSectionController controller && controller.GetItems().Count > 0) + { + if (_visibleContents.Contains(section)) + return; + + int visibleIndex = 0; + for (var i = 0; i < _inner.Count; i++) + { + var item = _inner[i]; + + if (item == section) + { + _visibleContents.Insert(visibleIndex, section); + break; + } + + visibleIndex++; + } + } + else if (_visibleContents.Contains(section)) + { + _visibleContents.Remove(section); + } + } public ShellSection this[int index] { @@ -24,7 +123,13 @@ namespace Xamarin.Forms public void Add(ShellSection item) => Inner.Add(item); - public void Clear() => Inner.Clear(); + public void Clear() + { + var list = Inner.ToList(); + Removing(Inner); + Inner.Clear(); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list)); + } public bool Contains(ShellSection item) => Inner.Contains(item); diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellUriHandler.cs b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellUriHandler.cs index 30aac4c..f1cefd8 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellUriHandler.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Shell/ShellUriHandler.cs @@ -499,14 +499,14 @@ namespace Xamarin.Forms IEnumerable results = null; switch (node) { - case Shell shell: - results = shell.Items; + case IShellController shell: + results = shell.GetItems(); break; - case ShellItem item: - results = item.Items; + case IShellItemController item: + results = item.GetItems(); break; - case ShellSection section: - results = section.Items; + case IShellSectionController section: + results = section.GetItems(); break; case ShellContent content: results = new object[0]; diff --git a/Xamarin.Forms/Xamarin.Forms.Core/StackLayout.cs b/Xamarin.Forms/Xamarin.Forms.Core/StackLayout.cs index c4f9322..493d93d 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/StackLayout.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/StackLayout.cs @@ -6,10 +6,10 @@ namespace Xamarin.Forms { public class StackLayout : Layout, IElementConfiguration { - public static readonly BindableProperty OrientationProperty = BindableProperty.Create("Orientation", typeof(StackOrientation), typeof(StackLayout), StackOrientation.Vertical, + public static readonly BindableProperty OrientationProperty = BindableProperty.Create(nameof(Orientation), typeof(StackOrientation), typeof(StackLayout), StackOrientation.Vertical, propertyChanged: (bindable, oldvalue, newvalue) => ((StackLayout)bindable).InvalidateLayout()); - public static readonly BindableProperty SpacingProperty = BindableProperty.Create("Spacing", typeof(double), typeof(StackLayout), 6d, + public static readonly BindableProperty SpacingProperty = BindableProperty.Create(nameof(Spacing), typeof(double), typeof(StackLayout), 6d, propertyChanged: (bindable, oldvalue, newvalue) => ((StackLayout)bindable).InvalidateLayout()); LayoutInformation _layoutInformation = new LayoutInformation(); @@ -40,7 +40,7 @@ namespace Xamarin.Forms protected override void LayoutChildren(double x, double y, double width, double height) { - if (!HasVisibileChildren()) + if (!HasVisibleChildren()) { return; } @@ -70,7 +70,7 @@ namespace Xamarin.Forms [EditorBrowsable(EditorBrowsableState.Never)] protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) { - if (!HasVisibileChildren()) + if (!HasVisibleChildren()) { return new SizeRequest(); } @@ -386,7 +386,7 @@ namespace Xamarin.Forms } } - bool HasVisibileChildren() + bool HasVisibleChildren() { for (var index = 0; index < InternalChildren.Count; index++) { diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Stepper.cs b/Xamarin.Forms/Xamarin.Forms.Core/Stepper.cs index 6aa4ca4..b4e7d21 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Stepper.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Stepper.cs @@ -32,7 +32,9 @@ namespace Xamarin.Forms public static readonly BindableProperty ValueProperty = BindableProperty.Create("Value", typeof(double), typeof(Stepper), 0.0, BindingMode.TwoWay, coerceValue: (bindable, value) => { var stepper = (Stepper)bindable; - return ((double)value).Clamp(stepper.Minimum, stepper.Maximum); + stepper.StepperPosition = Convert.ToInt32(((double)value - stepper.Minimum) / stepper.Increment); + var stepVal = stepper.Minimum + (stepper.StepperPosition * stepper.Increment); + return stepVal.Clamp(stepper.Minimum, stepper.Maximum); }, propertyChanged: (bindable, oldValue, newValue) => { var stepper = (Stepper)bindable; @@ -41,7 +43,9 @@ namespace Xamarin.Forms eh(stepper, new ValueChangedEventArgs((double)oldValue, (double)newValue)); }); - public static readonly BindableProperty IncrementProperty = BindableProperty.Create("Increment", typeof(double), typeof(Stepper), 1.0); + public static readonly BindableProperty IncrementProperty = BindableProperty.Create(nameof(Increment), typeof(double), typeof(Stepper), 1.0); + + private static readonly BindableProperty StepperPositionProperty = BindableProperty.Create(nameof(StepperPosition), typeof(int), typeof(Stepper), 0); readonly Lazy> _platformConfigurationRegistry; @@ -65,8 +69,9 @@ namespace Xamarin.Forms Maximum = max; } - Value = val.Clamp(min, max); + StepperPosition = (int)((val - min) / increment); Increment = increment; + Value = val.Clamp(min, max); } public double Increment @@ -93,11 +98,17 @@ namespace Xamarin.Forms set { SetValue(ValueProperty, value); } } + public int StepperPosition + { + get { return (int)GetValue(StepperPositionProperty); } + set { SetValue(StepperPositionProperty, value); } + } + public event EventHandler ValueChanged; - + public IPlatformElementConfiguration On() where T : IConfigPlatform { return _platformConfigurationRegistry.Value.On(); } } -} \ No newline at end of file +} diff --git a/Xamarin.Forms/Xamarin.Forms.Core/TabIndexExtensions.cs b/Xamarin.Forms/Xamarin.Forms.Core/TabIndexExtensions.cs index 1bd0c0c..5a39ed1 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/TabIndexExtensions.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/TabIndexExtensions.cs @@ -22,8 +22,8 @@ namespace Xamarin.Forms var descendantsOnPage = parentPage?.VisibleDescendants(); - if (parentPage is Shell shell) - descendantsOnPage = shell.Items; + if (parentPage is IShellController shell) + descendantsOnPage = shell.GetItems(); if (descendantsOnPage == null) return new Dictionary>(); diff --git a/Xamarin.Forms/Xamarin.Forms.Core/Xaml/Diagnostics/VisualDiagnostics.cs b/Xamarin.Forms/Xamarin.Forms.Core/Xaml/Diagnostics/VisualDiagnostics.cs index 17bbe9b..b133994 100644 --- a/Xamarin.Forms/Xamarin.Forms.Core/Xaml/Diagnostics/VisualDiagnostics.cs +++ b/Xamarin.Forms/Xamarin.Forms.Core/Xaml/Diagnostics/VisualDiagnostics.cs @@ -22,7 +22,7 @@ namespace Xamarin.Forms.Xaml.Diagnostics internal static void SendVisualTreeChanged(object parent, object child) { if (DebuggerHelper.DebuggerIsAttached) - VisualTreeChanged?.Invoke(parent, new VisualTreeChangeEventArgs(parent, child, -1, VisualTreeChangeType.Add)); + VisualTreeChanged?.Invoke(parent, new VisualTreeChangeEventArgs(parent, child, -1, child != null ? VisualTreeChangeType.Add : VisualTreeChangeType.Remove)); } public static event EventHandler VisualTreeChanged; diff --git a/Xamarin.Forms/Xamarin.Forms.Material.Tizen/Shell/MaterialNavigationView.cs b/Xamarin.Forms/Xamarin.Forms.Material.Tizen/Shell/MaterialNavigationView.cs index db2d42f..6adab07 100644 --- a/Xamarin.Forms/Xamarin.Forms.Material.Tizen/Shell/MaterialNavigationView.cs +++ b/Xamarin.Forms/Xamarin.Forms.Material.Tizen/Shell/MaterialNavigationView.cs @@ -3,11 +3,16 @@ using System.Collections.Generic; using ElmSharp; using Tizen.NET.MaterialComponents; using Xamarin.Forms.Platform.Tizen; +using EImage = ElmSharp.Image; namespace Xamarin.Forms.Material.Tizen { public class MaterialNavigationView : MNavigationView, INavigationView { + ImageSource _bgImageSource; + EImage _bg; + Aspect _bgImageAspect; + IDictionary _flyoutMenu = new Dictionary(); public MaterialNavigationView(EvasObject parent) : base(parent) @@ -15,6 +20,48 @@ namespace Xamarin.Forms.Material.Tizen MenuItemSelected += OnSelectedItemChanged; } + public Aspect BackgroundImageAspect + { + get + { + return _bgImageAspect; + } + set + { + _bgImageAspect = value; + if (_bg != null) + { + _bg.ApplyAspect(_bgImageAspect); + } + } + } + + public ImageSource BackgroundImageSource + { + get + { + return _bgImageSource; + } + set + { + _bgImageSource = value; + if(_bgImageSource != null) + { + if(_bg == null) + { + _bg = new EImage(this); + } + _bg.ApplyAspect(_bgImageAspect); + BackgroundImage = _bg; + _ = _bg.LoadFromImageSourceAsync(_bgImageSource); + } + else + { + BackgroundImage = null; + } + } + } + public event EventHandler SelectedItemChanged; public void BuildMenu(List> flyoutGroups) diff --git a/Xamarin.Forms/Xamarin.Forms.Material.Tizen/Xamarin.Forms.Material.Tizen.csproj b/Xamarin.Forms/Xamarin.Forms.Material.Tizen/Xamarin.Forms.Material.Tizen.csproj index 28c6154..588e4db 100644 --- a/Xamarin.Forms/Xamarin.Forms.Material.Tizen/Xamarin.Forms.Material.Tizen.csproj +++ b/Xamarin.Forms/Xamarin.Forms.Material.Tizen/Xamarin.Forms.Material.Tizen.csproj @@ -32,7 +32,7 @@ - + diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Deserializer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Deserializer.cs index 3366e06..035d7ea 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Deserializer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Deserializer.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; +using System.Diagnostics; +using System.IO.IsolatedStorage; using System.Runtime.Serialization; +using System.Threading.Tasks; using System.Xml; -using System.Diagnostics; -using System.IO; -using Xamarin.Forms.Internals; namespace Xamarin.Forms.Platform.Tizen { - internal class Deserializer : IDeserializer + internal class Deserializer : Internals.IDeserializer { const string PropertyStoreFile = "PropertyStore.forms"; @@ -19,33 +18,29 @@ namespace Xamarin.Forms.Platform.Tizen // Make sure to use Internal return Task.Run(() => { - var store = new TizenIsolatedStorageFile(); - Stream stream = null; - try + using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) { - stream = store.OpenFile(PropertyStoreFile, System.IO.FileMode.OpenOrCreate); - if (stream.Length == 0) - { + if (!store.FileExists(PropertyStoreFile)) return null; - } + + using (IsolatedStorageFileStream stream = store.OpenFile(PropertyStoreFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)) using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max)) { - stream = null; - var dcs = new DataContractSerializer(typeof(Dictionary)); - return (IDictionary)dcs.ReadObject(reader); - } - } - catch (Exception e) - { - Debug.WriteLine("Could not deserialize properties: " + e.Message); - Internals.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while reading Application properties: {e}"); - } - finally - { - if (stream != null) - { - stream.Dispose(); + if (stream.Length == 0) + return null; + + try + { + var dcs = new DataContractSerializer(typeof(Dictionary)); + return (IDictionary)dcs.ReadObject(reader); + } + catch (Exception e) + { + Debug.WriteLine("Could not deserialize properties: " + e.Message); + Internals.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while reading Application properties: {e}"); + } } + } return null; @@ -59,49 +54,43 @@ namespace Xamarin.Forms.Platform.Tizen // Make sure to use Internal return Task.Run(() => { - var success = false; - var store = new TizenIsolatedStorageFile(); - Stream stream = null; - try + using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) { - stream = store.OpenFile(PropertyStoreFile + ".tmp", System.IO.FileMode.OpenOrCreate); - using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream)) + // No need to write 0 properties if no file exists + if (properties.Count == 0 && !store.FileExists(PropertyStoreFile)) { - stream = null; - var dcs = new DataContractSerializer(typeof(Dictionary)); - dcs.WriteObject(writer, properties); - writer.Flush(); - success = true; + return; } - } - catch (Exception e) - { - Debug.WriteLine("Could not serialize properties: " + e.Message); - Internals.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while writing Application properties: {e}"); - } - finally - { - if (stream != null) + using (IsolatedStorageFileStream stream = store.OpenFile(PropertyStoreFile + ".tmp", System.IO.FileMode.OpenOrCreate)) + using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream)) { - stream.Dispose(); + try + { + var dcs = new DataContractSerializer(typeof(Dictionary)); + dcs.WriteObject(writer, properties); + writer.Flush(); + } + catch (Exception e) + { + Debug.WriteLine("Could not serialize properties: " + e.Message); + Internals.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while writing Application properties: {e}"); + return; + } } - } - - if (!success) - return; - try - { - if (store.FileExists(PropertyStoreFile)) - store.DeleteFile(PropertyStoreFile); - store.MoveFile(PropertyStoreFile + ".tmp", PropertyStoreFile); - } - catch (Exception e) - { - Debug.WriteLine("Could not move new serialized property file over old: " + e.Message); - Internals.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while writing Application properties: {e}"); + try + { + if (store.FileExists(PropertyStoreFile)) + store.DeleteFile(PropertyStoreFile); + store.MoveFile(PropertyStoreFile + ".tmp", PropertyStoreFile); + } + catch (Exception e) + { + Debug.WriteLine("Could not move new serialized property file over old: " + e.Message); + Internals.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while writing Application properties: {e}"); + } } }); } } -} +} \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/EmbeddedFontLoader.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/EmbeddedFontLoader.cs new file mode 100755 index 0000000..db5e295 --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/EmbeddedFontLoader.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using ElmSharp; +using TApplication = Tizen.Applications.Application; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class EmbeddedFontLoader : IEmbeddedFontLoader, IRegisterable + { + const string _fontCacheFolderName = "fonts"; + + public DirectoryInfo FontCacheDirectory { get; private set; } + + public EmbeddedFontLoader() + { + FontCacheDirectory = Directory.CreateDirectory(Path.Combine(TApplication.Current.DirectoryInfo.Data, _fontCacheFolderName)); + Utility.AppendGlobalFontPath(FontCacheDirectory.FullName); + } + + public (bool success, string filePath) LoadFont(EmbeddedFont font) + { + var filePath = Path.Combine(FontCacheDirectory.FullName, font.FontName); + if (File.Exists(filePath)) + return (true, filePath); + try + { + using (var fileStream = File.Create(filePath)) + { + font.ResourceStream.CopyTo(fileStream); + } + return (true, filePath); + } + catch (Exception ex) + { + Log.Error(ex.Message); + File.Delete(filePath); + } + return (false, null); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Extensions/FontExtensions.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Extensions/FontExtensions.cs new file mode 100755 index 0000000..6ba8c15 --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Extensions/FontExtensions.cs @@ -0,0 +1,50 @@ +using Xamarin.Forms.Core; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Platform.Tizen +{ + public static class FontExtensions + { + public static string ToNativeFontFamily(this string self) + { + if (string.IsNullOrEmpty(self)) + return null; + + var cleansedFont = CleanseFontName(self); + int index = cleansedFont.LastIndexOf('-'); + if (index != -1) + { + string font = cleansedFont.Substring(0, index); + string style = cleansedFont.Substring(index+1); + return $"{font}:style={style}"; + } + else + { + return cleansedFont; + } + } + + static string CleanseFontName(string fontName) + { + var fontFile = FontFile.FromString(fontName); + + if (!string.IsNullOrWhiteSpace(fontFile.Extension)) + { + var (hasFont, _) = FontRegistrar.HasFont(fontFile.FileNameWithExtension()); + if (hasFont) + return fontFile.PostScriptName; + } + else + { + foreach (var ext in FontFile.Extensions) + { + var formated = fontFile.FileNameWithExtension(ext); + var (hasFont, filePath) = FontRegistrar.HasFont(formated); + if (hasFont) + return fontFile.PostScriptName; + } + } + return fontFile.PostScriptName; + } + } +} diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Extensions/ImageExtensions.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Extensions/ImageExtensions.cs index 1eab7ab..f85f7c0 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Extensions/ImageExtensions.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Extensions/ImageExtensions.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using EImage = ElmSharp.Image; namespace Xamarin.Forms.Platform.Tizen @@ -28,6 +29,32 @@ namespace Xamarin.Forms.Platform.Tizen } } + public static async Task LoadFromImageSourceAsync(this EImage image, ImageSource source) + { + IImageSourceHandler handler; + bool isLoadComplate = false; + if (source != null && (handler = Forms.GetHandlerForObject(source)) != null) + { + isLoadComplate = await handler.LoadImageAsync(image, source); + } + if (!isLoadComplate) + { + //If it fails, call the Load function to remove the previous image. + image.Load(string.Empty); + } + + return isLoadComplate; + } + + public static bool LoadFromFile(this EImage image, string file) + { + if (!string.IsNullOrEmpty(file)) + { + return image.Load(ResourcePath.GetPath(file)); + } + return false; + } + public static bool IsNullOrEmpty(this ImageSource imageSource) => imageSource == null || imageSource.IsEmpty; } diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Forms.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Forms.cs old mode 100644 new mode 100755 index fb6a3c4..ea71ab5 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Forms.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Forms.cs @@ -425,6 +425,7 @@ namespace Xamarin.Forms } Elementary.Initialize(); Elementary.ThemeOverlay(); + Utility.AppendGlobalFontPath(@"/usr/share/fonts"); Device.PlatformServices = new TizenPlatformServices(); if (Device.info != null) @@ -485,7 +486,8 @@ namespace Xamarin.Forms typeof(ExportRendererAttribute), typeof(ExportImageSourceHandlerAttribute), typeof(ExportCellAttribute), - typeof(ExportHandlerAttribute) + typeof(ExportHandlerAttribute), + typeof(ExportFontAttribute) }); } } @@ -496,7 +498,8 @@ namespace Xamarin.Forms typeof(ExportRendererAttribute), typeof(ExportImageSourceHandlerAttribute), typeof(ExportCellAttribute), - typeof(ExportHandlerAttribute) + typeof(ExportHandlerAttribute), + typeof(ExportFontAttribute) }); } } @@ -530,7 +533,8 @@ namespace Xamarin.Forms typeof(ExportRendererAttribute), typeof(ExportImageSourceHandlerAttribute), typeof(ExportCellAttribute), - typeof(ExportHandlerAttribute) + typeof(ExportHandlerAttribute), + typeof(ExportFontAttribute) }); } } diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/LightweightPlatform.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/LightweightPlatform.cs index 132d22b..f6dff38 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/LightweightPlatform.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/LightweightPlatform.cs @@ -1,91 +1,222 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using System.Threading.Tasks; using ElmSharp; using Xamarin.Forms.Internals; +using EColor = ElmSharp.Color; namespace Xamarin.Forms.Platform.Tizen { - public class LightweightPlatform : BindableObject, ITizenPlatform + public class LightweightPlatform : ITizenPlatform, INavigation, IDisposable { - Page _page; - EvasObject _rootView; - bool _disposed; + NavigationModel _navModel = new NavigationModel(); + Native.Canvas _viewStack; + readonly PopupManager _popupManager; + bool _hasAlpha; + readonly EColor _defaultPlatformColor; - internal LightweightPlatform(EvasObject parent) + public LightweightPlatform(EvasObject parent) { Forms.NativeParent = parent; + _defaultPlatformColor = Device.Idiom == TargetIdiom.Phone ? EColor.White : EColor.Transparent; + _viewStack = new Native.Canvas(parent) + { + BackgroundColor = _defaultPlatformColor, + }; + _viewStack.SetAlignment(-1, -1); + _viewStack.SetWeight(1.0, 1.0); + _viewStack.LayoutUpdated += OnLayout; + _viewStack.Show(); + + if (Forms.UseMessagingCenter) + { + _popupManager = new PopupManager(this); + } } +#pragma warning disable 0067 public event EventHandler RootNativeViewChanged; +#pragma warning restore 0067 - public bool HasAlpha { get => false; set { } } - - public void Dispose() + public bool HasAlpha { - Dispose(true); - GC.SuppressFinalize(this); + get => _hasAlpha; + set + { + _hasAlpha = value; + _viewStack.BackgroundColor = _hasAlpha ? EColor.Transparent : _defaultPlatformColor; + } } - public EvasObject GetRootNativeView() => _rootView; + IPageController CurrentPageController => _navModel.CurrentPage as IPageController; + + IReadOnlyList INavigation.ModalStack => _navModel.Modals.ToList(); + + IReadOnlyList INavigation.NavigationStack => new List(); + + public void SetPage(Page page) + { + ResetChildren(); + _navModel = new NavigationModel(); + if (page == null) + return; + + _navModel.Push(page, null); + +#pragma warning disable CS0618 // Type or member is obsolete + page.Platform = this; +#pragma warning restore CS0618 // Type or member is obsolete + ((Application)page.RealParent).NavigationProxy.Inner = this; + + var renderer = Platform.CreateRenderer(page); + renderer.NativeView.Geometry = _viewStack.Geometry; + _viewStack.Children.Add(renderer.NativeView); + + CurrentPageController?.SendAppearing(); + } public bool SendBackButtonPressed() { - if (_page == null) return false; - return _page.SendBackButtonPressed(); + return _navModel?.CurrentPage?.SendBackButtonPressed() ?? false; } - public void SetPage(Page page) + public EvasObject GetRootNativeView() { - if (_page == page) return; - if (_page != null) + return _viewStack; + } + + public bool PageIsChildOfPlatform(Page page) + { + var parent = page.AncestorToRoot(); + return _navModel.Modals.FirstOrDefault() == page || _navModel.Roots.Contains(parent); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) { - var oldRenderer = Platform.GetRenderer(_page); - oldRenderer?.Dispose(); + _popupManager?.Dispose(); + _viewStack?.Unrealize(); } + } - _page = page; + Task INavigation.PopModalAsync() + { + return (this as INavigation).PopModalAsync(true); + } - if (_page == null) return; + Task INavigation.PopModalAsync(bool animated) + { + Page page = _navModel.PopModal(); + (page as IPageController)?.SendDisappearing(); -#pragma warning disable CS0618 // Type or member is obsolete - // The Platform property is no longer necessary, but we have to set it because some third-party - // library might still be retrieving it and using it - _page.Platform = this; -#pragma warning restore CS0618 // Type or member is obsolete + var renderer = Platform.GetRenderer(page); + _viewStack.Children.Remove(renderer.NativeView); + renderer.Dispose(); - var renderer = Platform.CreateRenderer(_page); - _rootView = renderer.NativeView; - RootNativeViewChanged?.Invoke(this, new RootNativeViewChangedEventArgs(_rootView)); - _rootView.Show(); + _viewStack.Children.LastOrDefault()?.Show(); - Device.StartTimer(TimeSpan.Zero, () => - { - _page?.SendAppearing(); - return false; - }); + CurrentPageController?.SendAppearing(); + return Task.FromResult(page); } - protected override void OnBindingContextChanged() + Task INavigation.PushModalAsync(Page modal) { - BindableObject.SetInheritedBindingContext(_page, base.BindingContext); - base.OnBindingContextChanged(); + return (this as INavigation).PushModalAsync(modal, true); } - protected virtual void Dispose(bool disposing) + Task INavigation.PushModalAsync(Page page, bool animated) { - if (_disposed) return; - if (disposing) + var previousPage = CurrentPageController; + previousPage?.SendDisappearing(); + + _navModel.PushModal(page); + + var lastTop = _viewStack.Children.LastOrDefault(); + + var renderer = Platform.GetOrCreateRenderer(page); + renderer.NativeView.Geometry = _viewStack.Geometry; + + _viewStack.Children.Add(renderer.NativeView); + if (lastTop != null) { - SetPage(null); + lastTop.Hide(); + renderer.NativeView.StackAbove(lastTop); } - _disposed = true; + + // Verify that the modal is still on the stack + if (_navModel.CurrentPage == page) + CurrentPageController.SendAppearing(); + return Task.CompletedTask; + } + + void INavigation.InsertPageBefore(Page page, Page before) + { + throw new InvalidOperationException("InsertPageBefore is not supported globally on Tizen, please use a NavigationPage."); + } + + Task INavigation.PopAsync() + { + return ((INavigation)this).PopAsync(true); + } + + Task INavigation.PopAsync(bool animated) + { + throw new InvalidOperationException("PopAsync is not supported globally on Tizen, please use a NavigationPage."); + } + + Task INavigation.PopToRootAsync() + { + return ((INavigation)this).PopToRootAsync(true); + } + + Task INavigation.PopToRootAsync(bool animated) + { + throw new InvalidOperationException("PopToRootAsync is not supported globally on Tizen, please use a NavigationPage."); + } + + Task INavigation.PushAsync(Page root) + { + return ((INavigation)this).PushAsync(root, true); + } + + Task INavigation.PushAsync(Page root, bool animated) + { + throw new InvalidOperationException("PushAsync is not supported globally on Tizen, please use a NavigationPage."); + } + + void INavigation.RemovePage(Page page) + { + throw new InvalidOperationException("RemovePage is not supported globally on Tizen, please use a NavigationPage."); } SizeRequest IPlatform.GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint) { return Platform.GetNativeSize(view, widthConstraint, heightConstraint); } + + void OnLayout(object sender, Native.LayoutEventArgs e) + { + foreach (var child in _viewStack.Children) + { + child.Geometry = _viewStack.Geometry; + } + } + + void ResetChildren() + { + var children = _viewStack.Children.ToList(); + _viewStack.Children.Clear(); + foreach (var child in children) + { + child.Unrealize(); + } + } } } diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Native/Image.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Native/Image.cs index 85c3398..3f06bbc 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Native/Image.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Native/Image.cs @@ -15,38 +15,7 @@ namespace Xamarin.Forms.Platform.Tizen.Native /// /// The parent EvasObject. public Image(EvasObject parent) : base(parent) - { - } - - /// - /// Loads image data from the given asynchronously. - /// - /// A task which will be completed when image data is loaded. - /// Image source specifying from where the image data has to be loaded. - public async Task LoadFromImageSourceAsync(ImageSource source) - { - IImageSourceHandler handler; - bool isLoadComplate = false; - if (source != null && (handler = Forms.GetHandlerForObject(source)) != null) - { - isLoadComplate = await handler.LoadImageAsync(this, source); - } - if (!isLoadComplate) - { - //If it fails, call the Load function to remove the previous image. - Load(string.Empty); - } - - return isLoadComplate; - } - - public bool LoadFromFile(string file) { - if (!string.IsNullOrEmpty(file)) - { - return Load(ResourcePath.GetPath(file)); - } - return false; } /// diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Platform.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Platform.cs index 76db94e..fe4620f 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Platform.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Platform.cs @@ -1,15 +1,11 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; +using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; -using System.ComponentModel; -using Xamarin.Forms.Internals; -using Xamarin.Forms.PlatformConfiguration.TizenSpecific; using ElmSharp; -using EProgressBar = ElmSharp.ProgressBar; -using EButton = ElmSharp.Button; -using EColor = ElmSharp.Color; +using Xamarin.Forms.Internals; [assembly: InternalsVisibleTo("Xamarin.Forms.Material")] @@ -85,6 +81,7 @@ namespace Xamarin.Forms.Platform.Tizen EvasObject GetRootNativeView(); bool HasAlpha { get; set; } event EventHandler RootNativeViewChanged; + bool PageIsChildOfPlatform(Page page); } public class RootNativeViewChangedEventArgs : EventArgs @@ -97,9 +94,8 @@ namespace Xamarin.Forms.Platform.Tizen { NavigationModel _navModel = new NavigationModel(); bool _disposed; - Native.Dialog _pageBusyDialog; - int _pageBusyCount; readonly Naviframe _internalNaviframe; + readonly PopupManager _popupManager; readonly HashSet _alerts = new HashSet(); @@ -110,13 +106,6 @@ namespace Xamarin.Forms.Platform.Tizen internal DefaultPlatform(EvasObject parent) { Forms.NativeParent = parent; - _pageBusyCount = 0; - if (Forms.UseMessagingCenter) - { - MessagingCenter.Subscribe(this, Page.BusySetSignalName, BusySetSignalNameHandler); - MessagingCenter.Subscribe(this, Page.AlertSignalName, AlertSignalNameHandler); - MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, ActionSheetSignalNameHandler); - } _internalNaviframe = new Naviframe(Forms.NativeParent) { @@ -127,6 +116,11 @@ namespace Xamarin.Forms.Platform.Tizen _internalNaviframe.SetWeight(1.0, 1.0); _internalNaviframe.Show(); _internalNaviframe.AnimationFinished += NaviAnimationFinished; + + if (Forms.UseMessagingCenter) + { + _popupManager = new PopupManager(this); + } } ~DefaultPlatform() @@ -223,14 +217,18 @@ namespace Xamarin.Forms.Platform.Tizen return _internalNaviframe as EvasObject; } + public bool PageIsChildOfPlatform(Page page) + { + var parent = page.AncestorToRoot(); + return Page == parent || _navModel.Roots.Contains(parent); + } + protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { - MessagingCenter.Unsubscribe(this, "Xamarin.SendAlert"); - MessagingCenter.Unsubscribe(this, "Xamarin.BusySet"); - MessagingCenter.Unsubscribe(this, "Xamarin.ShowActionSheet"); + _popupManager?.Dispose(); SetPage(null); _internalNaviframe.Unrealize(); } @@ -400,171 +398,6 @@ namespace Xamarin.Forms.Platform.Tizen tcs?.SetResult(true); } - void BusySetSignalNameHandler(Page sender, bool enabled) - { - // Verify that the page making the request is child of this platform - if (!PageIsChildOfPlatform(sender)) - return; - - if (null == _pageBusyDialog) - { - _pageBusyDialog = new Native.Dialog(Forms.NativeParent) - { - Orientation = PopupOrientation.Center, - BackgroundColor = EColor.Transparent - }; - - if (Device.Idiom == TargetIdiom.Phone) - { - _pageBusyDialog.SetPartColor("bg_title", EColor.Transparent); - _pageBusyDialog.SetPartColor("bg_content", EColor.Transparent); - } - else if (Device.Idiom == TargetIdiom.Watch) - { - _pageBusyDialog.Style = "circle"; - } - - var activity = new EProgressBar(_pageBusyDialog) - { - Style = "process_large", - IsPulseMode = true, - }; - activity.PlayPulse(); - activity.Show(); - - _pageBusyDialog.Content = activity; - - } - _pageBusyCount = Math.Max(0, enabled ? _pageBusyCount + 1 : _pageBusyCount - 1); - if (_pageBusyCount > 0) - { - _pageBusyDialog.Show(); - } - else - { - _pageBusyDialog.Dismiss(); - _pageBusyDialog = null; - } - } - - void AlertSignalNameHandler(Page sender, AlertArguments arguments) - { - // Verify that the page making the request is child of this platform - if (!PageIsChildOfPlatform(sender)) - return; - - Native.Dialog alert = Native.Dialog.CreateDialog(Forms.NativeParent, (arguments.Accept != null)); - - alert.Title = arguments.Title; - var message = arguments.Message.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace(Environment.NewLine, "
"); - alert.Message = message; - - EButton cancel = new EButton(alert) { Text = arguments.Cancel }; - alert.NegativeButton = cancel; - cancel.Clicked += (s, evt) => - { - arguments.SetResult(false); - alert.Dismiss(); - }; - - if (arguments.Accept != null) - { - EButton ok = new EButton(alert) { Text = arguments.Accept }; - alert.NeutralButton = ok; - ok.Clicked += (s, evt) => - { - arguments.SetResult(true); - alert.Dismiss(); - }; - } - - alert.BackButtonPressed += (s, evt) => - { - arguments.SetResult(false); - alert.Dismiss(); - }; - - alert.Show(); - _alerts.Add(alert); - alert.Dismissed += (s, e) => _alerts.Remove(alert); - } - - void ActionSheetSignalNameHandler(Page sender, ActionSheetArguments arguments) - { - // Verify that the page making the request is child of this platform - if (!PageIsChildOfPlatform(sender)) - return; - - Native.Dialog alert = Native.Dialog.CreateDialog(Forms.NativeParent); - - alert.Title = arguments.Title; - Box box = new Box(alert); - - if (null != arguments.Destruction) - { - Native.Button destruction = new Native.Button(alert) - { - Text = arguments.Destruction, - Style = ButtonStyle.Text, - TextColor = EColor.Red, - AlignmentX = -1 - }; - destruction.Clicked += (s, evt) => - { - arguments.SetResult(arguments.Destruction); - alert.Dismiss(); - }; - destruction.Show(); - box.PackEnd(destruction); - } - - foreach (string buttonName in arguments.Buttons) - { - Native.Button button = new Native.Button(alert) - { - Text = buttonName, - Style = ButtonStyle.Text, - AlignmentX = -1 - }; - button.Clicked += (s, evt) => - { - arguments.SetResult(buttonName); - alert.Dismiss(); - }; - button.Show(); - box.PackEnd(button); - } - - box.Show(); - alert.Content = box; - - if (null != arguments.Cancel) - { - EButton cancel = new EButton(Forms.NativeParent) { Text = arguments.Cancel }; - alert.NegativeButton = cancel; - cancel.Clicked += (s, evt) => - { - alert.Dismiss(); - }; - } - - alert.BackButtonPressed += (s, evt) => - { - alert.Dismiss(); - }; - - alert.Show(); - - _alerts.Add(alert); - alert.Dismissed += (s, e) => _alerts.Remove(alert); - } - - bool PageIsChildOfPlatform(Page page) - { - var parent = page.AncestorToRoot(); - return Page == parent || _navModel.Roots.Contains(parent); - } - SizeRequest IPlatform.GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint) { return Platform.GetNativeSize(view, widthConstraint, heightConstraint); diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/PopupManager.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/PopupManager.cs new file mode 100644 index 0000000..5f489ed --- /dev/null +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/PopupManager.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using ElmSharp; +using Xamarin.Forms.Internals; +using Xamarin.Forms.PlatformConfiguration.TizenSpecific; +using EButton = ElmSharp.Button; +using EColor = ElmSharp.Color; +using EProgressBar = ElmSharp.ProgressBar; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class PopupManager : IDisposable + { + ITizenPlatform _platform; + Native.Dialog _pageBusyDialog; + int _pageBusyCount; + readonly HashSet _alerts = new HashSet(); + + public PopupManager(ITizenPlatform platform) + { + _platform = platform; + MessagingCenter.Subscribe(this, Page.BusySetSignalName, OnBusySetRequest); + MessagingCenter.Subscribe(this, Page.AlertSignalName, OnAlertRequest); + MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, OnActionSheetRequest); + MessagingCenter.Subscribe(this, Page.PromptSignalName, OnPromptRequested); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + MessagingCenter.Unsubscribe(this, Page.AlertSignalName); + MessagingCenter.Unsubscribe(this, Page.BusySetSignalName); + MessagingCenter.Unsubscribe(this, Page.ActionSheetSignalName); + MessagingCenter.Unsubscribe(this, Page.PromptSignalName); + } + } + + void OnBusySetRequest(Page sender, bool enabled) + { + // Verify that the page making the request is child of this platform + if (!_platform.PageIsChildOfPlatform(sender)) + return; + + if (null == _pageBusyDialog) + { + _pageBusyDialog = new Native.Dialog(Forms.NativeParent) + { + Orientation = PopupOrientation.Center, + BackgroundColor = EColor.Transparent + }; + + if (Device.Idiom == TargetIdiom.Phone) + { + _pageBusyDialog.SetPartColor("bg_title", EColor.Transparent); + _pageBusyDialog.SetPartColor("bg_content", EColor.Transparent); + } + else if (Device.Idiom == TargetIdiom.Watch) + { + _pageBusyDialog.Style = "circle"; + } + + var activity = new EProgressBar(_pageBusyDialog) + { + Style = "process_large", + IsPulseMode = true, + }; + activity.PlayPulse(); + activity.Show(); + + _pageBusyDialog.Content = activity; + + } + _pageBusyCount = Math.Max(0, enabled ? _pageBusyCount + 1 : _pageBusyCount - 1); + if (_pageBusyCount > 0) + { + _pageBusyDialog.Show(); + } + else + { + _pageBusyDialog.Dismiss(); + _pageBusyDialog = null; + } + } + + void OnAlertRequest(Page sender, AlertArguments arguments) + { + // Verify that the page making the request is child of this platform + if (!_platform.PageIsChildOfPlatform(sender)) + return; + + var alert = Native.Dialog.CreateDialog(Forms.NativeParent, (arguments.Accept != null)); + + alert.Title = arguments.Title; + var message = arguments.Message.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace(Environment.NewLine, "
"); + alert.Message = message; + + var cancel = new EButton(alert) { Text = arguments.Cancel }; + alert.NegativeButton = cancel; + cancel.Clicked += (s, evt) => + { + arguments.SetResult(false); + alert.Dismiss(); + }; + + if (arguments.Accept != null) + { + var ok = new EButton(alert) { Text = arguments.Accept }; + alert.NeutralButton = ok; + ok.Clicked += (s, evt) => + { + arguments.SetResult(true); + alert.Dismiss(); + }; + } + + alert.BackButtonPressed += (s, evt) => + { + arguments.SetResult(false); + alert.Dismiss(); + }; + + alert.Show(); + _alerts.Add(alert); + alert.Dismissed += (s, e) => _alerts.Remove(alert); + } + + void OnActionSheetRequest(Page sender, ActionSheetArguments arguments) + { + // Verify that the page making the request is child of this platform + if (!_platform.PageIsChildOfPlatform(sender)) + return; + + var alert = Native.Dialog.CreateDialog(Forms.NativeParent); + + alert.Title = arguments.Title; + var box = new Box(alert); + + if (null != arguments.Destruction) + { + var destruction = new Native.Button(alert) + { + Text = arguments.Destruction, + Style = ButtonStyle.Text, + TextColor = EColor.Red, + AlignmentX = -1 + }; + destruction.Clicked += (s, evt) => + { + arguments.SetResult(arguments.Destruction); + alert.Dismiss(); + }; + destruction.Show(); + box.PackEnd(destruction); + } + + foreach (string buttonName in arguments.Buttons) + { + var button = new Native.Button(alert) + { + Text = buttonName, + Style = ButtonStyle.Text, + AlignmentX = -1 + }; + button.Clicked += (s, evt) => + { + arguments.SetResult(buttonName); + alert.Dismiss(); + }; + button.Show(); + box.PackEnd(button); + } + + box.Show(); + alert.Content = box; + + if (null != arguments.Cancel) + { + var cancel = new EButton(Forms.NativeParent) { Text = arguments.Cancel }; + alert.NegativeButton = cancel; + cancel.Clicked += (s, evt) => + { + alert.Dismiss(); + }; + } + + alert.BackButtonPressed += (s, evt) => + { + alert.Dismiss(); + }; + + alert.Show(); + + _alerts.Add(alert); + alert.Dismissed += (s, e) => _alerts.Remove(alert); + } + + void OnPromptRequested(Page sender, PromptArguments args) + { + // Verify that the page making the request is child of this platform + if (!_platform.PageIsChildOfPlatform(sender)) + return; + + var prompt = Native.Dialog.CreateDialog(Forms.NativeParent, (args.Accept != null)); + prompt.Title = args.Title; + + var entry = new Entry + { + MinimumWidthRequest = 200, + HorizontalOptions = LayoutOptions.FillAndExpand, + BackgroundColor = Color.FromRgb(250, 250, 250), + TextColor = Color.Black, + Keyboard = args.Keyboard, + }; + + if (!string.IsNullOrEmpty(args.Placeholder)) + { + entry.Placeholder = args.Placeholder; + } + if (args.MaxLength > 0) + { + entry.MaxLength = args.MaxLength; + } + + var layout = new StackLayout + { + Spacing = 10, + Children = + { + new Label + { + LineBreakMode = LineBreakMode.CharacterWrap, + TextColor = Device.Idiom == TargetIdiom.Watch ? Color.White : Color.Accent, + Text = args.Message, + HorizontalOptions = LayoutOptions.FillAndExpand, + HorizontalTextAlignment = TextAlignment.Center, + FontSize = Device.GetNamedSize(NamedSize.Subtitle, typeof(Label)), + }, + entry, + } + }; + + layout.Parent = sender; + var layoutrenderer = Platform.GetOrCreateRenderer(layout); + + var request = layout.Measure(Device.Idiom == TargetIdiom.Watch ? sender.Width * 0.7 : sender.Width, sender.Height); + (layoutrenderer as LayoutRenderer).RegisterOnLayoutUpdated(); + layoutrenderer.NativeView.MinimumHeight = Forms.ConvertToScaledPixel(request.Request.Height); + layoutrenderer.NativeView.MinimumWidth = Forms.ConvertToScaledPixel(request.Request.Width); + + prompt.Content = layoutrenderer.NativeView; + + var cancel = new EButton(prompt) { Text = args.Cancel }; + prompt.NegativeButton = cancel; + cancel.Clicked += (s, evt) => + { + args.SetResult(null); + prompt.Dismiss(); + }; + + if (args.Accept != null) + { + var ok = new EButton(prompt) { Text = args.Accept }; + prompt.NeutralButton = ok; + ok.Clicked += (s, evt) => + { + args.SetResult(entry.Text); + prompt.Dismiss(); + }; + } + + entry.Completed += (s, e) => + { + args.SetResult(entry.Text); + prompt.Dismiss(); + }; + + prompt.BackButtonPressed += (s, evt) => + { + prompt.Dismiss(); + }; + + prompt.Show(); + + _alerts.Add(prompt); + prompt.Dismissed += (s, e) => _alerts.Remove(prompt); + } + + } +} \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs index 668c6ee..66d93e0 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs @@ -52,6 +52,8 @@ using Xamarin.Forms.Platform.Tizen; [assembly: ExportCell(typeof(EntryCell), typeof(EntryCellRenderer))] [assembly: ExportCell(typeof(ViewCell), typeof(ViewCellRenderer))] +[assembly: ExportRenderer(typeof(EmbeddedFont), typeof(EmbeddedFontLoader))] + [assembly: ExportHandler(typeof(TapGestureRecognizer), typeof(TapGestureHandler))] [assembly: ExportHandler(typeof(PinchGestureRecognizer), typeof(PinchGestureHandler))] [assembly: ExportHandler(typeof(PanGestureRecognizer), typeof(PanGestureHandler))] diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs index aee2845..55f8534 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs @@ -118,7 +118,7 @@ namespace Xamarin.Forms.Platform.Tizen { if (Control is IButton ib) { - ib.FontFamily = Element.FontFamily; + ib.FontFamily = Element.FontFamily.ToNativeFontFamily(); } } diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs index b374c6d..9575dac 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs @@ -118,7 +118,7 @@ namespace Xamarin.Forms.Platform.Tizen void UpdateFontFamily() { - Control.FontFamily = Element.FontFamily; + Control.FontFamily = Element.FontFamily.ToNativeFontFamily(); } void UpdateFontAttributes() diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs index dd57006..8534174 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs @@ -111,7 +111,7 @@ namespace Xamarin.Forms.Platform.Tizen void UpdateFontFamily() { - Control.FontFamily = Element.FontFamily; + Control.FontFamily = Element.FontFamily.ToNativeFontFamily(); } void UpdateFontAttributes() diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs index 85c7d11..d936cd9 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs @@ -145,7 +145,7 @@ namespace Xamarin.Forms.Platform.Tizen { if (Control is IEntry ie) { - ie.FontFamily = Element.FontFamily; + ie.FontFamily = Element.FontFamily.ToNativeFontFamily(); } } diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs index 1d40f67..b534002 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Threading; using System.Threading.Tasks; +using EImage = ElmSharp.Image; using Xamarin.Forms.Platform.Tizen.Native; using Specific = Xamarin.Forms.PlatformConfiguration.TizenSpecific.Image; @@ -13,6 +14,7 @@ namespace Xamarin.Forms.Platform.Tizen RegisterPropertyHandler(Image.SourceProperty, UpdateSource); RegisterPropertyHandler(Image.AspectProperty, UpdateAspect); RegisterPropertyHandler(Image.IsOpaqueProperty, UpdateIsOpaque); + RegisterPropertyHandler(Image.IsAnimationPlayingProperty, UpdateIsAnimationPlaying); RegisterPropertyHandler(Specific.BlendColorProperty, UpdateBlendColor); RegisterPropertyHandler(Specific.FileProperty, UpdateFile); } @@ -71,6 +73,7 @@ namespace Xamarin.Forms.Platform.Tizen { UpdateIsOpaque(initialize); UpdateBlendColor(initialize); + UpdateIsAnimationPlaying(initialize); } void UpdateAspect(bool initialize) @@ -89,6 +92,15 @@ namespace Xamarin.Forms.Platform.Tizen Control.IsOpaque = Element.IsOpaque; } + void UpdateIsAnimationPlaying(bool initialize) + { + if (initialize && !Element.IsAnimationPlaying) + return; + + Control.IsAnimated = Element.IsAnimationPlaying; + Control.IsAnimationPlaying = Element.IsAnimationPlaying; + } + void UpdateBlendColor(bool initialize) { if (initialize && Specific.GetBlendColor(Element).IsDefault) @@ -100,12 +112,12 @@ namespace Xamarin.Forms.Platform.Tizen public interface IImageSourceHandler : IRegisterable { - Task LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)); + Task LoadImageAsync(EImage image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)); } public sealed class FileImageSourceHandler : IImageSourceHandler { - public Task LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) + public Task LoadImageAsync(EImage image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) { var filesource = imageSource as FileImageSource; if (filesource != null) @@ -120,7 +132,7 @@ namespace Xamarin.Forms.Platform.Tizen public sealed class StreamImageSourceHandler : IImageSourceHandler { - public async Task LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) + public async Task LoadImageAsync(EImage image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) { var streamsource = imageSource as StreamImageSource; if (streamsource != null && streamsource.Stream != null) @@ -137,7 +149,7 @@ namespace Xamarin.Forms.Platform.Tizen public sealed class UriImageSourceHandler : IImageSourceHandler { - public Task LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) + public Task LoadImageAsync(EImage image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) { var urisource = imageSource as UriImageSource; if (urisource != null && urisource.Uri != null) diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs index 7b35473..f916e2e 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs @@ -106,7 +106,7 @@ namespace Xamarin.Forms.Platform.Tizen Control.FontSize = Element.FontSize; Control.FontAttributes = Element.FontAttributes; - Control.FontFamily = Element.FontFamily; + Control.FontFamily = Element.FontFamily.ToNativeFontFamily(); Control.BatchCommit(); } diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs index 1fc450b..3176880 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs @@ -93,7 +93,7 @@ namespace Xamarin.Forms.Platform.Tizen ///
void FontFamilyPropertyHandler() { - Control.FontFamily = Element.FontFamily; + Control.FontFamily = Element.FontFamily.ToNativeFontFamily(); } /// diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs index 35c91d2..9a5d864 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs @@ -127,7 +127,7 @@ namespace Xamarin.Forms.Platform.Tizen void UpdateFontFamily() { - Control.FontFamily = Element.FontFamily; + Control.FontFamily = Element.FontFamily.ToNativeFontFamily(); } void UpdateFontAttributes() diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/INavigationView.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/INavigationView.cs index f88d603..c088d25 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/INavigationView.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/INavigationView.cs @@ -1,9 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using ElmSharp; using EColor = ElmSharp.Color; -using Xamarin.Forms; namespace Xamarin.Forms.Platform.Tizen { @@ -13,6 +11,10 @@ namespace Xamarin.Forms.Platform.Tizen EColor BackgroundColor { get; set; } + ImageSource BackgroundImageSource { get; set; } + + Aspect BackgroundImageAspect { get; set; } + void BuildMenu(List> flyout); event EventHandler SelectedItemChanged; diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/NavigationView.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/NavigationView.cs index 7719045..8b27c09 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/NavigationView.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/NavigationView.cs @@ -1,26 +1,30 @@ using System; -using System.Collections; using System.Collections.Generic; using ElmSharp; using EColor = ElmSharp.Color; +using EImage = ElmSharp.Image; +using NBox = Xamarin.Forms.Platform.Tizen.Native.Box; namespace Xamarin.Forms.Platform.Tizen { - public class NavigationView : Native.Box, INavigationView + public class NavigationView : Background, INavigationView { + NBox _box; + EImage _bg; + Aspect _bgImageAspect; + ImageSource _bgImageSource; EvasObject _header; GenList _menu; GenItemClass _defaultClass; EColor _backgroundColor; EColor _defaultBackgroundColor = EColor.White; - List _groups; IDictionary _flyoutMenu = new Dictionary(); public NavigationView(EvasObject parent) : base(parent) { Initialize(parent); - LayoutUpdated += (s, e) => + _box.LayoutUpdated += (s, e) => { UpdateChildGeometry(); }; @@ -39,10 +43,58 @@ namespace Xamarin.Forms.Platform.Tizen set { _backgroundColor = value; - EColor effectiveColor = _backgroundColor.IsDefault ? _defaultBackgroundColor : _backgroundColor; - _menu.BackgroundColor = effectiveColor; base.BackgroundColor = effectiveColor; + + if(_bg == null) + { + _menu.BackgroundColor = effectiveColor; + } + } + } + + public Aspect BackgroundImageAspect + { + get + { + return _bgImageAspect; + } + set + { + _bgImageAspect = value; + if (_bg != null) + { + _bg.ApplyAspect(_bgImageAspect); + } + } + } + + public ImageSource BackgroundImageSource + { + get + { + return _bgImageSource; + } + set + { + _bgImageSource = value; + if (_bgImageSource != null) + { + if (_bg == null) + { + _bg = new EImage(this); + } + _menu.BackgroundColor = EColor.Transparent; + SetPartContent("elm.swallow.background", _bg); + _bg.ApplyAspect(_bgImageAspect); + _ = _bg.LoadFromImageSourceAsync(_bgImageSource); + } + else + { + EColor effectiveColor = _backgroundColor.IsDefault ? _defaultBackgroundColor : _backgroundColor; + _menu.BackgroundColor = effectiveColor; + SetPartContent("elm.swallow.background", null); + } } } @@ -56,14 +108,14 @@ namespace Xamarin.Forms.Platform.Tizen { if (_header != null) { - UnPack(_header); + _box.UnPack(_header); _header.Hide(); } _header = value; if (_header != null) { - PackStart(_header); + _box.PackStart(_header); if (!_header.IsVisible) { _header.Show(); @@ -118,6 +170,15 @@ namespace Xamarin.Forms.Platform.Tizen void Initialize(EvasObject parent) { + _box = new Native.Box(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1 + }; + SetContent(_box); + _menu = new GenList(parent) { BackgroundColor = EColor.Transparent, @@ -132,7 +193,7 @@ namespace Xamarin.Forms.Platform.Tizen }; _menu.Show(); - PackEnd(_menu); + _box.PackEnd(_menu); _defaultClass = new GenItemClass("double_label") { diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellNavBar.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellNavBar.cs index 5bcb66a..b33ba7d 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellNavBar.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellNavBar.cs @@ -1,18 +1,20 @@ using System; using System.Reflection; using ElmSharp; -using EColor = ElmSharp.Color; using Xamarin.Forms.Platform.Tizen.Native; +using EColor = ElmSharp.Color; +using EButton = ElmSharp.Button; +using EImage = ElmSharp.Image; namespace Xamarin.Forms.Platform.Tizen { public class ShellNavBar : Native.Box { - Native.Image _menu = null; + EImage _menu = null; + EButton _menuButton = null; Native.Label _title = null; Native.SearchBar _nativeSearchHandler = null; EvasObject _nativeTitleView = null; - ShellSectionNavigation _shellSectionNavigation = null; SearchHandler _searchHandler = null; View _titleView = null; @@ -29,15 +31,18 @@ namespace Xamarin.Forms.Platform.Tizen bool _hasBackButton = false; - public ShellNavBar(IFlyoutController flyoutController, ShellSectionNavigation shellSectionNavigation) : base(Forms.NativeParent) + public ShellNavBar(IFlyoutController flyoutController) : base(Forms.NativeParent) { _flyoutController = flyoutController; - _shellSectionNavigation = shellSectionNavigation; - _menu = new Native.Image(Forms.NativeParent); - _menu.Clicked += OnMenuClicked; + _menuButton = new EButton(Forms.NativeParent); + _menuButton.Clicked += OnMenuClicked; + _menu = new EImage(Forms.NativeParent); UpdateMenuIcon(); _menu.Show(); + _menuButton.Show(); + + _menuButton.SetPartContent("icon", _menu); _title = new Native.Label(Forms.NativeParent) { @@ -49,7 +54,8 @@ namespace Xamarin.Forms.Platform.Tizen _title.Show(); BackgroundColor = _backgroudColor; - PackEnd(_menu); + _menuButton.BackgroundColor = _backgroudColor; + PackEnd(_menuButton); PackEnd(_title); LayoutUpdated += OnLayoutUpdated; } @@ -123,6 +129,7 @@ namespace Xamarin.Forms.Platform.Tizen set { _backgroudColor = value; + _menuButton.BackgroundColor = _backgroudColor; base.BackgroundColor = _backgroudColor; } } @@ -136,7 +143,7 @@ namespace Xamarin.Forms.Platform.Tizen set { _foregroudColor = value; - _menu.Color = value; + _menuButton.Color = value; } } @@ -164,11 +171,11 @@ namespace Xamarin.Forms.Platform.Tizen } } - async void UpdateMenuIcon() + void UpdateMenuIcon() { string file = _hasBackButton ? _backIcon : _menuIcon; - ImageSource source = ImageSource.FromResource(file, typeof(ShellNavBar).GetTypeInfo().Assembly); - bool ret = await _menu.LoadFromImageSourceAsync(source); + var path = Assembly.GetExecutingAssembly().GetManifestResourceStream(file); + _menu.Load(path); } void OnMenuClicked(object sender, EventArgs e) @@ -180,7 +187,7 @@ namespace Xamarin.Forms.Platform.Tizen } else if (_hasBackButton) { - _shellSectionNavigation.PopRequest(this, new Internals.NavigationRequestedEventArgs(_page, false)); + Shell.Current.CurrentItem.Navigation.PopAsync(); } else { @@ -258,8 +265,8 @@ namespace Xamarin.Forms.Platform.Tizen int titleLeftMargin = 40; int titleViewTopMargin = 40; - _menu.Move(e.Geometry.X + menuMargin, e.Geometry.Y + (e.Geometry.Height - menuSize) / 2); - _menu.Resize(menuSize, menuSize); + _menuButton.Move(e.Geometry.X + menuMargin, e.Geometry.Y + (e.Geometry.Height - menuSize) / 2); + _menuButton.Resize(menuSize, menuSize); if (_searchHandler != null) { diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellRenderer.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellRenderer.cs index e84aa97..51221de 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellRenderer.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellRenderer.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using ElmSharp; namespace Xamarin.Forms.Platform.Tizen @@ -18,6 +17,8 @@ namespace Xamarin.Forms.Platform.Tizen { RegisterPropertyHandler(Shell.CurrentItemProperty, UpdateCurrentItem); RegisterPropertyHandler(Shell.FlyoutBackgroundColorProperty, UpdateFlyoutBackgroundColor); + RegisterPropertyHandler(Shell.FlyoutBackgroundImageProperty, UpdateFlyoutBackgroundImage); + RegisterPropertyHandler(Shell.FlyoutBackgroundImageAspectProperty, UpdateFlyoutBackgroundImageAspect); RegisterPropertyHandler(Shell.FlyoutIsPresentedProperty, UpdateFlyoutIsPresented); } @@ -119,6 +120,16 @@ namespace Xamarin.Forms.Platform.Tizen _navigationView.BackgroundColor = Element.FlyoutBackgroundColor.ToNative(); } + void UpdateFlyoutBackgroundImageAspect() + { + _navigationView.BackgroundImageAspect = Element.FlyoutBackgroundImageAspect; + } + + void UpdateFlyoutBackgroundImage() + { + _navigationView.BackgroundImageSource = Element.FlyoutBackgroundImage; + } + void UpdateFlyoutIsPresented() { _native.IsOpen = Element.FlyoutIsPresented; diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellSectionNavigation.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellSectionNavigation.cs index f1a9a64..b30aad2 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellSectionNavigation.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/Shell/ShellSectionNavigation.cs @@ -30,7 +30,7 @@ namespace Xamarin.Forms.Platform.Tizen _section.PropertyChanged += OnSectionPropertyChanged; _rootPage = ((IShellContentController)_section.CurrentItem).GetOrCreateContent(); - _navBar = new ShellNavBar(flyoutController, this); + _navBar = new ShellNavBar(flyoutController); _navBar.Show(); var renderer = CreateShellSection(section); diff --git a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/StaticRegistrar.cs b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/StaticRegistrar.cs index 4f7b3ce..800ec41 100644 --- a/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/StaticRegistrar.cs +++ b/Xamarin.Forms/Xamarin.Forms.Platform.Tizen/StaticRegistrar.cs @@ -66,7 +66,7 @@ namespace Xamarin.Forms.Platform.Tizen public static void RegisterHandlers(Dictionary> customHandlers) { - ////Renderers + //Renderers Registered.Register(typeof(Layout), () => new LayoutRenderer()); Registered.Register(typeof(ScrollView), () => new ScrollViewRenderer()); Registered.Register(typeof(CarouselPage), () => new CarouselPageRenderer()); @@ -102,18 +102,21 @@ namespace Xamarin.Forms.Platform.Tizen Registered.Register(typeof(SwipeView), () => new SwipeViewRenderer()); Registered.Register(typeof(RefreshView), () => new RefreshViewRenderer()); - ////ImageSourceHandlers + //ImageSourceHandlers Registered.Register(typeof(FileImageSource), () => new FileImageSourceHandler()); Registered.Register(typeof(StreamImageSource), () => new StreamImageSourceHandler()); Registered.Register(typeof(UriImageSource), () => new UriImageSourceHandler()); - ////Cell Renderers + //Cell Renderers Registered.Register(typeof(TextCell), () => new TextCellRenderer()); Registered.Register(typeof(ImageCell), () => new ImageCellRenderer()); Registered.Register(typeof(SwitchCell), () => new SwitchCellRenderer()); Registered.Register(typeof(EntryCell), () => new EntryCellRenderer()); Registered.Register(typeof(ViewCell), () => new ViewCellRenderer()); + //Font Loaders + Registered.Register(typeof(EmbeddedFont), () => new EmbeddedFontLoader()); + //Dependencies DependencyService.Register(); DependencyService.Register(); diff --git a/Xamarin.Forms/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs b/Xamarin.Forms/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs index ce051ea..5616eca 100644 --- a/Xamarin.Forms/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs +++ b/Xamarin.Forms/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs @@ -347,21 +347,21 @@ namespace Xamarin.Forms.Xaml //If it's a BindableProberty, SetValue if (xpe == null && TrySetValue(xamlelement, property, attached, value, lineInfo, serviceProvider, out xpe)) { if (!(node is ValueNode) && value != null && !value.GetType().GetTypeInfo().IsValueType && XamlFilePathAttribute.GetFilePathForObject(context.RootElement) is string path) - VisualDiagnostics.RegisterSourceInfo(value, new Uri(path, UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); + VisualDiagnostics.RegisterSourceInfo(value, new Uri($"{path};assembly={context.RootAssembly.GetName().Name}", UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); return; } //If we can assign that value to a normal property, let's do it if (xpe == null && TrySetProperty(xamlelement, localName, value, lineInfo, serviceProvider, context, out xpe)) { if (!(node is ValueNode) && value != null && !value.GetType().GetTypeInfo().IsValueType && XamlFilePathAttribute.GetFilePathForObject(context.RootElement) is string path) - VisualDiagnostics.RegisterSourceInfo(value, new Uri(path, UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); + VisualDiagnostics.RegisterSourceInfo(value, new Uri($"{path};assembly={context.RootAssembly.GetName().Name}", UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); return; } //If it's an already initialized property, add to it if (xpe == null && TryAddToProperty(xamlelement, propertyName, value, xKey, lineInfo, serviceProvider, context, out xpe)) { if (!(node is ValueNode) && value != null && !value.GetType().GetTypeInfo().IsValueType && XamlFilePathAttribute.GetFilePathForObject(context.RootElement) is string path) - VisualDiagnostics.RegisterSourceInfo(value, new Uri(path, UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); + VisualDiagnostics.RegisterSourceInfo(value, new Uri($"{path};assembly={context.RootAssembly.GetName().Name}", UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); return; } diff --git a/Xamarin.Forms/Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs b/Xamarin.Forms/Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs index 8cf27cf..5dc7317 100644 --- a/Xamarin.Forms/Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs +++ b/Xamarin.Forms/Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs @@ -119,26 +119,59 @@ namespace Xamarin.Forms.Xaml if (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider xmlLineInfoProvider) xmlLineInfo = xmlLineInfoProvider.XmlLineInfo; - var split = match.Split(':'); - if (split.Length > 2) - throw new ArgumentException(); + var (prefix, name) = ParseName(match); + + var namespaceuri = nsResolver.LookupNamespace(prefix) ?? ""; - string prefix; //, name; - if (split.Length == 2) { - prefix = split[0]; - // name = split [1]; + IList typeArguments = null; + var childnodes = new List<(XmlName, INode)>(); + var contentname = new XmlName(null, null); + + if (remaining.StartsWith("}", StringComparison.Ordinal)) { + remaining = remaining.Substring(1); } else { - prefix = ""; - // name = split [0]; + Property parsed; + do { + parsed = ParseProperty(serviceProvider, ref remaining); + XmlName childname; + + if (parsed.name == null) { + childname = contentname; + } + else { + var (propertyPrefix, propertyName) = ParseName(parsed.name); + + childname = XamlParser.ParsePropertyName(new XmlName( + propertyPrefix == "" ? null : nsResolver.LookupNamespace(propertyPrefix), + propertyName)); + + if (childname.NamespaceURI == null && childname.LocalName == null) + continue; + } + + if (childname == XmlName.xTypeArguments) { + typeArguments = TypeArgumentsParser.ParseExpression(parsed.strValue, nsResolver, xmlLineInfo); + childnodes.Add((childname, new ValueNode(typeArguments, nsResolver))); + } + else { + var childnode = parsed.value as INode ?? new ValueNode(parsed.strValue, nsResolver); + childnodes.Add((childname, childnode)); + } + } + while (!parsed.last); } - Type type; - if (!(serviceProvider.GetService(typeof(IXamlTypeResolver)) is IXamlTypeResolver typeResolver)) - type = null; - else { - //The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix. - if (!typeResolver.TryResolve(match + "Extension", out type) && !typeResolver.TryResolve(match, out type)) { + + if (!(serviceProvider.GetService(typeof (IXamlTypeResolver)) is XamlTypeResolver typeResolver)) + throw new NotSupportedException(); + + var xmltype = new XmlType(namespaceuri, name + "Extension", typeArguments); + + //The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix. + if (!typeResolver.TryResolve(xmltype, out _)) { + xmltype = new XmlType(namespaceuri, name, typeArguments); + if (!typeResolver.TryResolve(xmltype, out _)) { var ex = new XamlParseException($"MarkupExtension not found for {match}", serviceProvider); if (ExceptionHandler != null) { ExceptionHandler(ex); @@ -148,49 +181,23 @@ namespace Xamarin.Forms.Xaml } } - var namespaceuri = nsResolver.LookupNamespace(prefix) ?? ""; - var xmltype = new XmlType(namespaceuri, type.Name, null); - - if (type == null) - throw new NotSupportedException(); - _node = xmlLineInfo == null ? new ElementNode(xmltype, null, nsResolver) : new ElementNode(xmltype, null, nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition); - if (remaining.StartsWith("}", StringComparison.Ordinal)) { - remaining = remaining.Substring(1); - return _node; - } - - string piece; - while ((piece = GetNextPiece(ref remaining, out var next)) != null) - HandleProperty(piece, serviceProvider, ref remaining, next != '='); + foreach (var (childname, childnode) in childnodes) { + childnode.Parent = _node; - return _node; - } - - protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider) - { - if (value == null && strValue == null) { - var xpe = new XamlParseException($"No value found for property '{prop}' in markup expression", serviceProvider); - if (ExceptionHandler != null) { - ExceptionHandler(xpe); - return; + if (childname == contentname) { + //ContentProperty + _node.CollectionItems.Add(childnode); + } + else { + _node.Properties[childname] = childnode; } - throw xpe; } - var nsResolver = serviceProvider.GetService(typeof (IXmlNamespaceResolver)) as IXmlNamespaceResolver; - - var childnode = value as INode ?? new ValueNode(strValue, nsResolver); - childnode.Parent = _node; - if (prop != null) { - var name = new XmlName(_node.NamespaceURI, prop); - _node.Properties[name] = childnode; - } - else //ContentProperty - _node.CollectionItems.Add(childnode); + return _node; } } } diff --git a/Xamarin.Forms/Xamarin.Forms.Xaml/MarkupExpressionParser.cs b/Xamarin.Forms/Xamarin.Forms.Xaml/MarkupExpressionParser.cs index 9123a72..a0297ab 100644 --- a/Xamarin.Forms/Xamarin.Forms.Xaml/MarkupExpressionParser.cs +++ b/Xamarin.Forms/Xamarin.Forms.Xaml/MarkupExpressionParser.cs @@ -38,6 +38,14 @@ namespace Xamarin.Forms.Xaml { abstract class MarkupExpressionParser { + protected struct Property + { + public bool last; + public string name; + public string strValue; + public object value; + } + public object ParseExpression(ref string expression, IServiceProvider serviceProvider) { if (serviceProvider == null) @@ -46,7 +54,7 @@ namespace Xamarin.Forms.Xaml return expression.Substring(2); if (expression[expression.Length - 1] != '}') - throw new Exception("Expression must end with '}'"); + throw new XamlParseException("Expression must end with '}'", serviceProvider); int len; string match; @@ -54,7 +62,7 @@ namespace Xamarin.Forms.Xaml return false; expression = expression.Substring(len).TrimStart(); if (expression.Length == 0) - throw new Exception("Expression did not end in '}'"); + throw new XamlParseException("Expression did not end in '}'", serviceProvider); var parser = Activator.CreateInstance(GetType()) as IExpressionParser; return parser.Parse(match, ref expression, serviceProvider); @@ -112,49 +120,56 @@ namespace Xamarin.Forms.Xaml return true; } - protected void HandleProperty(string prop, IServiceProvider serviceProvider, ref string remaining, bool isImplicit) + protected Property ParseProperty(IServiceProvider serviceProvider, ref string remaining) { char next; object value = null; string str_value; + string name; - if (isImplicit) - { - SetPropertyValue(null, prop, null, serviceProvider); - return; - } remaining = remaining.TrimStart(); - if (remaining.StartsWith("{", StringComparison.Ordinal)) - { - value = ParseExpression(ref remaining, serviceProvider); - remaining = remaining.TrimStart(); + if (remaining[0] == '{') + return ParsePropertyExpression(null, serviceProvider, ref remaining); - if (remaining.Length > 0 && remaining[0] == ',') - remaining = remaining.Substring(1); - else if (remaining.Length > 0 && remaining[0] == '}') - remaining = remaining.Substring(1); + str_value = GetNextPiece(serviceProvider, ref remaining, out next); + if (next == '=') { + remaining = remaining.TrimStart(); + if (remaining[0] == '{') + return ParsePropertyExpression(str_value, serviceProvider, ref remaining); - str_value = value as string; + name = str_value; + str_value = GetNextPiece(serviceProvider, ref remaining, out next); + } + else { + name = null; } - else - str_value = GetNextPiece(ref remaining, out next); - SetPropertyValue(prop, str_value, value, serviceProvider); + return new Property { last = next == '}', name = name, strValue = str_value, value = value }; } - protected abstract void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider); + private Property ParsePropertyExpression(string prop, IServiceProvider serviceProvider, ref string remaining) + { + bool last; + var value = ParseExpression(ref remaining, serviceProvider); + remaining = remaining.TrimStart(); + if (remaining.Length <= 0) + throw new XamlParseException("Unexpected end of markup expression", serviceProvider); + if (remaining[0] == ',') + last = false; + else if (remaining[0] == '}') + last = true; + else + throw new XamlParseException("Unexpected character following value string", serviceProvider); + + remaining = remaining.Substring(1); + return new Property { last = last, name = prop, strValue = value as string, value = value }; + } - protected string GetNextPiece(ref string remaining, out char next) + private string GetNextPiece(IServiceProvider serviceProvider, ref string remaining, out char next) { bool inString = false; int end = 0; char stringTerminator = '\0'; - remaining = remaining.TrimStart(); - if (remaining.Length == 0) - { - next = Char.MaxValue; - return null; - } var piece = new StringBuilder(); // If we're inside a quoted string we append all chars to our piece until we hit the ending quote. @@ -193,16 +208,10 @@ namespace Xamarin.Forms.Xaml } if (inString && end == remaining.Length) - throw new Exception("Unterminated quoted string"); - - if (end == remaining.Length && !remaining.EndsWith("}", StringComparison.Ordinal)) - throw new Exception("Expression did not end with '}'"); + throw new XamlParseException("Unterminated quoted string", serviceProvider); if (end == 0) - { - next = Char.MaxValue; - return null; - } + throw new XamlParseException("Empty value string in markup expression", serviceProvider); next = remaining[end]; remaining = remaining.Substring(end + 1); @@ -225,5 +234,15 @@ namespace Xamarin.Forms.Xaml return piece.ToString(); } + + protected static (string, string) ParseName(string name) + { + var split = name.Split(':'); + + if (split.Length > 2) + throw new ArgumentException(); + + return split.Length == 2 ? (split[0], split[1]) : ("", split[0]); + } } } \ No newline at end of file diff --git a/Xamarin.Forms/Xamarin.Forms.Xaml/MarkupExtensionParser.cs b/Xamarin.Forms/Xamarin.Forms.Xaml/MarkupExtensionParser.cs index 086c12b..ef203b2 100644 --- a/Xamarin.Forms/Xamarin.Forms.Xaml/MarkupExtensionParser.cs +++ b/Xamarin.Forms/Xamarin.Forms.Xaml/MarkupExtensionParser.cs @@ -42,14 +42,17 @@ namespace Xamarin.Forms.Xaml if (remaining == "}") return markupExtension.ProvideValue(serviceProvider); - string piece; - while ((piece = GetNextPiece(ref remaining, out char next)) != null) - HandleProperty(piece, serviceProvider, ref remaining, next != '='); + Property value; + do { + value = ParseProperty(serviceProvider, ref remaining); + SetPropertyValue(value.name, value.strValue, value.value, serviceProvider); + } + while (!value.last); return markupExtension.ProvideValue(serviceProvider); } - protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider) + private void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider) { MethodInfo setter; if (prop == null) { diff --git a/Xamarin.Forms/Xamarin.Forms.Xaml/XamlLoader.cs b/Xamarin.Forms/Xamarin.Forms.Xaml/XamlLoader.cs index 042c0d2..5bd7cfc 100644 --- a/Xamarin.Forms/Xamarin.Forms.Xaml/XamlLoader.cs +++ b/Xamarin.Forms/Xamarin.Forms.Xaml/XamlLoader.cs @@ -92,7 +92,7 @@ namespace Xamarin.Forms.Xaml var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), view, (IXmlNamespaceResolver)reader) { LineNumber = ((IXmlLineInfo)reader).LineNumber, LinePosition = ((IXmlLineInfo)reader).LinePosition }; if (XamlFilePathAttribute.GetFilePathForObject(view) is string path) { - VisualDiagnostics.RegisterSourceInfo(view, new Uri(path, UriKind.Relative), ((IXmlLineInfo)rootnode).LineNumber, ((IXmlLineInfo)rootnode).LinePosition); + VisualDiagnostics.RegisterSourceInfo(view, new Uri($"{path};assembly={view.GetType().GetTypeInfo().Assembly.GetName().Name}", UriKind.Relative), ((IXmlLineInfo)rootnode).LineNumber, ((IXmlLineInfo)rootnode).LinePosition); VisualDiagnostics.SendVisualTreeChanged(null, view); } XamlParser.ParseXaml(rootnode, reader); @@ -143,7 +143,7 @@ namespace Xamarin.Forms.Xaml inflatedView = rootnode.Root = visitorContext.Values[rootnode]; if (XamlFilePathAttribute.GetFilePathForObject(inflatedView) is string path) { - VisualDiagnostics.RegisterSourceInfo(inflatedView, new Uri(path, UriKind.Relative), ((IXmlLineInfo)rootnode).LineNumber, ((IXmlLineInfo)rootnode).LinePosition); + VisualDiagnostics.RegisterSourceInfo(inflatedView, new Uri($"{path};assembly={inflatedView.GetType().GetTypeInfo().Assembly.GetName().Name}", UriKind.Relative), ((IXmlLineInfo)rootnode).LineNumber, ((IXmlLineInfo)rootnode).LinePosition); VisualDiagnostics.SendVisualTreeChanged(null, inflatedView); } visitorContext.RootElement = inflatedView as BindableObject; diff --git a/Xamarin.Forms/Xamarin.Forms.Xaml/XamlParser.cs b/Xamarin.Forms/Xamarin.Forms.Xaml/XamlParser.cs index 73fb60a..5fbed18 100644 --- a/Xamarin.Forms/Xamarin.Forms.Xaml/XamlParser.cs +++ b/Xamarin.Forms/Xamarin.Forms.Xaml/XamlParser.cs @@ -219,58 +219,15 @@ namespace Xamarin.Forms.Xaml var namespaceUri = reader.NamespaceURI; if (reader.LocalName.Contains(".") && namespaceUri == "") namespaceUri = ((IXmlNamespaceResolver)reader).LookupNamespace(""); - var propertyName = new XmlName(namespaceUri, reader.LocalName); + var propertyName = ParsePropertyName(new XmlName(namespaceUri, reader.LocalName)); - object value = reader.Value; + if (propertyName.NamespaceURI == null && propertyName.LocalName == null) + continue; - if (reader.NamespaceURI == X2006Uri) - { - switch (reader.LocalName) { - case "Key": - propertyName = XmlName.xKey; - break; - case "Name": - propertyName = XmlName.xName; - break; - case "Class": - case "FieldModifier": - continue; - default: - Debug.WriteLine("Unhandled attribute {0}", reader.Name); - continue; - } - } + object value = reader.Value; - if (reader.NamespaceURI == X2009Uri) - { - switch (reader.LocalName) { - case "Key": - propertyName = XmlName.xKey; - break; - case "Name": - propertyName = XmlName.xName; - break; - case "TypeArguments": - propertyName = XmlName.xTypeArguments; - value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader); - break; - case "DataType": - propertyName = XmlName.xDataType; - break; - case "Class": - case "FieldModifier": - continue; - case "FactoryMethod": - propertyName = XmlName.xFactoryMethod; - break; - case "Arguments": - propertyName = XmlName.xArguments; - break; - default: - Debug.WriteLine("Unhandled attribute {0}", reader.Name); - continue; - } - } + if (propertyName == XmlName.xTypeArguments) + value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader); var propertyNode = GetValueNode(value, reader); attributes.Add(new KeyValuePair(propertyName, propertyNode)); @@ -279,6 +236,51 @@ namespace Xamarin.Forms.Xaml return attributes; } + public static XmlName ParsePropertyName(XmlName name) + { + if (name.NamespaceURI == X2006Uri) + { + switch (name.LocalName) { + case "Key": + return XmlName.xKey; + case "Name": + return XmlName.xName; + case "Class": + case "FieldModifier": + return new XmlName(null, null); + default: + Debug.WriteLine("Unhandled attribute {0}", name); + return new XmlName(null, null); + } + } + + if (name.NamespaceURI == X2009Uri) + { + switch (name.LocalName) { + case "Key": + return XmlName.xKey; + case "Name": + return XmlName.xName; + case "TypeArguments": + return XmlName.xTypeArguments; + case "DataType": + return XmlName.xDataType; + case "Class": + case "FieldModifier": + return new XmlName(null, null); + case "FactoryMethod": + return XmlName.xFactoryMethod; + case "Arguments": + return XmlName.xArguments; + default: + Debug.WriteLine("Unhandled attribute {0}", name); + return new XmlName(null, null); + } + } + + return name; + } + static IList PrefixesToIgnore(IList> xmlns) { var prefixes = new List(); diff --git a/Xamarin.Forms/Xamarin.Forms.Xaml/XamlServiceProvider.cs b/Xamarin.Forms/Xamarin.Forms.Xaml/XamlServiceProvider.cs index 8490857..9eed91d 100644 --- a/Xamarin.Forms/Xamarin.Forms.Xaml/XamlServiceProvider.cs +++ b/Xamarin.Forms/Xamarin.Forms.Xaml/XamlServiceProvider.cs @@ -196,6 +196,13 @@ namespace Xamarin.Forms.Xaml.Internals return exception == null; } + internal bool TryResolve(XmlType xmlType, out Type type) + { + XamlParseException exception; + type = getTypeFromXmlName(xmlType, null, currentAssembly, out exception); + return exception == null; + } + Type Resolve(string qualifiedTypeName, IServiceProvider serviceProvider, out XamlParseException exception) { exception = null; -- 2.7.4