diff --git a/.azure-devops-android-tests.yml b/.azure-devops-android-tests.yml index 7c84ea04978e..e577d5faabad 100644 --- a/.azure-devops-android-tests.yml +++ b/.azure-devops-android-tests.yml @@ -32,7 +32,7 @@ jobs: testRunTitle: 'Android Test Run' testResultsFormat: 'NUnit' testResultsFiles: '$(build.sourcesdirectory)/build/TestResult.xml' - failTaskOnFailedTests: true + failTaskOnFailedTests: false # see https://github.com/unoplatform/uno/issues/1259 for details - task: PublishBuildArtifacts@1 condition: always() diff --git a/build/android-uitest-wait-systemui.sh b/build/android-uitest-wait-systemui.sh index e6b46c721df4..bdbcf5b18578 100644 --- a/build/android-uitest-wait-systemui.sh +++ b/build/android-uitest-wait-systemui.sh @@ -32,8 +32,13 @@ while [[ -z ${LAUNCHER_READY} ]]; do *) echo "Waiting for launcher..." sleep 3 + + # For some reason the messaging app can be brought up in front + # (DEBUG) Current focus: mCurrentFocus=Window{1170051 u0 com.google.android.apps.messaging/com.google.android.apps.messaging.ui.ConversationListActivity} + # Try bringing back the home screen to check on the launcher. + $ANDROID_HOME/platform-tools/adb shell input keyevent KEYCODE_HOME ;; esac done -echo "Launcher is ready :-)" +echo "Launcher is ready!" diff --git a/doc/ReleaseNotes/_ReleaseNotes.md b/doc/ReleaseNotes/_ReleaseNotes.md index 867ba0dc24dd..029c8458d43a 100644 --- a/doc/ReleaseNotes/_ReleaseNotes.md +++ b/doc/ReleaseNotes/_ReleaseNotes.md @@ -48,7 +48,10 @@ * Adjust the behavior of `DisplayInformation.LogicalDpi` to match UWP's behavior * [Android] Ensure TextBox spell-check is properly enabled/disabled on all devices. * Fix ComboBox disappearing items when items are views (#1078) +* [iOS] TextBox with `AcceptsReturn=True` crashes ListView +* [Android/iOS] Fixed Arc command in paths * TemplateReuse not called when dataContext is set +* Checks dataContext before applying FallbackValue to explicit set bindings ## Release 1.45.0 ### Features @@ -136,6 +139,7 @@ * [WASM] ListView - support item margins correctly * [iOS] Fix items dependency property propagation in ListView items * [Wasm] Add UI Testing support through for `Uno.UI.Helpers.Automation.GetDependencyPropertyValue`\ +* 132984 [Android] Notch support on Android ### Breaking Changes * The `WebAssemblyRuntime.InvokeJSUnmarshalled` method with three parameters has been removed. diff --git a/doc/articles/concepts/overview/philosophy-of-uno.md b/doc/articles/concepts/overview/philosophy-of-uno.md new file mode 100644 index 000000000000..7c9ae5b0ab1b --- /dev/null +++ b/doc/articles/concepts/overview/philosophy-of-uno.md @@ -0,0 +1,29 @@ +# Philosophy of Uno + +This document outlines the philosophy of Uno. It guides the development of past and future major architectural decisions. + +## Leverage existing tools + +We stand on the shoulders of giants, Microsoft's tooling is a treat to work with: + +- [Edit and Continue](https://docs.microsoft.com/en-us/visualstudio/debugger/edit-and-continue) +- [Live Visual Tree](https://docs.microsoft.com/en-us/visualstudio/debugger/inspect-xaml-properties-while-debugging). +- [XAML Hot Reload](https://docs.microsoft.com/en-us/visualstudio/debugger/xaml-hot-reload?view=vs-2019) + +The promise of the Uno Platform is to enable build your app with those tools and then deploy it to iOS, Android, and the web via WebAssembly.  + +## Create rich, responsive UIs + +Bland apps don't quite cut it these days. Strong support for animations, templating, and custom visual effects is a must. When phones come in all sizes and manufacturers are [gouging holes out of the screen area](https://www.cnet.com/pictures/phones-with-notches/), your app's layout had better be responsive.  + +## Let views do views + +Separation of model, view and presentation keeps your code loosely coupled and easy to maintain. Features like data binding and attached properties let you write clean, elegant MVVM-style code.  + +## Native intercompatibility (leave an escape hatch) + +100% code reuse is the ideal, but it must also be easy to access functionality specific to a single platform, or to incorporate native third-party libraries., and the Uno Platform must make as much as possible to not stand in the way. + +## Performance is a feature + +The slow antelope gets eaten, and the slow app gets 1-star ratings. We've done a lot of optimisation on the basis of profiling in real-world use cases, and we'll continue to do so.  diff --git a/doc/articles/toc.yml b/doc/articles/toc.yml index eb15392ceb79..60c34d2a381e 100644 --- a/doc/articles/toc.yml +++ b/doc/articles/toc.yml @@ -2,6 +2,8 @@ items: - name: What is the Uno Platform? href: intro.md + - name: Philosophy of Uno + href: concepts/overview/philosophy-of-uno.md - name: API differences href: api-differences.md - name: Features list diff --git a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.ContentControl.cs b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.ContentControl.cs index 09a96bcfb4d0..592ba7229da8 100644 --- a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.ContentControl.cs +++ b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.ContentControl.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using SamplesApp.UITests.TestFramework; using Uno.UITest.Helpers; using Uno.UITest.Helpers.Queries; @@ -13,6 +14,7 @@ namespace SamplesApp.UITests public partial class UnoSamples_Tests : SampleControlUITestBase { [Test] + [AutoRetry] public void ContentPresenter_Template() { Run("Uno.UI.Samples.Content.UITests.ContentPresenter.ContentPresenter_Template"); @@ -37,6 +39,7 @@ public void ContentPresenter_Template() } [Test] + [AutoRetry] public void ContentPresenter_Changing_ContentTemplate() { Run("Uno.UI.Samples.Content.UITests.ContentPresenter.ContentPresenter_Changing_ContentTemplate"); @@ -50,6 +53,7 @@ public void ContentPresenter_Changing_ContentTemplate() } [Test] + [AutoRetry] public void ContentControl_Changing_ContentTemplate() { Run("Uno.UI.Samples.Content.UITests.ContentControlTestsControl.ContentControl_Changing_ContentTemplate"); diff --git a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.Popup.cs b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.Popup.cs index 0e639f30d72f..e95df42351ec 100644 --- a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.Popup.cs +++ b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.Popup.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using NUnit.Framework; using SamplesApp.UITests; +using SamplesApp.UITests.TestFramework; using Uno.UITest.Helpers; using Uno.UITest.Helpers.Queries; @@ -16,6 +17,7 @@ partial class UnoSamples_Tests : SampleControlUITestBase { [Test] + [AutoRetry] [Ignore("TODO Popups are not removed properly")] public void Popup_Dismissable_Validation() { @@ -48,6 +50,7 @@ public void Popup_Dismissable_Validation() } [Test] + [AutoRetry] [Ignore("TODO Popups are not removed properly")] public void Popup_NonDismissable_Validation() { @@ -81,6 +84,7 @@ public void Popup_NonDismissable_Validation() } [Test] + [AutoRetry] [Ignore("TODO Popups are not removed properly")] public void Popup_NoFixedHeight_Validation() { diff --git a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.RadioButton.cs b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.RadioButton.cs index 1712d4a63ac5..fb21bb6b0e03 100644 --- a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.RadioButton.cs +++ b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.RadioButton.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using SamplesApp.UITests.TestFramework; using Uno.UITest.Helpers; using Uno.UITest.Helpers.Queries; using Xamarin.UITest; @@ -14,6 +15,7 @@ namespace SamplesApp.UITests partial class UnoSamples_Tests : SampleControlUITestBase { [Test] + [AutoRetry] public void RadioButton_IsEnabled_Validation() { Run("UITests.Shared.Windows_UI_Xaml_Controls.RadioButtonTests.RadioButton_IsEnabled_Automated"); @@ -48,6 +50,7 @@ public void RadioButton_IsEnabled_Validation() } [Test] + [AutoRetry] public void RadioButton_DoubleTap_Validation() { Run("UITests.Shared.Windows_UI_Xaml_Controls.RadioButtonTests.RadioButton_IsEnabled_Automated"); @@ -83,6 +86,7 @@ public void RadioButton_DoubleTap_Validation() } [Test] + [AutoRetry] public void RadioButton_StatePreservation() { Run("UITests.Shared.Windows_UI_Xaml_Controls.RadioButtonTests.RadioButton_IsEnabled_Automated"); diff --git a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.Touch.cs b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.Touch.cs index aa5b1797aba1..b5a2921b2cc6 100644 --- a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.Touch.cs +++ b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.Touch.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using SamplesApp.UITests.TestFramework; using Uno.UITest.Helpers; using Uno.UITest.Helpers.Queries; @@ -13,6 +14,7 @@ namespace SamplesApp.UITests partial class UnoSamples_Tests : SampleControlUITestBase { [Test] + [AutoRetry] public void Touch_Validation() { Run("Uno.UI.Samples.Content.UITests.TouchEventsTests.Touch"); diff --git a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.XBind.cs b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.XBind.cs index 2bd63788e9ab..bd4543f3adee 100644 --- a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.XBind.cs +++ b/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.XBind.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using SamplesApp.UITests.TestFramework; using Uno.UITest.Helpers; using Uno.UITest.Helpers.Queries; @@ -13,6 +14,7 @@ namespace SamplesApp.UITests public partial class UnoSamples_Tests : SampleControlUITestBase { [Test] + [AutoRetry] public void XBind_Validation() { Run("Uno.UI.Samples.Content.UITests.XBind.XBind_Simple"); diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_ApplicationModel_Resources/ResourceLoader_Simple.cs b/src/SamplesApp/SamplesApp.UITests/Windows_ApplicationModel_Resources/ResourceLoader_Simple.cs index f043517e5446..0cf97f22d83a 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_ApplicationModel_Resources/ResourceLoader_Simple.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_ApplicationModel_Resources/ResourceLoader_Simple.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using SamplesApp.UITests.TestFramework; using Uno.UITest.Helpers; using Uno.UITest.Helpers.Queries; @@ -13,6 +14,7 @@ namespace SamplesApp.UITests.Windows_ApplicationModel_Resources public class ResourceLoader_Simple : SampleControlUITestBase { [Test] + [AutoRetry] public void ValidateResourceLoader_Simple() { Run("UITests.Shared.Windows_ApplicationModel_Resources_ResourceLoader.ResourceLoader_Simple"); diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml/FocusManagerTests/UnoSamples_Tests.FocusManager.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml/FocusManagerTests/UnoSamples_Tests.FocusManager.cs index f9aedca12e4c..6180c593d40a 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml/FocusManagerTests/UnoSamples_Tests.FocusManager.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml/FocusManagerTests/UnoSamples_Tests.FocusManager.cs @@ -16,6 +16,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Controls.FocusManagerTests public partial class FocusManagerTests_Tests : SampleControlUITestBase { [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_Border_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -39,6 +40,7 @@ public void FocusManager_GetFocusedElement_Border_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_Button_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -63,6 +65,7 @@ public void FocusManager_GetFocusedElement_Button_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_Button_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -89,6 +92,7 @@ public void FocusManager_GetFocusedElement_Button_LostFocus_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_CheckBox_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -113,6 +117,7 @@ public void FocusManager_GetFocusedElement_CheckBox_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_CheckBox_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -139,6 +144,7 @@ public void FocusManager_GetFocusedElement_CheckBox_LostFocus_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_Grid_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -162,6 +168,7 @@ public void FocusManager_GetFocusedElement_Grid_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_HyperlinkButton_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -188,6 +195,7 @@ public void FocusManager_GetFocusedElement_HyperlinkButton_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_HyperlinkButton_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -214,6 +222,7 @@ public void FocusManager_GetFocusedElement_HyperlinkButton_LostFocus_Validation( } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_Image_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -237,6 +246,7 @@ public void FocusManager_GetFocusedElement_Image_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_Rectangle_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -260,6 +270,7 @@ public void FocusManager_GetFocusedElement_Rectangle_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_TextBlock_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -283,6 +294,7 @@ public void FocusManager_GetFocusedElement_TextBlock_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_TextBoxMultiLine_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -307,6 +319,7 @@ public void FocusManager_GetFocusedElement_TextBoxMultiLine_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_TextBoxMultiLine_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -333,6 +346,7 @@ public void FocusManager_GetFocusedElement_TextBoxMultiLine_LostFocus_Validation } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_TextBoxSingleLine_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -356,6 +370,7 @@ public void FocusManager_GetFocusedElement_TextBoxSingleLine_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_TextBoxSingleLine_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -383,6 +398,7 @@ public void FocusManager_GetFocusedElement_TextBoxSingleLine_LostFocus_Validatio } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ToggleButton_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -406,6 +422,7 @@ public void FocusManager_GetFocusedElement_ToggleButton_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ToggleButton_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -432,6 +449,7 @@ public void FocusManager_GetFocusedElement_ToggleButton_LostFocus_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ComboBox_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -456,6 +474,7 @@ public void FocusManager_GetFocusedElement_ComboBox_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ComboBox_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -482,6 +501,7 @@ public void FocusManager_GetFocusedElement_ComboBox_LostFocus_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ComboBoxItem_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -509,6 +529,7 @@ public void FocusManager_GetFocusedElement_ComboBoxItem_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ComboBoxItem_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -539,6 +560,7 @@ public void FocusManager_GetFocusedElement_ComboBoxItem_LostFocus_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ScrollViewer_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -563,6 +585,7 @@ public void FocusManager_GetFocusedElement_ScrollViewer_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ListViewItem_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); @@ -587,6 +610,7 @@ public void FocusManager_GetFocusedElement_ListViewItem_Validation() } [Test] + [AutoRetry] public void FocusManager_GetFocusedElement_ListViewItem_LostFocus_Validation() { Run("Uno.UI.Samples.Content.UITests.FocusManager.FocusManager_GetFocus_Automated"); diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ButtonTests/UnoSamples_Tests.Button_IsEnabled_Automated.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ButtonTests/UnoSamples_Tests.Button_IsEnabled_Automated.cs index 9bd3eebd2f6e..a5adbf4eb46e 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ButtonTests/UnoSamples_Tests.Button_IsEnabled_Automated.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ButtonTests/UnoSamples_Tests.Button_IsEnabled_Automated.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using NUnit.Framework; using SamplesApp.UITests; +using SamplesApp.UITests.TestFramework; using Uno.UITest.Helpers; using Uno.UITest.Helpers.Queries; using Xamarin.UITest; @@ -15,6 +16,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Controls.ButtonTests partial class Button_Tests : SampleControlUITestBase { [Test] + [AutoRetry] public void Button_IsEnabled_Validation() { Run("Uno.UI.Samples.Content.UITests.ButtonTestsControl.Button_IsEnabled_Automated"); @@ -41,6 +43,7 @@ public void Button_IsEnabled_Validation() } [Test] + [AutoRetry] public void CheckBox_IsEnabled_Validation() { Run("Uno.UI.Samples.Content.UITests.ButtonTestsControl.CheckBox_IsEnabled_Automated"); @@ -73,6 +76,7 @@ public void CheckBox_IsEnabled_Validation() } [Test] + [AutoRetry] public void ToggleButton_IsEnabled_Validation() { Run("Uno.UI.Samples.Content.UITests.ButtonTestsControl.ToggleButton_IsEnabled_Automated"); @@ -105,6 +109,7 @@ public void ToggleButton_IsEnabled_Validation() } [Test] + [AutoRetry] public void ToggleSwitch_IsEnabled_Validation() { Run("Uno.UI.Samples.Content.UITests.ButtonTestsControl.ToggleSwitch_IsEnable_Automated"); @@ -130,6 +135,7 @@ public void ToggleSwitch_IsEnabled_Validation() } [Test] + [AutoRetry] public void HyperlinkButton_IsEnabled_Validation() { Run("Uno.UI.Samples.Content.UITests.ButtonTestsControl.HyperlinkButton_IsEnabled_Automated"); @@ -156,6 +162,7 @@ public void HyperlinkButton_IsEnabled_Validation() } [Test] + [AutoRetry] public void CheckBox_IsEnabled_StatePreservation() { Run("Uno.UI.Samples.Content.UITests.ButtonTestsControl.CheckBox_IsEnabled_Automated"); @@ -194,6 +201,7 @@ public void CheckBox_IsEnabled_StatePreservation() } [Test] + [AutoRetry] public void CheckBox_DoubleTapValidation() { Run("Uno.UI.Samples.Content.UITests.ButtonTestsControl.CheckBox_IsEnabled_Automated"); diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ButtonTests/UnoSamples_Tests.Button_Opacity_Automated.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ButtonTests/UnoSamples_Tests.Button_Opacity_Automated.cs index 01ebf407443a..bc9fed2cb903 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ButtonTests/UnoSamples_Tests.Button_Opacity_Automated.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ButtonTests/UnoSamples_Tests.Button_Opacity_Automated.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using SamplesApp.UITests; +using SamplesApp.UITests.TestFramework; using System; using System.Collections.Generic; using System.Linq; @@ -14,6 +15,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Controls.ButtonTests public partial class Button_Tests : SampleControlUITestBase { [Test] + [AutoRetry] public void Button_IsOpacity_Validation() { Run("Uno.UI.Samples.Content.UITests.ButtonTestsControl.Button_Opacity_Automated"); diff --git a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.ComboBox.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ComboBoxTests/UnoSamples_Tests.ComboBoxTests.cs similarity index 52% rename from src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.ComboBox.cs rename to src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ComboBoxTests/UnoSamples_Tests.ComboBoxTests.cs index 161f9cb27976..b5c1d18e5b03 100644 --- a/src/SamplesApp/SamplesApp.UITests/UnoSamples_Tests.ComboBox.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ComboBoxTests/UnoSamples_Tests.ComboBoxTests.cs @@ -1,18 +1,33 @@ -using System; +using NUnit.Framework; +using SamplesApp.UITests.TestFramework; +using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; -using NUnit.Framework; using Uno.UITest.Helpers; +using Uno.UITest.Helpers.Queries; -namespace SamplesApp.UITests +namespace SamplesApp.UITests.Windows_UI_Xaml_Controls.ComboBoxTests { [TestFixture] - partial class UnoSamples_Tests : SampleControlUITestBase + public partial class ComboBoxTests_Tests : SampleControlUITestBase { [Test] - public void ComboBox_Kidnapping() + [ActivePlatforms(Platform.iOS)] + public void ComboBoxTests_PickerDefaultValue() + { + Run("SamplesApp.Wasm.Windows_UI_Xaml_Controls.ComboBox.ComboBox_Picker"); + + _app.WaitForElement(_app.Marked("theComboBox")); + var theComboBox = _app.Marked("theComboBox"); + + Assert.IsNull(theComboBox.GetDependencyPropertyValue("SelectedItem")); + } + + [Test] + public void ComboBoxTests_Kidnapping() { Run("UITests.Shared.Windows_UI_Xaml_Controls.ComboBox.ComboBox_ComboBoxItem_Selection"); @@ -26,16 +41,16 @@ public void ComboBox_Kidnapping() _app.Tap(button); var first = _app.FindWithin("_tb1", presenter); - Assert.AreEqual("Item 1", GetText(first)); + Assert.AreEqual("Item 1", first.GetDependencyPropertyValue("Text")); _app.Tap(comboBox); _app.TapCoordinates(300, 100); first = _app.FindWithin("_tb1", presenter); - Assert.AreEqual("Item 1", GetText(first)); + Assert.AreEqual("Item 1", first.GetDependencyPropertyValue("Text")); _app.Tap(button); var second = _app.FindWithin("_tb2", presenter); - Assert.AreEqual("Item 2", GetText(second)); + Assert.AreEqual("Item 2", second.GetDependencyPropertyValue("Text")); Configuration.AttemptToFindTargetBeforeScrolling = scrollingInitial; } diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ContentDialog/UnoSamples_Tests.ContentDialog.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ContentDialog/UnoSamples_Tests.ContentDialog.cs index 370063d0a544..0c3584e0dc61 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ContentDialog/UnoSamples_Tests.ContentDialog.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ContentDialog/UnoSamples_Tests.ContentDialog.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using SamplesApp.UITests.TestFramework; using System; using System.Collections.Generic; using System.Drawing; @@ -14,6 +15,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Controls.TimePickerTests public partial class ContentDialog_Tests : SampleControlUITestBase { [Test] + [AutoRetry] public void Simple_ContentDialog_01_Primary() { Run("UITests.Shared.Windows_UI_Xaml_Controls.ContentDialogTests.ContentDialog_Simple"); @@ -40,6 +42,7 @@ public void Simple_ContentDialog_01_Primary() } [Test] + [AutoRetry] public void Simple_ContentDialog_01_Primary_Disabled() { Run("UITests.Shared.Windows_UI_Xaml_Controls.ContentDialogTests.ContentDialog_Simple"); @@ -75,6 +78,7 @@ public void Simple_ContentDialog_01_Primary_Disabled() } [Test] + [AutoRetry] public void Simple_ContentDialog_01_Secondary() { Run("UITests.Shared.Windows_UI_Xaml_Controls.ContentDialogTests.ContentDialog_Simple"); @@ -101,6 +105,7 @@ public void Simple_ContentDialog_01_Secondary() } [Test] + [AutoRetry] public void Simple_ContentDialog_01_PrimaryCommand() { Run("UITests.Shared.Windows_UI_Xaml_Controls.ContentDialogTests.ContentDialog_Simple"); @@ -129,6 +134,7 @@ public void Simple_ContentDialog_01_PrimaryCommand() } [Test] + [AutoRetry] public void Simple_ContentDialog_01_Close() { Run("UITests.Shared.Windows_UI_Xaml_Controls.ContentDialogTests.ContentDialog_Simple"); @@ -155,6 +161,7 @@ public void Simple_ContentDialog_01_Close() } [Test] + [AutoRetry] public void Simple_ContentDialog_01_TypeInner() { Run("UITests.Shared.Windows_UI_Xaml_Controls.ContentDialogTests.ContentDialog_Simple"); diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/TimePickerTests/UnoSamples_Tests.TimePicker.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/TimePickerTests/UnoSamples_Tests.TimePicker.cs index 4224081a1460..d3b7fa9efbf3 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/TimePickerTests/UnoSamples_Tests.TimePicker.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/TimePickerTests/UnoSamples_Tests.TimePicker.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using SamplesApp.UITests.TestFramework; using System; using System.Collections.Generic; using System.Drawing; @@ -14,6 +15,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Controls.TimePickerTests public partial class TimePickerTests_Tests : SampleControlUITestBase { [Test] + [AutoRetry] [Ignore("Not available yet")] public void TimePickerFlyout_DiscardChanges() { @@ -52,6 +54,7 @@ public void TimePickerFlyout_DiscardChanges() } [Test] + [AutoRetry] [Ignore("Not available yet")] public void TimePickerFlyout_ApplyChanges() { @@ -89,5 +92,20 @@ public void TimePickerFlyout_ApplyChanges() // To do Task Number: - 155260 complete test case for IOS.KD } } + + [Test] + public void TimePickerFlyout_DoesntApplyDefaultTime() + { + Run("SamplesApp.Samples.TimePicker.Sample1"); + + _app.WaitForElement(_app.Marked("theTimePicker")); + var theTimePicker = _app.Marked("theTimePicker"); + + // Assert initial state + if (DateTime.Now.TimeOfDay != new TimeSpan(12, 0, 0)) + { + Assert.AreEqual("12:00:00", theTimePicker.GetDependencyPropertyValue("Time")?.ToString()); + } + } } } diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Input/DragCoordinates_Tests.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Input/DragCoordinates_Tests.cs new file mode 100644 index 000000000000..5e140e3c78b7 --- /dev/null +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Input/DragCoordinates_Tests.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Uno.UITest.Helpers.Queries; +using Query = System.Func; +using StringQuery = System.Func>; + +namespace SamplesApp.UITests.Windows_UI_Xaml_Controls.TimePickerTests +{ + public class DragCoordinates_Tests : SampleControlUITestBase + { + [Test] + [Ignore("https://github.com/unoplatform/uno/issues/1257")] + public void DragBorder01() + { + Run("UITests.Shared.Windows_UI_Xaml_Input.Pointers.DragCoordinates_Automated"); + + Query testSelector = q => q.Text("DragCoordinates 01"); + + Query rootCanvas = q => q.Marked("rootCanvas"); + Query myBorder = q => q.Marked("myBorder"); + Query topValue = q => q.Marked("borderPositionTop"); + Query leftValue = q => q.Marked("borderPositionLeft"); + + _app.WaitForElement(testSelector); + _app.Tap(testSelector); + + _app.WaitForElement(rootCanvas); + + _app.Screenshot("tb01 - Initial"); + + _app.WaitForDependencyPropertyValue(topValue, "Text", "0"); + _app.WaitForDependencyPropertyValue(leftValue, "Text", "0"); + + _app.Screenshot("DragBorder01 - Step 1"); + + var topBorderRect = _app.Query(myBorder).First().Rect; + + _app.DragCoordinates(topBorderRect.CenterX, topBorderRect.CenterY, topBorderRect.CenterX + 50, topBorderRect.CenterY + 50); + + _app.Screenshot("DragBorder01 - Step 2"); + + _app.WaitForDependencyPropertyValue(topValue, "Text", "50"); + _app.WaitForDependencyPropertyValue(leftValue, "Text", "50"); + } + } +} diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 2d4f80087a7e..6ef2d9ea2983 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -1553,6 +1553,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -2461,6 +2465,7 @@ TextBox_TextChanging.xaml + ToggleSwitch_TemplateReuse.xaml @@ -3228,6 +3233,7 @@ Keyboard_Showing_Dismissal.xaml + RoutedEventsPage.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ComboBox/ComboBox_ItemsSource.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ComboBox/ComboBox_ItemsSource.xaml index a8a353c7839a..4dc51362ff39 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ComboBox/ComboBox_ItemsSource.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ComboBox/ComboBox_ItemsSource.xaml @@ -14,7 +14,8 @@ - + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ComboBox/ComboBox_Picker.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ComboBox/ComboBox_Picker.xaml index 17dc8ce66359..d6ed68ae1d81 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ComboBox/ComboBox_Picker.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ComboBox/ComboBox_Picker.xaml @@ -359,8 +359,10 @@ - + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ListView/ListView_Inside_ListView.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ListView/ListView_Inside_ListView.xaml.cs index 5c7c58756e9e..33474e263774 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ListView/ListView_Inside_ListView.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ListView/ListView_Inside_ListView.xaml.cs @@ -4,7 +4,7 @@ namespace SamplesApp.Windows_UI_Xaml_Controls.ListView { - [SampleControlInfo("ListView", "ListView_BoundSelectedItem", typeof(ListViewSelectionsViewModel))] + [SampleControlInfo("ListView", "ListView_Inside_ListView", typeof(ListViewSelectionsViewModel))] public sealed partial class ListView_Inside_ListView : UserControl { public ListView_Inside_ListView() diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ListView/ListView_TextBox.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ListView/ListView_TextBox.xaml index 14b016d0b41d..dc0161d1ef79 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ListView/ListView_TextBox.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ListView/ListView_TextBox.xaml @@ -9,9 +9,13 @@ d:DesignWidth="400"> + + + - diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/Models/TimePickerViewModel.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/Models/TimePickerViewModel.cs index 9a6aabd341a9..c65cc2439ef5 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/Models/TimePickerViewModel.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/Models/TimePickerViewModel.cs @@ -9,7 +9,7 @@ namespace SamplesApp.Windows_UI_Xaml_Controls.Models [Bindable] public class TimePickerViewModel : ViewModelBase { - private TimeSpan _time = DateTime.Now.TimeOfDay; + private TimeSpan _time = new TimeSpan(12,0,0); public TimePickerViewModel(CoreDispatcher dispatcher) : base(dispatcher) { diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/TextBox/Input_Test_NoScrollViewer_Automated.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/TextBox/Input_Test_NoScrollViewer_Automated.xaml index 81c25e517b9b..62dbd993cabb 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/TextBox/Input_Test_NoScrollViewer_Automated.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/TextBox/Input_Test_NoScrollViewer_Automated.xaml @@ -4,7 +4,7 @@ xmlns:local="using:Uno.UI.Samples.Content.UITests.TextBoxControl" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"> - + @@ -22,12 +22,12 @@ + Margin="0,4,0,0" /> + Margin="0,4,0,0"/> + Margin="0,4,0,0" /> + Margin="0,4,0,0" /> + Margin="0,4,0,0" /> - \ No newline at end of file + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/TimePicker/Sample1.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/TimePicker/Sample1.xaml index 10a511796106..ab85d8159eec 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/TimePicker/Sample1.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/TimePicker/Sample1.xaml @@ -1,26 +1,28 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="using:ControlTests" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:xamarin="http://nventive.com/xamarin" + xmlns:ios="http://nventive.com/ios" + xmlns:android="http://nventive.com/android" + xmlns:native_ios="using:UIKit" + xmlns:native_android="using:Android.Widget" + xmlns:controls="using:Uno.UI.Samples.Controls" + mc:Ignorable="xamarin android ios native_ios native_android"> + x:Name="theTimePicker" + Margin="15,10" + HorizontalAlignment="Center" + VerticalAlignment="Top" + MinuteIncrement="1" + Time="{Binding [Time], Mode=TwoWay}" + ClockIdentifier="24HourClock"> diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/DragCoordinates_Automated.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/DragCoordinates_Automated.xaml new file mode 100644 index 000000000000..09cc37af1828 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/DragCoordinates_Automated.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/DragCoordinates_Automated.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/DragCoordinates_Automated.xaml.cs new file mode 100644 index 000000000000..af368986bf60 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Pointers/DragCoordinates_Automated.xaml.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Uno.UI.Samples.Controls; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace UITests.Shared.Windows_UI_Xaml_Input.Pointers +{ + [SampleControlInfo("Pointers", "DragCoordinates_Automated")] + public sealed partial class DragCoordinates_Automated : UserControl + { + public DragCoordinates_Automated() + { + this.InitializeComponent(); + + Point startPos = new Point(); + bool pressed = false; + + myBorder.PointerPressed += (s, e) => { + Console.WriteLine("Pointer pressed"); + startPos = e.GetCurrentPoint(myBorder).Position; + pressed = true; + myBorder.CapturePointer(e.Pointer); + }; + + myBorder.PointerMoved += (s, e) => { + if(pressed) + { + Canvas.SetTop(myBorder, e.GetCurrentPoint(rootCanvas).Position.Y - startPos.Y); + Canvas.SetLeft(myBorder, e.GetCurrentPoint(rootCanvas).Position.X - startPos.X); + } + }; + + myBorder.PointerCanceled += (s, e) => { + Console.WriteLine("Pointer cancelled"); + }; + + myBorder.PointerReleased += (s, e) => + { + Console.WriteLine("Pointer released"); + myBorder.ReleasePointerCapture(e.Pointer); + pressed = false; + }; + } + } +} diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/PathTestsControl/Path_Arc.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/PathTestsControl/Path_Arc.xaml index 613cd9da7cb6..c82c5c2d24e7 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/PathTestsControl/Path_Arc.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/PathTestsControl/Path_Arc.xaml @@ -39,6 +39,13 @@ StrokeThickness="5" /> + + + + + diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs index 90a848cabae3..e3ca0fccced7 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs @@ -952,7 +952,9 @@ void WriteThemeResourceDeclaration(string resourceKey, IEnumerable _childrenNeedingForceLayout = new ArrayList(); static { try { @@ -146,11 +147,30 @@ public final void nativeFinishLayoutOverride(){ protected abstract void onLayoutCore(boolean changed, int left, int top, int right, int bottom); + private boolean _isInOnLayoutCore; + protected final void onLayout(boolean changed, int left, int top, int right, int bottom) { if(!_unoLayoutOverride) { - onLayoutCore(changed, left, top, right, bottom); + try + { + _isInOnLayoutCore = true; + + _childrenNeedingForceLayout.clear(); + onLayoutCore(changed, left, top, right, bottom); + + if (!_childrenNeedingForceLayout.isEmpty()) { + for (int i = 0; i < _childrenNeedingForceLayout.size(); i++) { + _childrenNeedingForceLayout.get(i).forceLayout(); + } + _childrenNeedingForceLayout.clear(); + } + } + finally + { + _isInOnLayoutCore = false; + } } } @@ -299,9 +319,22 @@ public final void requestLayout() } super.requestLayout(); + + // If we request to layout during the layout core pass, we need to force the view + // to redraw by asking its parent to request a new layout for this view + if(_isInOnLayoutCore){ + if(getParent() instanceof UnoViewGroup){ + UnoViewGroup unoViewGroupParent = (UnoViewGroup) getParent(); + unoViewGroupParent.setChildNeedsForceLayout(this); + } + } } } + private void setChildNeedsForceLayout(UnoViewGroup child){ + _childrenNeedingForceLayout.add(child); + } + public final void invalidate() { super.invalidate(); diff --git a/src/Uno.UI.Tests/BinderTests/Given_Binder.cs b/src/Uno.UI.Tests/BinderTests/Given_Binder.cs index 5f59fc85f52a..6b69212bf4d7 100644 --- a/src/Uno.UI.Tests/BinderTests/Given_Binder.cs +++ b/src/Uno.UI.Tests/BinderTests/Given_Binder.cs @@ -848,6 +848,21 @@ public void When_ExplicitUpdateSourceTrigger_UpdateSource() Assert.AreEqual(-42, target.MyProperty); } + [TestMethod] + public void When_ExplicitSetBindingBetweenProperties_IsNotFallBackValue() + { + var source = new MyBindingSource { IntValue = 42 }; + var target = new MyControl(); + var target2 = new MyObjectTest(); + + target.SetBinding(MyControl.MyPropertyProperty, new Binding { Source = source, Path = nameof(MyBindingSource.IntValue), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.Explicit }); + target2.Binding(nameof(MyControl.MyProperty), nameof(MyControl.MyProperty), source: target, BindingMode.TwoWay); + + Assert.AreEqual(42, source.IntValue); + Assert.AreEqual(42, target.MyProperty); + Assert.AreEqual(42, target2.MyProperty); + } + [TestMethod] public void When_SelfRelativeSource() { @@ -884,7 +899,7 @@ partial void OnDataContextChangedPartial(DependencyPropertyChangedEventArgs e) DataContextChangedCount++; } - #region ChildrenBinders DependencyProperty +#region ChildrenBinders DependencyProperty public IList ChildrenBinders { @@ -906,7 +921,7 @@ private void OnChildrenBindersChanged(DependencyPropertyChangedEventArgs e) { } - #endregion +#endregion } @@ -923,7 +938,7 @@ public Target2(BaseTarget parent = null) } - #region TargetValue DependencyProperty +#region TargetValue DependencyProperty public int TargetValue { @@ -941,14 +956,14 @@ private void OnTargetValueChanged(DependencyPropertyChangedEventArgs e) TargetValueSetCount++; } - #endregion +#endregion public int TargetValueSetCount { get; set; } - #region Brush DependencyProperty +#region Brush DependencyProperty public Brush Brush { @@ -966,12 +981,12 @@ private void OnBrushChanged(DependencyPropertyChangedEventArgs e) BrushSetCount++; } - #endregion +#endregion public int BrushSetCount { get; set; } - #region Object DependencyProperty +#region Object DependencyProperty public Object Object { @@ -989,7 +1004,7 @@ private void OnObjectChanged(DependencyPropertyChangedEventArgs e) objectSetCount++; } - #endregion +#endregion public int objectSetCount { get; set; } } @@ -1165,7 +1180,7 @@ public int Property2 public partial class MyObjectTest : DependencyObject { - #region MyProperty DependencyProperty +#region MyProperty DependencyProperty public int MyProperty { @@ -1175,14 +1190,14 @@ public int MyProperty // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... public static readonly DependencyProperty MyPropertyProperty = - DependencyProperty.Register("MyProperty", typeof(int), typeof(object), new PropertyMetadata(0)); + DependencyProperty.Register("MyProperty", typeof(int), typeof(MyObjectTest), new PropertyMetadata(0)); private void OnMyPropertyChanged(DependencyPropertyChangedEventArgs e) { } - #endregion +#endregion } diff --git a/src/Uno.UI.sln b/src/Uno.UI.sln index e40f17d885b5..5c30db557035 100644 --- a/src/Uno.UI.sln +++ b/src/Uno.UI.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28516.95 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.645 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Uno.UI", "Uno.UI", "{416684CF-A4E3-4079-B380-3FF0B00E433C}" EndProject @@ -132,7 +132,7 @@ Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "SamplesApp.Wasm.UITests", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.UI.TestComparer", "Uno.UI.TestComparer\Uno.UI.TestComparer.csproj", "{8B51C7BA-BE67-4707-97CF-A57190236860}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamplesApp.UITests", "SamplesApp\SamplesApp.UITests\SamplesApp.UITests.csproj", "{F65D1C57-8BF7-4894-B491-3AD94F889C4D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SamplesApp.UITests", "SamplesApp\SamplesApp.UITests\SamplesApp.UITests.csproj", "{F65D1C57-8BF7-4894-B491-3AD94F889C4D}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -150,7 +150,6 @@ Global SamplesApp\SamplesApp.Shared\SamplesApp.Shared.projitems*{b71ae380-558c-4d6c-81d8-9c043b50262c}*SharedItemsImports = 4 SamplesApp\SamplesApp.UnitTests.Shared\SamplesApp.UnitTests.Shared.projitems*{b71ae380-558c-4d6c-81d8-9c043b50262c}*SharedItemsImports = 4 SamplesApp\UITests.Shared\UITests.Shared.projitems*{b71ae380-558c-4d6c-81d8-9c043b50262c}*SharedItemsImports = 4 - SamplesApp\UITests.Shared\UITests.Shared.projitems*{beb0ab3a-16a7-49cf-ad5e-f4a53b985afe}*SharedItemsImports = 13 SamplesApp\Benchmarks.Shared\Benchmarks.Shared.projitems*{f4581f51-40d6-4035-8a1d-65cc6917d0a9}*SharedItemsImports = 13 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Uno.UI/Controls/BindableNSView.macOS.cs b/src/Uno.UI/Controls/BindableNSView.macOS.cs index 3d1ff5178fa7..0f5e7216fc8f 100644 --- a/src/Uno.UI/Controls/BindableNSView.macOS.cs +++ b/src/Uno.UI/Controls/BindableNSView.macOS.cs @@ -19,9 +19,9 @@ namespace Uno.UI.Controls { public partial class BindableNSView : NSView, INotifyPropertyChanged, DependencyObject, IShadowChildrenProvider, IEnumerable { - private List _shadowChildren = new List(); + private MaterializableList _shadowChildren = new MaterializableList(0); - List IShadowChildrenProvider.ChildrenShadow => _shadowChildren; + List IShadowChildrenProvider.ChildrenShadow => _shadowChildren.Materialized; internal IReadOnlyList ChildrenShadow => _shadowChildren; @@ -29,14 +29,14 @@ public override void DidAddSubview(NSView NSView) { base.DidAddSubview(NSView); - // Reference the list as we don't know where + // Reference the list as we don't know where // the items has been added other than by getting the complete list. // Subviews materializes a new array at every call, which makes it safe to // reference. - _shadowChildren = Subviews.ToList(); + _shadowChildren = new MaterializableList(Subviews); } - internal List.Enumerator GetChildrenEnumerator() => _shadowChildren.GetEnumerator(); + internal List.Enumerator GetChildrenEnumerator() => _shadowChildren.Materialized.GetEnumerator(); public override void WillRemoveSubview(NSView NSView) { @@ -90,7 +90,7 @@ private void Initialize() /// internal void MoveViewTo(int oldIndex, int newIndex) { - var newShadow = _shadowChildren.ToList(); + var newShadow = _shadowChildren.Materialized; var view = newShadow[oldIndex]; @@ -105,7 +105,7 @@ internal void MoveViewTo(int oldIndex, int newIndex) // BringSubviewToFront(newShadow[i]); } - _shadowChildren = newShadow.ToList(); + _shadowChildren = new MaterializableList(newShadow); } protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) diff --git a/src/Uno.UI/Controls/BindableUIView.iOS.cs b/src/Uno.UI/Controls/BindableUIView.iOS.cs index 928e0cc6ae22..6dee47ef566e 100644 --- a/src/Uno.UI/Controls/BindableUIView.iOS.cs +++ b/src/Uno.UI/Controls/BindableUIView.iOS.cs @@ -1,17 +1,17 @@ -using Uno.Collections; -using Microsoft.Practices.ServiceLocation; +using Microsoft.Practices.ServiceLocation; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; -using Uno.Extensions; -using Uno.UI.DataBinding; using System.Runtime.CompilerServices; using System.Drawing; -using Uno.Disposables; using Windows.UI.Xaml; -using System.ComponentModel; using Windows.UI.Xaml.Media; +using Uno.Collections; +using Uno.Disposables; +using Uno.Extensions; +using Uno.UI.DataBinding; #if XAMARIN_IOS_UNIFIED using Foundation; @@ -25,9 +25,9 @@ namespace Uno.UI.Controls { public partial class BindableUIView : UIView, INotifyPropertyChanged, DependencyObject, IShadowChildrenProvider { - private List _shadowChildren = new List(); + private MaterializableList _shadowChildren = new MaterializableList(); - List IShadowChildrenProvider.ChildrenShadow => _shadowChildren; + List IShadowChildrenProvider.ChildrenShadow => _shadowChildren.Materialized; internal IReadOnlyList ChildrenShadow => _shadowChildren; @@ -35,14 +35,14 @@ public override void SubviewAdded(UIView uiview) { base.SubviewAdded(uiview); - // Reference the list as we don't know where + // Reference the list as we don't know where // the items has been added other than by getting the complete list. // Subviews materializes a new array at every call, which makes it safe to // reference. - _shadowChildren = Subviews.ToList(); + _shadowChildren = new MaterializableList(Subviews); } - internal List.Enumerator GetChildrenEnumerator() => _shadowChildren.GetEnumerator(); + internal List.Enumerator GetChildrenEnumerator() => _shadowChildren.Materialized.GetEnumerator(); public override void WillRemoveSubview(UIView uiview) { @@ -93,12 +93,12 @@ private void Initialize() /// /// The trick for this method is to move the child from one position to the other /// without calling RemoveView and AddView. In this context, the only way to do this is - /// to call BringSubviewToFront, which is the only available method on UIView that manipulates + /// to call BringSubviewToFront, which is the only available method on UIView that manipulates /// the index of a view, even if it does not allow for specifying an index. /// internal void MoveViewTo(int oldIndex, int newIndex) { - var newShadow = _shadowChildren.ToList(); + var newShadow = _shadowChildren.Materialized; var view = newShadow[oldIndex]; @@ -112,7 +112,7 @@ internal void MoveViewTo(int oldIndex, int newIndex) BringSubviewToFront(newShadow[i]); } - _shadowChildren = newShadow.ToList(); + _shadowChildren = new MaterializableList(newShadow); } protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) diff --git a/src/Uno.UI/Controls/BindableView.Android.cs b/src/Uno.UI/Controls/BindableView.Android.cs index 17b6e9846d33..a1a93becf67c 100644 --- a/src/Uno.UI/Controls/BindableView.Android.cs +++ b/src/Uno.UI/Controls/BindableView.Android.cs @@ -10,6 +10,7 @@ using Windows.UI.Xaml.Media; using Android.Graphics; using Android.Views.Animations; +using Uno.Collections; using Uno.Disposables; using Uno.UI.Media; @@ -24,7 +25,7 @@ public partial class BindableView : DependencyObject, IShadowChildrenProvider { - private readonly List _childrenShadow = new List(); + private readonly MaterializableList _childrenShadow = new MaterializableList(); public event PropertyChangedEventHandler PropertyChanged; @@ -67,7 +68,7 @@ protected override void OnLayoutCore(bool changed, int l, int t, int r, int b) /// /// /// This method exists to avoid having to call twice the View - /// methods to get the measured width and height, and pay for the + /// methods to get the measured width and height, and pay for the /// interop cost. /// internal static Windows.Foundation.Size GetNativeMeasuredDimensionsFast(View view) @@ -86,14 +87,14 @@ internal static Windows.Foundation.Size GetNativeMeasuredDimensionsFast(View vie /// /// Provides a shadowed list of views, used to limit the impact of the marshalling. /// - List IShadowChildrenProvider.ChildrenShadow => _childrenShadow; + List IShadowChildrenProvider.ChildrenShadow => _childrenShadow.Materialized; - internal List.Enumerator GetChildrenEnumerator() => _childrenShadow.GetEnumerator(); + internal List.Enumerator GetChildrenEnumerator() => _childrenShadow.Materialized.GetEnumerator(); /// /// Gets the view at a specific position. /// - /// This method does not call the actual GetChildAt method, + /// This method does not call the actual GetChildAt method, /// but an optimized version of it that does not require a hierarchy /// observer. public new virtual View GetChildAt(int position) => _childrenShadow[position]; @@ -101,7 +102,7 @@ internal static Windows.Foundation.Size GetNativeMeasuredDimensionsFast(View vie /// /// Gets the number of child views. /// - /// This method does not call the actual ChildCount method, + /// This method does not call the actual ChildCount method, /// but an optimized version of it that does not require a hierarchy /// observer. public new virtual int ChildCount => _childrenShadow.Count; @@ -109,7 +110,7 @@ internal static Windows.Foundation.Size GetNativeMeasuredDimensionsFast(View vie /// /// Adds a view to the current view group. /// - /// This method does not call the actual AddView method, + /// This method does not call the actual AddView method, /// but an optimized version of it that does not require a hierarchy /// observer. public new virtual void AddView(View view) @@ -122,7 +123,7 @@ internal static Windows.Foundation.Size GetNativeMeasuredDimensionsFast(View vie /// /// Adds a view to the current view group using a position index. /// - /// This method does not call the actual AddView method, + /// This method does not call the actual AddView method, /// but an optimized version of it that does not require a hierarchy /// observer. public new virtual void AddView(View view, int index) @@ -135,7 +136,7 @@ internal static Windows.Foundation.Size GetNativeMeasuredDimensionsFast(View vie /// /// Removes a view from the current view group. /// - /// This method does not call the actual AddView method, + /// This method does not call the actual AddView method, /// but an optimized version of it that does not require a hierarchy /// observer. public new virtual void RemoveView(View view) @@ -158,7 +159,7 @@ internal static Windows.Foundation.Size GetNativeMeasuredDimensionsFast(View vie /// /// Removes a view from the current view group using a position index. /// - /// This method does not call the actual AddView method, + /// This method does not call the actual AddView method, /// but an optimized version of it that does not require a hierarchy /// observer. public new virtual void RemoveViewAt(int index) @@ -188,7 +189,7 @@ internal static Windows.Foundation.Size GetNativeMeasuredDimensionsFast(View vie /// /// The trick for this method is to move the child from one position to the other /// without calling RemoveView and AddView. In this context, the only way to do this is - /// to call BringToFront, which is the only available method on ViewGroup that manipulates + /// to call BringToFront, which is the only available method on ViewGroup that manipulates /// the index of a view, even if it does not allow for specifying an index. /// internal void MoveViewTo(int oldIndex, int newIndex) @@ -244,7 +245,7 @@ protected virtual void OnChildViewAdded(View view) } /// - /// Provides a default implementation for the HitCheck + /// Provides a default implementation for the HitCheck /// performed in the UnoViewGroup java class. /// /// @@ -272,7 +273,7 @@ internal IDisposable PreventRequestLayout() } /// - /// Called whenever the current view is being removed from its parent. This method is + /// Called whenever the current view is being removed from its parent. This method is /// called only if the parent is a BindableView. /// protected override void OnRemovedFromParent() @@ -297,7 +298,7 @@ protected void RaisePropertyChanged([CallerMemberName] string propertyName = nul /// Resets the dependency object parent for non BindableView views, but that implement IDependencyObject provider. /// /// - /// This is required on Android because native instances + /// This is required on Android because native instances /// can't be notificed if their parent changes. provides this behavior /// by intercepting add/remove children and calls . /// relies on knowing that the of a @@ -317,7 +318,7 @@ private static void ResetDependencyObjectParent(View view) } /// - /// Call from non- parents from and in + /// Call from non- parents from and in /// order to clear the correctly. /// internal void NotifyRemovedFromParent() diff --git a/src/Uno.UI/DataBinding/BindingExpression.cs b/src/Uno.UI/DataBinding/BindingExpression.cs index b6e395da85cf..6d181e3d4881 100644 --- a/src/Uno.UI/DataBinding/BindingExpression.cs +++ b/src/Uno.UI/DataBinding/BindingExpression.cs @@ -115,14 +115,17 @@ Binding binding _isElementNameSource = true; } - - ApplyFallbackValue(); + if (!(GetWeakDataContext()?.IsAlive ?? false)) + { + ApplyFallbackValue(); + } + ApplyExplicitSource(); ApplyElementName(); } private ManagedWeakReference GetWeakDataContext() - => _isElementNameSource || _explicitSourceStore.IsAlive ? _explicitSourceStore : _dataContext; + => _isElementNameSource || (_explicitSourceStore?.IsAlive ?? false) ? _explicitSourceStore : _dataContext; /// /// Sends the current binding target value to the binding source property in TwoWay bindings. diff --git a/src/Uno.UI/DataBinding/BindingPath.cs b/src/Uno.UI/DataBinding/BindingPath.cs index b5016e32a7fc..36bf562d6362 100644 --- a/src/Uno.UI/DataBinding/BindingPath.cs +++ b/src/Uno.UI/DataBinding/BindingPath.cs @@ -97,7 +97,7 @@ internal BindingPath(string path, object fallbackValue, DependencyPropertyValueP /// /// An enumerable of binding items /// - /// The DataContext and PropertyType of the descriptor may be null + /// The DataContext and PropertyType of the descriptor may be null /// if the binding is incomplete (the DataContext may be null, or the path is invalid) /// public IEnumerable GetPathItems() @@ -136,7 +136,7 @@ private IEnumerable ParseDependencyPropertyAccess(string[] items) /// Registers a property changed registration handler. /// /// The handled to be called when a property needs to be observed. - /// This method exists to provide layer separation, + /// This method exists to provide layer separation, /// when BindingPath is in the presentation layer, and DependencyProperty is in the (some) Views layer. /// public static void RegisterPropertyChangedRegistrationHandler(PropertyChangedRegistrationHandler handler) @@ -153,7 +153,7 @@ public string Path } /// - /// Provides the value of the using the + /// Provides the value of the using the /// current using the current precedence. /// public object Value @@ -182,8 +182,8 @@ public object Value } /// - /// Gets the value of the DependencyProperty with a - /// precedence immediately below the one specified at the creation + /// Gets the value of the DependencyProperty with a + /// precedence immediately below the one specified at the creation /// of the BindingPath. /// /// The lower precedence value @@ -214,7 +214,7 @@ internal void SetLocalValue(object value) /// /// Clears the value of the current precendence. /// - /// After this call, the value returned + /// After this call, the value returned /// by will be of the next available /// precedence. public void ClearValue() @@ -336,7 +336,7 @@ private static IDisposable SubscribeToNotifyPropertyChanged(ManagedWeakReference return Disposable.Create(() => { // This weak reference ensure that the closure will not link - // the caller and the callee, in the same way "newValueActionWeak" + // the caller and the callee, in the same way "newValueActionWeak" // does not link the callee to the caller. var that = dataContextReference.Target as INotifyPropertyChanged; @@ -478,15 +478,37 @@ internal object GetSubstituteValue() return GetSourceValue(_substituteValueGetter); } + private bool _isDataContextChanging; + private void OnDataContextChanged() { if (DataContext != null) { ClearCachedGetters(); + if (_propertyChanged.Disposable != null) + { + try + { + _isDataContextChanging = true; + _propertyChanged.Disposable = null; + } + finally + { + _isDataContextChanging = false; + } + } _propertyChanged.Disposable = SubscribeToPropertyChanged((previousValue, newValue, shouldRaiseValueChanged) => { + if (_isDataContextChanging && newValue == DependencyProperty.UnsetValue) + { + // We're in a "resubscribe" scenario, so we don't need to + // pass through the DependencyProperty.UnsetValue. + // We simply discard this update. + return; + } + if (Next != null) { Next.DataContext = newValue; @@ -693,11 +715,10 @@ private void RaiseValueChanged(object newValue) /// A disposable to be called when the subscription is disposed. private IDisposable SubscribeToPropertyChanged(PropertyChangedHandler action) { - var disposable = new CompositeDisposable(); - + var disposables = new CompositeDisposable((_propertyChangedHandlers.Count * 3)); foreach (var handler in _propertyChangedHandlers) { - object previousValue = null; + object previousValue = default; Action updateProperty = () => { @@ -723,16 +744,15 @@ private IDisposable SubscribeToPropertyChanged(PropertyChangedHandler action) // in this disposable. The reference is attached to the source's // object lifetime, to the target (bound) object. // - // The registerations made by _propertyChangedHandlers are all + // All registrations made by _propertyChangedHandlers are // weak with regards to the delegates that are provided. - disposable.Add(() => updateProperty = null); - - disposable.Add(handlerDisposable); - disposable.Add(disposeAction); + disposables.Add(() => updateProperty = null); + disposables.Add(handlerDisposable); + disposables.Add(disposeAction); } } - return disposable; + return disposables; } public void Dispose() diff --git a/src/Uno.UI/LayoutProvider.Android.cs b/src/Uno.UI/LayoutProvider.Android.cs index 9b415e70916e..dd9d24ebcbfb 100644 --- a/src/Uno.UI/LayoutProvider.Android.cs +++ b/src/Uno.UI/LayoutProvider.Android.cs @@ -7,7 +7,11 @@ using Android.Util; using Android.Views; using Android.Widget; +using Windows.Devices.Sensors; using Windows.Foundation; +using Windows.Graphics.Display; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; using Rect = Android.Graphics.Rect; namespace Uno.UI @@ -15,9 +19,12 @@ namespace Uno.UI internal class LayoutProvider { public delegate void LayoutChangedListener(Rect statusBar, Rect keyboard, Rect navigationBar); + public delegate void InsetsChangedListener(Thickness insets); public event LayoutChangedListener LayoutChanged; + public event InsetsChangedListener InsetsChanged; + public Thickness Insets { get; internal set; } = new Thickness(0, 0, 0, 0); public Rect StatusBarRect { get; private set; } = new Rect(0, 0, 0, 0); public Rect KeyboardRect { get; private set; } = new Rect(0, 0, 0, 0); public Rect NavigationBarRect { get; private set; } = new Rect(0, 0, 0, 0); @@ -29,12 +36,12 @@ public LayoutProvider(Activity activity) { this._activity = activity; - _adjustNothingLayoutProvider = new GlobalLayoutProvider(activity, null) + _adjustNothingLayoutProvider = new GlobalLayoutProvider(activity, null, null) { SoftInputMode = SoftInput.AdjustNothing | SoftInput.StateUnchanged, InputMethodMode = InputMethod.NotNeeded, }; - _adjustResizeLayoutProvider = new GlobalLayoutProvider(activity, MeasureLayout) + _adjustResizeLayoutProvider = new GlobalLayoutProvider(activity, MeasureLayout, MeasureInsets) { SoftInputMode = SoftInput.AdjustResize | SoftInput.StateUnchanged, InputMethodMode = InputMethod.Needed, @@ -50,6 +57,13 @@ internal void Start(View view) _adjustResizeLayoutProvider.Start(view); } } + + internal void StartListenInsets() + { + _adjustNothingLayoutProvider.StartListenInsets(); + _adjustResizeLayoutProvider.StartListenInsets(); + } + internal void Stop() { _adjustNothingLayoutProvider.Stop(); @@ -60,16 +74,36 @@ private void MeasureLayout(PopupWindow sender) { // We can obtain the size of keyboard by comparing the layout of two popup windows // where one (AdjustResize) resizes to keyboard and one(AdjustNothing) that doesn't: - // [size] realMetrics : screen + // [size] realMetrics : screen + // [size] metrics : screen - dead zones + // [rect] displayRect : screen - (bottom: nav_bar) // [rect] adjustNothingFrame : screen - (top: status_bar) - (bottom: nav_bar) // [rect] adjustResizeFrame : screen - (top: status_bar) - (bottom: keyboard + nav_bar) var realMetrics = Get(_activity.WindowManager.DefaultDisplay.GetRealMetrics); + var metrics = Get(_activity.WindowManager.DefaultDisplay.GetMetrics); + var displayRect = Get(_activity.WindowManager.DefaultDisplay.GetRectSize); var adjustNothingFrame = Get(_adjustNothingLayoutProvider.ContentView.GetWindowVisibleDisplayFrame); var adjustResizeFrame = Get(_adjustResizeLayoutProvider.ContentView.GetWindowVisibleDisplayFrame); + var orientation = DisplayInformation.GetForCurrentView().CurrentOrientation; + StatusBarRect = new Rect(0, 0, realMetrics.WidthPixels, adjustNothingFrame.Top); KeyboardRect = new Rect(0, adjustResizeFrame.Bottom, realMetrics.WidthPixels, adjustNothingFrame.Bottom); - NavigationBarRect = new Rect(0, adjustNothingFrame.Bottom, realMetrics.WidthPixels, realMetrics.HeightPixels); + + switch (orientation) + { + case DisplayOrientations.Landscape: + NavigationBarRect = new Rect(0, 0, metrics.WidthPixels - displayRect.Width(), metrics.HeightPixels); + break; + case DisplayOrientations.LandscapeFlipped: + NavigationBarRect = new Rect(adjustNothingFrame.Width(), 0, metrics.WidthPixels - displayRect.Width(), metrics.HeightPixels); + break; + // Miss portrait flipped + case DisplayOrientations.Portrait: + default: + NavigationBarRect = new Rect(0, adjustNothingFrame.Bottom, realMetrics.WidthPixels, realMetrics.HeightPixels); + break; + } LayoutChanged?.Invoke(StatusBarRect, KeyboardRect, NavigationBarRect); @@ -82,18 +116,32 @@ private void MeasureLayout(PopupWindow sender) } } + private void MeasureInsets(PopupWindow sender, WindowInsets insets) + { + Insets = new Thickness( + ViewHelper.PhysicalToLogicalPixels(insets.SystemWindowInsetLeft), + ViewHelper.PhysicalToLogicalPixels(insets.SystemWindowInsetTop), + ViewHelper.PhysicalToLogicalPixels(insets.SystemWindowInsetRight), + ViewHelper.PhysicalToLogicalPixels(insets.SystemWindowInsetBottom) + ); + + InsetsChanged?.Invoke(Insets); + } - private class GlobalLayoutProvider : PopupWindow, ViewTreeObserver.IOnGlobalLayoutListener + private class GlobalLayoutProvider : PopupWindow, ViewTreeObserver.IOnGlobalLayoutListener, View.IOnApplyWindowInsetsListener { public delegate void GlobalLayoutListener(PopupWindow sender); + public delegate void WindowInsetsListener(PopupWindow sender, WindowInsets insets); - private readonly GlobalLayoutListener _listener; + private readonly GlobalLayoutListener _globalListener; + private readonly WindowInsetsListener _insetsListener; private readonly Activity _activity; - public GlobalLayoutProvider(Activity activity, GlobalLayoutListener listener) : base(activity) + public GlobalLayoutProvider(Activity activity, GlobalLayoutListener globalListener, WindowInsetsListener insetsListener) : base(activity) { - this._activity = activity; - this._listener = listener; + _activity = activity; + _globalListener = globalListener; + _insetsListener = insetsListener; ContentView = new LinearLayout(_activity.BaseContext) { @@ -113,17 +161,31 @@ public void Start(View view) ContentView.ViewTreeObserver.AddOnGlobalLayoutListener(this); } } + + public void StartListenInsets() + { + _activity.Window.DecorView.SetOnApplyWindowInsetsListener(this); + } + public void Stop() { if (IsShowing) { Dismiss(); ContentView.ViewTreeObserver.RemoveOnGlobalLayoutListener(this); + _activity.Window.DecorView.SetOnApplyWindowInsetsListener(null); } } // event hook - public void OnGlobalLayout() => _listener?.Invoke(this); + public void OnGlobalLayout() => _globalListener?.Invoke(this); + + public WindowInsets OnApplyWindowInsets(View v, WindowInsets insets) + { + _insetsListener?.Invoke(this, insets); + // We need to consume insets here since we will handle them in the Window.Android.cs + return insets.ConsumeSystemWindowInsets(); + } } } } diff --git a/src/Uno.UI/Media/PathStreamGeometryContext.cs b/src/Uno.UI/Media/PathStreamGeometryContext.cs index b75909051663..be0df6557902 100644 --- a/src/Uno.UI/Media/PathStreamGeometryContext.cs +++ b/src/Uno.UI/Media/PathStreamGeometryContext.cs @@ -151,12 +151,12 @@ private static Point CenterFromPointsAndRadius(Point point1, Point point2, doubl var x3 = (x1 + x2) / 2; var x = sign - ? x3 + Sqrt(Pow(radius, 2) - Pow((q / 2), 2)) * (y1 - y2) / q - : x3 - Sqrt(Pow(radius, 2) - Pow((q / 2), 2)) * (y1 - y2) / q; + ? x3 + Sqrt(Max(0, Pow(radius, 2) - Pow((q / 2), 2))) * (y1 - y2) / q + : x3 - Sqrt(Max(0, Pow(radius, 2) - Pow((q / 2), 2))) * (y1 - y2) / q; var y = sign - ? y3 + Sqrt(Pow(radius, 2) - Pow((q / 2), 2)) * (x2 - x1) / q - : y3 - Sqrt(Pow(radius, 2) - Pow((q / 2), 2)) * (x2 - x1) / q; + ? y3 + Sqrt(Max(0, Pow(radius, 2) - Pow((q / 2), 2))) * (x2 - x1) / q + : y3 - Sqrt(Max(0, Pow(radius, 2) - Pow((q / 2), 2))) * (x2 - x1) / q; return new Point(x, y); } diff --git a/src/Uno.UI/UI/Xaml/ApplicationActivity.Android.cs b/src/Uno.UI/UI/Xaml/ApplicationActivity.Android.cs index d5f42ebd2d2e..78dd3cc63e37 100644 --- a/src/Uno.UI/UI/Xaml/ApplicationActivity.Android.cs +++ b/src/Uno.UI/UI/Xaml/ApplicationActivity.Android.cs @@ -12,6 +12,7 @@ using Uno.UI; using Windows.UI.ViewManagement; using Windows.UI.Xaml.Media; +using Windows.Devices.Sensors; namespace Windows.UI.Xaml { @@ -27,7 +28,6 @@ public class ApplicationActivity : Controls.NativePage private InputPane _inputPane; - public ApplicationActivity(IntPtr ptr, Android.Runtime.JniHandleOwnership owner) : base(ptr, owner) { Initialize(); @@ -53,6 +53,12 @@ public override void OnAttachedToWindow() // Cannot call this in ctor: see // https://stackoverflow.com/questions/10593022/monodroid-error-when-calling-constructor-of-custom-view-twodscrollview#10603714 RaiseConfigurationChanges(); + Devices.Sensors.SimpleOrientationSensor.GetDefault().OrientationChanged += OnSensorOrientationChanged; + } + + private void OnSensorOrientationChanged(SimpleOrientationSensor sender, SimpleOrientationSensorOrientationChangedEventArgs args) + { + RaiseConfigurationChanges(); } private void OnInputPaneVisibilityChanged(InputPane sender, InputPaneVisibilityEventArgs args) @@ -63,7 +69,7 @@ private void OnInputPaneVisibilityChanged(InputPane sender, InputPaneVisibilityE // using either SoftInput.AdjustResize or SoftInput.AdjustPan. args.EnsuredFocusedElementInView = true; } - } + } protected override void InitializeComponent() { @@ -111,9 +117,24 @@ protected override void OnCreate(Bundle bundle) LayoutProvider = new LayoutProvider(this); LayoutProvider.LayoutChanged += OnLayoutChanged; + LayoutProvider.InsetsChanged += OnInsetsChanged; + + // We need to start listening insets as soon as we can since it is raised only once the DecorView is set + LayoutProvider.StartListenInsets(); + RaiseConfigurationChanges(); } + private void OnInsetsChanged(Thickness insets) + { + if (Xaml.Window.Current != null) + { + //Set insets before raising the size changed event + Xaml.Window.Current.Insets = insets; + Xaml.Window.Current.RaiseNativeSizeChanged(); + } + } + public override void SetContentView(View view) { if (view != null) diff --git a/src/Uno.UI/UI/Xaml/BatchCollectionWrapper.cs b/src/Uno.UI/UI/Xaml/BatchCollectionWrapper.cs index 19df48edbdf4..3e86ca0a0421 100644 --- a/src/Uno.UI/UI/Xaml/BatchCollectionWrapper.cs +++ b/src/Uno.UI/UI/Xaml/BatchCollectionWrapper.cs @@ -1,10 +1,7 @@ using System; -using System.Collections.ObjectModel; using System.Collections.Generic; using System.Collections.Specialized; -using System.Threading.Tasks; using System.Linq; -using Uno.Extensions; using System.Collections; namespace Windows.UI.Xaml.Controls diff --git a/src/Uno.UI/UI/Xaml/Controls/ListViewBase/ListViewBaseSource.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/ListViewBase/ListViewBaseSource.iOS.cs index da432cb7ee2d..ebf4b5f2c116 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ListViewBase/ListViewBaseSource.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ListViewBase/ListViewBaseSource.iOS.cs @@ -599,7 +599,8 @@ private CGSize GetTemplateSize(DataTemplate dataTemplate, NSString elementKind) // applied until view is loaded. Owner.XamlParent.AddSubview(BlockLayout); BlockLayout.AddSubview(container); - size = Owner.NativeLayout.Layouter.MeasureChild(container, new Size(double.MaxValue, double.MaxValue)); + // Measure with PositiveInfinity rather than MaxValue, since some views handle this better. + size = Owner.NativeLayout.Layouter.MeasureChild(container, new Size(double.PositiveInfinity, double.PositiveInfinity)); if ((size.Height > nfloat.MaxValue / 2 || size.Width > nfloat.MaxValue / 2) && this.Log().IsEnabled(LogLevel.Warning) @@ -937,11 +938,11 @@ private Size AdjustAvailableSize(Size availableSize) { if (ScrollOrientation == Orientation.Vertical) { - availableSize.Height = float.MaxValue; + availableSize.Height = double.PositiveInfinity; } else { - availableSize.Width = float.MaxValue; + availableSize.Width = double.PositiveInfinity; } return availableSize; } diff --git a/src/Uno.UI/UI/Xaml/Controls/Picker/Picker.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/Picker/Picker.iOS.cs index 98e139b0c10d..b5ef3f66a871 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Picker/Picker.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Picker/Picker.iOS.cs @@ -63,7 +63,7 @@ public override CGSize SizeThatFits(CGSize size) } public object[] Items { get; private set; } = new object[] { null }; // ensure there's always a null present to allow deselection - + partial void OnItemsSourceChangedPartialNative(object oldItemsSource, object newItemsSource) { if (oldItemsSource is INotifyCollectionChanged oldObservableCollection) @@ -81,7 +81,17 @@ partial void OnItemsSourceChangedPartialNative(object oldItemsSource, object new private void OnItemSourceChanged(object collection, NotifyCollectionChangedEventArgs _) { - Items = (collection as IEnumerable)?.ToObjectArray() ?? new object[0]; + if (SelectedItem == null) + { + Items = new[] { (object)null } + .Concat((collection as IEnumerable)?.ToObjectArray() ?? new object[0]) + .ToObjectArray(); + } + else + { + Items = (collection as IEnumerable)?.ToObjectArray() ?? new object[0]; + } + ReloadAllComponents(); if (!Items.Contains(SelectedItem)) @@ -102,6 +112,13 @@ partial void OnSelectedItemChangedPartial(object oldSelectedItem, object newSele return; } } + else if (newSelectedItem != null && Items[0] == null) + { + // On ItemSelection remove initial null item at Items[0] + Items = Items + .Skip(1) + .ToObjectArray(); + } Select(row, component: 0, animated: true); diff --git a/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Android.cs b/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Android.cs index 53d73661460e..d498990ab1a4 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.Android.cs @@ -19,7 +19,7 @@ namespace Windows.UI.Xaml.Controls { public partial class ScrollContentPresenter : UnoTwoDScrollView, IShadowChildrenProvider, DependencyObject { - private readonly static List _emptyList = new List(0); + private static readonly List _emptyList = new List(0); private ScrollBarVisibility _verticalScrollBarVisibility; public ScrollBarVisibility VerticalScrollBarVisibility diff --git a/src/Uno.UI/UI/Xaml/Controls/TimePicker/TimePickerSelector.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/TimePicker/TimePickerSelector.iOS.cs index fcebcb8427bd..8dd7656ccb62 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TimePicker/TimePickerSelector.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TimePicker/TimePickerSelector.iOS.cs @@ -76,7 +76,7 @@ internal void SaveTime() { if (_picker != null) { - if (_newDate != _initialTime) + if ((_newDate != null) && _newDate != _initialTime) { var time = _newDate.ToTimeSpan(_picker.TimeZone.GetSecondsFromGMT); diff --git a/src/Uno.UI/UI/Xaml/DependencyProperty.cs b/src/Uno.UI/UI/Xaml/DependencyProperty.cs index 2c359abaeacd..31484e79a34f 100644 --- a/src/Uno.UI/UI/Xaml/DependencyProperty.cs +++ b/src/Uno.UI/UI/Xaml/DependencyProperty.cs @@ -64,7 +64,7 @@ private DependencyProperty(string name, Type propertyType, Type ownerType, Prope _metadata.Add(_ownerType, defaultMetadata ?? new PropertyMetadata(null)); - // Improve the performance of the hash code by + // Improve the performance of the hash code by CachedHashCode = _name.GetHashCode() ^ ownerType.GetHashCode(); } @@ -76,8 +76,8 @@ private DependencyProperty(string name, Type propertyType, Type ownerType, Prope /// dependency property, or that cannot be used in a data binding context. Those types /// are forcibly ignored, even if they inherit from . /// - private static bool CanAutoInheritDataContext(Type propertyType) - => typeof(DependencyObject).IsAssignableFrom(propertyType) + private static bool CanAutoInheritDataContext(Type propertyType) + => typeof(DependencyObject).IsAssignableFrom(propertyType) && propertyType != typeof(Style) && !typeof(FrameworkTemplate).IsAssignableFrom(propertyType); @@ -163,10 +163,12 @@ internal int CachedHashCode } /// - /// Specifies a static value that is used by the dependency property system rather than null to indicate that + /// Specifies a static value that is used by the dependency property system rather than null to indicate that /// the property exists, but does not have its value set by the dependency property system. /// - public static readonly object UnsetValue = new object(); + public static readonly object UnsetValue = new UnsetValueClass(); + + private class UnsetValueClass { } /// /// Retrieves the property metadata value for the dependency property as registered to a type. You specify the type you want info from as a type reference. @@ -181,7 +183,7 @@ public PropertyMetadata GetMetadata(Type forType) if ( !IsTypeDependencyObject(forType) - // This check must be removed when Panel.Children will support only + // This check must be removed when Panel.Children will support only // UIElement as its elements. See #103492 && !forType.Is<_View>() ) @@ -260,7 +262,7 @@ internal bool IsTypeNullable get { return _isTypeNullable; } } - internal object GetFallbackDefaultValue() + internal object GetFallbackDefaultValue() => _fallbackDefaultValue != null ? _fallbackDefaultValue : _fallbackDefaultValue = Activator.CreateInstance(Type); /// @@ -444,12 +446,12 @@ private static DependencyProperty[] InternalGetPropertiesForType(Type type) /// Forces the invocation of the cctor of a type and its base types that may contain DependencyProperty registrations. /// /// - /// This is required because of the lazy initialization nature of the classes that do not contain - /// an explicit type ctor, but contain statically initialized fields. DependencyProperty.Register may not be called as a + /// This is required because of the lazy initialization nature of the classes that do not contain + /// an explicit type ctor, but contain statically initialized fields. DependencyProperty.Register may not be called as a /// result, if none of the DependencyProperty fields are accessed prior to the enumeration of the fields via reflection. - /// + /// /// This method avoids requiring controls to include an explicit type constructor to function properly. - /// + /// /// See: http://stackoverflow.com/questions/6729841/why-did-the-beforefieldinit-behavior-change-in-net-4 /// private static void ForceInitializeTypeConstructor(Type type) @@ -495,7 +497,7 @@ private static DependencyProperty[] InternalGetDependencyObjectPropertiesForType ( prop.HasAutoDataContextInherit - // We must include explicitly marked properties for now, until the + // We must include explicitly marked properties for now, until the // metadata generator can provide this information. || propertyOptions.HasValueInheritsDataContext() ) diff --git a/src/Uno.UI/UI/Xaml/FrameworkElementExtensions.cs b/src/Uno.UI/UI/Xaml/FrameworkElementExtensions.cs index 5f3747c323ec..6b29d2519b75 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkElementExtensions.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkElementExtensions.cs @@ -43,7 +43,6 @@ public static T Binding(this T element, string property, string propertyPath, return element; } -#if !NET461 public static T Binding(this T element, string property, string propertyPath, object source, BindingMode mode) where T : DependencyObject { return element.Binding(property, @@ -55,7 +54,6 @@ public static T Binding(this T element, string property, string propertyPath, } ); } -#endif public static T Binding(this T element, string property, BindingBase binding) where T : DependencyObject { diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/CircleEase.Android.cs b/src/Uno.UI/UI/Xaml/Media/Animation/CircleEase.Android.cs new file mode 100644 index 000000000000..c948ee90a931 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Media/Animation/CircleEase.Android.cs @@ -0,0 +1,13 @@ +using System; +using Android.Animation; + +namespace Windows.UI.Xaml.Media.Animation +{ + partial class CircleEase + { + internal override ITimeInterpolator CreateTimeInterpolator() + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/EasingFunctionBase.Android.cs b/src/Uno.UI/UI/Xaml/Media/Animation/EasingFunctionBase.Android.cs index ac07237380ce..b1d41941b3c8 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/EasingFunctionBase.Android.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/EasingFunctionBase.Android.cs @@ -5,8 +5,8 @@ namespace Windows.UI.Xaml.Media.Animation { - public partial class EasingFunctionBase + public abstract partial class EasingFunctionBase { - internal virtual ITimeInterpolator CreateTimeInterpolator() { throw new InvalidOperationException("Don't call base.CreateTimeInterpolator()"); } + internal abstract ITimeInterpolator CreateTimeInterpolator(); } } diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/EasingFunctionHelpers.Android.cs b/src/Uno.UI/UI/Xaml/Media/Animation/EasingFunctionHelpers.Android.cs index 961bebfca23a..c70d3f032efd 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/EasingFunctionHelpers.Android.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/EasingFunctionHelpers.Android.cs @@ -8,7 +8,7 @@ namespace Windows.UI.Xaml.Media.Animation { internal static class EasingFunctionHelpers { - internal static ITimeInterpolator GetPowerTimeInterpolator(int power, EasingMode mode) + internal static ITimeInterpolator GetPowerTimeInterpolator(float power, EasingMode mode) { switch (mode) { diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/ExponentialEase.Android.cs b/src/Uno.UI/UI/Xaml/Media/Animation/ExponentialEase.Android.cs new file mode 100644 index 000000000000..76e9951aff54 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Media/Animation/ExponentialEase.Android.cs @@ -0,0 +1,12 @@ +using Android.Animation; + +namespace Windows.UI.Xaml.Media.Animation +{ + partial class ExponentialEase + { + internal override ITimeInterpolator CreateTimeInterpolator() + { + return EasingFunctionHelpers.GetPowerTimeInterpolator((float)Exponent, EasingMode); + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/ExponentialEase.cs b/src/Uno.UI/UI/Xaml/Media/Animation/ExponentialEase.cs index c0a6fddff7f8..17ff0f58530b 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/ExponentialEase.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/ExponentialEase.cs @@ -4,24 +4,18 @@ namespace Windows.UI.Xaml.Media.Animation { - public partial class ExponentialEase + public partial class ExponentialEase : EasingFunctionBase { - public double Exponent + public double Exponent { - get - { - return (double)this.GetValue(ExponentProperty); - } - set - { - this.SetValue(ExponentProperty, value); - } + get => (double)this.GetValue(ExponentProperty); + set => this.SetValue(ExponentProperty, value); } - public static global::Windows.UI.Xaml.DependencyProperty ExponentProperty { get; } = - Windows.UI.Xaml.DependencyProperty.Register( - "Exponent", typeof(double), - typeof(global::Windows.UI.Xaml.Media.Animation.ExponentialEase), + public static DependencyProperty ExponentProperty { get; } = + DependencyProperty.Register( + "Exponent", typeof(double), + typeof(global::Windows.UI.Xaml.Media.Animation.ExponentialEase), new FrameworkPropertyMetadata( defaultValue: 1.0, propertyChangedCallback: (s, e) => (s as ExponentialEase)?.OnExponentChanged((double)e.OldValue, (double)e.NewValue) @@ -35,7 +29,7 @@ internal virtual void OnExponentChanged(double oldValue, double newValue) partial void OnExponentChangedPartial(double oldValue, double newValue); - public ExponentialEase() + public ExponentialEase() { } diff --git a/src/Uno.UI/UI/Xaml/Window.Android.cs b/src/Uno.UI/UI/Xaml/Window.Android.cs index 8ffa1467aeb7..c426738d4bc4 100644 --- a/src/Uno.UI/UI/Xaml/Window.Android.cs +++ b/src/Uno.UI/UI/Xaml/Window.Android.cs @@ -1,10 +1,11 @@ #if XAMARIN_ANDROID +using System; using Android.App; using Android.Util; using Android.Views; using Uno.UI; -using Uno.UI.Controls; using Windows.Foundation; +using Windows.Graphics.Display; using Windows.UI.Core; using Windows.UI.ViewManagement; using Windows.UI.Xaml.Controls; @@ -26,6 +27,8 @@ public Window() InitializeCommon(); } + internal Thickness Insets { get; set; } + internal int SystemUiVisibility { get; set; } private void InternalSetContent(UIElement value) @@ -77,21 +80,51 @@ internal void RaiseNativeSizeChanged() var display = (ContextHelper.Current as Activity)?.WindowManager?.DefaultDisplay; var fullScreenMetrics = new DisplayMetrics(); - // To get the real size of the screen, we should use GetRealMetrics - // GetMetrics or Resources.DisplayMetrics return the usable metrics, ignoring the bottom rounded space on device like LG G7 ThinQ for example - display?.GetRealMetrics(outMetrics: fullScreenMetrics); + display?.GetMetrics(outMetrics: fullScreenMetrics); var newBounds = ViewHelper.PhysicalToLogicalPixels(new Rect(0, 0, fullScreenMetrics.WidthPixels, fullScreenMetrics.HeightPixels)); - var statusBarHeightExcluded = GetLogicalStatusBarHeightExcluded(); - var navigationBarHeightExcluded = GetLogicalNavigationBarHeightExcluded(); + var statusBarSizeExcluded = GetLogicalStatusBarSizeExcluded(); + var navigationBarSizeExcluded = GetLogicalNavigationBarSizeExcluded(); + + // Actually, we need to check visibility of nav bar and status bar since the insets don't + UpdateInsetsWithVisibilities(); + + var topHeightExcluded = Math.Max(Insets.Top, statusBarSizeExcluded); - var newVisibleBounds = new Rect( - x: newBounds.X, - y: newBounds.Y + statusBarHeightExcluded, - width: newBounds.Width, - height: newBounds.Height - statusBarHeightExcluded - navigationBarHeightExcluded - ); + var orientation = DisplayInformation.GetForCurrentView().CurrentOrientation; + var newVisibleBounds = new Rect(); + + switch (orientation) + { + // StatusBar on top, NavigationBar on right + case DisplayOrientations.Landscape: + newVisibleBounds = new Rect( + x: newBounds.X + Insets.Left, + y: newBounds.Y + topHeightExcluded, + width: newBounds.Width - (Insets.Left + Math.Max(Insets.Right, navigationBarSizeExcluded)), + height: newBounds.Height - topHeightExcluded - Insets.Bottom + ); + break; + // StatusBar on top, NavigationBar on left + case DisplayOrientations.LandscapeFlipped: + newVisibleBounds = new Rect( + x: newBounds.X + Math.Max(Insets.Left, navigationBarSizeExcluded), + y: newBounds.Y + topHeightExcluded, + width: newBounds.Width - (Math.Max(Insets.Left, navigationBarSizeExcluded) + Insets.Right), + height: newBounds.Height - topHeightExcluded - Insets.Bottom + ); + break; + // StatusBar on top, NavigationBar on bottom + default: + newVisibleBounds = new Rect( + x: newBounds.X + Insets.Left, + y: newBounds.Y + topHeightExcluded, + width: newBounds.Width - (Insets.Left + Insets.Right), + height: newBounds.Height - topHeightExcluded - Math.Max(Insets.Bottom, navigationBarSizeExcluded) + ); + break; + } ApplicationView.GetForCurrentView()?.SetVisibleBounds(newVisibleBounds); @@ -107,22 +140,58 @@ internal void RaiseNativeSizeChanged() } } - private double GetLogicalStatusBarHeightExcluded() + internal void UpdateInsetsWithVisibilities() { - var logicalStatusBarHeight = 0d; - var activity = ContextHelper.Current as Activity; - var decorView = activity.Window.DecorView; - var isStatusBarVisible = ((int)decorView.SystemUiVisibility & (int)SystemUiFlags.Fullscreen) == 0; + var newInsets = new Thickness(); + var orientation = DisplayInformation.GetForCurrentView().CurrentOrientation; + + // Navigation bar check (depending of the orientation + switch (orientation) + { + // StatusBar on top, NavigationBar on bottom + case DisplayOrientations.Portrait: + newInsets.Top = IsStatusBarVisible() ? Insets.Top : 0d; + newInsets.Bottom = IsNavigationBarVisible() ? Insets.Bottom : 0d; + newInsets.Left = Insets.Left; + newInsets.Right = Insets.Right; + break; + // StatusBar on top, NavigationBar on right + case DisplayOrientations.Landscape: + newInsets.Top = IsStatusBarVisible() ? Insets.Top : 0d; + newInsets.Right = IsNavigationBarVisible() ? Insets.Right : 0d; + newInsets.Left = Insets.Left; + newInsets.Bottom = Insets.Bottom; + break; + // StatusBar on top, NavigationBar on bottom + case DisplayOrientations.PortraitFlipped: + newInsets.Top = IsStatusBarVisible() ? Insets.Top : 0d; + newInsets.Bottom = IsNavigationBarVisible() ? Insets.Bottom : 0d; + newInsets.Left = Insets.Left; + newInsets.Right = Insets.Right; + break; + // StatusBar on top, NavigationBar on left + case DisplayOrientations.LandscapeFlipped: + newInsets.Top = IsStatusBarVisible() ? Insets.Top : 0d; + newInsets.Left = IsNavigationBarVisible() ? Insets.Left : 0d; + newInsets.Bottom = Insets.Bottom; + newInsets.Right = Insets.Right; + break; + default: + break; + } - var isStatusBarTranslucent = - activity.Window.Attributes.Flags.HasFlag(WindowManagerFlags.TranslucentStatus) - || activity.Window.Attributes.Flags.HasFlag(WindowManagerFlags.LayoutNoLimits); + Insets = newInsets; + } + + private double GetLogicalStatusBarSizeExcluded() + { + var logicalStatusBarHeight = 0d; // The real metrics excluded the StatusBar only if it is plain. // We want to substract it if it is translucent. Otherwise, it will be like we substract it twice. - if (isStatusBarVisible && isStatusBarTranslucent) + if (IsStatusBarVisible() && IsStatusBarTranslucent()) { - int resourceId = Android.Content.Res.Resources.System.GetIdentifier("status_bar_height", "dimen", "android"); + var resourceId = Android.Content.Res.Resources.System.GetIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { logicalStatusBarHeight = ViewHelper.PhysicalToLogicalPixels(Android.Content.Res.Resources.System.GetDimensionPixelSize(resourceId)); @@ -132,17 +201,18 @@ private double GetLogicalStatusBarHeightExcluded() return logicalStatusBarHeight; } - private double GetLogicalNavigationBarHeightExcluded() + private double GetLogicalNavigationBarSizeExcluded() { - var activity = ContextHelper.Current as Activity; - var isStatusBarTranslucent = - activity.Window.Attributes.Flags.HasFlag(WindowManagerFlags.TranslucentNavigation) - || activity.Window.Attributes.Flags.HasFlag(WindowManagerFlags.LayoutNoLimits); + var orientation = DisplayInformation.GetForCurrentView().CurrentOrientation; + + var navigationBarSize = orientation == DisplayOrientations.Landscape || orientation == DisplayOrientations.LandscapeFlipped + ? ApplicationActivity.Instance.LayoutProvider.NavigationBarRect.Width() + : ApplicationActivity.Instance.LayoutProvider.NavigationBarRect.Height(); // The real metrics excluded the NavigationBar only if it is plain. // We want to substract it if it is translucent. Otherwise, it will be like we substract it twice. - return isStatusBarTranslucent - ? ViewHelper.PhysicalToLogicalPixels(ApplicationActivity.Instance.LayoutProvider.NavigationBarRect.Height()) + return IsNavigationBarVisible() && IsNavigationBarTranslucent() + ? ViewHelper.PhysicalToLogicalPixels(navigationBarSize) : 0; } @@ -161,6 +231,58 @@ internal void DisplayFullscreen(UIElement element) _fullWindow.Child = element; } } + + #region StatusBar properties + private bool IsStatusBarVisible() + { + var decorView = (ContextHelper.Current as Activity)?.Window?.DecorView; + + if (decorView == null) + { + throw new global::System.Exception("Cannot check NavigationBar visibility property. DecorView is not defined yet."); + } + + return ((int)decorView.SystemUiVisibility & (int)SystemUiFlags.Fullscreen) == 0; + } + + public bool IsStatusBarTranslucent() + { + if (!(ContextHelper.Current is Activity activity)) + { + throw new Exception("Cannot check NavigationBar translucent property. Activity is not defined yet."); + } + + return activity.Window.Attributes.Flags.HasFlag(WindowManagerFlags.TranslucentStatus) + || activity.Window.Attributes.Flags.HasFlag(WindowManagerFlags.LayoutNoLimits); ; + } + #endregion + + #region NavigationBar properties + private bool IsNavigationBarVisible() + { + var decorView = (ContextHelper.Current as Activity)?.Window?.DecorView; + if (decorView == null) + { + throw new global::System.Exception("Cannot check NavigationBar visibility property. DecorView is not defined yet."); + } + + var visibility = decorView.SystemUiVisibility; + return visibility.HasFlag(SystemUiFlags.HideNavigation) + || visibility.HasFlag(SystemUiFlags.LayoutHideNavigation); + } + + private bool IsNavigationBarTranslucent() + { + if (!(ContextHelper.Current is Activity activity)) + { + throw new Exception("Cannot check NavigationBar translucent property. Activity is not defined yet."); + } + + var flags = activity.Window.Attributes.Flags; + return flags.HasFlag(WindowManagerFlags.TranslucentNavigation) + || flags.HasFlag(WindowManagerFlags.LayoutNoLimits); + } + #endregion } } #endif diff --git a/src/Uno.UWP/Collections/MaterializableList.cs b/src/Uno.UWP/Collections/MaterializableList.cs index 176cb08cf257..5f109731a3c9 100644 --- a/src/Uno.UWP/Collections/MaterializableList.cs +++ b/src/Uno.UWP/Collections/MaterializableList.cs @@ -12,9 +12,29 @@ namespace Uno.Collections /// THIS IS NOT THREAD-SAFE. It is designed to be used on /// the UI thread. /// - public class MaterializableList : IList + public class MaterializableList : IList, IReadOnlyList { - private readonly List _listImplementation = new List(); + private readonly List _innerList; + + public MaterializableList() + { + _innerList = new List(); + } + + public MaterializableList(int capacity) + { + _innerList = new List(capacity); + } + + public MaterializableList(List collection) + { + _innerList = collection; + } + + public MaterializableList(IEnumerable collection) + { + _innerList = new List(collection); + } public IEnumerator GetEnumerator() => Materialized.GetEnumerator(); @@ -22,56 +42,56 @@ public class MaterializableList : IList public void Add(T item) { - _listImplementation.Add(item); + _innerList.Add(item); _materialized = null; } public void Clear() { - _listImplementation.Clear(); + _innerList.Clear(); _materialized = null; } - public bool Contains(T item) => _listImplementation.Contains(item); + public bool Contains(T item) => _innerList.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => _listImplementation.CopyTo(array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => _innerList.CopyTo(array, arrayIndex); public bool Remove(T item) { _materialized = null; - return _listImplementation.Remove(item); + return _innerList.Remove(item); } - public int Count => _listImplementation.Count; + public int Count => _innerList.Count; public bool IsReadOnly => false; - public int IndexOf(T item) => _listImplementation.IndexOf(item); + public int IndexOf(T item) => _innerList.IndexOf(item); public void Insert(int index, T item) { - _listImplementation.Insert(index, item); + _innerList.Insert(index, item); _materialized = null; } public void RemoveAt(int index) { - _listImplementation.RemoveAt(index); + _innerList.RemoveAt(index); _materialized = null; } public T this[int index] { - get => _listImplementation[index]; + get => _innerList[index]; set { - _listImplementation[index] = value; + _innerList[index] = value; _materialized = null; } } private List _materialized; - public List Materialized => _materialized ?? (_materialized = _listImplementation.ToList()); + public List Materialized => _materialized ?? (_materialized = _innerList.ToList()); } }