diff --git a/build/nuget/Uno.WinUI.nuspec b/build/nuget/Uno.WinUI.nuspec index c3e5bb87e603..66c1e278d755 100644 --- a/build/nuget/Uno.WinUI.nuspec +++ b/build/nuget/Uno.WinUI.nuspec @@ -608,6 +608,7 @@ + diff --git a/doc/articles/features/working-with-xaml-hot-reload.md b/doc/articles/features/working-with-xaml-hot-reload.md index c32f95322b95..24422ab5f5b7 100644 --- a/doc/articles/features/working-with-xaml-hot-reload.md +++ b/doc/articles/features/working-with-xaml-hot-reload.md @@ -4,7 +4,11 @@ uid: Uno.Features.HotReload # Hot Reload -The Uno Platform Hot Reload feature provides a way to modify the XAML and C# of your running application, in order to iterate faster on UI or code changes. This makes the inner developer loop faster. +The Uno Platform **Hot Reload** feature provides a way to modify the XAML and C# of your running application, in order to iterate faster on UI or code changes. This makes the inner developer loop faster. + +**Hot Reload** is part of the **Uno Platform Studio**, a suite of tools designed to streamline your cross-platform app development and boost productivity. + +[➜ Learn more about Uno Platform Studio](xref:Uno.Platform.Studio.Overview) ## Features @@ -296,7 +300,7 @@ Mobile targets are currently using a limited version of XAML Hot Reload and do n Hot Reload displays a visual indicator to help you further monitor changes while developing. It displays new information every time Hot Reload is triggered. The indicator is enabled by default within the `UseStudio()` method which is located in the root `App.xaml.cs` file. This displays an overlay which hosts the visual indicator. If you wish to disable it, you simply have to provide the following boolean: `EnableHotReload(disableIndicator: true)`, removing the overlay from the view.

- A hot reload visual indicator + A hot reload visual indicator

> [!TIP] @@ -305,7 +309,7 @@ Hot Reload displays a visual indicator to help you further monitor changes while The indicator displays the current connection status. Clicking on it will open a flyout containing all events or changes that were applied by Hot Reload. These events display more details about Hot Reload changes, such as its status and impacted files.

- A window showing events from Hot Reload + A window showing events from Hot Reload

### Statuses diff --git a/doc/articles/guides/creating-custom-controls.md b/doc/articles/guides/creating-custom-controls.md index 9e4a9e509bbf..8679b09baac7 100644 --- a/doc/articles/guides/creating-custom-controls.md +++ b/doc/articles/guides/creating-custom-controls.md @@ -17,9 +17,9 @@ If you're not familiar with concepts such as **dependency properties**, **contro ## Ensuring Cross-Platform Compatibility -To ensure your custom control functions properly across all platforms, make sure that the `Themes/generic.xaml` file is included in your Uno project and correctly referenced for each platform. On non-Windows platforms, this file might not be automatically included, and you may need to adjust your project settings or add custom build steps to ensure it is properly referenced. +To ensure your custom control functions properly across all platforms, make sure that the `Themes/Generic.xaml` file is included in your Uno project and correctly referenced for each platform. On non-Windows platforms, this file might not be automatically included, and you may need to adjust your project settings or add custom build steps to ensure it is properly referenced. -In WinUI apps, `Themes/generic.xaml` is the standard location for default styles. It should be supported across all platforms and automatically referenced when Uno searches for implicit or default styles in any `TemplatedControl` defined in `generic.xaml`. Additionally, it's common to use a `MergedDictionary` to reference resources from other directories within `generic.xaml`. Currently, styles defined in `Themes/generic.xaml` are not found automatically across platforms in Uno. +In WinUI apps, `Themes/Generic.xaml` is the standard location for default styles. It should be supported across all platforms and automatically referenced when Uno searches for implicit or default styles in any `TemplatedControl` defined in `Generic.xaml`. Additionally, it's common to use a `MergedDictionary` to reference resources from other directories within `Generic.xaml`. Currently, styles defined in `Themes/Generic.xaml` are not found automatically across platforms in Uno. To resolve this, you can define styles in `App.xaml` and use a `MergedDictionary` to pull in the resources, as shown in the following example: diff --git a/doc/articles/migrating-from-previous-releases.md b/doc/articles/migrating-from-previous-releases.md index 83be4df90077..e6c2cd09639e 100644 --- a/doc/articles/migrating-from-previous-releases.md +++ b/doc/articles/migrating-from-previous-releases.md @@ -4,7 +4,11 @@ uid: Uno.Development.MigratingFromPreviousReleases # Migrating from Previous Releases of Uno Platform -This article details the migration steps required to migrate from one version to the next when breaking changes are being introduced. +## Uno Platform 5.6 + +### Lazy loading + +To align the behavior with WinUI, lazy loading using `x:Load="False"` and `x:DeferLoadStrategy="lazy"` is no longer affected with changes to the visibility of the lazily-loaded element. Previously, binding the `Visibility` property of the lazily-loaded element and then updating the source of the binding to make the element visible would cause the element to materialize (i.e. load). This is no longer the case. To load the element, add an `x:Name` to the element and call `FindName` with th given name. ## Uno Platform 5.5 diff --git a/doc/articles/studio/Assets/Introducing-Uno-Platform-Studio.png b/doc/articles/studio/Assets/Introducing-Uno-Platform-Studio.png new file mode 100644 index 000000000000..b61141163400 Binary files /dev/null and b/doc/articles/studio/Assets/Introducing-Uno-Platform-Studio.png differ diff --git a/doc/articles/studio/Hot Design/Assets/canvas-select-multiple-items.png b/doc/articles/studio/Hot Design/Assets/canvas-select-multiple-items.png new file mode 100644 index 000000000000..188a22a3c176 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/canvas-select-multiple-items.png differ diff --git a/doc/articles/studio/Hot Design/Assets/canvas-select-single-item.png b/doc/articles/studio/Hot Design/Assets/canvas-select-single-item.png new file mode 100644 index 000000000000..20b2d535aaa1 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/canvas-select-single-item.png differ diff --git a/doc/articles/studio/Hot Design/Assets/counter-app.png b/doc/articles/studio/Hot Design/Assets/counter-app.png new file mode 100644 index 000000000000..6444f20df295 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/counter-app.png differ diff --git a/doc/articles/studio/Hot Design/Assets/delete-elements.png b/doc/articles/studio/Hot Design/Assets/delete-elements.png new file mode 100644 index 000000000000..29c23330e8b8 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/delete-elements.png differ diff --git a/doc/articles/studio/Hot Design/Assets/enter-hot-design-mode.png b/doc/articles/studio/Hot Design/Assets/enter-hot-design-mode.png new file mode 100644 index 000000000000..909369557c2e Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/enter-hot-design-mode.png differ diff --git a/doc/articles/studio/Hot Design/Assets/form-factor-and-zoom-flyout.png b/doc/articles/studio/Hot Design/Assets/form-factor-and-zoom-flyout.png new file mode 100644 index 000000000000..0762f6accdcb Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/form-factor-and-zoom-flyout.png differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/1-remove-stackpanel.gif b/doc/articles/studio/Hot Design/Assets/gifs/1-remove-stackpanel.gif new file mode 100644 index 000000000000..e2f117594291 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/1-remove-stackpanel.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/2-add-stackpanel.gif b/doc/articles/studio/Hot Design/Assets/gifs/2-add-stackpanel.gif new file mode 100644 index 000000000000..4134c9491361 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/2-add-stackpanel.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/2.1-add-stackpanel.gif b/doc/articles/studio/Hot Design/Assets/gifs/2.1-add-stackpanel.gif new file mode 100644 index 000000000000..764c9764583a Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/2.1-add-stackpanel.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/2.2-add-stackpanel.gif b/doc/articles/studio/Hot Design/Assets/gifs/2.2-add-stackpanel.gif new file mode 100644 index 000000000000..739f15c6a7d3 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/2.2-add-stackpanel.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/3-stackpanel-alignment.gif b/doc/articles/studio/Hot Design/Assets/gifs/3-stackpanel-alignment.gif new file mode 100644 index 000000000000..da45dc79b8f2 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/3-stackpanel-alignment.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/3.1-textbox-clear-text.gif b/doc/articles/studio/Hot Design/Assets/gifs/3.1-textbox-clear-text.gif new file mode 100644 index 000000000000..234945c2242e Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/3.1-textbox-clear-text.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/4-add-image-source.gif b/doc/articles/studio/Hot Design/Assets/gifs/4-add-image-source.gif new file mode 100644 index 000000000000..81742285cb69 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/4-add-image-source.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/4.1-search-properties.gif b/doc/articles/studio/Hot Design/Assets/gifs/4.1-search-properties.gif new file mode 100644 index 000000000000..6c2129eda13b Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/4.1-search-properties.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/4.2-resize-property-view.gif b/doc/articles/studio/Hot Design/Assets/gifs/4.2-resize-property-view.gif new file mode 100644 index 000000000000..c70acb5dc358 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/4.2-resize-property-view.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/5-multi-selection.gif b/doc/articles/studio/Hot Design/Assets/gifs/5-multi-selection.gif new file mode 100644 index 000000000000..80199764ae12 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/5-multi-selection.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/6-button-style.gif b/doc/articles/studio/Hot Design/Assets/gifs/6-button-style.gif new file mode 100644 index 000000000000..29ed17de0bc4 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/6-button-style.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/7-textbox-binding.gif b/doc/articles/studio/Hot Design/Assets/gifs/7-textbox-binding.gif new file mode 100644 index 000000000000..cca7976cbdf3 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/7-textbox-binding.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/8-button-command.gif b/doc/articles/studio/Hot Design/Assets/gifs/8-button-command.gif new file mode 100644 index 000000000000..e051786efbf8 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/8-button-command.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/9-wrapup.gif b/doc/articles/studio/Hot Design/Assets/gifs/9-wrapup.gif new file mode 100644 index 000000000000..afdcea8fa41a Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/9-wrapup.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/gifs/toolbox-drag.gif b/doc/articles/studio/Hot Design/Assets/gifs/toolbox-drag.gif new file mode 100644 index 000000000000..2803a4543d45 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/gifs/toolbox-drag.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/hot-design-dark-theme.png b/doc/articles/studio/Hot Design/Assets/hot-design-dark-theme.png new file mode 100644 index 000000000000..efa6f9fd97ed Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/hot-design-dark-theme.png differ diff --git a/doc/articles/studio/Hot Design/Assets/hot-design-light-theme.png b/doc/articles/studio/Hot Design/Assets/hot-design-light-theme.png new file mode 100644 index 000000000000..6679f1e99f15 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/hot-design-light-theme.png differ diff --git a/doc/articles/studio/Hot Design/Assets/hot-design-views-highlighted.png b/doc/articles/studio/Hot Design/Assets/hot-design-views-highlighted.png new file mode 100644 index 000000000000..921a515bd807 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/hot-design-views-highlighted.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-binding.png b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-binding.png new file mode 100644 index 000000000000..afb6a30120ca Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-binding.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-mixed-responsive.png b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-mixed-responsive.png new file mode 100644 index 000000000000..db39c2a4ddba Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-mixed-responsive.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-none.png b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-none.png new file mode 100644 index 000000000000..97bec1113789 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-none.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-resource.png b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-resource.png new file mode 100644 index 000000000000..6dcdde6bc322 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-resource.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-xaml.png b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-xaml.png new file mode 100644 index 000000000000..302740ceb326 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-advcd-button-xaml.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-alignment-property.png b/doc/articles/studio/Hot Design/Assets/properties-view-alignment-property.png new file mode 100644 index 000000000000..69de2dddd2ac Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-alignment-property.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-autosuggest-property.png b/doc/articles/studio/Hot Design/Assets/properties-view-autosuggest-property.png new file mode 100644 index 000000000000..ea7261014196 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-autosuggest-property.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-button-flyout.png b/doc/articles/studio/Hot Design/Assets/properties-view-button-flyout.png new file mode 100644 index 000000000000..ec485fc674a9 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-button-flyout.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-text-empty.png b/doc/articles/studio/Hot Design/Assets/properties-view-text-empty.png new file mode 100644 index 000000000000..ada320b6e725 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-text-empty.png differ diff --git a/doc/articles/studio/Hot Design/Assets/properties-view-text-property.png b/doc/articles/studio/Hot Design/Assets/properties-view-text-property.png new file mode 100644 index 000000000000..3ddaee8c0e0b Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/properties-view-text-property.png differ diff --git a/doc/articles/studio/Hot Design/Assets/studio-toolbar.png b/doc/articles/studio/Hot Design/Assets/studio-toolbar.png new file mode 100644 index 000000000000..b4259f2bcceb Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/studio-toolbar.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-connection-status.png b/doc/articles/studio/Hot Design/Assets/toolbar-connection-status.png new file mode 100644 index 000000000000..54de215ba926 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-connection-status.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-dark-theme.png b/doc/articles/studio/Hot Design/Assets/toolbar-dark-theme.png new file mode 100644 index 000000000000..ad1fca64f016 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-dark-theme.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-form-factor.png b/doc/articles/studio/Hot Design/Assets/toolbar-form-factor.png new file mode 100644 index 000000000000..11950f31d044 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-form-factor.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-hot-design-enter-icon.png b/doc/articles/studio/Hot Design/Assets/toolbar-hot-design-enter-icon.png new file mode 100644 index 000000000000..9f6dd0ab5229 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-hot-design-enter-icon.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-hot-design-icon.png b/doc/articles/studio/Hot Design/Assets/toolbar-hot-design-icon.png new file mode 100644 index 000000000000..9eae7e272307 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-hot-design-icon.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-light-theme.png b/doc/articles/studio/Hot Design/Assets/toolbar-light-theme.png new file mode 100644 index 000000000000..a530d3d9ba81 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-light-theme.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-more-options.png b/doc/articles/studio/Hot Design/Assets/toolbar-more-options.png new file mode 100644 index 000000000000..7f05cc2488ef Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-more-options.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-pause.png b/doc/articles/studio/Hot Design/Assets/toolbar-pause.png new file mode 100644 index 000000000000..5dc5eaad62ec Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-pause.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-play.png b/doc/articles/studio/Hot Design/Assets/toolbar-play.png new file mode 100644 index 000000000000..1bf5cd3e20cc Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-play.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-redo.png b/doc/articles/studio/Hot Design/Assets/toolbar-redo.png new file mode 100644 index 000000000000..f66a42bed9ad Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-redo.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbar-undo.png b/doc/articles/studio/Hot Design/Assets/toolbar-undo.png new file mode 100644 index 000000000000..fdd272628bf1 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbar-undo.png differ diff --git a/doc/articles/studio/Hot Design/Assets/toolbox-drag.gif b/doc/articles/studio/Hot Design/Assets/toolbox-drag.gif new file mode 100644 index 000000000000..f4348c44c6c0 Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/toolbox-drag.gif differ diff --git a/doc/articles/studio/Hot Design/Assets/vs-hot-reload-icon.png b/doc/articles/studio/Hot Design/Assets/vs-hot-reload-icon.png new file mode 100644 index 000000000000..9696dbd4e3ea Binary files /dev/null and b/doc/articles/studio/Hot Design/Assets/vs-hot-reload-icon.png differ diff --git a/doc/articles/studio/Hot Design/hot-design-getstarted-counter-tutorial.md b/doc/articles/studio/Hot Design/hot-design-getstarted-counter-tutorial.md new file mode 100644 index 000000000000..1c76f3d17f6b --- /dev/null +++ b/doc/articles/studio/Hot Design/hot-design-getstarted-counter-tutorial.md @@ -0,0 +1,283 @@ +--- +uid: Uno.HotDesign.GetStarted.CounterTutorial +--- + +# Create a Counter App with Hot Design™ + +This tutorial will guide you through using Hot Design to create a simple counter application. The application will include: + +- An `Image` at the top. +- A `TextBox` below the image, where you can set the step size for incrementing the counter. +- A `TextBlock` below the `TextBox`, displaying the current counter value. +- A `Button` at the bottom labeled **"Increment Counter by Step Size"**, which updates the counter value based on the step size entered. + +

+ Counter App Preview +

+ +> [!NOTE] +> This tutorial is based on the [XAML + MVUX variant](xref:Uno.Workshop.Counter.XAML.MVUX) of the Counter app tutorial. It demonstrates how to create a simple cross-platform app using Uno Platform. Explore other tutorial variants [here](xref:Uno.Workshop.Counter). +> +> [!IMPORTANT] +> At the current stage of the **Hot Design™** beta, **only the Desktop platform is supported**. Other platforms are undergoing stabilization for Hot Design support and will be available in future updates. +> +> For now, you can use the **Desktop** platform to create your UI with the runtime visual designer. Once you’re satisfied with your design, you can test the app on other platforms by launching it as you would normally. + +## Set Up Your Environment for Hot Design + +> [!IMPORTANT] +> If you're new to developing with Uno Platform, start by [setting up your environment](xref:Uno.GetStarted). Once your environment is ready, proceed directly to the next section, **Creating the Counter Application**. + +For existing applications, take this opportunity to update to the [latest **Uno.Sdk** version](https://www.nuget.org/packages/Uno.Sdk). Refer to our [migration guide](xref:Uno.Development.MigratingFromPreviousReleases) for upgrade steps. + +To start using **Hot Design**, ensure you are signed in with your Uno Platform account. Follow [these instructions](xref:Uno.GetStarted.Licensing) to register and sign in. + +Once you're using the **latest stable 5.5 Uno.Sdk version or higher**, you can access **Hot Design** by clicking the **flame** icon in the diagnostics overlay that appears over your app. + +

+ Hot Design flame icon to enter in design mode +

+ +## Creating the Counter Application + +### [Visual Studio](#tab/vs) + +- Launch **Visual Studio** and click on **Create new project** on the Start Window. Alternatively, if you're already in Visual Studio, click **New, Project** from the **File** menu. +- Type "Uno Platform" in the search box +- Click **Uno Platform App**, then **Next** +- Name the project `Counter` and click **Create** +At this point you'll enter the **Uno Platform Template Wizard**, giving you options to customize the generated application. For this tutorial, we're only going to configure the presentation framework. +- Select **Blank** in **Presets** selection +- Select the **Presentation** tab and choose **MVUX** +- Click **Create** to complete the wizard +The template will create a solution with a single cross-platform project, named `Counter`, ready to run. + +### [Rider](#tab/rider) + +- Launch **Rider** and click on **New Solution** on the Start Window +- From the left menu, under the **Uno Platform** section, select **Uno Platform App** +At this point, you'll see options for creating a new Uno app, allowing you to customize the generated application. For this tutorial, we will only configure the presentation framework. +- Name the project `Counter` +- Select **Blank** in **Presets** selection +- Select the **Presentation** tab and choose **MVUX** +- Click **Create** to complete the creation +The template will create a solution with a single cross-platform project, named `Counter`, ready to run. + +### [VS Code](#tab/vscode) + +- Launch The Live Wizard by clicking [here](https://new.platform.uno/) +- Name the project `Counter` and click **Start** +- Select **Blank** in **Presets** selection +- Select the **Presentation** tab and choose **MVUX** +- Click **Create** to complete the wizard +- Copy the `dotnet new` command and run it from a terminal +This will create a new folder called **Counter** containing the new application. + +### [Command Line](#tab/cli) + +> [!NOTE] +> If you don't have the Uno Platform dotnet new templates installed, follow [these instructions](https://aka.platform.uno/dotnet-new-templates). + +From the command line, run the following command: + +```dotnetcli +dotnet new unoapp -preset blank -presentation mvux -o Counter +``` + +This will create a new folder called **Counter** containing the new application. + +--- + +## Assets + +First, we need to add the image file to the application. Download this [SVG image](https://aka.platform.uno/counter-tutorial-svg-uno-logo) and add it to the **Assets** folder. Once added, rebuild the application to ensure the image is included in the application package. + +> [!NOTE] +> If you're working in Visual Studio, select the newly added **logo.svg** file in the **Solution Explorer**, open the **Properties** window, and ensure the **Build Action** property is set to **`UnoImage`**. For other IDEs, no further action is required as the template automatically sets the **Build Action** to **`UnoImage`** for all files in the **Assets** folder. +> +> For more information on **Uno.Resizetizer** functionalities, visit [Get Started with Uno.Resizetizer](xref:Uno.Resizetizer.GettingStarted). + +## Run the app + +Before you run the application, switch the target platform to **Desktop** (net8.0-desktop) to enable Hot Design during debugging. For more information on how to switch the target platform, visit the documentation page for your IDE: + +- [Visual Studio](xref:Uno.GettingStarted.CreateAnApp.VS2022#debug-the-app) +- [VS Code](xref:Uno.GettingStarted.CreateAnApp.VSCode#debug-the-app) +- [Rider](xref:Uno.GettingStarted.CreateAnApp.Rider#debug-the-app) + +> [!IMPORTANT] +> At the current stage of the **Hot Design™** beta, **only the Desktop platform is supported**. Other platforms are undergoing stabilization for Hot Design support and will be available in future updates. +> +> For now, you can use the **Desktop** platform to create your UI with the runtime visual designer. Once you’re satisfied with your design, you can test the app on other platforms by launching it as you would normally. +> +> [!IMPORTANT] +> If you're using Visual Studio, you can choose to start it with or without debugging. +> If you're using VS Code or Rider, start the app **without the debugger**. + +Now, let's run the app. + +## Sign in with your Uno Platform Account + +If is not already previously done, to start using **Hot Design**, ensure you are signed in with your Uno Platform account. Follow [these instructions](xref:Uno.GetStarted.Licensing) to register and sign in. + +## Enter Hot Design Mode + +To start editing the UI, enter **Hot Design** by clicking the **flame** icon in the diagnostics overlay that appears over your app (default position is in the top-left corner of the application window). + +> [!NOTE] +> If you don't see the **Hot Design** flame icon, ensure that you are [signed in with your Uno Platform Account](xref:Uno.GetStarted.Licensing), enrolled in the current beta, and using the [latest stable 5.5 Uno.Sdk version or higher](https://www.nuget.org/packages/Uno.Sdk). + +

+ Hot Design flame icon to enter design mode +

+ +## Change the Layout + +We are all set to start adding controls to create our Counter app. Follow the steps below: + +> [!NOTE] +> When making changes via **Hot Design**, the XAML will automatically update to reflect your edits. Similarly, any changes made directly to the XAML will be reflected in the design. + +### Remove Existing Elements + +1. Remove the existing `StackPanel`. In the **Elements** window, select the `StackPanel`, right-click, and choose **Delete StackPanel**. + + ![Removing the StackPanel](Assets/gifs/1-remove-stackpanel.gif) + +### Add a `StackPanel` + +1. Let's add the container to hold our elements by adding a `StackPanel`. In the **Toolbox** window, search for "StackPanel". Once it appears in the search results, drag it onto the **Canvas**. + + ![StackPanel](Assets/gifs/2-add-stackpanel.gif) + + Alternatively, you can drag the element from the **Toolbox** and drop it onto the **Elements** window. + + ![StackPanel](Assets/gifs/2.1-add-stackpanel.gif) + + Another way would be to select the **existing element** in the **Elements** window where you want to add a new item, then double-click the desired item in the **Toolbox** window to add it as a child of the target. + + ![StackPanel](Assets/gifs/2.2-add-stackpanel.gif) + +1. Now, let's edit a property of the `StackPanel` to align it vertically and horizontally to the center. Select the `StackPanel` from the **Elements** window or the **Canvas**. In the **Properties** window, on the right side of the app, find the `VerticalAlignment` property and set it to **Center**, then do the same for `HorizontalAlignment`. + + ![StackPanel Alignment](Assets/gifs/3-stackpanel-alignment.gif) + +### Add an `Image` element + +1. Next, add an `Image` element to the `StackPanel`. In the **Toolbox** window, search for "Image". Once it appears in the results, drag it onto the `StackPanel` using either the **Canvas** or the **Elements** window. + + > [!NOTE] + > The image will appear with zero height until a source is set. + +1. Now that the `Image` element is added, let's set the source for our `Image` element. In the **Properties** window, locate the `Source` property. Start typing the name of the image we previously added, and the results should appear. Select **Assets/logo.png**. + + ![Image Source](Assets/gifs/4-add-image-source.gif) + +1. Now, let's edit some properties to enhance its appearance. In the **Properties** window, use the search button to find properties. Search for "Width" and set its value to **150**. Do the same for `Height`. Our `Image` element is now complete! + + ![Image](Assets/gifs/4.1-search-properties.gif) + +### Add a `TextBox` element + +1. The next step is to add a `TextBox` that will hold the increasing step value for our Counter app. In the **Toolbox** window, search for "TextBox." Once it appears in the results, drag it onto the `StackPanel`, making sure to place it under the `Image` element. +1. Now, let's set the `TextBox` properties. In the **Properties** window, set the `PlaceholderText` to "Step Size" and set the `TextAlignment` to **Center**. Reset the `Text` property by clicking the **Advanced** button to open the **Advanced Property** flyout, followed by clicking the **Reset** button. + + ![Reset Text](Assets/gifs/3.1-textbox-clear-text.gif) + +### Add a `TextBlock` element + +1. The next element to add is the `TextBlock`, which will display the current value of our Counter app. In the **Toolbox** window, search for "TextBlock." Once it appears in the results, drag it onto the `StackPanel`, ensuring it is placed under the `TextBox`. +1. Let's edit the `TextBlock` properties. In the **Properties** window, set the `Text` to "Counter: 1"; and, set the `TextAlignment` to **Center**. + +### Add a `Button` element + +1. The final element is the `Button` that will increment the **Count** value. From the **Toolbox** window, search for "Button" and once the result appears, drag it onto the `StackPanel`, making sure it is added under the `TextBlock` element. +1. Set the `Button` properties. In the **Properties** window, set the `Content` to "Increment Counter by Step Size". + +> [!NOTE] +> If there's insufficient room to edit the `Content` property you can resize the **Properties** window by dragging the left edge of the **Properties** window to the left. + +![Resize Properties Window](Assets/gifs/4.2-resize-property-view.gif) + +### Multi-selection + +Hot Design allows you to select multiple elements and edit common properties simultaneously. Let's try it: + +1. Hold the **Ctrl** key on your keyboard and click on the `Image`, the `TextBox` and the `TextBlock` (the `Button` should still be selected from the previous step). +2. In the **Properties** window, set `HorizontalAlignment` to **Center** and `Margin` to **12**. + + ![Multi Selection](Assets/gifs/5-multi-selection.gif) + +> [!NOTE] +> You can also use multi-selection from the **Elements** window by holding the **Ctrl** key while clicking on each node. + +### Style Picker + +Hot Design allows you to apply existing styles to your elements for a polished appearance. Let's change the style of our `Button`: + +1. Select the `Button`, either from the **Elements** window or the **Canvas**. +2. At the top of the **Properties** window, locate the Style Picker. +3. Choose **ButtonRevealStyle** to apply it. + + ![Button Style](Assets/gifs/6-button-style.gif) + +## MainModel and Data Binding + +As part of creating the application, we selected MVUX as the presentation framework. This added a reference to [**MVUX**](https://aka.platform.uno/mvux) which is responsible for managing our Models and generating the necessary bindings. +Without closing the application, return to your IDE and add a new class named `MainModel` and paste the following code to the newly created class: + +```csharp +namespace Counter; + +internal partial record Countable(int Count, int Step) +{ + public Countable Increment() => this with + { + Count = Count + Step + }; +} + +internal partial record MainModel +{ + public IState Countable => State.Value(this, () => new Countable(0, 1)); + public ValueTask IncrementCounter() + => Countable.UpdateAsync(c => c?.Increment()); +} +``` + +As the application uses MVUX, the `MainModel` class is used to generate a bindable ViewModel, `MainViewModel`. Modify `MainPage.xaml.cs` to make an instance of `MainViewModel` available to be data bound and connected to the UI. Add `DataContext = new MainViewModel();` to `MainPage.xaml.cs` right after `InitializeComponent();`. + +After making these changes in the IDE, save all files and return to Hot Design. + +> [!NOTE] +> VS Code and Rider will automatically trigger **Hot Reload** when the files are saved. +> +> In Visual Studio, you can manually trigger **Hot Reload** by clicking the Hot Reload button ![TextBox](Assets/vs-hot-reload-icon.png) on the **Visual Studio top toolbar**. + +### Set Binding + +#### TextBox + +Now, we need to bind the `TextBox`'s `Text` to the `Countable.Step` property of our ViewModel. +In the **Properties** window, locate the `Text` property. Click the **Advanced** button to the right of the `TextBox` to open the **Advanced Property** flyout. Select **Binding**. In the **Path** dropdown, locate the `Countable` property of our ViewModel, click the arrow to expand its properties, and select `Step`. Finally, from the **Mode** dropdown, select **TwoWay**. + +![TextBox](Assets/gifs/7-textbox-binding.gif) + +#### TextBlock + +For the `TextBlock`, bind the `Text` property to `Countable.Count`, just as we did with the `TextBox`. For this step it is not necessary to set the `Mode`. + +#### Button + +Finally, let's bind the **Command** to the `IncrementCounter` task of our ViewModel. + +![Button Command](Assets/gifs/8-button-command.gif) + +## Wrap Up + +At this point, you should have a working counter application. Click the **Play** button in the **Toolbar**, adjust the step size, and click the button to see the application in action. + +> [!NOTE] +> The **Play** button lets you interact with the app directly within **Hot Design**, without needing to leave the editor. Once you're done interacting with the application, you can click the **Pause** button to return to designing your application. If you wish to leave Hot Design and return to the running application, you can click the **Flame** button in the **Toolbar**. + +![WrapUp](Assets/gifs/9-wrapup.gif) diff --git a/doc/articles/studio/Hot Design/hot-design-getstarted-guide.md b/doc/articles/studio/Hot Design/hot-design-getstarted-guide.md new file mode 100644 index 000000000000..f0ff914599ef --- /dev/null +++ b/doc/articles/studio/Hot Design/hot-design-getstarted-guide.md @@ -0,0 +1,186 @@ +--- +uid: Uno.HotDesign.GetStarted.Guide +--- + +# Getting Started with Hot Design™ + +Hot Design™ is the next-generation runtime Visual Designer for cross-platform .NET applications, transforming your live, running app into a real-time Designer. + +This guide provides the steps to set up Hot Design and introduces its key features and visual design capabilities. + +Use this guide to set up Hot Design and start creating and refining user interfaces efficiently and intuitively. + +## Set Up Your Environment for Hot Design + +> [!IMPORTANT] +> If you're new to developing with Uno Platform, make sure to set up your environment by [following our getting started guide](xref:Uno.GetStarted). + +To start using **Hot Design**, ensure you are signed in with your Uno Platform account. Follow [these instructions](xref:Uno.GetStarted.Licensing) to register and sign in. + +For existing applications, take this opportunity to update to the [latest **Uno.Sdk** version](https://www.nuget.org/packages/Uno.Sdk). Refer to our [migration guide](xref:Uno.Development.MigratingFromPreviousReleases) for upgrade steps. + +Once you're using the **latest stable 5.5 Uno.Sdk version or higher**, you can access **Hot Design** by clicking the **flame** icon in the diagnostics overlay that appears over your app. + +

+ Hot Design flame icon to enter in design mode +

+ +> [!IMPORTANT] +> At the current stage of the **Hot Design™** beta, **only the Desktop platform is supported**. Other platforms are undergoing stabilization for Hot Design support and will be available in future updates. +> +> For now, you can use the **Desktop** platform to create your UI with the runtime visual designer. Once you’re satisfied with your design, you can test the app on other platforms by launching it as you would normally. + +## Hot Design Core Tool Windows + +Once in Hot Design, your running app becomes an interactive canvas. +Hot Design offers an intuitive interface for designing and interacting with your app. This enables you to seamlessly create, edit, and refine your app's user interface in real-time, streamlining the design process for maximum efficiency and simplicity. + +![Hot Design Core Tool Windows Highlighted](Assets/hot-design-views-highlighted.png) + +Here are the tool windows available on the interactive canvas: + +### Toolbox + +Located on the upper-left side, the **Toolbox** window provides a categorized list of available controls you can use in your application, including available custom and third-party UI controls. It features a search bar for quickly finding specific controls, which you can drag and drop directly onto the canvas or the **Elements** window for easy integration into your design. + +### Elements + +Below the **Toolbox**, the **Elements** window displays the hierarchical structure of your app. It represents the visual tree of your app, allowing you to select and organize elements. Clicking on an element in this window highlights it on the canvas for detailed modifications. + +### Canvas + +The main **Canvas** in the center of the interface represents your running app. It is an interactive area where you can visually design and interact with the user interface. You can select controls, see their outlines, and preview any changes made to the layout or properties. + +### Properties + +The **Properties** window, located on the right side of the interactive canvas, displays the attributes of the currently selected element on the canvas. By default, it highlights **Smart** properties, prioritizing the most commonly adjusted settings for the element. If you need access to all available properties, you can switch to the **All** view. + +This window also allows you to search for specific properties and make adjustments directly, such as modifying styles, layouts, appearances, data bindings, resources, responsiveness, and interactions, to customize your UI elements effectively. + +### Toolbar + +

+ Hot Design Toolbar +

+ +Located at the top of the interactive canvas, the **Toolbar** streamlines your design workflow by providing quick access to essential actions and tools, such as: + +- Enter Hot Design Toolbar flame icon Entering **Hot Design** mode. + +- Leave Hot Design Toolbar flame icon Leaving **Hot Design** mode. + +- Hot Design Toolbar play iconHot Design Toolbar pause icon Playing with the live running app to test functionality and pausing to return to adjusting properties, layout, and other design aspects without leaving the interactive designer. + +- Hot Design Toolbar undo iconHot Design Toolbar redo icon Undoing and redoing changes. + +- Hot Design Toolbar form factor icon Changing the form factor of the app to test different screen sizes. + +- Hot Design Toolbar light theme iconHot Design Toolbar dark theme icon Switching between light and dark themes. + +- Hot Design Toolbar connection status icon Viewing the connection status and the latest updates from **Hot Reload**. + +- Hot Design Toolbar more options icon More options, including showing or hiding the various tool windows, providing flexibility in customizing your design workspace. + +## Using Hot Design + +### Selecting elements + +You can select controls on the app's current screen by simply clicking on them. A visual adorner will appear around the selected elements, clearly indicating their boundaries. The type, height, and width of the selected element are displayed below the adorner for easy reference. + +

+ Selecting a single item on the main canvas +

+ +You can also click on controls in the **Elements** window. This provides an alternative to clicking directly on the canvas, making it easier to precisely select small elements or to choose the container of a visual element rather than the element itself. + +To select multiple elements, hold down the `Ctrl` key while clicking. This enables you to view and edit shared properties in the **Properties** window. + +

+ Selecting multiple items on the main canvas +

+ +### Placing and Deleting Elements + +You can add controls to your app by dragging them from the **Toolbox** onto the canvas. + +Alternatively, you can drag them directly into the **Elements** window to position them within a specific hierarchical level. + +![Dragging item from Toolbox into the Elements window](Assets/gifs/toolbox-drag.gif) + +To delete a control, right-click on it either in the canvas or the **Elements** window and select the delete option. + +

+ Delete an element from the Element window +

+ +### Setting Properties + +The **Properties** window displays the current values of a control's properties, which can be modified in various ways. Examples include: + +- **Changing a property value**, such as the **Text** property of a `TextBlock` control: + + ![Text property of a TextBlock control](Assets/properties-view-text-property.png) + +- **Adjusting the alignment** of a control: + + ![Control alignment example](Assets/properties-view-alignment-property.png) + +- **Using the autosuggest box** to set a property, such as the **Background** property of a control: + + ![Background property with autosuggest](Assets/properties-view-autosuggest-property.png) + +For advanced options, clicking the **Advanced** button opens a flyout with three settings for each property: **Value**, **Binding**, or **Resource**. + +![Three options for property setting and reset button](Assets/properties-view-button-flyout.png) + +You can quickly identify the type of value set for a property by the icon displayed on the **Advanced** button. For example: + +- ![None](Assets/properties-view-advcd-button-none.png) indicates that nothing is set. +- ![XAML](Assets/properties-view-advcd-button-xaml.png) indicates a **Literal**/**XAML** value is set. +- ![Binding](Assets/properties-view-advcd-button-binding.png) indicates a **Binding** is set. +- ![Resource](Assets/properties-view-advcd-button-resource.png) indicates a **Resource** is set. +- ![Mixed Responsive](Assets/properties-view-advcd-button-mixed-responsive.png) indicates **Mixed Responsive** values is set using Responsive Extension. + +> [!TIP] +> To quickly clear a property's value, click the **Reset** button. Cleared properties will behave as though they weren't specified in the original XAML file. + +If a property is not set, it will appear similar to this: + +![Unset property](Assets/properties-view-text-empty.png) + +### Changing the Form Factor + +The **Toolbar** provides the ability to change the form factor of your app within Hot Design. This feature is represented in the Toolbar by the following icon: + +

+Hot Design Toolbar form factor icon +

+ +The height and width of your running app will dynamically adjust to match the selected form factor. You can also specify a custom height and width for precise testing. + +

+Form factor and zoom level flyout +

+ +At the bottom of the flyout, you can view and adjust the current zoom level. Modifying this setting will dynamically scale Hot Design's view of your app, making it easier to fine-tune your design. + +### Toggling Theme + +The **Toolbar** includes a feature to toggle between your app's light and dark themes. This also updates the Hot Design layout to match the selected theme. Use this feature to validate your app's theme-sensitive styles and ensure proper responsiveness to theme changes. + +

+Example Hot Design with Light ThemeExample Hot Design with Dark Theme +

+ +### Interaction with the Canvas + +You can interact with the canvas using the following mouse and keyboard shortcuts: + +- **Ctrl + Scroll mouse**: Zoom in or out. +- **Scroll mouse**: Scroll the canvas up or down (only works when zoomed in). +- **Shift + Scroll mouse**: Scroll the canvas left or right (only works when zoomed in). +- **Click and drag with the mouse wheel**: Scroll the canvas in any direction (only works when zoomed in). + +### Tutorial + +For a step-by-step tutorial on getting started with Hot Design, refer to the [Create a Counter App with Hot Design™](xref:Uno.HotDesign.GetStarted.CounterTutorial) tutorial. diff --git a/doc/articles/studio/Hot Design/hot-design-overview.md b/doc/articles/studio/Hot Design/hot-design-overview.md new file mode 100644 index 000000000000..bcf897e2651b --- /dev/null +++ b/doc/articles/studio/Hot Design/hot-design-overview.md @@ -0,0 +1,53 @@ +--- +uid: Uno.HotDesign.Overview +--- + +# Hot Design™ Overview + +Welcome to the **Hot Design™** documentation! This guide provides everything you need to start using Hot Design, the next-generation runtime visual designer for cross-platform .NET applications. + +> [!Video https://www.youtube-nocookie.com/embed/fODrUH0zno0] + +## What is Hot Design™? + +**Hot Design™** transforms your live, running app into a real-time visual designer that works with any IDE on any OS. It allows you to make UI changes on the fly without restarting your app or losing state, while seamlessly synchronizing your XAML code and visual designs. + +In addition, [Hot Reload](xref:Uno.Features.HotReload) works seamlessly with Hot Design, allowing you to see UI changes instantly without rebuilding your app. This boosts productivity, reduces iteration time, and provides real-time feedback for both visual and functional tweaks in your UI. Hot Reload also includes a visual indicator to help you monitor changes as you develop, further enhancing your workflow. + +**Hot Design™** is part of the **Uno Platform Studio**, a suite of tools designed to streamline your cross-platform app development and boost productivity. + +[➜ Learn more about Uno Platform Studio](xref:Uno.Platform.Studio.Overview) + +### Key Features + +Hot Design empowers you to: + +- **Achieve the Fastest Inner DevLoop**: With a single click, turn your running app into a visual Designer. Another click returns you to your app, keeping you in your workflow without disruption. +- **Design in Real Time**: Modify your app’s UI instantly while it’s running, enabling fast, interactive development. +- **Leverage Your Favorite IDE**: Seamlessly integrate with Visual Studio, VS Code, or JetBrains Rider on any OS, with IDE-agnostic support. +- **Synchronize Code and Designer**: Reflect changes instantly between the Designer and code, ensuring your live app and XAML remain a single source of truth. +- **Integrate Live Data**: Connect your UI to data sources intuitively and see real-time updates to data bindings as you build, simplifying the process. +- **Work Directly with Real Data**: Skip mock data creation by working with actual data sources from your running app, gaining a true-to-life feel of your app's behavior. Mock data is also supported for added flexibility. +- **Reuse Custom & 3rd-Party Controls**: Incorporate custom and third-party UI components effortlessly while maintaining a consistent look and behavior across platforms. +- **Manage State with Flexibility**: Work seamlessly with MVVM or MVUX to consume real-time data while keeping UI logic separate from core logic. +- **Apply Styles Easily**: Enhance your app’s UI and UX with predefined styles, applied effortlessly in just a few clicks — no coding required. +- **Explore Responsive Layouts**: Test layout options with a single click and instantly visualize how your app adapts to different devices and form factors. +- **Switch Themes Effortlessly**: Toggle between light and dark modes with one click to ensure a consistent user experience across color schemes. +- **Design on Remote Devices**: Fine-tune your UI directly on remote devices or emulators, instantly seeing changes without the need for constant redeployment. +- **Simplify Property Management**: Use Smart Properties to quickly find, modify, and bind key UI properties without leaving the live design environment, saving time and effort. + +## Why Hot Design™? + +**Hot Design™** brings together runtime UI design, live data integration, and cross-platform development to streamline your app-building process. It empowers you to work more efficiently, stay in the flow, and deliver polished, cross-platform apps with ease. + +By simplifying UI development and accelerating your workflow, Hot Design helps you stay productive and focus on creating great applications. + +**Let’s get started!** + +## What You’ll Find in This Documentation + +- **[Get Started Guide](xref:Uno.HotDesign.GetStarted.Guide)** + Getting started with setting up Hot Design and exploring the key areas and features of the visual designer it offers. + +- **[Counter App Tutorial](xref:Uno.HotDesign.GetStarted.CounterTutorial)** + A hands-on walkthrough for building the [Counter App](xref:Uno.Workshop.Counter) using Hot Design, showcasing its features and workflow in action. diff --git a/doc/articles/studio/Hot Reload/hot-reload-overview.md b/doc/articles/studio/Hot Reload/hot-reload-overview.md new file mode 100644 index 000000000000..4bdf1523f27e --- /dev/null +++ b/doc/articles/studio/Hot Reload/hot-reload-overview.md @@ -0,0 +1,5 @@ +--- +uid: Uno.Platform.Studio.HotReload.Overview +--- + +[!include[working-with-xaml-hot-reload](../../features/working-with-xaml-hot-reload.md)] diff --git a/doc/articles/studio/studio-overview.md b/doc/articles/studio/studio-overview.md new file mode 100644 index 000000000000..c1ffe3aea105 --- /dev/null +++ b/doc/articles/studio/studio-overview.md @@ -0,0 +1,40 @@ +--- +uid: Uno.Platform.Studio.Overview +--- + +# Uno Platform Studio Overview + +**Uno Platform Studio** is a set of productivity tools designed to enhance developer productivity, be it for design handoff, to radically improving developer inner dev loop with Hot Reload and industry-first, cross-platform runtime Visual Designer for .NET - Hot Design. Uno Platform Studio empowers developers to stay in their flow, enabling seamless cross-platform app development for every platform .NET runs on. + +![Introducing Uno Platform Studio](assets/introducing-uno-platform-studio.png) + +## What is Uno Platform Studio? + +**Uno Platform Studio** revolutionizes how developers design, build, and iterate on their applications. + +It includes three key tools, each purpose-built to streamline your workflow: + +- **[Hot Design™](xref:Uno.HotDesign.Overview)** + The industry-first runtime Visual Designer for cross-platform .NET Applications. Transform your running app into a Designer from any IDE on any OS and create polished interfaces with ease. + + [➜ Learn more about Hot Design™](xref:Uno.HotDesign.GetStarted.Guide) + +- **[Hot Reload](xref:Uno.Features.HotReload)** + Reliably update any code in your app and get instant confirmation your changes were applied, with a new App Indicator to monitor changes while you develop. + + [➜ Learn more about Hot Reload](xref:Uno.HotReload.GetStarted.Guide) + +- **[Design-to-Code](xref:Uno.Figma.GetStarted)** + Generate ready-to-use, well-structured XAML or C# Markup directly from your Figma designs with one click, completely eliminating manual design handoff. + + [➜ Learn more about Design-to-Code](xref:Uno.Figma.GetStarted) + +## Why Choose Uno Platform Studio? + +Uno Platform Studio is designed to make cross-platform development fast, seamless, and enjoyable: + +- Stay in your flow by working directly in your preferred IDE and Figma design tool on any OS. +- Build apps for every platform .NET runs on. +- Streamline workflows with tools that integrate design, development, and iteration. + +**Start your journey with Uno Platform Studio today and take your app development productivity to the next level!** diff --git a/doc/articles/toc.yml b/doc/articles/toc.yml index 610381a5feb8..481d4237870c 100644 --- a/doc/articles/toc.yml +++ b/doc/articles/toc.yml @@ -411,6 +411,24 @@ - name: FAQ href: xref:Uno.Development.FAQ +- name: Studio + items: + - name: Overview + href: xref:Uno.Platform.Studio.Overview + - name: Hot Reload + href: xref:Uno.Platform.Studio.HotReload.Overview + - name: Hot Design + href: xref:Uno.HotDesign.Overview + items: + - name: Overview + href: xref:Uno.HotDesign.Overview + - name: Getting Started with Hot Design + href: xref:Uno.HotDesign.GetStarted.Guide + - name: Counter Tutorial + href: xref:Uno.HotDesign.GetStarted.CounterTutorial + - name: Design-to-Code + href: external/figma-docs/toc.yml + - name: Reference items: - name: Overview @@ -672,10 +690,6 @@ - name: Toolkit #topicHref: external/uno.toolkit.ui/doc/getting-started.md href: external/uno.toolkit.ui/doc/toc.yml - -- name: Figma - #topicHref: xref:Uno.Figma.GetStarted - href: external/figma-docs/toc.yml - name: Tooling items: diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ImageTests/UnoSamples_Tests.Image.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ImageTests/UnoSamples_Tests.Image.cs index 0c69e7cd7224..6f98d4ff4ce8 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ImageTests/UnoSamples_Tests.Image.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/ImageTests/UnoSamples_Tests.Image.cs @@ -64,11 +64,8 @@ public void ImageStretch_None() void HasValidSize(string name) { - var element = _app.Marked(name); - _app.WaitForElement(element); - var rect = _app.Query(element).First().Rect; - Assert.That(rect.Width != 0); - Assert.That(rect.Height != 0); + var rect = _app.Query(q => q.All().Marked(name)).First().Rect; + _app.WaitFor(() => rect.Width != 0 && rect.Height != 0, timeout: TimeSpan.FromSeconds(2)); } HasValidSize("image01"); diff --git a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Presentation/SampleChooserViewMode.Properties.cs b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Presentation/SampleChooserViewMode.Properties.cs index c2937137c60b..ae8ff1259625 100644 --- a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Presentation/SampleChooserViewMode.Properties.cs +++ b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Presentation/SampleChooserViewMode.Properties.cs @@ -25,6 +25,7 @@ using Uno.UI.Xaml.Core; using WinUICoreServices = Uno.UI.Xaml.Core.CoreServices; using Uno.UI; +using Uno; #endif namespace SampleControl.Presentation @@ -449,6 +450,22 @@ public bool UseRtl } #if HAS_UNO + public bool SimulateTouch + { +#if DEBUG + get => WinRTFeatureConfiguration.DebugOptions.SimulateTouch; +#else + get => false; +#endif + set + { +#if DEBUG + WinRTFeatureConfiguration.DebugOptions.SimulateTouch = value; + RaisePropertyChanged(); +#endif + } + } + public bool PreventLightDismissOnWindowDeactivated { get => FeatureConfiguration.Popup.PreventLightDismissOnWindowDeactivated; diff --git a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleChooserControl.xaml b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleChooserControl.xaml index f6b720d11032..ea8c4821a919 100644 --- a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleChooserControl.xaml +++ b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleChooserControl.xaml @@ -573,6 +573,11 @@ + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/CalendarView/CalendarDatePicker_Features.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/CalendarView/CalendarDatePicker_Features.xaml index 8509c8172efe..624cc51cd3f0 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/CalendarView/CalendarDatePicker_Features.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/CalendarView/CalendarDatePicker_Features.xaml @@ -29,6 +29,20 @@ + + + + + + + + + + + + + + ChineseLunarCalendar GregorianCalendar @@ -55,6 +69,7 @@ CalendarIdentifier="{Binding SelectedItem.Content, ElementName=cid, FallbackValue=GregorianCalendar}" FirstDayOfWeek="{Binding SelectedItem.Content, ElementName=dow, FallbackValue=Sunday}" DayOfWeekFormat="{Binding SelectedItem.Content, ElementName=dowf}" + DateFormat="{Binding SelectedItem.Content, ElementName=dateformat}" IsTodayHighlighted="{Binding IsChecked, ElementName=today}"/> IsEnabled diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ImageTests/Image_Stretch_None.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ImageTests/Image_Stretch_None.xaml index 3b8c02167d81..1449ae7ab4cc 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ImageTests/Image_Stretch_None.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ImageTests/Image_Stretch_None.xaml @@ -27,7 +27,7 @@ @@ -39,7 +39,7 @@ @@ -56,7 +56,7 @@ VerticalAlignment="Center" HorizontalAlignment="Center" /> - - - - - - + #FF0000 + #FF8000 + #FFFF00 + #008000 + #0000FF + #A000C0 + + + + + + @@ -162,18 +168,18 @@ Original Colors: (should never change) - - - - - - - - - - - - + + + + + + + + + + + + @@ -186,12 +192,12 @@ Animated block: - - - - - - + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media_Animation/ColorAnimationUsingKeyFrames_Fill.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media_Animation/ColorAnimationUsingKeyFrames_Fill.xaml.cs index b411821661ba..c30d9ba2a958 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media_Animation/ColorAnimationUsingKeyFrames_Fill.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media_Animation/ColorAnimationUsingKeyFrames_Fill.xaml.cs @@ -5,7 +5,7 @@ namespace UITests.Windows_UI_Xaml_Media_Animation { - [Sample("Animations")] + [Sample("Animations", IsManualTest = true)] public sealed partial class ColorAnimationUsingKeyFrames_Fill : Page { public ColorAnimationUsingKeyFrames_Fill() diff --git a/src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs b/src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs index fcd38071e58c..791b8c9aba9e 100644 --- a/src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs +++ b/src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs @@ -599,20 +599,21 @@ private static string GetFullyQualifiedType(this ITypeSymbol type, bool includeG /// /// The dependency-property or the attached dependency-property setter /// The property type - public static INamedTypeSymbol? FindDependencyPropertyType(this ISymbol propertyOrSetter) + public static INamedTypeSymbol? FindDependencyPropertyType(this ISymbol propertyOrSetter, bool unwrapNullable = true) { - if (propertyOrSetter is IPropertySymbol dependencyProperty) + var type = propertyOrSetter switch { - return dependencyProperty.Type.OriginalDefinition is { SpecialType: SpecialType.System_Nullable_T } - ? (dependencyProperty.Type as INamedTypeSymbol)?.TypeArguments[0] as INamedTypeSymbol - : dependencyProperty.Type as INamedTypeSymbol; - } - else if (propertyOrSetter is IMethodSymbol { IsStatic: true, Parameters.Length: 2 } attachedPropertySetter) + IPropertySymbol dp => dp.Type, + IMethodSymbol { IsStatic: true, Parameters.Length: 2 } adpSetter => adpSetter.Parameters[1].Type, + + _ => null, + }; + if (unwrapNullable && type?.IsNullable(out var innerType) == true) { - return attachedPropertySetter.Parameters[1].Type as INamedTypeSymbol; + type = innerType; } - return null; + return type as INamedTypeSymbol; } } } diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Given_LazyLoading.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Given_LazyLoading.cs new file mode 100644 index 000000000000..2d8e360ad10c --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Given_LazyLoading.cs @@ -0,0 +1,123 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Testing; +using Uno.UI.SourceGenerators.Tests.Verifiers; + +namespace Uno.UI.SourceGenerators.Tests.XamlCodeGeneratorTests; + +using Verify = XamlSourceGeneratorVerifier; + +[TestClass] +public class Given_LazyLoading +{ + [TestMethod] + public async Task When_DeferLoadStrategy_No_Visibility_Set() + { + var xamlFile = new XamlFile("MainPage.xaml", + """ + + + + + + + + + + """); + + var test = new Verify.Test(xamlFile) + { + TestState = + { + Sources = + { + """ + using System; + using Microsoft.UI.Xaml; + using Microsoft.UI.Xaml.Controls; + + namespace TestRepro + { + public sealed partial class MainPage : Page + { + public MainPage() + { + this.InitializeComponent(); + } + } + } + """ + } + }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net70.AddPackages(ImmutableArray.Create(new PackageIdentity("Uno.WinUI", "5.0.118"))), + DisableBuildReferences = true, + }.AddGeneratedSources(); + + await test.RunAsync(); + } + + [TestMethod] + public async Task When_xLoad_Child_Has_xBind() + { + var xamlFile = new XamlFile("MainPage.xaml", + """ + + + + + + + """); + + var test = new Verify.Test(xamlFile) + { + TestState = + { + Sources = + { + """ + using System; + using Microsoft.UI.Xaml; + using Microsoft.UI.Xaml.Controls; + + namespace TestRepro + { + public sealed partial class MainPage : Page + { + public MainPage() + { + this.InitializeComponent(); + } + + public string InnerText + { + get { return (string)GetValue(InnerTextProperty); } + set { SetValue(InnerTextProperty, value); } + } + + public static readonly DependencyProperty InnerTextProperty = + DependencyProperty.Register("InnerText", typeof(string), typeof(MainPage), new PropertyMetadata("My inner text")); + } + } + """ + } + }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net70.AddPackages(ImmutableArray.Create(new PackageIdentity("Uno.WinUI", "5.0.118"))), + DisableBuildReferences = true, + }.AddGeneratedSources(); + + await test.RunAsync(); + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIIOFDOTAFE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIIOFDOTAFE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 7028f75a1bfd..65f295fa7b90 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIIOFDOTAFE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIIOFDOTAFE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -520,8 +520,8 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFPLR/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFPLR/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 50630afafc69..14d428a2c00b 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFPLR/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFPLR/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -318,7 +318,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFPLS/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFPLS/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 03820a9bd7b7..00be0c571d11 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFPLS/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFPLS/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -203,7 +203,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFRT/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFRT/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 2bf5ae4c462e..25d9f901aaf7 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFRT/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/SOSLIOFRT/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -178,7 +178,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TAA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TAA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index a8cda57d2588..525ffc8e395f 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TAA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TAA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -178,8 +178,8 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TBTNSICB/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TBTNSICB/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index e7e1b9928652..d174782f553a 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TBTNSICB/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TBTNSICB/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -173,7 +173,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 9a3a0753c9e7..d2c3ad9a4a47 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -163,7 +163,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIM/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIM/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index ea1117eec05b..0afbaaaa9257 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIM/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIM/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -281,10 +281,10 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); - owner._component_2.UpdateResourceBindings(resourceContextProvider: null); - owner._component_3.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); + owner._component_2.UpdateResourceBindings(); + owner._component_3.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TMEWSFEL/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TMEWSFEL/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 61bbbb0b32fb..cd548616f8c0 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TMEWSFEL/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TMEWSFEL/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -127,7 +127,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TMEWSWA/XamlCodeGenerator_MainWindow_c93db19a7202d9eb84ddc41d72fcb89b.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TMEWSWA/XamlCodeGenerator_MainWindow_c93db19a7202d9eb84ddc41d72fcb89b.cs index b39bde13ce4f..49e4974989eb 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TMEWSWA/XamlCodeGenerator_MainWindow_c93db19a7202d9eb84ddc41d72fcb89b.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TMEWSWA/XamlCodeGenerator_MainWindow_c93db19a7202d9eb84ddc41d72fcb89b.cs @@ -139,7 +139,7 @@ void IMainWindow_Bindings.Update() void IMainWindow_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainWindow_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TPXBRXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TPXBRXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 5a9c9ca43324..f4b6dfb0cfd1 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TPXBRXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TPXBRXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -327,9 +327,9 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); - owner._component_2.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); + owner._component_2.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TRDAAPSSTP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TRDAAPSSTP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 20dfc191585a..b353a45113b8 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TRDAAPSSTP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TRDAAPSSTP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -174,7 +174,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TTIXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TTIXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 57ae623693fa..30fc51a7a721 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TTIXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TTIXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -471,8 +471,8 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TTW/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TTW/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index f6e23a5a2a9c..3e52c509e1d7 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TTW/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TTW/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -178,8 +178,8 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TXBRXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TXBRXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 342c0df83f4a..412d8268678c 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TXBRXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TXBRXLE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -329,9 +329,9 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); - owner._component_2.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); + owner._component_2.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WBOP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WBOP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 334eff8fba29..de8c4154b0cb 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WBOP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WBOP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -165,7 +165,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_GlobalStaticResources.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_GlobalStaticResources.cs new file mode 100644 index 000000000000..0dc326678c53 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_GlobalStaticResources.cs @@ -0,0 +1,58 @@ +// +namespace MyProject +{ + /// + /// Contains all the static resources defined for the application + /// + public sealed partial class GlobalStaticResources + { + static bool _initialized; + private static bool _stylesRegistered; + private static bool _dictionariesRegistered; + internal static global::Uno.UI.Xaml.XamlParseContext __ParseContext_ { get; } = new global::Uno.UI.Xaml.XamlParseContext() + { + AssemblyName = "TestProject", + } + ; + + static GlobalStaticResources() + { + Initialize(); + } + public static void Initialize() + { + if (!_initialized) + { + _initialized = true; + global::Uno.UI.GlobalStaticResources.Initialize(); + global::Uno.UI.Toolkit.GlobalStaticResources.Initialize(); + global::Uno.UI.GlobalStaticResources.RegisterDefaultStyles(); + global::Uno.UI.Toolkit.GlobalStaticResources.RegisterDefaultStyles(); + global::Uno.UI.GlobalStaticResources.RegisterResourceDictionariesBySource(); + global::Uno.UI.Toolkit.GlobalStaticResources.RegisterResourceDictionariesBySource(); + } + } + public static void RegisterDefaultStyles() + { + if(!_stylesRegistered) + { + _stylesRegistered = true; + RegisterDefaultStyles_MainPage_d6cd66944958ced0c513e0a04797b51d(); + } + } + // Register ResourceDictionaries using ms-appx:/// syntax, this is called for external resources + public static void RegisterResourceDictionariesBySource() + { + if(!_dictionariesRegistered) + { + _dictionariesRegistered = true; + } + } + // Register ResourceDictionaries using ms-resource:/// syntax, this is called for local resources + internal static void RegisterResourceDictionariesBySourceLocal() + { + } + static partial void RegisterDefaultStyles_MainPage_d6cd66944958ced0c513e0a04797b51d(); + + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_LocalizationResources.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_LocalizationResources.cs new file mode 100644 index 000000000000..115ce87c0105 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_LocalizationResources.cs @@ -0,0 +1,2 @@ +// +[assembly: global::System.Reflection.AssemblyMetadata("UnoHasLocalizationResources", "False")] \ No newline at end of file diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs new file mode 100644 index 000000000000..0013d351ffbf --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WDLSNVS/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -0,0 +1,183 @@ +// +#pragma warning disable CS0114 +#pragma warning disable CS0108 +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Uno.UI; +using Uno.UI.Xaml; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.UI.Xaml.Shapes; +using Windows.UI.Text; +using Uno.Extensions; +using Uno; +using Uno.UI.Helpers; +using Uno.UI.Helpers.Xaml; +using MyProject; + +#if __ANDROID__ +using _View = Android.Views.View; +#elif __IOS__ +using _View = UIKit.UIView; +#elif __MACOS__ +using _View = AppKit.NSView; +#else +using _View = Microsoft.UI.Xaml.UIElement; +#endif + +namespace TestRepro +{ + partial class MainPage : global::Microsoft.UI.Xaml.Controls.Page + { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + private const string __baseUri_prefix_MainPage_d6cd66944958ced0c513e0a04797b51d = "ms-appx:///TestProject/"; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + private const string __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d = "ms-appx:///TestProject/"; + private global::Microsoft.UI.Xaml.NameScope __nameScope = new global::Microsoft.UI.Xaml.NameScope(); + private void InitializeComponent() + { + NameScope.SetNameScope(this, __nameScope); + var __that = this; + base.IsParsing = true; + // Source 0\MainPage.xaml (Line 1:2) + base.Content = + new global::Microsoft.UI.Xaml.Controls.StackPanel + { + IsParsing = true, + // Source 0\MainPage.xaml (Line 9:3) + Children = + { + new global::Microsoft.UI.Xaml.Controls.TextBlock + { + IsParsing = true, + Text = "Immediate content", + // Source 0\MainPage.xaml (Line 10:4) + } + .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler0)(c0 => + { + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c0, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c0.CreationComplete(); + } + )) + , + new Microsoft.UI.Xaml.ElementStub( () => + new global::Microsoft.UI.Xaml.Controls.Border + { + IsParsing = true, + Name = "LazyLoadedBorder", + // Source 0\MainPage.xaml (Line 11:4) + Child = + new global::Microsoft.UI.Xaml.Controls.TextBlock + { + IsParsing = true, + Text = "Lazy Content", + // Source 0\MainPage.xaml (Line 12:5) + } + .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler0)(c1 => + { + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c1, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c1.CreationComplete(); + } + )) + } + .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler1)(c2 => + { + __nameScope.RegisterName("LazyLoadedBorder", c2); + __that.LazyLoadedBorder = c2; + // DeferLoadStrategy Lazy + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c2, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c2.CreationComplete(); + } + )) + ) .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler2)(c3 => + { + c3.Name = "LazyLoadedBorder"; + _LazyLoadedBorderSubject.ElementInstance = c3; + } + )) + , + } + } + .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler3)(c4 => + { + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c4, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c4.CreationComplete(); + } + )) + ; + + this + .GenericApply(((c5) => + { + // Source 0\MainPage.xaml (Line 1:2) + + // WARNING Property c5.base does not exist on {http://schemas.microsoft.com/winfx/2006/xaml/presentation}Page, the namespace is http://www.w3.org/XML/1998/namespace. This error was considered irrelevant by the XamlFileGenerator + } + )) + .GenericApply(((c6) => + { + // Class TestRepro.MainPage + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c6, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c6.CreationComplete(); + } + )) + ; + OnInitializeCompleted(); + + } + partial void OnInitializeCompleted(); + private global::Microsoft.UI.Xaml.Data.ElementNameSubject _LazyLoadedBorderSubject = new global::Microsoft.UI.Xaml.Data.ElementNameSubject(); + private global::Microsoft.UI.Xaml.Controls.Border LazyLoadedBorder + { + get + { + return (global::Microsoft.UI.Xaml.Controls.Border)_LazyLoadedBorderSubject.ElementInstance; + } + set + { + _LazyLoadedBorderSubject.ElementInstance = value; + } + } + } +} +namespace MyProject +{ + static class MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions + { + public delegate void XamlApplyHandler0(global::Microsoft.UI.Xaml.Controls.TextBlock instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.Controls.TextBlock MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.Controls.TextBlock instance, XamlApplyHandler0 handler) + { + handler(instance); + return instance; + } + public delegate void XamlApplyHandler1(global::Microsoft.UI.Xaml.Controls.Border instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.Controls.Border MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.Controls.Border instance, XamlApplyHandler1 handler) + { + handler(instance); + return instance; + } + public delegate void XamlApplyHandler2(global::Microsoft.UI.Xaml.ElementStub instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.ElementStub MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.ElementStub instance, XamlApplyHandler2 handler) + { + handler(instance); + return instance; + } + public delegate void XamlApplyHandler3(global::Microsoft.UI.Xaml.Controls.StackPanel instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.Controls.StackPanel MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.Controls.StackPanel instance, XamlApplyHandler3 handler) + { + handler(instance); + return instance; + } + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_GlobalStaticResources.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_GlobalStaticResources.cs new file mode 100644 index 000000000000..0dc326678c53 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_GlobalStaticResources.cs @@ -0,0 +1,58 @@ +// +namespace MyProject +{ + /// + /// Contains all the static resources defined for the application + /// + public sealed partial class GlobalStaticResources + { + static bool _initialized; + private static bool _stylesRegistered; + private static bool _dictionariesRegistered; + internal static global::Uno.UI.Xaml.XamlParseContext __ParseContext_ { get; } = new global::Uno.UI.Xaml.XamlParseContext() + { + AssemblyName = "TestProject", + } + ; + + static GlobalStaticResources() + { + Initialize(); + } + public static void Initialize() + { + if (!_initialized) + { + _initialized = true; + global::Uno.UI.GlobalStaticResources.Initialize(); + global::Uno.UI.Toolkit.GlobalStaticResources.Initialize(); + global::Uno.UI.GlobalStaticResources.RegisterDefaultStyles(); + global::Uno.UI.Toolkit.GlobalStaticResources.RegisterDefaultStyles(); + global::Uno.UI.GlobalStaticResources.RegisterResourceDictionariesBySource(); + global::Uno.UI.Toolkit.GlobalStaticResources.RegisterResourceDictionariesBySource(); + } + } + public static void RegisterDefaultStyles() + { + if(!_stylesRegistered) + { + _stylesRegistered = true; + RegisterDefaultStyles_MainPage_d6cd66944958ced0c513e0a04797b51d(); + } + } + // Register ResourceDictionaries using ms-appx:/// syntax, this is called for external resources + public static void RegisterResourceDictionariesBySource() + { + if(!_dictionariesRegistered) + { + _dictionariesRegistered = true; + } + } + // Register ResourceDictionaries using ms-resource:/// syntax, this is called for local resources + internal static void RegisterResourceDictionariesBySourceLocal() + { + } + static partial void RegisterDefaultStyles_MainPage_d6cd66944958ced0c513e0a04797b51d(); + + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_LocalizationResources.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_LocalizationResources.cs new file mode 100644 index 000000000000..115ce87c0105 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_LocalizationResources.cs @@ -0,0 +1,2 @@ +// +[assembly: global::System.Reflection.AssemblyMetadata("UnoHasLocalizationResources", "False")] \ No newline at end of file diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs new file mode 100644 index 000000000000..d62b73cfb196 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WLCHB/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -0,0 +1,288 @@ +// +#pragma warning disable CS0114 +#pragma warning disable CS0108 +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Uno.UI; +using Uno.UI.Xaml; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.UI.Xaml.Shapes; +using Windows.UI.Text; +using Uno.Extensions; +using Uno; +using Uno.UI.Helpers; +using Uno.UI.Helpers.Xaml; +using MyProject; + +#if __ANDROID__ +using _View = Android.Views.View; +#elif __IOS__ +using _View = UIKit.UIView; +#elif __MACOS__ +using _View = AppKit.NSView; +#else +using _View = Microsoft.UI.Xaml.UIElement; +#endif + +namespace TestRepro +{ + partial class MainPage : global::Microsoft.UI.Xaml.Controls.Page + { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + private const string __baseUri_prefix_MainPage_d6cd66944958ced0c513e0a04797b51d = "ms-appx:///TestProject/"; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + private const string __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d = "ms-appx:///TestProject/"; + private global::Microsoft.UI.Xaml.NameScope __nameScope = new global::Microsoft.UI.Xaml.NameScope(); + private void InitializeComponent() + { + NameScope.SetNameScope(this, __nameScope); + var __that = this; + base.IsParsing = true; + // Source 0\MainPage.xaml (Line 1:2) + base.Content = + new Microsoft.UI.Xaml.ElementStub( () => + new global::Microsoft.UI.Xaml.Controls.ContentControl + { + IsParsing = true, + Name = "topLevelContent", + // Source 0\MainPage.xaml (Line 9:3) + Content = + new global::Microsoft.UI.Xaml.Controls.TextBlock + { + IsParsing = true, + Name = "innerTextBlock", + // Source 0\MainPage.xaml (Line 10:4) + } + .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler0)(c0 => + { + /* _isTopLevelDictionary:False */ + __that._component_0 = c0; + __nameScope.RegisterName("innerTextBlock", c0); + __that.innerTextBlock = c0; + // FieldModifier public + c0.SetBinding( + global::Microsoft.UI.Xaml.Controls.TextBlock.TextProperty, + new Microsoft.UI.Xaml.Data.Binding() + { + Mode = global::Microsoft.UI.Xaml.Data.BindingMode.OneWay, + } + .BindingApply(___b => /*defaultBindModeOneTime InnerText*/ global::Uno.UI.Xaml.BindingHelper.SetBindingXBindProvider(___b, __that, ___ctx => ___ctx is global::TestRepro.MainPage ___tctx ? (TryGetInstance_xBind_1(___tctx, out var bindResult1) ? (true, bindResult1) : (false, default)) : (false, default), null , new [] {"InnerText"})) + ); + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c0, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c0.CreationComplete(); + } + )) + , + } + .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler1)(c1 => + { + __nameScope.RegisterName("topLevelContent", c1); + __that.topLevelContent = c1; + // FieldModifier public + // Load false + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c1, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c1.CreationComplete(); + } + )) + ) .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler2)(c2 => + { + c2.Name = "topLevelContent"; + _topLevelContentSubject.ElementInstance = c2; + __that._component_1 = c2; + var _component_1_update_That = (this as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference; + var _component_1_update_subject_capture = _topLevelContentSubject; + void _component_1_update(global::Microsoft.UI.Xaml.ElementStub sender) + { + if (_component_1_update_That.Target is global::TestRepro.MainPage that) + { + if (sender.IsMaterialized) + { + that.Bindings.UpdateResources(); + that.Bindings.NotifyXLoad("topLevelContent"); + } + else + { + _topLevelContentSubject.ElementInstance = null; + _innerTextBlockSubject.ElementInstance = null; + _innerTextBlockSubject.ElementInstance = null; + } + } + } + c2.MaterializationChanged += _component_1_update; + var owner = this; + void _component_1_materializing(object sender) + { + if (_component_1_update_That.Target is global::TestRepro.MainPage that) + { + that._component_0.ApplyXBind(); + that._component_0.UpdateResourceBindings(); + } + } + c2.Materializing += _component_1_materializing; + } + )) + ; + + this + .GenericApply(((c3) => + { + // Source 0\MainPage.xaml (Line 1:2) + + // WARNING Property c3.base does not exist on {http://schemas.microsoft.com/winfx/2006/xaml/presentation}Page, the namespace is http://www.w3.org/XML/1998/namespace. This error was considered irrelevant by the XamlFileGenerator + } + )) + .GenericApply(((c4) => + { + // Class TestRepro.MainPage + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c4, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c4.CreationComplete(); + } + )) + ; + OnInitializeCompleted(); + + Bindings = new MainPage_Bindings(this); + ((global::Microsoft.UI.Xaml.FrameworkElement)this).Loading += (s, e) => + { + __that.Bindings.Update(); + __that.Bindings.UpdateResources(); + } + ; + } + partial void OnInitializeCompleted(); + private global::Microsoft.UI.Xaml.Data.ElementNameSubject _innerTextBlockSubject = new global::Microsoft.UI.Xaml.Data.ElementNameSubject(); + public global::Microsoft.UI.Xaml.Controls.TextBlock innerTextBlock + { + get + { + return (global::Microsoft.UI.Xaml.Controls.TextBlock)_innerTextBlockSubject.ElementInstance; + } + set + { + _innerTextBlockSubject.ElementInstance = value; + } + } + private global::Microsoft.UI.Xaml.Data.ElementNameSubject _topLevelContentSubject = new global::Microsoft.UI.Xaml.Data.ElementNameSubject(); + public global::Microsoft.UI.Xaml.Controls.ContentControl topLevelContent + { + get + { + return (global::Microsoft.UI.Xaml.Controls.ContentControl)_topLevelContentSubject.ElementInstance; + } + set + { + _topLevelContentSubject.ElementInstance = value; + } + } + private global::Microsoft.UI.Xaml.Markup.ComponentHolder _component_0_Holder = new global::Microsoft.UI.Xaml.Markup.ComponentHolder(isWeak: true); + private global::Microsoft.UI.Xaml.Controls.TextBlock _component_0 + { + get + { + return (global::Microsoft.UI.Xaml.Controls.TextBlock)_component_0_Holder.Instance; + } + set + { + _component_0_Holder.Instance = value; + } + } + private global::Microsoft.UI.Xaml.Markup.ComponentHolder _component_1_Holder = new global::Microsoft.UI.Xaml.Markup.ComponentHolder(isWeak: false); + private global::Microsoft.UI.Xaml.ElementStub _component_1 + { + get + { + return (global::Microsoft.UI.Xaml.ElementStub)_component_1_Holder.Instance; + } + set + { + _component_1_Holder.Instance = value; + } + } + private interface IMainPage_Bindings + { + void Initialize(); + void Update(); + void UpdateResources(); + void StopTracking(); + void NotifyXLoad(string name); + } + #pragma warning disable 0169 // Suppress unused field warning in case Bindings is not used. + private IMainPage_Bindings Bindings; + #pragma warning restore 0169 + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + private class MainPage_Bindings : IMainPage_Bindings + { + #if UNO_HAS_UIELEMENT_IMPLICIT_PINNING + private global::System.WeakReference _ownerReference; + private global::TestRepro.MainPage Owner { get => (global::TestRepro.MainPage)_ownerReference?.Target; set => _ownerReference = new global::System.WeakReference(value); } + #else + private global::TestRepro.MainPage Owner { get; set; } + #endif + public MainPage_Bindings(global::TestRepro.MainPage owner) + { + Owner = owner; + } + void IMainPage_Bindings.NotifyXLoad(string name) + { + } + void IMainPage_Bindings.Initialize() + { + } + void IMainPage_Bindings.Update() + { + var owner = Owner; + owner._component_0.ApplyXBind(); + } + void IMainPage_Bindings.UpdateResources() + { + var owner = Owner; + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); + } + void IMainPage_Bindings.StopTracking() + { + } + } + private static bool TryGetInstance_xBind_1(global::TestRepro.MainPage ___tctx, out object o) + { + o = null; + o = ___tctx.InnerText; + return true; + } + } +} +namespace MyProject +{ + static class MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions + { + public delegate void XamlApplyHandler0(global::Microsoft.UI.Xaml.Controls.TextBlock instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.Controls.TextBlock MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.Controls.TextBlock instance, XamlApplyHandler0 handler) + { + handler(instance); + return instance; + } + public delegate void XamlApplyHandler1(global::Microsoft.UI.Xaml.Controls.ContentControl instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.Controls.ContentControl MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.Controls.ContentControl instance, XamlApplyHandler1 handler) + { + handler(instance); + return instance; + } + public delegate void XamlApplyHandler2(global::Microsoft.UI.Xaml.ElementStub instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.ElementStub MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.ElementStub instance, XamlApplyHandler2 handler) + { + handler(instance); + return instance; + } + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WNBOP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WNBOP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index d3f808e05a2f..771a3047b3e3 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WNBOP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WNBOP/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -165,7 +165,7 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPADNE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPADNE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 62ba5564cffb..7ff7fc66f88d 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPADNE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPADNE/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -233,9 +233,9 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); - owner._component_2.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); + owner._component_2.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE__name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE__name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 62ba5564cffb..7ff7fc66f88d 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE__name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE__name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -233,9 +233,9 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); - owner._component_2.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); + owner._component_2.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE_m_name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE_m_name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 62ba5564cffb..7ff7fc66f88d 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE_m_name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE_m_name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -233,9 +233,9 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); - owner._component_2.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); + owner._component_2.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE_name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE_name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs index 62ba5564cffb..7ff7fc66f88d 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE_name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WOPAE_name/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -233,9 +233,9 @@ void IMainPage_Bindings.Update() void IMainPage_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); - owner._component_1.UpdateResourceBindings(resourceContextProvider: null); - owner._component_2.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); + owner._component_1.UpdateResourceBindings(); + owner._component_2.UpdateResourceBindings(); } void IMainPage_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WXOWCP/XamlCodeGenerator_Binding_Xaml_Object_With_Common_Properties_4891310bc693a433ba9a8e9f5113f94f.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WXOWCP/XamlCodeGenerator_Binding_Xaml_Object_With_Common_Properties_4891310bc693a433ba9a8e9f5113f94f.cs index b365d60b3226..74a748900e00 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WXOWCP/XamlCodeGenerator_Binding_Xaml_Object_With_Common_Properties_4891310bc693a433ba9a8e9f5113f94f.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WXOWCP/XamlCodeGenerator_Binding_Xaml_Object_With_Common_Properties_4891310bc693a433ba9a8e9f5113f94f.cs @@ -195,7 +195,7 @@ void IBinding_Xaml_Object_With_Common_Properties_Bindings.Update() void IBinding_Xaml_Object_With_Common_Properties_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IBinding_Xaml_Object_With_Common_Properties_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WXOWXOP/XamlCodeGenerator_Binding_Xaml_Object_With_Xaml_Object_Properties_5147419e44d1bc3e3f86860ad528476f.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WXOWXOP/XamlCodeGenerator_Binding_Xaml_Object_With_Xaml_Object_Properties_5147419e44d1bc3e3f86860ad528476f.cs index 05bddbe9f9ce..a2fac3b32d81 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WXOWXOP/XamlCodeGenerator_Binding_Xaml_Object_With_Xaml_Object_Properties_5147419e44d1bc3e3f86860ad528476f.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/WXOWXOP/XamlCodeGenerator_Binding_Xaml_Object_With_Xaml_Object_Properties_5147419e44d1bc3e3f86860ad528476f.cs @@ -187,7 +187,7 @@ void IBinding_Xaml_Object_With_Xaml_Object_Properties_Bindings.Update() void IBinding_Xaml_Object_With_Xaml_Object_Properties_Bindings.UpdateResources() { var owner = Owner; - owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + owner._component_0.UpdateResourceBindings(); } void IBinding_Xaml_Object_With_Xaml_Object_Properties_Bindings.StopTracking() { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/BaseStorageService.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/BaseStorageService.cs deleted file mode 100644 index 5eb6a55592de..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/BaseStorageService.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.Channel; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal abstract class BaseStorageService - { - /// - /// Peeked transmissions dictionary (maps file name to its full path). Holds all the transmissions that were peeked. - /// - /// - /// Note: The value (=file's full path) is not required in the Storage implementation. - /// If there was a concurrent Abstract Data Type Set it would have been used instead. - /// However, since there is no concurrent Set, dictionary is used and the second value is ignored. - /// - protected IDictionary PeekedTransmissions; - - /// - /// Gets or sets the maximum size of the storage in bytes. When limit is reached, the Enqueue method will drop new - /// transmissions. - /// - internal ulong CapacityInBytes { get; set; } - - /// - /// Gets or sets the maximum number of files. When limit is reached, the Enqueue method will drop new transmissions. - /// - internal uint MaxFiles { get; set; } - - internal abstract string StorageDirectoryPath { get; } - - /// - /// Initializes the - /// - /// A folder name. Under this folder all the transmissions will be saved. - internal abstract void Init(string desireStorageDirectoryPath); - - internal abstract StorageTransmission Peek(); - - internal abstract void Delete(StorageTransmission transmission); - - internal abstract Task EnqueueAsync(Transmission transmission); - - protected void OnPeekedItemDisposed(string fileName) - { - try - { - if (PeekedTransmissions.ContainsKey(fileName)) - { - PeekedTransmissions.Remove(fileName); - } - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException(e, "Failed to remove the item from storage items."); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FixedSizeQueue.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FixedSizeQueue.cs deleted file mode 100644 index e3663ec5c373..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FixedSizeQueue.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System.Collections.Generic; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// A light fixed size queue. If Enqueue is called and queue's limit has reached the last item will be removed. - /// This data structure is thread safe. - /// - internal class FixedSizeQueue - { - private readonly int _maxSize; - private readonly Queue _queue = new Queue(); - private readonly object _queueLockObj = new object(); - - internal FixedSizeQueue(int maxSize) - { - _maxSize = maxSize; - } - - internal void Enqueue(T item) - { - lock (_queueLockObj) - { - if (_queue.Count == _maxSize) - { - _queue.Dequeue(); - } - - _queue.Enqueue(item); - } - } - - internal bool Contains(T item) - { - lock (_queueLockObj) - { - return _queue.Contains(item); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FlushManager.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FlushManager.cs deleted file mode 100644 index 850bcf769121..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FlushManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -// // Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using Microsoft.ApplicationInsights.Channel; -using Microsoft.ApplicationInsights.Extensibility.Implementation; -using IChannelTelemetry = Microsoft.ApplicationInsights.Channel.ITelemetry; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// This class handles all the logic for flushing the In Memory buffer to the persistent storage. - /// - internal class FlushManager - { - /// - /// The storage that is used to persist all the transmissions. - /// - private readonly BaseStorageService _storage; - - /// - /// Initializes a new instance of the class. - /// - /// The storage that persists the telemetries. - internal FlushManager(BaseStorageService storage) - { - _storage = storage; - } - - /// - /// Gets or sets the service endpoint. - /// - /// - /// Q: Why flushManager knows about the endpoint? - /// A: Storage stores Transmission objects and Transmission objects contain the endpoint address. - /// - internal Uri EndpointAddress { get; set; } - - - /// - /// Persist the in-memory telemetry items. - /// - internal void Flush(IChannelTelemetry telemetryItem) - { - if (telemetryItem != null) - { - byte[] data = JsonSerializer.Serialize(new[] { telemetryItem }); - Transmission transmission = new Transmission( - EndpointAddress, - data, - "application/x-json-stream", - JsonSerializer.CompressionType); - - _storage.EnqueueAsync(transmission).ConfigureAwait(false).GetAwaiter().GetResult(); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannel.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannel.cs deleted file mode 100644 index c470535cfba0..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannel.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Threading; -using Microsoft.ApplicationInsights.Channel; -using IChannelTelemetry = Microsoft.ApplicationInsights.Channel.ITelemetry; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// Represents a communication channel for sending telemetry to Application Insights via HTTPS. - /// - internal sealed class PersistenceChannel : ITelemetryChannel - { - internal const string TelemetryServiceEndpoint = "https://dc.services.visualstudio.com/v2/track"; - - private readonly FlushManager _flushManager; - - private int _disposeCount; - private readonly BaseStorageService _storage; - private readonly PersistenceTransmitter _transmitter; - - /// - /// Initializes a new instance of the class. - /// - /// - /// Full path of a directory name. Under this folder all the transmissions will be saved. - /// Setting this value groups channels, even from different processes. - /// If 2 (or more) channels has the same storageFolderName only one channel will perform the sending even if the - /// channel is in a different process/AppDomain/Thread. - /// - /// - /// Defines the number of senders. A sender is a long-running thread that sends telemetry batches in intervals defined - /// by . - /// So the amount of senders also defined the maximum amount of http channels opened at the same time. - /// - public PersistenceChannel(string storageDirectoryPath = null, int sendersCount = 1) - { - _storage = new StorageService(); - _storage.Init(storageDirectoryPath); - _transmitter = new PersistenceTransmitter(_storage, sendersCount); - _flushManager = new FlushManager(_storage); - EndpointAddress = TelemetryServiceEndpoint; - } - - /// - /// Gets or sets an interval between each successful sending. - /// - /// - /// On error scenario this value is ignored and the interval will be defined using an exponential back-off - /// algorithm. - /// - public TimeSpan? SendingInterval - { - get => _transmitter.SendingInterval; - set => _transmitter.SendingInterval = value; - } - - - /// - /// Gets or sets the maximum amount of files allowed in storage. When the limit is reached telemetries will be dropped. - /// - public uint MaxTransmissionStorageFilesCapacity - { - get => _storage.MaxFiles; - set => _storage.MaxFiles = value; - } - - /// - /// This flag has no effect. But it is required by base class - /// - public bool? DeveloperMode { get; set; } - - /// - /// Gets or sets the HTTP address where the telemetry is sent. - /// - public string EndpointAddress - { - get => _flushManager.EndpointAddress.ToString(); - - set - { - string address = value ?? TelemetryServiceEndpoint; - _flushManager.EndpointAddress = new Uri(address); - } - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - public void Dispose() - { - if (Interlocked.Increment(ref _disposeCount) == 1) - { - _transmitter?.Dispose(); - } - } - - /// - /// Sends an instance of ITelemetry through the channel. - /// - public void Send(IChannelTelemetry item) - { - _flushManager.Flush(item); - } - - /// - /// No operation, send will always flush. So nothing will be in memory - /// - public void Flush() - { - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannelDebugLog.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannelDebugLog.cs deleted file mode 100644 index 18b9541a2d4d..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannelDebugLog.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Globalization; -using System.IO; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal static class PersistenceChannelDebugLog - { - private static readonly bool _isEnabled = IsEnabledByEnvironment(); - - private static bool IsEnabledByEnvironment() - { - if (bool.TryParse(Environment.GetEnvironmentVariable("UNO_SOURCEGEN_ENABLE_PERSISTENCE_CHANNEL_DEBUG_OUTPUT"), out var enabled)) - { - return enabled; - } - - return false; - } - - public static void WriteLine(string message) - { - if (_isEnabled) - { - Console.WriteLine(message); - } - } - - internal static void WriteException(Exception exception, string format, params string[] args) - { - var message = string.Format(CultureInfo.InvariantCulture, format, args); - WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} Exception: {1}", message, exception.ToString())); - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceTransmitter.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceTransmitter.cs deleted file mode 100644 index 67aeb23b8328..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceTransmitter.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// Implements throttled and persisted transmission of telemetry to Application Insights. - /// - internal class PersistenceTransmitter : IDisposable - { - /// - /// The number of times this object was disposed. - /// - private int _disposeCount; - - /// - /// A list of senders that sends transmissions. - /// - private readonly List _senders = new List(); - - /// - /// The storage that is used to persist all the transmissions. - /// - private readonly BaseStorageService _storage; - - /// - /// Initializes a new instance of the class. - /// - /// The transmissions storage. - /// The number of senders to create. - /// - /// A boolean value that indicates if this class should try and create senders. This is a - /// workaround for unit tests purposes only. - /// - internal PersistenceTransmitter(BaseStorageService storage, int sendersCount, bool createSenders = true) - { - _storage = storage; - if (createSenders) - { - for (int i = 0; i < sendersCount; i++) - { - _senders.Add(new Sender(_storage, this)); - } - } - } - - /// - /// Gets or sets the interval between each successful sending. - /// - internal TimeSpan? SendingInterval { get; set; } - - /// - /// Disposes the object. - /// - public void Dispose() - { - if (Interlocked.Increment(ref _disposeCount) == 1) - { - StopSenders(); - } - } - - /// - /// Stops the senders. - /// - /// As long as there is no Start implementation, this method should only be called from Dispose. - private void StopSenders() - { - if (_senders == null) - { - return; - } - - List stoppedTasks = new List(); - foreach (Sender sender in _senders) - { - stoppedTasks.Add(sender.StopAsync()); - } - - Task.WaitAll(stoppedTasks.ToArray()); - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/Sender.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/Sender.cs deleted file mode 100644 index 49f24a39f016..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/Sender.cs +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Globalization; -using System.Net; -using System.Net.NetworkInformation; -using System.Threading; -using System.Threading.Tasks; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// Fetch transmissions from the storage and sends it. - /// - internal class Sender : IDisposable - { - /// - /// The default sending interval. - /// - private readonly TimeSpan _defaultSendingInterval; - - /// - /// A wait handle that flags the sender when to start sending again. The type is protected for unit test. - /// - protected readonly AutoResetEvent DelayHandler; - - /// - /// Holds the maximum time for the exponential back-off algorithm. The sending interval will grow on every HTTP - /// Exception until this max value. - /// - private readonly TimeSpan _maxIntervalBetweenRetries = TimeSpan.FromHours(1); - - /// - /// When storage is empty it will be queried again after this interval. - /// Decreasing to 5 sec to send first data (users and sessions). - /// - private readonly TimeSpan _sendingIntervalOnNoData = TimeSpan.FromSeconds(5); - - /// - /// A wait handle that is being set when Sender is no longer sending. - /// - private readonly AutoResetEvent _stoppedHandler; - - /// - /// The number of times this object was disposed. - /// - private int _disposeCount; - - /// - /// The amount of time to wait, in the stop method, until the last transmission is sent. - /// If time expires, the stop method will return even if the transmission hasn't been sent. - /// - private readonly TimeSpan _drainingTimeout; - - /// - /// A boolean value that indicates if the sender should be stopped. The sender's while loop is checking this boolean - /// value. - /// - private bool _stopped; - - /// - /// The transmissions storage. - /// - private readonly BaseStorageService _storage; - - /// - /// Holds the transmitter. - /// - private readonly PersistenceTransmitter _transmitter; - - /// - /// Initializes a new instance of the class. - /// - /// The storage that holds the transmissions to send. - /// - /// The persistence transmitter that manages this Sender. - /// The transmitter will be used as a configuration class, it exposes properties like SendingInterval that will be read - /// by Sender. - /// - /// - /// A boolean value that determines if Sender should start sending immediately. This is only - /// used for unit tests. - /// - internal Sender(BaseStorageService storage, PersistenceTransmitter transmitter, bool startSending = true) - { - _stopped = false; - DelayHandler = new AutoResetEvent(false); - _stoppedHandler = new AutoResetEvent(false); - _drainingTimeout = TimeSpan.FromSeconds(100); - _defaultSendingInterval = TimeSpan.FromSeconds(5); - - _transmitter = transmitter; - _storage = storage; - - if (startSending) - { - // It is currently possible for the long - running task to be executed(and thereby block during WaitOne) on the UI thread when - // called by a task scheduled on the UI thread. Explicitly specifying TaskScheduler.Default - // when calling StartNew guarantees that Sender never blocks the main thread. - Task.Factory.StartNew(SendLoop, CancellationToken.None, TaskCreationOptions.LongRunning, - TaskScheduler.Default) - .ContinueWith( - t => PersistenceChannelDebugLog.WriteException(t.Exception, "Sender: Failure in SendLoop"), - TaskContinuationOptions.OnlyOnFaulted); - } - } - - /// - /// Gets the interval between each successful sending. - /// - private TimeSpan SendingInterval - { - get - { - if (_transmitter.SendingInterval != null) - { - return _transmitter.SendingInterval.Value; - } - - return _defaultSendingInterval; - } - } - - /// - /// Disposes the managed objects. - /// - public void Dispose() - { - if (Interlocked.Increment(ref _disposeCount) == 1) - { - StopAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - - DelayHandler.Dispose(); - _stoppedHandler.Dispose(); - } - } - - /// - /// Stops the sender. - /// - internal Task StopAsync() - { - // After delayHandler is set, a sending iteration will immediately start. - // Setting stopped to true, will cause the iteration to skip the actual sending and stop immediately. - _stopped = true; - DelayHandler.Set(); - - // if delayHandler was set while a transmission was being sent, the return task will wait for it to finish, for an additional second, - // before it will mark the task as completed. - return Task.Run(() => - { - try - { - _stoppedHandler.WaitOne(_drainingTimeout); - } - catch (ObjectDisposedException) - { - } - }); - } - - /// - /// Send transmissions in a loop. - /// - protected void SendLoop() - { - TimeSpan prevSendingInterval = TimeSpan.Zero; - TimeSpan sendingInterval = _sendingIntervalOnNoData; - try - { - while (!_stopped) - { - using (StorageTransmission transmission = _storage.Peek()) - { - if (_stopped) - { - // This second verification is required for cases where 'stopped' was set while peek was happening. - // Once the actual sending starts the design is to wait until it finishes and deletes the transmission. - // So no extra validation is required. - break; - } - - // If there is a transmission to send - send it. - if (transmission != null) - { - bool shouldRetry = Send(transmission, ref sendingInterval); - if (!shouldRetry) - { - // If retry is not required - delete the transmission. - _storage.Delete(transmission); - } - } - else - { - sendingInterval = _sendingIntervalOnNoData; - } - } - - LogInterval(prevSendingInterval, sendingInterval); - DelayHandler.WaitOne(sendingInterval); - prevSendingInterval = sendingInterval; - } - - _stoppedHandler.Set(); - } - catch (ObjectDisposedException) - { - } - } - - /// - /// Sends a transmission and handle errors. - /// - /// The transmission to send. - /// - /// When this value returns it will hold a recommendation for when to start the next sending - /// iteration. - /// - /// True, if there was sent error and we need to retry sending, otherwise false. - protected virtual bool Send(StorageTransmission transmission, ref TimeSpan nextSendInterval) - { - try - { - if (transmission != null) - { - bool isConnected = NetworkInterface.GetIsNetworkAvailable(); - - // there is no internet connection available, return than. - if (!isConnected) - { - PersistenceChannelDebugLog.WriteLine( - "Cannot send data to the server. Internet connection is not available"); - return true; - } - - transmission.SendAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - - // After a successful sending, try immediately to send another transmission. - nextSendInterval = SendingInterval; - } - } - catch (WebException e) - { - int? statusCode = GetStatusCode(e); - nextSendInterval = CalculateNextInterval(statusCode, nextSendInterval, _maxIntervalBetweenRetries); - return IsRetryable(statusCode, e.Status); - } - catch (Exception e) - { - nextSendInterval = CalculateNextInterval(null, nextSendInterval, _maxIntervalBetweenRetries); - PersistenceChannelDebugLog.WriteException(e, "Unknown exception during sending"); - } - - return false; - } - - /// - /// Log next interval. Only log the interval when it changes by more then a minute. So if interval grow by 1 minute or - /// decreased by 1 minute it will be logged. - /// Logging every interval will just make the log noisy. - /// - private static void LogInterval(TimeSpan prevSendInterval, TimeSpan nextSendInterval) - { - if (Math.Abs(nextSendInterval.TotalSeconds - prevSendInterval.TotalSeconds) > 60) - { - PersistenceChannelDebugLog.WriteLine("next sending interval: " + nextSendInterval); - } - } - - /// - /// Return the status code from the web exception or null if no such code exists. - /// - private static int? GetStatusCode(WebException e) - { - if (e.Response is HttpWebResponse httpWebResponse) - { - return (int)httpWebResponse.StatusCode; - } - - return null; - } - - /// - /// Returns true if or are retryable. - /// - private static bool IsRetryable(int? httpStatusCode, WebExceptionStatus webExceptionStatus) - { - switch (webExceptionStatus) - { - case WebExceptionStatus.ProxyNameResolutionFailure: - case WebExceptionStatus.NameResolutionFailure: - case WebExceptionStatus.Timeout: - case WebExceptionStatus.ConnectFailure: - return true; - } - - if (httpStatusCode == null) - { - return false; - } - - switch (httpStatusCode.Value) - { - case 503: // Server in maintenance. - case 408: // invalid request - case 500: // Internal Server Error - case 502: // Bad Gateway, can be common when there is no network. - case 511: // Network Authentication Required - return true; - } - - return false; - } - - /// - /// Calculates the next interval using exponential back-off algorithm (with the exceptions of few error codes that - /// reset the interval to . - /// - private TimeSpan CalculateNextInterval(int? httpStatusCode, TimeSpan currentSendInterval, TimeSpan maxInterval) - { - // if item is expired, no need for exponential back-off - if (httpStatusCode != null && httpStatusCode.Value == 400 /* expired */) - { - return SendingInterval; - } - - // exponential back-off. - if (Math.Abs(currentSendInterval.TotalSeconds) < 1) - { - return TimeSpan.FromSeconds(1); - } - - double nextIntervalInSeconds = Math.Min(currentSendInterval.TotalSeconds * 2, maxInterval.TotalSeconds); - - return TimeSpan.FromSeconds(nextIntervalInSeconds); - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingCollection.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingCollection.cs deleted file mode 100644 index 051adda5cea0..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingCollection.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal abstract class SnapshottingCollection : ICollection - where TCollection : class, ICollection - { - protected readonly TCollection Collection; - protected TCollection snapshot; - - protected SnapshottingCollection(TCollection collection) - { - Debug.Assert(collection != null, "collection"); - Collection = collection; - } - - public int Count => GetSnapshot().Count; - - public bool IsReadOnly => false; - - public void Add(TItem item) - { - lock (Collection) - { - Collection.Add(item); - snapshot = default; - } - } - - public void Clear() - { - lock (Collection) - { - Collection.Clear(); - snapshot = default; - } - } - - public bool Contains(TItem item) - { - return GetSnapshot().Contains(item); - } - - public void CopyTo(TItem[] array, int arrayIndex) - { - GetSnapshot().CopyTo(array, arrayIndex); - } - - public bool Remove(TItem item) - { - lock (Collection) - { - bool removed = Collection.Remove(item); - if (removed) - { - snapshot = default; - } - - return removed; - } - } - - public IEnumerator GetEnumerator() - { - return GetSnapshot().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - protected abstract TCollection CreateSnapshot(TCollection collection); - - protected TCollection GetSnapshot() - { - TCollection localSnapshot = snapshot; - if (localSnapshot == null) - { - lock (Collection) - { - snapshot = CreateSnapshot(Collection); - localSnapshot = snapshot; - } - } - - return localSnapshot; - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingDictionary.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingDictionary.cs deleted file mode 100644 index 83897f5d0069..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingDictionary.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System.Collections.Generic; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal class SnapshottingDictionary : - SnapshottingCollection, IDictionary>, IDictionary - { - public SnapshottingDictionary() - : base(new Dictionary()) - { - } - - public ICollection Keys => GetSnapshot().Keys; - - public ICollection Values => GetSnapshot().Values; - - public TValue this[TKey key] - { - get => GetSnapshot()[key]; - - set - { - lock (Collection) - { - Collection[key] = value; - snapshot = null; - } - } - } - - public void Add(TKey key, TValue value) - { - lock (Collection) - { - Collection.Add(key, value); - snapshot = null; - } - } - - public bool ContainsKey(TKey key) - { - return GetSnapshot().ContainsKey(key); - } - - public bool Remove(TKey key) - { - lock (Collection) - { - bool removed = Collection.Remove(key); - if (removed) - { - snapshot = null; - } - - return removed; - } - } - - public bool TryGetValue(TKey key, out TValue value) - { - return GetSnapshot().TryGetValue(key, out value); - } - - protected sealed override IDictionary CreateSnapshot(IDictionary collection) - { - return new Dictionary(collection); - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageService.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageService.cs deleted file mode 100644 index b43163c0d495..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageService.cs +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.Channel; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal sealed class StorageService : BaseStorageService - { - private const string DefaultStorageFolderName = "TelemetryStorageService"; - private readonly FixedSizeQueue _deletedFilesQueue = new FixedSizeQueue(10); - - private readonly object _peekLockObj = new object(); - private readonly object _storageFolderLock = new object(); - private string _storageDirectoryPath; - private string _storageDirectoryPathUsed; - private long _storageCountFiles; - private bool _storageFolderInitialized; - private long _storageSize; - private uint _transmissionsDropped; - - /// - /// Gets the storage's folder name. - /// - internal override string StorageDirectoryPath => _storageDirectoryPath; - - /// - /// Gets the storage folder. If storage folder couldn't be created, null will be returned. - /// - private string StorageFolder - { - get - { - if (!_storageFolderInitialized) - { - lock (_storageFolderLock) - { - if (!_storageFolderInitialized) - { - try - { - _storageDirectoryPathUsed = _storageDirectoryPath; - - if (!Directory.Exists(_storageDirectoryPathUsed)) - { - Directory.CreateDirectory(_storageDirectoryPathUsed); - } - } - catch (Exception e) - { - _storageDirectoryPathUsed = null; - PersistenceChannelDebugLog.WriteException(e, "Failed to create storage folder"); - } - - _storageFolderInitialized = true; - } - } - } - - return _storageDirectoryPathUsed; - } - } - - internal override void Init(string storageDirectoryPath) - { - PeekedTransmissions = new SnapshottingDictionary(); - - VerifyOrSetDefaultStorageDirectoryPath(storageDirectoryPath); - - CapacityInBytes = 10 * 1024 * 1024; // 10 MB - MaxFiles = 100; - - Task.Run(DeleteObsoleteFiles) - .ContinueWith( - task => - { - PersistenceChannelDebugLog.WriteException( - task.Exception, - "Storage: Unhandled exception in DeleteObsoleteFiles"); - }, - TaskContinuationOptions.OnlyOnFaulted); - } - - private void VerifyOrSetDefaultStorageDirectoryPath(string desireStorageDirectoryPath) - { - if (string.IsNullOrEmpty(desireStorageDirectoryPath)) - { - _storageDirectoryPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - DefaultStorageFolderName); - } - else - { - if (!Path.IsPathRooted(desireStorageDirectoryPath)) - { - throw new ArgumentException($"{nameof(desireStorageDirectoryPath)} need to be rooted (full path)"); - } - - _storageDirectoryPath = desireStorageDirectoryPath; - } - } - - /// - /// Reads an item from the storage. Order is Last-In-First-Out. - /// When the Transmission is no longer needed (it was either sent or failed with a non-retryable error) it should be - /// disposed. - /// - internal override StorageTransmission Peek() - { - IEnumerable files = GetFiles("*.trn", 50); - - lock (_peekLockObj) - { - foreach (string file in files) - { - try - { - // if a file was peeked before, skip it (wait until it is disposed). - if (PeekedTransmissions.ContainsKey(file) == false && - _deletedFilesQueue.Contains(file) == false) - { - // Load the transmission from disk. - StorageTransmission storageTransmissionItem = LoadTransmissionFromFileAsync(file) - .ConfigureAwait(false).GetAwaiter().GetResult(); - - // when item is disposed it should be removed from the peeked list. - storageTransmissionItem.Disposing = item => OnPeekedItemDisposed(file); - - // add the transmission to the list. - PeekedTransmissions.Add(file, storageTransmissionItem.FullFilePath); - return storageTransmissionItem; - } - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException( - e, - "Failed to load an item from the storage. file: {0}", - file); - } - } - } - - return null; - } - - internal override void Delete(StorageTransmission item) - { - try - { - if (StorageFolder == null) - { - return; - } - - // Initial storage size calculation. - CalculateSize(); - - long fileSize = GetSize(item.FileName); - File.Delete(Path.Combine(StorageFolder, item.FileName)); - - _deletedFilesQueue.Enqueue(item.FileName); - - // calculate size - Interlocked.Add(ref _storageSize, -fileSize); - Interlocked.Decrement(ref _storageCountFiles); - } - catch (IOException e) - { - PersistenceChannelDebugLog.WriteException(e, "Failed to delete a file. file: {0}", item == null ? "null" : item.FullFilePath); - } - } - - internal override async Task EnqueueAsync(Transmission transmission) - { - try - { - if (transmission == null || StorageFolder == null) - { - return; - } - - // Initial storage size calculation. - CalculateSize(); - - if ((ulong)_storageSize >= CapacityInBytes || _storageCountFiles >= MaxFiles) - { - // if max storage capacity has reached, drop the transmission (but log every 100 lost transmissions). - if (_transmissionsDropped++ % 100 == 0) - { - PersistenceChannelDebugLog.WriteLine("Total transmissions dropped: " + _transmissionsDropped); - } - - return; - } - - // Writes content to a temporary file and only then rename to avoid the Peek from reading the file before it is being written. - // Creates the temp file name - string tempFileName = Guid.NewGuid().ToString("N"); - - // Now that the file got created we can increase the files count - Interlocked.Increment(ref _storageCountFiles); - - // Saves transmission to the temp file - await SaveTransmissionToFileAsync(transmission, tempFileName).ConfigureAwait(false); - - // Now that the file is written increase storage size. - long temporaryFileSize = GetSize(tempFileName); - Interlocked.Add(ref _storageSize, temporaryFileSize); - - // Creates a new file name - string now = DateTime.UtcNow.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture); - string newFileName = string.Format(CultureInfo.InvariantCulture, "{0}_{1}.trn", now, tempFileName); - - // Renames the file - File.Move(Path.Combine(StorageFolder, tempFileName), Path.Combine(StorageFolder, newFileName)); - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException(e, "EnqueueAsync"); - } - } - - private async Task SaveTransmissionToFileAsync(Transmission transmission, string file) - { - try - { - using (Stream stream = File.OpenWrite(Path.Combine(StorageFolder, file))) - { - await StorageTransmission.SaveAsync(transmission, stream).ConfigureAwait(false); - } - } - catch (UnauthorizedAccessException) - { - string message = - string.Format( - CultureInfo.InvariantCulture, - "Failed to save transmission to file. UnauthorizedAccessException. File path: {0}, FileName: {1}", - StorageFolder, file); - PersistenceChannelDebugLog.WriteLine(message); - throw; - } - } - - private async Task LoadTransmissionFromFileAsync(string file) - { - try - { - using (Stream stream = File.OpenRead(Path.Combine(StorageFolder, file))) - { - StorageTransmission storageTransmissionItem = - await StorageTransmission.CreateFromStreamAsync(stream, file).ConfigureAwait(false); - return storageTransmissionItem; - } - } - catch (Exception e) - { - string message = - string.Format( - CultureInfo.InvariantCulture, - "Failed to load transmission from file. File path: {0}, FileName: {1}, Exception: {2}", - "storageFolderName", file, e); - PersistenceChannelDebugLog.WriteLine(message); - throw; - } - } - - /// - /// Get files from . - /// - /// Define the logic for sorting the files. - /// Defines a file extension. This method will return only files with this extension. - /// - /// Define how many files to return. This can be useful when the directory has a lot of files, in that case - /// GetFilesAsync will have a performance hit. - /// - private IEnumerable GetFiles(string filterByExtension, int top) - { - try - { - if (StorageFolder != null) - { - return Directory.GetFiles(StorageFolder, filterByExtension).Take(top); - } - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException(e, "Peek failed while get files from storage."); - } - - return Enumerable.Empty(); - } - - /// - /// Gets a file's size. - /// - private long GetSize(string file) - { - using (FileStream stream = File.OpenRead(Path.Combine(StorageFolder, file))) - { - return stream.Length; - } - } - - /// - /// Check the storage limits and return true if they reached. - /// Storage limits are defined by the number of files and the total size on disk. - /// - private void CalculateSize() - { - string[] storageFiles = Directory.GetFiles(StorageFolder, "*.*"); - - _storageCountFiles = storageFiles.Length; - - long storageSizeInBytes = 0; - foreach (string file in storageFiles) - { - storageSizeInBytes += GetSize(file); - } - - _storageSize = storageSizeInBytes; - } - - /// - /// Enqueue is saving a transmission to a tmp file and after a successful write operation it renames it to a - /// trn file. - /// A file without a trn extension is ignored by Storage.Peek(), so if a process is taken down before rename - /// happens - /// it will stay on the disk forever. - /// This thread deletes files with the tmp extension that exists on disk for more than 5 minutes. - /// - private void DeleteObsoleteFiles() - { - try - { - IEnumerable files = GetFiles("*.tmp", 50); - foreach (string file in files) - { - DateTime creationTime = File.GetCreationTimeUtc(Path.Combine(StorageFolder, file)); - // if the file is older then 5 minutes - delete it. - if (DateTime.UtcNow - creationTime >= TimeSpan.FromMinutes(5)) - { - File.Delete(Path.Combine(StorageFolder, file)); - } - } - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException(e, "Failed to delete tmp files."); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageTransmission.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageTransmission.cs deleted file mode 100644 index 0070b3ede03b..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageTransmission.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Globalization; -using System.IO; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.Channel; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal class StorageTransmission : Transmission, IDisposable - { - internal Action Disposing; - - protected StorageTransmission(string fullPath, Uri address, byte[] content, string contentType, - string contentEncoding) - : base(address, content, contentType, contentEncoding) - { - FullFilePath = fullPath; - FileName = Path.GetFileName(fullPath); - } - - internal string FileName { get; } - - internal string FullFilePath { get; } - - /// - /// Disposing the storage transmission. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Creates a new transmission from the specified . - /// - /// Return transmission loaded from file; return null if the file is corrupted. - internal static async Task CreateFromStreamAsync(Stream stream, string fileName) - { - StreamReader reader = new StreamReader(stream); - Uri address = await ReadAddressAsync(reader).ConfigureAwait(false); - string contentType = await ReadHeaderAsync(reader, "Content-Type").ConfigureAwait(false); - string contentEncoding = await ReadHeaderAsync(reader, "Content-Encoding").ConfigureAwait(false); - byte[] content = await ReadContentAsync(reader).ConfigureAwait(false); - return new StorageTransmission(fileName, address, content, contentType, contentEncoding); - } - - /// - /// Saves the transmission to the specified . - /// - internal static async Task SaveAsync(Transmission transmission, Stream stream) - { - StreamWriter writer = new StreamWriter(stream); - try - { - await writer.WriteLineAsync(transmission.EndpointAddress.ToString()).ConfigureAwait(false); - await writer.WriteLineAsync("Content-Type" + ":" + transmission.ContentType).ConfigureAwait(false); - await writer.WriteLineAsync("Content-Encoding" + ":" + transmission.ContentEncoding) - .ConfigureAwait(false); - await writer.WriteLineAsync(string.Empty).ConfigureAwait(false); - await writer.WriteAsync(Convert.ToBase64String(transmission.Content)).ConfigureAwait(false); - } - finally - { - writer.Flush(); - } - } - - private static async Task ReadHeaderAsync(TextReader reader, string headerName) - { - string line = await reader.ReadLineAsync().ConfigureAwait(false); - if (string.IsNullOrEmpty(line)) - { - throw new FormatException(string.Format(CultureInfo.InvariantCulture, "{0} header is expected.", - headerName)); - } - - string[] parts = line.Split(':'); - if (parts.Length != 2) - { - throw new FormatException(string.Format(CultureInfo.InvariantCulture, - "Unexpected header format. {0} header is expected. Actual header: {1}", headerName, line)); - } - - if (parts[0] != headerName) - { - throw new FormatException(string.Format(CultureInfo.InvariantCulture, - "{0} header is expected. Actual header: {1}", headerName, line)); - } - - return parts[1].Trim(); - } - - private static async Task ReadAddressAsync(TextReader reader) - { - string addressLine = await reader.ReadLineAsync().ConfigureAwait(false); - if (string.IsNullOrEmpty(addressLine)) - { - throw new FormatException("Transmission address is expected."); - } - - Uri address = new Uri(addressLine); - return address; - } - - private static async Task ReadContentAsync(TextReader reader) - { - string content = await reader.ReadToEndAsync().ConfigureAwait(false); - if (string.IsNullOrEmpty(content) || content == Environment.NewLine) - { - throw new FormatException("Content is expected."); - } - - return Convert.FromBase64String(content); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - Action disposingDelegate = Disposing; - disposingDelegate?.Invoke(this); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryClient.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryClient.cs deleted file mode 100644 index e38c207ae50b..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryClient.cs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.Extensibility; -using Microsoft.DotNet.PlatformAbstractions; - -namespace Uno.UI.SourceGenerators.Telemetry -{ - public class Telemetry - { - internal static string CurrentSessionId; - private TelemetryClient _client; - private Dictionary _commonProperties; - private Dictionary _commonMeasurements; - private TelemetryConfiguration _telemetryConfig; - private Task _trackEventTask; - private string _storageDirectoryPath; - private string _settingsStorageDirectoryPath; - private PersistenceChannel.PersistenceChannel _persistenceChannel; - private const string InstrumentationKey = "9a44058e-1913-4721-a979-9582ab8bedce"; - private const string TelemetryOptout = "UNO_PLATFORM_TELEMETRY_OPTOUT"; - - public bool Enabled { get; } - - public Telemetry() : this(null) { } - - public Telemetry(Func enabledProvider) : this(null, enabledProvider: enabledProvider) { } - - public Telemetry( - string sessionId, - bool blockThreadInitialization = false, - Func enabledProvider = null) - { - if (bool.TryParse(Environment.GetEnvironmentVariable(TelemetryOptout), out var telemetryOptOut)) - { - Enabled = !telemetryOptOut; - } - else - { - Enabled = !enabledProvider?.Invoke() ?? true; - } - - if (!Enabled) - { - return; - } - - // Store the session ID in a static field so that it can be reused - CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); - if (blockThreadInitialization) - { - InitializeTelemetry(); - } - else - { - //initialize in task to offload to parallel thread - _trackEventTask = Task.Run(() => InitializeTelemetry()); - } - } - - public void TrackEvent( - string eventName, - (string key, string value)[] properties, - (string key, double value)[] measurements) - => TrackEvent(eventName, properties?.ToDictionary(p => p.key, p => p.value), measurements?.ToDictionary(p => p.key, p => p.value)); - - public void TrackEvent(string eventName, IDictionary properties, - IDictionary measurements) - { - if (!Enabled) - { - return; - } - - //continue the task in different threads - _trackEventTask = _trackEventTask.ContinueWith( - x => TrackEventTask(eventName, properties, measurements) - ); - } - - public void Flush() - { - if (!Enabled || _trackEventTask == null) - { - return; - } - - // Skip the wait if the task has not yet been activated - if (_trackEventTask.Status != TaskStatus.WaitingForActivation) - { - _trackEventTask.Wait(TimeSpan.FromSeconds(1)); - } - } - - public void Dispose() - { - _persistenceChannel?.Dispose(); - _telemetryConfig?.Dispose(); - } - - public void ThreadBlockingTrackEvent(string eventName, IDictionary properties, IDictionary measurements) - { - if (!Enabled) - { - return; - } - TrackEventTask(eventName, properties, measurements); - } - - private void InitializeTelemetry() - { - try - { - _storageDirectoryPath = Path.Combine(Path.GetTempPath(), ".uno", "telemetry"); - - // Store the settings on in the user profile for linux - if (RuntimeEnvironment.OperatingSystemPlatform == Platform.Linux) - { - _settingsStorageDirectoryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".uno", "telemetry"); - } - else - { - _settingsStorageDirectoryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Uno Platform", "telemetry"); - } - - _persistenceChannel = new PersistenceChannel.PersistenceChannel( - storageDirectoryPath: _storageDirectoryPath); - - _persistenceChannel.SendingInterval = TimeSpan.FromMilliseconds(1); - - _commonProperties = new TelemetryCommonProperties(_settingsStorageDirectoryPath).GetTelemetryCommonProperties(); - _commonMeasurements = new Dictionary(); - - _telemetryConfig = new TelemetryConfiguration { InstrumentationKey = InstrumentationKey }; - _client = new TelemetryClient(_telemetryConfig); - _client.InstrumentationKey = InstrumentationKey; - _client.Context.User.Id = _commonProperties[TelemetryCommonProperties.MachineId]; - _client.Context.Session.Id = CurrentSessionId; - _client.Context.Device.OperatingSystem = RuntimeEnvironment.OperatingSystem; - } - catch (Exception e) - { - _client = null; - // we don't want to fail the tool if telemetry fails. - Debug.Fail(e.ToString()); - } - } - - private void TrackEventTask( - string eventName, - IDictionary properties, - IDictionary measurements) - { - if (_client == null) - { - return; - } - - try - { - var eventProperties = GetEventProperties(properties); - var eventMeasurements = GetEventMeasures(measurements); - - _client.TrackEvent(PrependProducerNamespace(eventName), eventProperties, eventMeasurements); - } - catch (Exception e) - { - Debug.Fail(e.ToString()); - } - } - - private static string PrependProducerNamespace(string eventName) - { - return "uno/generation/" + eventName; - } - - private Dictionary GetEventMeasures(IDictionary measurements) - { - var eventMeasurements = new Dictionary(_commonMeasurements); - if (measurements != null) - { - foreach (var measurement in measurements) - { - eventMeasurements[measurement.Key] = measurement.Value; - } - } - return eventMeasurements; - } - - private Dictionary GetEventProperties(IDictionary properties) - { - if (properties != null) - { - var eventProperties = new Dictionary(_commonProperties); - foreach (var property in properties) - { - eventProperties[property.Key] = property.Value; - } - return eventProperties; - } - else - { - return _commonProperties; - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryCommonProperties.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryCommonProperties.cs deleted file mode 100644 index 977160813fc2..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryCommonProperties.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using Microsoft.DotNet.PlatformAbstractions; -using System.IO; -using System.Security; -using Microsoft.Win32; -using System.Linq; -using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; -using RuntimeInformation = System.Runtime.InteropServices.RuntimeInformation; -using System.Runtime.InteropServices; -using System.Diagnostics; -using System.Reflection; -using Uno.UI.SourceGenerators.Helpers; -using System.Security.Cryptography; -using System.Net.NetworkInformation; - -namespace Uno.UI.SourceGenerators.Telemetry -{ - internal class TelemetryCommonProperties - { - public TelemetryCommonProperties( - string storageDirectoryPath, - Func getCurrentDirectory = null) - { - _getCurrentDirectory = getCurrentDirectory ?? Directory.GetCurrentDirectory; - _storageDirectoryPath = storageDirectoryPath; - } - - private Func _getCurrentDirectory; - private string _storageDirectoryPath; - public const string OSVersion = "OS Version"; - public const string OSPlatform = "OS Platform"; - public const string OutputRedirected = "Output Redirected"; - public const string RuntimeId = "Runtime Id"; - public const string MachineId = "Machine ID"; - public const string ProductVersion = "Product Version"; - public const string TelemetryProfile = "Telemetry Profile"; - public const string CurrentPathHash = "Current Path Hash"; - public const string KernelVersion = "Kernel Version"; - - private const string TelemetryProfileEnvironmentVariable = "DOTNET_CLI_TELEMETRY_PROFILE"; - - public Dictionary GetTelemetryCommonProperties() => new Dictionary - { - {OSVersion, RuntimeEnvironment.OperatingSystemVersion}, - {OSPlatform, RuntimeEnvironment.OperatingSystemPlatform.ToString()}, - {OutputRedirected, Console.IsOutputRedirected.ToString()}, - {RuntimeId, RuntimeEnvironment.GetRuntimeIdentifier()}, - {ProductVersion, GetProductVersion()}, - {TelemetryProfile, Environment.GetEnvironmentVariable(TelemetryProfileEnvironmentVariable)}, - {MachineId, GetMachineId()}, - {CurrentPathHash, HashBuilder.Build(_getCurrentDirectory())}, - {KernelVersion, GetKernelVersion()}, - }; - - private string GetMachineId() - { - var machineHashPath = Path.Combine(_storageDirectoryPath, ".machinehash"); - - if (File.Exists(machineHashPath)) - { - if (File.ReadAllText(machineHashPath) is { Length: 32 /* hash */ or 36 /* guid */ } readHash) - { - return readHash; - } - } - - string hash = null; - try - { - var macAddr = - ( - from nic in NetworkInterface.GetAllNetworkInterfaces() - where nic.OperationalStatus == OperationalStatus.Up - select nic.GetPhysicalAddress().ToString() - ).FirstOrDefault(); - - hash = HashBuilder.Build(macAddr); - - if (!Directory.Exists(Path.GetDirectoryName(machineHashPath))) - { - Directory.CreateDirectory(Path.GetDirectoryName(machineHashPath)); - } - - File.WriteAllText(machineHashPath, hash); - } - catch (Exception e) - { - Debug.Fail($"Failed to get Mac address: {e}"); - - // if the hash was set, but the write failed, let's continue. - hash ??= Guid.NewGuid().ToString(); - } - - return hash; - } - - private string GetProductVersion() - { - if (this.GetType().Assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute)).FirstOrDefault() is AssemblyInformationalVersionAttribute attribute) - { - return attribute.InformationalVersion; - } - - return "Unknown"; - } - - - /// - /// Returns a string identifying the OS kernel. - /// For Unix this currently comes from "uname -srv". - /// For Windows this currently comes from RtlGetVersion(). - /// - /// Here are some example values: - /// - /// Alpine.36 Linux 4.9.60-linuxkit-aufs #1 SMP Mon Nov 6 16:00:12 UTC 2017 - /// Centos.73 Linux 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 15:04:05 UTC 2017 - /// Debian.87 Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1+deb8u2 (2017-03-07) - /// Debian.90 Linux 4.9.0-2-amd64 #1 SMP Debian 4.9.18-1 (2017-03-30) - /// fedora.25 Linux 4.11.3-202.fc25.x86_64 #1 SMP Mon Jun 5 16:38:21 UTC 2017 - /// Fedora.26 Linux 4.14.15-200.fc26.x86_64 #1 SMP Wed Jan 24 04:26:15 UTC 2018 - /// Fedora.27 Linux 4.14.14-300.fc27.x86_64 #1 SMP Fri Jan 19 13:19:54 UTC 2018 - /// OpenSuse.423 Linux 4.4.104-39-default #1 SMP Thu Jan 4 08:11:03 UTC 2018 (7db1912) - /// RedHat.69 Linux 2.6.32-696.20.1.el6.x86_64 #1 SMP Fri Jan 12 15:07:59 EST 2018 - /// RedHat.72 Linux 3.10.0-514.21.1.el7.x86_64 #1 SMP Sat Apr 22 02:41:35 EDT 2017 - /// RedHat.73 Linux 3.10.0-514.21.1.el7.x86_64 #1 SMP Sat Apr 22 02:41:35 EDT 2017 - /// SLES.12 Linux 4.4.103-6.38-default #1 SMP Mon Dec 25 20:44:33 UTC 2017 (e4b9067) - /// suse.422 Linux 4.4.49-16-default #1 SMP Sun Feb 19 17:40:35 UTC 2017 (70e9954) - /// Ubuntu.1404 Linux 3.19.0-65-generic #73~14.04.1-Ubuntu SMP Wed Jun 29 21:05:22 UTC 2016 - /// Ubuntu.1604 Linux 4.13.0-1005-azure #7-Ubuntu SMP Mon Jan 8 21:37:36 UTC 2018 - /// Ubuntu.1604.WSL Linux 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014 - /// Ubuntu.1610 Linux 4.8.0-45-generic #48-Ubuntu SMP Fri Mar 24 11:46:39 UTC 2017 - /// Ubuntu.1704 Linux 4.10.0-19-generic #21-Ubuntu SMP Thu Apr 6 17:04:57 UTC 2017 - /// Ubuntu.1710 Linux 4.13.0-25-generic #29-Ubuntu SMP Mon Jan 8 21:14:41 UTC 2018 - /// OSX1012 Darwin 16.7.0 Darwin Kernel Version 16.7.0: Thu Jan 11 22:59:40 PST 2018; root:xnu-3789.73.8~1/RELEASE_X86_64 - /// OSX1013 Darwin 17.4.0 Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64 - /// Windows.10 Microsoft Windows 10.0.14393 - /// Windows.10.Core Microsoft Windows 10.0.14393 - /// Windows.10.Nano Microsoft Windows 10.0.14393 - /// Windows.7 Microsoft Windows 6.1.7601 S - /// Windows.81 Microsoft Windows 6.3.9600 - /// - private static string GetKernelVersion() - { - return RuntimeInformation.OSDescription; - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj b/src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj index 483b92d29ba2..2675e89377fa 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj @@ -12,8 +12,7 @@ - - + diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.Telemetry.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.Telemetry.cs index f04a169f51b3..ae4596b71890 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.Telemetry.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.Telemetry.cs @@ -9,18 +9,21 @@ using Uno.Roslyn; using Microsoft.CodeAnalysis; using Uno.Extensions; -using Uno.UI.SourceGenerators.Telemetry; +using Uno.DevTools.Telemetry; namespace Uno.UI.SourceGenerators.XamlGenerator { internal partial class XamlCodeGeneration { - private Telemetry.Telemetry _telemetry; + private const string InstrumentationKey = "9a44058e-1913-4721-a979-9582ab8bedce"; + + private Telemetry _telemetry; public bool IsRunningCI => !Environment.GetEnvironmentVariable("TF_BUILD").IsNullOrEmpty() // https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?tabs=yaml&view=azure-devops#system-variables || !Environment.GetEnvironmentVariable("TRAVIS").IsNullOrEmpty() // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables || !Environment.GetEnvironmentVariable("JENKINS_URL").IsNullOrEmpty() // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables + || !Environment.GetEnvironmentVariable("GITHUB_REPOSITORY").IsNullOrEmpty() // https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables || !Environment.GetEnvironmentVariable("APPVEYOR").IsNullOrEmpty(); // https://www.appveyor.com/docs/environment-variables/ private void InitTelemetry(GeneratorExecutionContext context) @@ -32,7 +35,24 @@ private void InitTelemetry(GeneratorExecutionContext context) || telemetryOptOut.Equals("1", StringComparison.OrdinalIgnoreCase) || _isDesignTimeBuild; - _telemetry = new Telemetry.Telemetry(isTelemetryOptout); + string getCurrentDirectory() + { + var solutionDir = context.GetMSBuildPropertyValue("SolutionDir"); + if (!string.IsNullOrEmpty(solutionDir)) + { + return solutionDir; + } + + var projectDir = context.GetMSBuildPropertyValue("MSBuildProjectFullPath"); + if (!string.IsNullOrEmpty(projectDir)) + { + return projectDir; + } + + return Environment.CurrentDirectory; + } + + _telemetry = new Telemetry(InstrumentationKey, enabledProvider: isTelemetryOptout, currentDirectoryProvider: getCurrentDirectory); } private bool IsTelemetryEnabled => _telemetry?.Enabled ?? false; diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs index ad2efa1db815..9c46c9a78a15 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs @@ -12,7 +12,7 @@ using Uno.Roslyn; using Microsoft.CodeAnalysis; using Uno.Extensions; -using Uno.UI.SourceGenerators.Telemetry; +using Uno.DevTools.Telemetry; using Uno.UI.Xaml; using System.Drawing; using __uno::Uno.Xaml; diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs index 0db416f50d22..e0de4c87b13f 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis; using Uno.Roslyn; using Uno.UI.SourceGenerators.Helpers; -using Uno.UI.SourceGenerators.Telemetry; +using Uno.DevTools.Telemetry; namespace Uno.UI.SourceGenerators.XamlGenerator { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs index 715bdc5be6b4..1af7fa57c6be 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs @@ -1303,7 +1303,7 @@ private void BuildCompiledBindings(IndentedStringBuilder writer) if (HasMarkupExtensionNeedingComponent(component.XamlObject) && IsDependencyObject(component.XamlObject)) { - writer.AppendLineIndented($"owner.{component.MemberName}.UpdateResourceBindings(resourceContextProvider: {(component.ResourceContext is { } ctx ? $"owner.{ctx.MemberName}" : "null")});"); + writer.AppendLineIndented($"owner.{component.MemberName}.UpdateResourceBindings({(component.ResourceContext is { } ctx ? $"resourceContextProvider: owner.{ctx.MemberName}" : "")});"); } } } @@ -4637,7 +4637,7 @@ private string GetCustomMarkupExtensionValue(XamlMemberDefinition member, string var property = FindProperty(member.Member); var declaringType = property?.ContainingType; - var propertyType = property?.FindDependencyPropertyType(); + var propertyType = property?.FindDependencyPropertyType(unwrapNullable: false); if (declaringType == null || propertyType == null) { @@ -4683,7 +4683,8 @@ private string GetCustomMarkupExtensionValue(XamlMemberDefinition member, string var provider = $"{globalized.MarkupHelper}.CreateParserContext({providerDetails.JoinBy(", ")})"; var provideValue = $"(({globalized.IMarkupExtensionOverrides})new {globalized.MarkupType}{markupInitializer}).ProvideValue({provider})"; - if (IsImplementingInterface(propertyType, Generation.IConvertibleSymbol.Value)) + var unwrappedPropertyType = propertyType.IsNullable(out var nullableInnerType) ? nullableInnerType as INamedTypeSymbol : propertyType; + if (IsImplementingInterface(unwrappedPropertyType, Generation.IConvertibleSymbol.Value)) { provideValue = $"{globalized.XamlBindingHelper}.ConvertValue(typeof({globalized.PvtpType}), {provideValue})"; } @@ -6358,8 +6359,6 @@ private IEnumerable EnumerateSubElements(IEnumerable EnumerateSubElements(IEnumerable "); - var elementStubHolderNameStatement = ""; + var disposable = new DisposableAction(() => + { + writer.AppendIndented(")"); + }); - if (HasImplicitViewPinning) - { - // Build the ElemenStub builder holder variable to ensute that the element stub - // does not keep a hard reference to "this" through the closure needed to keep - // the namescope and other variables. The ElementStub, in turn keeps a weak - // reference to the builder's target instance. - var elementStubHolderName = $"_elementStubHolder_{CurrentScope.ElementStubHolders.Count}"; - elementStubHolderNameStatement = $"{elementStubHolderName} = "; - CurrentScope.ElementStubHolders.Add(elementStubHolderName); - } + var xLoadScopeDisposable = XLoadScope(); - writer.AppendLineIndented($"new {XamlConstants.Types.ElementStub}({elementStubHolderNameStatement} () => "); + return new DisposableAction(() => + { + disposable?.Dispose(); - var disposable = new DisposableAction(() => + string closureName; + using (var innerWriter = CreateApplyBlock(writer, Generation.ElementStubSymbol.Value, out closureName)) { - writer.AppendIndented(")"); - }); + var elementStubType = new XamlType(XamlConstants.BaseXamlNamespace, "ElementStub", new List(), new XamlSchemaContext()); - var xLoadScopeDisposable = XLoadScope(); + if (nameMember != null) + { + innerWriter.AppendLineIndented( + $"{closureName}.Name = \"{nameMember.Value}\";" + ); + + // Set the element name to the stub, then when the stub will be replaced + // the actual target control will override it. + innerWriter.AppendLineIndented( + $"_{nameMember.Value}Subject.ElementInstance = {closureName};" + ); + } - return new DisposableAction(() => - { - disposable?.Dispose(); + var members = new List(); - string closureName; - using (var innerWriter = CreateApplyBlock(writer, Generation.ElementStubSymbol.Value, out closureName)) + if (hasLoadMarkup) { - var elementStubType = new XamlType(XamlConstants.BaseXamlNamespace, "ElementStub", new List(), new XamlSchemaContext()); + members.Add(GenerateBinding("Load", loadMember, definition)); + } - if (hasDataContextMarkup) - { - // We need to generate the datacontext binding, since the Visibility - // may require it to bind properly. + var isInsideFrameworkTemplate = IsMemberInsideFrameworkTemplate(definition).isInside; - GenerateBinding("DataContext", dataContextMember, definition); - } - - if (nameMember != null) - { - innerWriter.AppendLineIndented( - $"{closureName}.Name = \"{nameMember.Value}\";" - ); + var needsBindingUpdates = HasXBindMarkupExtension(definition) || HasMarkupExtensionNeedingComponent(definition); + var childrenNeedBindingUpdates = CurrentXLoadScope?.Components.Any(c => HasXBindMarkupExtension(c.ObjectDefinition) || HasMarkupExtensionNeedingComponent(c.ObjectDefinition)) ?? false; + if ((!_isTopLevelDictionary || isInsideFrameworkTemplate) && (needsBindingUpdates || childrenNeedBindingUpdates)) + { + var xamlObjectDef = new XamlObjectDefinition(elementStubType, 0, 0, definition, namespaces: null); + xamlObjectDef.Members.AddRange(members); - // Set the element name to the stub, then when the stub will be replaced - // the actual target control will override it. - innerWriter.AppendLineIndented( - $"_{nameMember.Value}Subject.ElementInstance = {closureName};" - ); - } + var componentName = AddComponentForParentScope(xamlObjectDef).MemberName; + writer.AppendLineIndented($"__that.{componentName} = {closureName};"); - if (hasLoadMarkup || hasVisibilityMarkup) + if (!isInsideFrameworkTemplate) { + EnsureXClassName(); - var members = new List(); + writer.AppendLineIndented($"var {componentName}_update_That = ({CurrentResourceOwnerName} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference;"); - if (hasLoadMarkup) + if (nameMember != null) { - members.Add(GenerateBinding("Load", loadMember, definition)); + writer.AppendLineIndented($"var {componentName}_update_subject_capture = _{nameMember.Value}Subject;"); } - if (hasVisibilityMarkup) + using (writer.BlockInvariant($"void {componentName}_update(global::Microsoft.UI.Xaml.ElementStub sender)")) { - members.Add(GenerateBinding("Visibility", visibilityMember, definition)); - } - - var isInsideFrameworkTemplate = IsMemberInsideFrameworkTemplate(definition).isInside; - if ((!_isTopLevelDictionary || isInsideFrameworkTemplate) - && (HasXBindMarkupExtension(definition) || HasMarkupExtensionNeedingComponent(definition))) - { - var xamlObjectDef = new XamlObjectDefinition(elementStubType, 0, 0, definition, namespaces: null); - xamlObjectDef.Members.AddRange(members); - - var componentName = AddComponentForParentScope(xamlObjectDef).MemberName; - writer.AppendLineIndented($"__that.{componentName} = {closureName};"); - - if (!isInsideFrameworkTemplate) + using (writer.BlockInvariant($"if ({componentName}_update_That.Target is {_xClassName} that)")) { - EnsureXClassName(); - - writer.AppendLineIndented($"var {componentName}_update_That = ({CurrentResourceOwnerName} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference;"); - - if (nameMember != null) + using (writer.BlockInvariant($"if (sender.IsMaterialized)")) { - writer.AppendLineIndented($"var {componentName}_update_subject_capture = _{nameMember.Value}Subject;"); + writer.AppendLineIndented($"that.Bindings.UpdateResources();"); + if (nameMember?.Value is string xName) + { + writer.AppendLineIndented($"that.Bindings.NotifyXLoad(\"{xName}\");"); + } } - using (writer.BlockInvariant($"void {componentName}_update(global::Microsoft.UI.Xaml.ElementStub sender)")) + using (writer.BlockInvariant("else")) { - using (writer.BlockInvariant($"if ({componentName}_update_That.Target is {_xClassName} that)")) + var elementNames = FindNamesIn(definition); + foreach (var elementName in elementNames) { - - using (writer.BlockInvariant($"if (sender.IsMaterialized)")) - { - writer.AppendLineIndented($"that.Bindings.UpdateResources();"); - if (nameMember?.Value is string xName) - { - writer.AppendLineIndented($"that.Bindings.NotifyXLoad(\"{xName}\");"); - } - } - - using (writer.BlockInvariant("else")) - { - var elementNames = FindNamesIn(definition); - foreach (var elementName in elementNames) - { - writer.AppendLineIndented($"_{elementName}Subject.ElementInstance = null;"); - } - } + writer.AppendLineIndented($"_{elementName}Subject.ElementInstance = null;"); } } + } + } - writer.AppendLineIndented($"{closureName}.MaterializationChanged += {componentName}_update;"); + writer.AppendLineIndented($"{closureName}.MaterializationChanged += {componentName}_update;"); - writer.AppendLineIndented($"var owner = this;"); + writer.AppendLineIndented($"var owner = this;"); - if (_isHotReloadEnabled) - { - // Attach the current context to itself to avoid having a closure in the lambda - writer.AppendLineIndented($"global::Uno.UI.Helpers.MarkupHelper.SetElementProperty({closureName}, \"{componentName}_owner\", owner);"); - } + if (_isHotReloadEnabled) + { + // Attach the current context to itself to avoid having a closure in the lambda + writer.AppendLineIndented($"global::Uno.UI.Helpers.MarkupHelper.SetElementProperty({closureName}, \"{componentName}_owner\", owner);"); + } - using (writer.BlockInvariant($"void {componentName}_materializing(object sender)")) - { - if (_isHotReloadEnabled) - { - writer.AppendLineIndented($"var owner = global::Uno.UI.Helpers.MarkupHelper.GetElementProperty<{CurrentScope.ClassName}>(sender, \"{componentName}_owner\");"); - } + using (writer.BlockInvariant($"void {componentName}_materializing(object sender)")) + { + if (_isHotReloadEnabled) + { + writer.AppendLineIndented($"var owner = global::Uno.UI.Helpers.MarkupHelper.GetElementProperty<{CurrentScope.ClassName}>(sender, \"{componentName}_owner\");"); + } - // Refresh the bindings when the ElementStub is unloaded. This assumes that - // ElementStub will be unloaded **after** the stubbed control has been created - // in order for the component field to be filled, and Bindings.Update() to do its work. + // Refresh the bindings when the ElementStub is unloaded. This assumes that + // ElementStub will be unloaded **after** the stubbed control has been created + // in order for the component field to be filled, and Bindings.Update() to do its work. - using (writer.BlockInvariant($"if ({componentName}_update_That.Target is {_xClassName} that)")) + using (writer.BlockInvariant($"if ({componentName}_update_That.Target is {_xClassName} that)")) + { + if (CurrentXLoadScope != null) + { + foreach (var component in CurrentXLoadScope.Components) { - if (CurrentXLoadScope != null) - { - foreach (var component in CurrentXLoadScope.Components) - { - writer.AppendLineIndented($"that.{component.VariableName}.ApplyXBind();"); - writer.AppendLineIndented($"that.{component.VariableName}.UpdateResourceBindings();"); - } - - BuildxBindEventHandlerInitializers(writer, CurrentXLoadScope.xBindEventsHandlers, "that."); - } + writer.AppendLineIndented($"that.{component.VariableName}.ApplyXBind();"); + writer.AppendLineIndented($"that.{component.VariableName}.UpdateResourceBindings();"); } - } - writer.AppendLineIndented($"{closureName}.Materializing += {componentName}_materializing;"); - } - else - { - // TODO for https://github.com/unoplatform/uno/issues/6700 + BuildxBindEventHandlerInitializers(writer, CurrentXLoadScope.xBindEventsHandlers, "that."); + } } } + + writer.AppendLineIndented($"{closureName}.Materializing += {componentName}_materializing;"); } else { - if (visibilityMember != null) - { - innerWriter.AppendLineInvariantIndented( - "{0}.Visibility = {1};", - closureName, - BuildLiteralValue(visibilityMember) - ); - } + // TODO for https://github.com/unoplatform/uno/issues/6700 } + } - XamlMemberDefinition GenerateBinding(string name, XamlMemberDefinition? memberDefinition, XamlObjectDefinition owner) + XamlMemberDefinition GenerateBinding(string name, XamlMemberDefinition? memberDefinition, XamlObjectDefinition owner) + { + var def = new XamlMemberDefinition( + new XamlMember(name, + elementStubType, + false + ), 0, 0, + owner + ); + + if (memberDefinition != null) { - var def = new XamlMemberDefinition( - new XamlMember(name, - elementStubType, - false - ), 0, 0, - owner - ); - - if (memberDefinition != null) - { - def.Objects.AddRange(memberDefinition.Objects); - } + def.Objects.AddRange(memberDefinition.Objects); + } - BuildComplexPropertyValue(innerWriter, def, closureName + ".", closureName); + BuildComplexPropertyValue(innerWriter, def, closureName + ".", closureName); - return def; - } + return def; } - - xLoadScopeDisposable?.Dispose(); } - ); - } + + xLoadScopeDisposable?.Dispose(); + }); } return null; diff --git a/src/SourceGenerators/XamlGenerationTests/CustomMarkupExtensions.xaml b/src/SourceGenerators/XamlGenerationTests/CustomMarkupExtensions.xaml index 28a847ff9a9d..4f0a0f22c8af 100644 --- a/src/SourceGenerators/XamlGenerationTests/CustomMarkupExtensions.xaml +++ b/src/SourceGenerators/XamlGenerationTests/CustomMarkupExtensions.xaml @@ -5,7 +5,8 @@ xmlns:local="using:XamlGenerationTests.Shared.MarkupExtensions" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ext="clr-namespace:XamlGenerationTests.Shared.MarkupExtensions" - mc:Ignorable="d"> + xmlns:void="There is no mistake so great that it cannot be undone." + mc:Ignorable="d void"> @@ -72,7 +73,10 @@ - + + + + diff --git a/src/SourceGenerators/XamlGenerationTests/CustomMarkupExtensions.xaml.cs b/src/SourceGenerators/XamlGenerationTests/CustomMarkupExtensions.xaml.cs index 6a9d4a3d8166..3cc9e32e6f01 100644 --- a/src/SourceGenerators/XamlGenerationTests/CustomMarkupExtensions.xaml.cs +++ b/src/SourceGenerators/XamlGenerationTests/CustomMarkupExtensions.xaml.cs @@ -111,6 +111,11 @@ protected override object ProvideValue() } } + public class ReturnNullExtension : MarkupExtension + { + protected override object ProvideValue() => null; + } + public class ComplexObject { public string StringProp { get; set; } diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs index cc25b21bbee4..bfb27344276c 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs @@ -17,8 +17,9 @@ public class AddIns public static IImmutableList Discover(string solutionFile) { var tmp = Path.GetTempFileName(); - var command = $"build \"{solutionFile}\" --target:UnoDumpTargetFrameworks \"-p:UnoDumpTargetFrameworksTargetFile={tmp}\" --verbosity quiet"; - var result = ProcessHelper.RunProcess("dotnet", command); + var wd = Path.GetDirectoryName(solutionFile); + var command = $"build \"{solutionFile}\" -t:UnoDumpTargetFrameworks \"-p:UnoDumpTargetFrameworksTargetFile={tmp}\" --verbosity quiet"; + var result = ProcessHelper.RunProcess("dotnet", command, wd); var targetFrameworks = Read(tmp); if (targetFrameworks.IsEmpty) @@ -50,8 +51,8 @@ public static IImmutableList Discover(string solutionFile) foreach (var targetFramework in targetFrameworks) { tmp = Path.GetTempFileName(); - command = $"build \"{solutionFile}\" --target:UnoDumpRemoteControlAddIns \"-p:UnoDumpRemoteControlAddInsTargetFile={tmp}\" --verbosity quiet --framework \"{targetFramework}\" -nowarn:MSB4057"; - result = ProcessHelper.RunProcess("dotnet", command); + command = $"build \"{solutionFile}\" -t:UnoDumpRemoteControlAddIns \"-p:UnoDumpRemoteControlAddInsTargetFile={tmp}\" --verbosity quiet --framework \"{targetFramework}\" -nowarn:MSB4057"; + result = ProcessHelper.RunProcess("dotnet", command, wd); if (!string.IsNullOrWhiteSpace(result.error)) { if (_log.IsEnabled(LogLevel.Warning)) diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowNative.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowNative.cs index 89af44792c64..9d90cd34adbf 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowNative.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSWindowNative.cs @@ -54,6 +54,10 @@ public MacOSWindowNative(WinUIWindow winUIWindow, Microsoft.UI.Xaml.XamlRoot xam NativeWindowReady?.Invoke(this, this); + if (NativeUno.uno_application_is_bundled()) + { + Windows.Storage.StorageFile.ResourcePathBase = IOPath.Combine(Windows.ApplicationModel.Package.Current.InstalledPath, "..", "Resources"); + } UpdateWindowPropertiesFromPackage(); UpdateWindowPropertiesFromApplicationView(); } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs index 30bb1dc9561a..47ae58701e01 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs @@ -166,6 +166,10 @@ internal static partial class NativeUno [return: MarshalAs(UnmanagedType.I1)] internal static partial bool uno_application_query_url_support(string url); + [LibraryImport("libUnoNativeMac.dylib")] + [return: MarshalAs(UnmanagedType.I1)] + internal static partial bool uno_application_is_bundled(); + [LibraryImport("libUnoNativeMac.dylib")] internal static unsafe partial void uno_set_drawing_callbacks( delegate* unmanaged[Cdecl] metalCallback, diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h index 46f8832eb003..0fb4900ab266 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.h @@ -22,6 +22,7 @@ void uno_application_set_badge(const char *badge); void uno_application_set_icon(const char *path); bool uno_application_open_url(const char *url); bool uno_application_query_url_support(const char *url); +bool uno_application_is_bundled(void); typedef bool (*application_can_exit_fn_ptr)(void); application_can_exit_fn_ptr uno_get_application_can_exit_callback(void); diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m index 3a1adbc2eedc..5eac3ebf1a47 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOApplication.m @@ -102,6 +102,11 @@ void uno_set_application_can_exit_callback(application_can_exit_fn_ptr p) application_can_exit = p; } +bool uno_application_is_bundled(void) +{ + return NSRunningApplication.currentApplication.bundleIdentifier != nil; +} + void uno_application_quit(void) { #if DEBUG diff --git a/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs b/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs index a82b2487509d..4519d56e698e 100644 --- a/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs +++ b/src/Uno.UI.RuntimeTests/Tests/TemplatedParent/TemplatedParentTests.cs @@ -289,6 +289,40 @@ public async Task VisualStateGroup_TP_Inheritance() """; VerifyTree(expectations, setup, checkVSG: true); } + + [TestMethod] + public Task LateTemplateSwapping_NonContentControl() => LateTemplateSwapping(); + + [TestMethod] + public Task LateTemplateSwapping_ContentControl() => LateTemplateSwapping(); + + public async Task LateTemplateSwapping() where TControl : Control, new() + { + var templateA = XamlHelper.LoadXaml(""" + + + Template A + + + """); + var templateB = XamlHelper.LoadXaml(""" + + + Template B + + + """); + + var sut = new TControl(); + + sut.Template = templateA; + await UITestHelper.Load(sut, x => x.IsLoaded); + sut.FindFirstDescendantOrThrow("RootA"); + + sut.Template = templateB; + await UITestHelper.WaitForIdle(); + sut.FindFirstDescendantOrThrow("RootB"); + } } public partial class TemplatedParentTests // helper methods { diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_Globalization/Given_DateTimeFormatter.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_Globalization/Given_DateTimeFormatter.cs new file mode 100644 index 000000000000..557870ff2c19 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_Globalization/Given_DateTimeFormatter.cs @@ -0,0 +1,134 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.Globalization.DateTimeFormatting; +using System.Globalization; +using Windows.Globalization; +using System.Linq; +using System.Collections.Generic; + +namespace Uno.UI.RuntimeTests.Tests.Windows_Globalization; + +[TestClass] +public class Given_DateTimeFormatter +{ + [TestMethod] + public void When_CreatingWithFormatTemplate_ShouldInitializeCorrectly() + { + var formatter = new DateTimeFormatter("longdate"); + Assert.IsNotNull(formatter); + Assert.AreEqual("longdate", formatter.Template); + } + + [TestMethod] + public void When_UsingCustomLanguages_ShouldSetLanguagesCorrectly() + { + var languages = new[] { "fr-FR", "en-US" }; + var formatter = new DateTimeFormatter("longdate", languages); + + CollectionAssert.AreEqual(languages, formatter.Languages.ToList()); + } + + [TestMethod] + public void When_FormattingDateTimeOffset_ShouldProduceFormattedString() + { + var formatter = new DateTimeFormatter("longdate"); + var dateToFormat = new DateTimeOffset(2024, 1, 15, 0, 0, 0, TimeSpan.Zero); + + var formattedDate = formatter.Format(dateToFormat); + Assert.IsNotNull(formattedDate); + Assert.IsTrue(formattedDate.Length > 0); + } + + [TestMethod] + public void When_SettingNumeralSystem_ShouldUpdateProperty() + { + var formatter = new DateTimeFormatter("longdate"); + formatter.NumeralSystem = "arab"; + + Assert.AreEqual("arab", formatter.NumeralSystem); + } + + [TestMethod] + public void When_CheckingPatterns_ShouldNotBeEmpty() + { + var formatter = new DateTimeFormatter("longdate"); + + Assert.IsTrue(formatter.Patterns.Count > 0); + } + + [TestMethod] + public void When_CallingFormatWithTimeZone_ShouldThrowNotSupportedException() + { + var formatter = new DateTimeFormatter("longdate"); + var dateToFormat = new DateTimeOffset(2024, 1, 15, 0, 0, 0, TimeSpan.Zero); + + Assert.ThrowsException(() => + formatter.Format(dateToFormat, "UTC")); + } + + [TestMethod] + public void When_CreatingFormatter_ShouldUseApplicationLanguages() + { + var formatter = new DateTimeFormatter("longdate"); + + CollectionAssert.AreEqual(ApplicationLanguages.Languages.ToList(), formatter.Languages.ToList()); + } + + [TestMethod] + public void When_FormattingDateVariants_ShouldProduceExpectedFormats() + { + var expectedResults = new Dictionary + { + { "{day.integer}/{month.integer}/{year.full}", "27/6/2024" }, + { "{month.full} {year.full}", "June 2024" }, + { "{month.full} {day.integer}", "June 27" }, + { "{dayofweek.abbreviated}, {day.integer} {month.abbreviated} {year.full}", "Thu, 27 Jun 2024" }, + { "{year.full}-{month.integer}-{day.integer}", "2024-6-27" }, + { "{day.integer}/{month.integer}/{year.abbreviated}", "27/6/24" }, + { "{day.integer} {month.abbreviated} {year.full}", "27 Jun 2024" }, + { "{month.full} {day.integer}, {year.full}", "June 27, 2024" }, + { "{month.abbreviated} {day.integer}, {year.full}", "Jun 27, 2024" }, + { "{dayofweek.full}, {day.integer} {month.full} {year.full}", "Thursday, 27 June 2024" }, + { "{dayofweek.abbreviated}, {day.integer} {month.abbreviated} {year.abbreviated}", "Thu, 27 Jun 24" } + }; + + foreach (var kvp in expectedResults) + { + var template = kvp.Key; + var expected = kvp.Value; + + var formatter = new DateTimeFormatter(template); + var formattedDate = formatter.Format(new(2024, 6, 27, 14, 30, 0, TimeSpan.Zero)); + + Assert.AreEqual(expected, formattedDate, $"Mismatch for template: {template}"); + } + } + + // TODO(DT): Currently is throwing unexpected results on WinUI as well, can't compare the expected results + /* [TestMethod] + public void When_FormattingTimeVariants_ShouldProduceExpectedFormats() + { + var expectedResults = new Dictionary + { + { "{hour.integer}:{minute.integer}", "14:30" }, + { "{hour.integer}:{minute.integer}:{second.integer}", "14:30:00" }, + { "{hour.integer}:{minute.integer} {period.abbreviated(2)}", "2:30 PM" }, + { "{hour.integer}:{minute.integer}:{second.integer} {period.abbreviated(2)}", "2:30:00 PM" }, + { "{minute.integer}:{second.integer}", "30:00" }, + { "{second.integer}", "0" }, + { "{hour.integer}:{minute.integer}:{second.integer} UTC", "14:30:00 UTC" } + }; + + foreach (var kvp in expectedResults) + { + var template = kvp.Key; + var expected = kvp.Value; + + var formatter = new DateTimeFormatter(template); + var formattedTime = formatter.Format(_testDate); + + Assert.AreEqual(expected, formattedTime, $"Mismatch for template: {template}"); + Console.WriteLine($"Template: {template}, Result: {formattedTime}"); + } + }*/ +} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/xLoad_Visibility.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/xLoad_Visibility.xaml new file mode 100644 index 000000000000..b7a40d5a296d --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/xLoad_Visibility.xaml @@ -0,0 +1,12 @@ + + + + + + diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/xLoad_Visibility.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/xLoad_Visibility.xaml.cs new file mode 100644 index 000000000000..d4ac806e753e --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/xLoad_Visibility.xaml.cs @@ -0,0 +1,17 @@ +using Microsoft.UI.Xaml.Controls; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml.Controls +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class xLoad_Visibility : Page + { + public xLoad_Visibility() + { + this.InitializeComponent(); + } + } +} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_xLoad.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_xLoad.cs index 342cfdcce659..5612484e7cd9 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_xLoad.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_xLoad.cs @@ -1,7 +1,10 @@ #nullable enable #if !WINAPPSDK using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Private.Infrastructure; @@ -9,6 +12,7 @@ using Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml.Controls; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; using Uno.UI.RuntimeTests.Helpers; using SamplesApp.UITests; @@ -289,6 +293,7 @@ public async Task When_Binding_xLoad_Nested() Assert.IsNull(SUT.tb06); } +#if HAS_UNO #if __ANDROID__ [Ignore("https://github.com/unoplatform/uno/issues/7305")] #endif @@ -332,10 +337,11 @@ public async Task When_Binding_xLoad_Nested_With_ElementStub_LoadCount() Assert.AreEqual(2, SUT.TopLevelVisiblity1GetCount); Assert.AreEqual(0, SUT.TopLevelVisiblity1SetCount); - var tb01Stub = SUT.FindFirstChild(e => e.Name == "tb01")!; - var tb02Stub = SUT.FindFirstChild(e => e.Name == "tb02")!; - var panel01Stub = SUT.FindFirstChild(e => e.Name == "panel01")!; - var panel03Stub = SUT.FindFirstChild(e => e.Name == "panel03")!; + var stubs = GetAllChildren(SUT).OfType().ToList(); + var tb01Stub = stubs.First(e => e.Name == "tb01"); + var tb02Stub = stubs.First(e => e.Name == "tb02")!; + var panel01Stub = stubs.First(e => e.Name == "panel01")!; + var panel03Stub = stubs.First(e => e.Name == "panel03"); var tb01StubChangedCount = 0; tb01Stub.MaterializationChanged += _ => tb01StubChangedCount++; @@ -379,7 +385,7 @@ public async Task When_Binding_xLoad_Nested_With_ElementStub_LoadCount() Assert.AreEqual(1, tb02StubChangedCount); Assert.AreEqual(1, panel01StubChangedCount); - var panel02Stub = SUT.FindFirstChild(e => e.Name == "panel02")!; + var panel02Stub = GetAllChildren(SUT).OfType().First(e => e.Name == "panel02"); var panel02StubChangedCount = 0; panel02Stub.MaterializationChanged += _ => panel02StubChangedCount++; @@ -456,6 +462,30 @@ public async Task When_Binding_xLoad_Nested_With_ElementStub_LoadCount() Assert.AreEqual(2, tb02StubChangedCount); Assert.AreEqual(2, panel01StubChangedCount); Assert.AreEqual(2, panel02StubChangedCount); + + IEnumerable GetAllChildren(UIElement element) + { + yield return element; + foreach (var child in VisualTreeHelper.GetChildren(element).OfType()) + { + foreach (var childChild in GetAllChildren(child)) + { + yield return childChild; + } + } + } + } +#endif + + [TestMethod] + public async Task When_xLoad_Visibility_Set() + { + var SUT = new xLoad_Visibility(); + TestServices.WindowHelper.WindowContent = SUT; + await TestServices.WindowHelper.WaitForIdle(); + + Assert.AreEqual(1, SUT.GetChildren().Count(c => c is ElementStub)); + Assert.AreEqual(0, SUT.GetChildren().Count(c => c is Border)); } } } diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Frame.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Frame.cs index 76e2ab4e8ea6..20af2e901e42 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Frame.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Frame.cs @@ -437,6 +437,58 @@ public void When_NavigatingBetweenPages() Assert.IsTrue(_navigateOrderTracker.FrameNavigated); } + [TestMethod] + public async Task When_Exception_In_Page_Ctor() + { + var SUT = new Frame() + { + Width = 200, + Height = 200 + }; + +#if HAS_UNO + bool navigationFailed = false; + SUT.NavigationFailed += (s, e) => navigationFailed = true; +#endif + + TestServices.WindowHelper.WindowContent = SUT; + await TestServices.WindowHelper.WaitForLoaded(SUT); + + var exception = Assert.ThrowsException(() => SUT.Navigate(typeof(ExceptionInCtorPage))); + Assert.AreEqual("Crashed", exception.Message); +#if HAS_UNO + if (FeatureConfiguration.Frame.UseWinUIBehavior) + { + // This is only valid with WinUI Frame behavior + Assert.IsFalse(navigationFailed); + } +#endif + } + + [TestMethod] + public async Task When_Exception_In_OnNavigatedTo() + { + var SUT = new Frame() + { + Width = 200, + Height = 200 + }; + + bool navigationFailed = false; + SUT.NavigationFailed += (s, e) => + { + navigationFailed = true; + e.Handled = true; + }; + + TestServices.WindowHelper.WindowContent = SUT; + await TestServices.WindowHelper.WaitForLoaded(SUT); + + var exception = Assert.ThrowsException(() => SUT.Navigate(typeof(ExceptionInOnNavigatedToPage))); + Assert.AreEqual("Crashed", exception.Message); + Assert.IsTrue(navigationFailed); + } + [TestCleanup] public void Cleanup() { @@ -613,3 +665,28 @@ private async void FrameNavigateFirstPage_Loaded(object sender, Microsoft.UI.Xam public partial class FrameNavigateSecondPage : Page { } + +public partial class ExceptionInCtorPage : Page +{ + public ExceptionInCtorPage() + { + throw new NotSupportedException("Crashed"); + } +} + +public partial class ExceptionInOnNavigatedToPage : Page +{ + public ExceptionInOnNavigatedToPage() + { + } + + protected +#if HAS_UNO + internal +#endif + override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + throw new NotSupportedException("Crashed"); + } +} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/Given_MarkupExtension.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/Given_MarkupExtension.cs index b2939cf7f8c2..023d76c16773 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/Given_MarkupExtension.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/Given_MarkupExtension.cs @@ -94,6 +94,16 @@ public void When_MarkupExtension_Enum() Assert.AreEqual(Orientation.Horizontal, page.EnumMarkupExtension_Horizontal.Orientation); Assert.AreEqual(Orientation.Vertical, page.EnumMarkupExtension_Vertical.Orientation); } + + [TestMethod] + public void When_MarkupExtension_ReturnNullForNullableStructType() + { + // it also shouldn't throw here + var page = new MarkupExtension_CodegenTypeCast(); + + Assert.IsTrue(page.Control.IsChecked, "sanity check failed: Control.IsChecked is not true"); + Assert.IsNull(page.SUT.IsChecked, "Property value should be set to null by the markup-extension"); + } #endif } } diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/MarkupExtension_CodegenTypeCast.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/MarkupExtension_CodegenTypeCast.xaml new file mode 100644 index 000000000000..6aae30029512 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/MarkupExtension_CodegenTypeCast.xaml @@ -0,0 +1,11 @@ + + + + + + diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/MarkupExtension_CodegenTypeCast.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/MarkupExtension_CodegenTypeCast.xaml.cs new file mode 100644 index 000000000000..3edcf665e1d5 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Markup/MarkupExtension_CodegenTypeCast.xaml.cs @@ -0,0 +1,26 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; + +namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Markup; + +public sealed partial class MarkupExtension_CodegenTypeCast : Page +{ + public MarkupExtension_CodegenTypeCast() + { + this.InitializeComponent(); + } +} +public partial class AlreadyCheckedBox : CheckBox +{ + public AlreadyCheckedBox() + { + // setting a default value, so we may observe ReturnNullExtension working or not. + this.IsChecked = true; + } +} + +public class ReturnNullExtension : MarkupExtension +{ + protected override object ProvideValue() => null; +} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media/Given_VisualTreeHelper.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media/Given_VisualTreeHelper.cs index f64b8d74dfab..87b7a1f9973e 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media/Given_VisualTreeHelper.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media/Given_VisualTreeHelper.cs @@ -16,6 +16,7 @@ using Microsoft.UI.Xaml.Media; using FluentAssertions; using Uno.Extensions; +using Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml.Controls; using static Private.Infrastructure.TestServices; namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media @@ -149,6 +150,24 @@ public async Task When_HitTestTranslatedElement() #if !UNO_HAS_MANAGED_POINTERS [Ignore("Root visual tree elements are not configured properly to use managed hit testing.")] #endif +#if __MACOS__ + [Ignore("Currently fails on macOS, part of #9282 epic")] +#endif + public async Task When_ElementStub_Not_Counted() + { + var SUT = new xLoad_Visibility(); + WindowHelper.WindowContent = SUT; + await WindowHelper.WaitForIdle(); + + Assert.AreEqual(0, VisualTreeHelper.GetChildrenCount(SUT)); + Assert.IsNull(VisualTreeHelper.GetChild(SUT, 0)); + } + + [TestMethod] + [RunsOnUIThread] +#if !UNO_HAS_MANAGED_POINTERS + [Ignore("Root visual tree elements are not configured properly to use managed hit testing.")] +#endif #if __MACOS__ [Ignore("Currently fails on macOS, part of #9282 epic")] #endif diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml/Given_xLoad.cs b/src/Uno.UI.Tests/Windows_UI_Xaml/Given_xLoad.cs index 6900de65f6a8..16d08a7d6c29 100644 --- a/src/Uno.UI.Tests/Windows_UI_Xaml/Given_xLoad.cs +++ b/src/Uno.UI.Tests/Windows_UI_Xaml/Given_xLoad.cs @@ -34,7 +34,7 @@ public void When_xLoad_Multiple() var stubs = SUT.EnumerateAllChildren().OfType(); - Assert.AreEqual(7, stubs.Count()); + Assert.AreEqual(8, stubs.Count()); } [TestMethod] @@ -76,11 +76,12 @@ public void When_xLoad_Deferred_VisibilityBinding() Assert.IsNull(SUT.border7); + // Changing the visibility DOES NOT materialize the lazily-loaded element. SUT.DataContext = true; - - Assert.IsNotNull(SUT.border7); + Assert.IsNull(SUT.border7); var border = SUT.FindName("border7"); + Assert.IsNotNull(SUT.border7); Assert.AreEqual(SUT.border7, border); } @@ -96,12 +97,14 @@ public void When_xLoad_Deferred_VisibilityxBind() Assert.IsNull(SUT.border8); + // Changing the visibility DOES NOT materialize the lazily-loaded element. SUT.MyVisibility = true; SUT.Measure(new Size(42, 42)); - Assert.IsNotNull(SUT.border8); + Assert.IsNull(SUT.border8); var border1 = SUT.FindName("border8"); + Assert.IsNotNull(SUT.border8); Assert.AreEqual(SUT.border8, border1); } diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs index 19801fda981a..7f4b28772021 100644 --- a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs +++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs @@ -910,11 +910,13 @@ public void When_xLoad() SUT.TopLevelVisiblity = true; - Assert.IsNotNull(SUT.topLevelContent); - Assert.IsNotNull(SUT.innerTextBlock); - Assert.AreEqual("My inner text", SUT.innerTextBlock.Text); + // Changing the visibility DOES NOT materialize the lazily-loaded element. + Assert.IsNull(SUT.topLevelContent); + Assert.IsNull(SUT.innerTextBlock); var topLevelContent = SUT.FindName("topLevelContent") as FrameworkElement; + Assert.IsNotNull(SUT.topLevelContent); + Assert.IsNotNull(SUT.innerTextBlock); Assert.AreEqual(Visibility.Visible, topLevelContent.Visibility); SUT.InnerText = "Updated !"; @@ -946,15 +948,21 @@ public void When_xLoad_DataTemplate() data.TopLevelVisiblity = true; - Assert.AreEqual(0, innerRoot.EnumerateAllChildren().OfType().Count()); + // Changing the visibility DOES NOT materialize the lazily-loaded element. + Assert.AreEqual(1, innerRoot.EnumerateAllChildren().OfType().Count()); + // Calling FindName on an element inside a lazy-loaded element won't materialize the lazy-loaded element var innerTextBlock = SUT.FindName("innerTextBlock") as TextBlock; - Assert.IsNotNull(innerTextBlock); - Assert.AreEqual(data.InnerText, innerTextBlock.Text); + Assert.AreEqual(1, innerRoot.EnumerateAllChildren().OfType().Count()); + Assert.IsNull(innerTextBlock); data.TopLevelVisiblity = false; var topLevelContent = SUT.FindName("topLevelContent") as FrameworkElement; + Assert.AreEqual(0, innerRoot.EnumerateAllChildren().OfType().Count()); + innerTextBlock = SUT.FindName("innerTextBlock") as TextBlock; + Assert.IsNotNull(innerTextBlock); + Assert.AreEqual(data.InnerText, innerTextBlock.Text); Assert.AreEqual(Visibility.Collapsed, topLevelContent.Visibility); } diff --git a/src/Uno.UI/UI/Xaml/Controls/CalendarView/CalendarViewBaseItemChrome.cs b/src/Uno.UI/UI/Xaml/Controls/CalendarView/CalendarViewBaseItemChrome.cs index 97784b786a68..cd67654b44c8 100644 --- a/src/Uno.UI/UI/Xaml/Controls/CalendarView/CalendarViewBaseItemChrome.cs +++ b/src/Uno.UI/UI/Xaml/Controls/CalendarView/CalendarViewBaseItemChrome.cs @@ -112,9 +112,7 @@ private void RemoveTemplateChild() } #endif - private UIElement GetFirstChildNoAddRef() => GetFirstChild(); - - private UIElement GetFirstChild() + internal override UIElement GetFirstChild() { UIElement spFirstChild; // added in UIElement.GetFirstChild() diff --git a/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs b/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs index a06c5d0cfa88..63b8fd3bb619 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs @@ -207,7 +207,30 @@ public ControlTemplate Template private protected virtual void OnTemplateChanged(DependencyPropertyChangedEventArgs e) { -#if !UNO_HAS_ENHANCED_LIFECYCLE +#if UNO_HAS_ENHANCED_LIFECYCLE + if (e.OldValue != e.NewValue) + { + // Reset the template bindings for this control + //ClearPropertySubscriptions(); + + // When the control template property is set, we clear the visual children + var pUIElement = this.GetFirstChild(); + if (pUIElement is { }) + { + //CFrameworkTemplate* pNewTemplate = NULL; + //if (e.NewValue?.GetType() == valueObject) + //{ + // IFC(DoPointerCast(pNewTemplate, args.m_value.AsObject())); + //} + //else if (args.m_value.GetType() != valueNull) + //{ + // IFC(E_INVALIDARG); + //} + RemoveChild(pUIElement); + //IFC(GetContext()->RemoveNameScope(this, Jupiter::NameScoping::NameScopeType::TemplateNameScope)); + } + } +#else _updateTemplate = true; SetUpdateControlTemplate(); #endif diff --git a/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.legacy.cs b/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.legacy.cs index 75021ad1ecbc..58f4468fca55 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.legacy.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.legacy.cs @@ -153,7 +153,7 @@ private bool InnerNavigate(PageStackEntry entry, NavigationMode mode) Application.Current.RaiseRecoverableUnhandledException(new InvalidOperationException("Navigation failed", exception)); } - return false; + throw; } finally { diff --git a/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.partial.mux.cs b/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.partial.mux.cs index f2e35d210d6e..6e100102ae85 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.partial.mux.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Frame/Frame.partial.mux.cs @@ -301,6 +301,7 @@ private bool NavigateWithTransitionInfoImpl(Type sourcePageType, object paramete catch { pCanNavigate = false; + throw; } Cleanup: ; @@ -585,6 +586,8 @@ private void ChangeContent(object oldObject, object newObject, object parameter, { Content = oldObject; } + + throw; } } diff --git a/src/Uno.UI/UI/Xaml/ElementStub.cs b/src/Uno.UI/UI/Xaml/ElementStub.cs index 68dcff04f236..70616e926c94 100644 --- a/src/Uno.UI/UI/Xaml/ElementStub.cs +++ b/src/Uno.UI/UI/Xaml/ElementStub.cs @@ -121,7 +121,6 @@ public ElementStub(Func contentBuilder) : this() public ElementStub() { - Visibility = Visibility.Collapsed; } private static void OnLoadChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) @@ -148,20 +147,6 @@ protected override Size MeasureOverride(Size availableSize) protected override Size ArrangeOverride(Size finalSize) => ArrangeFirstChild(finalSize); - protected override void OnVisibilityChanged(Visibility oldValue, Visibility newValue) - { - base.OnVisibilityChanged(oldValue, newValue); - - if (ContentBuilder != null - && oldValue == Visibility.Collapsed - && newValue == Visibility.Visible - && Parent != null - ) - { - Materialize(isVisibilityChanged: true); - } - } - #if UNO_HAS_ENHANCED_LIFECYCLE internal override void EnterImpl(EnterParams @params, int depth) { @@ -179,16 +164,14 @@ private protected override void OnLoaded() { base.OnLoaded(); - if (ContentBuilder != null - && Visibility == Visibility.Visible - ) + if (ContentBuilder != null && Load) { Materialize(); } } public void Materialize() - => Materialize(isVisibilityChanged: false); + => MaterializeInner(); private void RaiseMaterializing() { @@ -198,11 +181,11 @@ private void RaiseMaterializing() } } - private void Materialize(bool isVisibilityChanged) + private void MaterializeInner() { if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) { - this.Log().Debug($"ElementStub.Materialize(isVibilityChanged: {isVisibilityChanged})"); + this.Log().Debug($"ElementStub.Materialize()"); } if (_content == null && !_isMaterializing) @@ -219,17 +202,6 @@ private void Materialize(bool isVisibilityChanged) TemplatedParentScope.UpdateTemplatedParent(_content as DependencyObject, GetTemplatedParent(), reapplyTemplateBindings: true); #endif - if (isVisibilityChanged && - _content is DependencyObject contentAsDO) - { - var visibilityProperty = GetVisibilityProperty(_content); - - // Set the visibility at the same precedence it was currently set with on the stub. - var precedence = this.GetCurrentHighestValuePrecedence(visibilityProperty); - - contentAsDO.SetValue(visibilityProperty, Visibility.Visible, precedence); - } - MaterializationChanged?.Invoke(this); } finally @@ -257,18 +229,6 @@ private void Dematerialize() MaterializationChanged?.Invoke(this); } } - - private static DependencyProperty GetVisibilityProperty(View view) - { - if (view is FrameworkElement) - { - return VisibilityProperty; - } - else - { - return DependencyProperty.GetProperty(view.GetType(), nameof(Visibility)); - } - } } } #endif diff --git a/src/Uno.UI/UI/Xaml/FrameworkElement.cs b/src/Uno.UI/UI/Xaml/FrameworkElement.cs index cd0f17591dde..1f6ebb6a0918 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkElement.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkElement.cs @@ -1064,7 +1064,9 @@ private protected virtual bool HasTemplateChild() return GetFirstChild() is not null; } - private UIElement/*?*/ GetFirstChild() + internal UIElement GetFirstChildNoAddRef() => GetFirstChild(); + + internal virtual UIElement/*?*/ GetFirstChild() { #if __CROSSRUNTIME__ && !__NETSTD_REFERENCE__ if (GetChildren() is { Count: > 0 } children) diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/AnimatorFactory.skia.cs b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/AnimatorFactory.skia.cs index 1da8b5f96de9..4f94c3697d50 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/AnimatorFactory.skia.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/AnimatorFactory.skia.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Uno.Extensions; -using Uno.Foundation.Logging; using Windows.UI; namespace Microsoft.UI.Xaml.Media.Animation @@ -14,6 +9,6 @@ private static IValueAnimator CreateDouble(Timeline timeline, double startingVal => new DispatcherDoubleAnimator(startingValue, targetValue); private static IValueAnimator CreateColor(Timeline timeline, ColorOffset startingValue, ColorOffset targetValue) - => new ImmediateAnimator(targetValue); + => new DispatcherColorAnimator(startingValue, targetValue); } } diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherFloatAnimator.cs b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherAnimator.cs similarity index 70% rename from src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherFloatAnimator.cs rename to src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherAnimator.cs index efef50d52a07..3ca3b1f39daf 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherFloatAnimator.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherAnimator.cs @@ -3,14 +3,14 @@ namespace Microsoft.UI.Xaml.Media.Animation { - internal sealed class DispatcherFloatAnimator : CPUBoundAnimator + internal abstract class DispatcherAnimator : CPUBoundAnimator where T : struct { public const int DefaultFrameRate = 30; private readonly int _frameRate; private readonly DispatcherTimer _timer; - public DispatcherFloatAnimator(float from, float to, int frameRate = DefaultFrameRate) + public DispatcherAnimator(T from, T to, int frameRate = DefaultFrameRate) : base(from, to) { _frameRate = frameRate; @@ -24,6 +24,6 @@ public DispatcherFloatAnimator(float from, float to, int frameRate = DefaultFram protected override void SetStartFrameDelay(long delayMs) => _timer.Interval = TimeSpan.FromMilliseconds(delayMs); protected override void SetAnimationFramesInterval() => _timer.Interval = TimeSpan.FromSeconds(1d / _frameRate); - protected override float GetUpdatedValue(long frame, float from, float to) => (float)_easing.Ease(frame, from, to, Duration); + protected abstract override T GetUpdatedValue(long frame, T from, T to); } } diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherColorAnimator.cs b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherColorAnimator.cs new file mode 100644 index 000000000000..d60fe84cc5bb --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherColorAnimator.cs @@ -0,0 +1,16 @@ +using System; +using Windows.UI; + +namespace Microsoft.UI.Xaml.Media.Animation +{ + internal sealed class DispatcherColorAnimator(ColorOffset from, ColorOffset to, int frameRate = DispatcherAnimator.DefaultFrameRate) + : DispatcherAnimator(from, to, frameRate) + { + protected override ColorOffset GetUpdatedValue(long frame, ColorOffset from, ColorOffset to) => + ColorOffset.FromArgb( + (int)Math.Round(_easing.Ease(frame, from.A, to.A, Duration)), + (int)Math.Round(_easing.Ease(frame, from.R, to.R, Duration)), + (int)Math.Round(_easing.Ease(frame, from.G, to.G, Duration)), + (int)Math.Round(_easing.Ease(frame, from.B, to.B, Duration))); + } +} diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherDoubleAnimator.cs b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherDoubleAnimator.cs index 4e1007d6b03a..201934c72516 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherDoubleAnimator.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/DispatcherDoubleAnimator.cs @@ -1,29 +1,8 @@ -using System; -using System.Linq; - -namespace Microsoft.UI.Xaml.Media.Animation +namespace Microsoft.UI.Xaml.Media.Animation { - internal sealed class DispatcherDoubleAnimator : CPUBoundAnimator + internal sealed class DispatcherDoubleAnimator(double from, double to, int frameRate = DispatcherAnimator.DefaultFrameRate) + : DispatcherAnimator(from, to, frameRate) { - public const int DefaultFrameRate = 30; - - private readonly int _frameRate; - private readonly DispatcherTimer _timer; - - public DispatcherDoubleAnimator(double from, double to, int frameRate = DefaultFrameRate) - : base(from, to) - { - _frameRate = frameRate; - _timer = new DispatcherTimer(); - _timer.Tick += OnFrame; - } - - protected override void EnableFrameReporting() => _timer.Start(); - protected override void DisableFrameReporting() => _timer.Stop(); - - protected override void SetStartFrameDelay(long delayMs) => _timer.Interval = TimeSpan.FromMilliseconds(delayMs); - protected override void SetAnimationFramesInterval() => _timer.Interval = TimeSpan.FromSeconds(1d / _frameRate); - protected override double GetUpdatedValue(long frame, double from, double to) => (float)_easing.Ease(frame, from, to, Duration); } } diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/ColorAnimation.cs b/src/Uno.UI/UI/Xaml/Media/Animation/ColorAnimation.cs index 34fd1e728767..4e24390a6604 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/ColorAnimation.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/ColorAnimation.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Uno; using Windows.UI; @@ -138,6 +134,10 @@ ColorOffset IAnimation.Convert(object value) { return (ColorOffset)Colors.Parse(s); } + else if (value is Color c) + { + return (ColorOffset)c; + } // TODO: handle int? return default(ColorOffset); diff --git a/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs b/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs index 4c73029f4e64..ec5db878fd85 100644 --- a/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs +++ b/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs @@ -119,27 +119,27 @@ public static IEnumerable FindElementsInHostCoordinates(Rect intersec return (reference as _ViewGroup)? .GetChildren() .OfType() + .Where(c => c is not ElementStub) .ElementAtOrDefault(childIndex); #else return (reference as UIElement)? .GetChildren() + .Where(c => c is not ElementStub) .ElementAtOrDefault(childIndex); #endif } - internal static _View GetViewGroupChild(_ViewGroup reference, int childIndex) => (reference as _ViewGroup)?.GetChildren().ElementAtOrDefault(childIndex); - public static int GetChildrenCount(DependencyObject reference) { #if XAMARIN return (reference as _ViewGroup)? .GetChildren() .OfType() - .Count() ?? 0; + .Count(c => c is not ElementStub) ?? 0; #else return (reference as UIElement)? .GetChildren() - .Count ?? 0; + .Count(c => c is not ElementStub) ?? 0; #endif } diff --git a/src/Uno.UWP/Devices/Input/PointerDevice.cs b/src/Uno.UWP/Devices/Input/PointerDevice.cs index d5370fb7f7ea..e434727b98d1 100644 --- a/src/Uno.UWP/Devices/Input/PointerDevice.cs +++ b/src/Uno.UWP/Devices/Input/PointerDevice.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization.Formatters; +using Uno; namespace Windows.Devices.Input { @@ -10,6 +11,13 @@ public partial class PointerDevice internal static PointerDevice For(PointerDeviceType type) { +#if DEBUG + if (WinRTFeatureConfiguration.DebugOptions.SimulateTouch) + { + type = PointerDeviceType.Touch; + } +#endif + // We cache them as we don't implement any other properties than the PointerDeviceType // but this is probably not really valid... switch (type) diff --git a/src/Uno.UWP/FeatureConfiguration/WinRTFeatureConfiguration.cs b/src/Uno.UWP/FeatureConfiguration/WinRTFeatureConfiguration.cs index 22fd4c4d4d5b..d18e6a7ee8c9 100644 --- a/src/Uno.UWP/FeatureConfiguration/WinRTFeatureConfiguration.cs +++ b/src/Uno.UWP/FeatureConfiguration/WinRTFeatureConfiguration.cs @@ -45,4 +45,14 @@ public static class StoreContext public static bool TestMode { get; set; } } #endif + +#if DEBUG + internal static class DebugOptions + { + /// + /// Adjusts all PointerPoint instances as if they were of type Touch. + /// + public static bool SimulateTouch { get; set; } + } +#endif } diff --git a/src/Uno.UWP/Globalization/DateTimeFormatting/DateTimeFormatter.cs b/src/Uno.UWP/Globalization/DateTimeFormatting/DateTimeFormatter.cs index 862130ed9309..9c4f25831e95 100644 --- a/src/Uno.UWP/Globalization/DateTimeFormatting/DateTimeFormatter.cs +++ b/src/Uno.UWP/Globalization/DateTimeFormatting/DateTimeFormatter.cs @@ -326,7 +326,9 @@ private IDictionary BuildLookup(string language) { "timezone.abbreviated" , "zz" }, { "timezone.full" , "zzz" }, { "dayofweek" , "dddd" } , + { "day.integer" , "d" }, { "day" , "%d" } , + { "month.integer" , "M" }, { "month" , "MMMM" } , { "year" , "yyyy" } , { "hour.integer(1)" , "%h" }, diff --git a/src/Uno.UWP/Security/Credentials/PasswordCredential.cs b/src/Uno.UWP/Security/Credentials/PasswordCredential.cs index 2e1d6039794b..0f2bad9f5770 100644 --- a/src/Uno.UWP/Security/Credentials/PasswordCredential.cs +++ b/src/Uno.UWP/Security/Credentials/PasswordCredential.cs @@ -1,46 +1,45 @@ using System; -namespace Windows.Security.Credentials +namespace Windows.Security.Credentials; + +public sealed partial class PasswordCredential { - public sealed partial class PasswordCredential + private string _userName; + private string _resource; + private string _password; + + public PasswordCredential() + : this(string.Empty, string.Empty, string.Empty) + { + } + + public PasswordCredential(string resource, string userName, string password) + { + Resource = resource; + UserName = userName; + Password = password; + } + + public string Resource + { + get => _resource; + set => _resource = value ?? throw new ArgumentNullException(nameof(Resource)); + } + + public string UserName + { + get => _userName; + set => _userName = value ?? throw new ArgumentNullException(nameof(UserName)); + } + + public string Password + { + get => _password; + set => _password = value ?? throw new ArgumentNullException(nameof(Password)); + } + + public void RetrievePassword() { - private string _userName; - private string _resource; - private string _password; - - public PasswordCredential() - : this(string.Empty, string.Empty, string.Empty) - { - } - - public PasswordCredential(string resource, string userName, string password) - { - Resource = resource; - UserName = userName; - Password = password; - } - - public string Resource - { - get => _resource; - set => _resource = value ?? throw new ArgumentNullException(nameof(Resource)); - } - - public string UserName - { - get => _userName; - set => _userName = value ?? throw new ArgumentNullException(nameof(UserName)); - } - - public string Password - { - get => _password; - set => _password = value ?? throw new ArgumentNullException(nameof(Password)); - } - - public void RetrievePassword() - { - // Nothing to do, we never hide the password - } + // Nothing to do, we never hide the password } } diff --git a/src/Uno.UWP/Security/Credentials/PasswordVault.Android.cs b/src/Uno.UWP/Security/Credentials/PasswordVault.Android.cs index 72c242cfb3f8..1a0fba29ab4f 100644 --- a/src/Uno.UWP/Security/Credentials/PasswordVault.Android.cs +++ b/src/Uno.UWP/Security/Credentials/PasswordVault.Android.cs @@ -13,182 +13,181 @@ using Javax.Crypto.Spec; using CipherMode = Javax.Crypto.CipherMode; -namespace Windows.Security.Credentials +namespace Windows.Security.Credentials; + +sealed partial class PasswordVault { - sealed partial class PasswordVault + public PasswordVault() + : this(Build.VERSION.SdkInt > BuildVersionCodes.LollipopMr1 ? new KeyStorePersister() : (IPersister)new UnSecureKeyStorePersister()) { - public PasswordVault() - : this(Build.VERSION.SdkInt > BuildVersionCodes.LollipopMr1 ? new KeyStorePersister() : (IPersister)new UnSecureKeyStorePersister()) - { - } + } - public PasswordVault(string filePath) - : this(Build.VERSION.SdkInt > BuildVersionCodes.LollipopMr1 ? new KeyStorePersister() : (IPersister)new UnSecureKeyStorePersister()) - { - } + public PasswordVault(string filePath) + : this(Build.VERSION.SdkInt > BuildVersionCodes.LollipopMr1 ? new KeyStorePersister() : (IPersister)new UnSecureKeyStorePersister()) + { + } - private sealed class KeyStorePersister : FilePersister - { - private const string _notSupported = @"There is no way to properly persist secured content on this device. + private sealed class KeyStorePersister : FilePersister + { + private const string _notSupported = @"There is no way to properly persist secured content on this device. The 'AndroidKeyStore' is missing (or is innacessible), but it is a requirement for the 'PasswordVault' to store data securly. This usually means that the device is using an API older than 18 (4.3). More details: https://developer.android.com/reference/java/security/KeyStore"; - private const string _algo = KeyProperties.KeyAlgorithmAes; - private const string _block = KeyProperties.BlockModeCbc; - private const string _padding = KeyProperties.EncryptionPaddingPkcs7; - private const string _fullTransform = _algo + "/" + _block + "/" + _padding; - private const string _provider = "AndroidKeyStore"; - private const string _alias = "uno_passwordvault"; - private static readonly byte[] _iv = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(_alias)); + private const string _algo = KeyProperties.KeyAlgorithmAes; + private const string _block = KeyProperties.BlockModeCbc; + private const string _padding = KeyProperties.EncryptionPaddingPkcs7; + private const string _fullTransform = _algo + "/" + _block + "/" + _padding; + private const string _provider = "AndroidKeyStore"; + private const string _alias = "uno_passwordvault"; + private static readonly byte[] _iv = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(_alias)); - private readonly IKey _key; + private readonly IKey _key; - public KeyStorePersister(string filePath = null) - : base(filePath) + public KeyStorePersister(string filePath = null) + : base(filePath) + { + KeyStore store; + try { - KeyStore store; - try - { - store = KeyStore.GetInstance(_provider); - } - catch (Exception e) - { - throw new NotSupportedException(_notSupported, e); - } - if (store == null) - { - throw new NotSupportedException(_notSupported); - } + store = KeyStore.GetInstance(_provider); + } + catch (Exception e) + { + throw new NotSupportedException(_notSupported, e); + } + if (store == null) + { + throw new NotSupportedException(_notSupported); + } - store.Load(null); + store.Load(null); - if (store.ContainsAlias(_alias)) - { - var key = store.GetKey(_alias, null); + if (store.ContainsAlias(_alias)) + { + var key = store.GetKey(_alias, null); - _key = key; - } - else - { - var generator = KeyGenerator.GetInstance(_algo, _provider); - generator.Init(new KeyGenParameterSpec.Builder(_alias, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt) - .SetBlockModes(_block) - .SetEncryptionPaddings(_padding) - .SetRandomizedEncryptionRequired(false) - .Build()); - _key = generator.GenerateKey(); - } + _key = key; } - - /// - protected override Stream Encrypt(Stream outputStream) + else { - var cipher = Cipher.GetInstance(_fullTransform); - var iv = new IvParameterSpec(_iv, 0, cipher.BlockSize); + var generator = KeyGenerator.GetInstance(_algo, _provider); + generator.Init(new KeyGenParameterSpec.Builder(_alias, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt) + .SetBlockModes(_block) + .SetEncryptionPaddings(_padding) + .SetRandomizedEncryptionRequired(false) + .Build()); + _key = generator.GenerateKey(); + } + } - cipher.Init(CipherMode.EncryptMode, _key, iv); + /// + protected override Stream Encrypt(Stream outputStream) + { + var cipher = Cipher.GetInstance(_fullTransform); + var iv = new IvParameterSpec(_iv, 0, cipher.BlockSize); - return new CipherStreamAdapter(new CipherOutputStream(outputStream, cipher)); - } + cipher.Init(CipherMode.EncryptMode, _key, iv); - /// - protected override Stream Decrypt(Stream inputStream) - { - var cipher = Cipher.GetInstance(_fullTransform); - var iv = new IvParameterSpec(_iv, 0, cipher.BlockSize); + return new CipherStreamAdapter(new CipherOutputStream(outputStream, cipher)); + } - cipher.Init(CipherMode.DecryptMode, _key, iv); + /// + protected override Stream Decrypt(Stream inputStream) + { + var cipher = Cipher.GetInstance(_fullTransform); + var iv = new IvParameterSpec(_iv, 0, cipher.BlockSize); - return new InputStreamInvoker(new CipherInputStream(inputStream, cipher)); - } + cipher.Init(CipherMode.DecryptMode, _key, iv); + + return new InputStreamInvoker(new CipherInputStream(inputStream, cipher)); + } + + private class CipherStreamAdapter : Stream + { + private readonly CipherOutputStream _output; + private readonly Stream _adapter; - private class CipherStreamAdapter : Stream + private bool _isDisposed; + + public CipherStreamAdapter(CipherOutputStream output) { - private readonly CipherOutputStream _output; - private readonly Stream _adapter; + _output = output; + _adapter = new OutputStreamInvoker(output); + } - private bool _isDisposed; + public override bool CanRead => _adapter.CanRead; - public CipherStreamAdapter(CipherOutputStream output) - { - _output = output; - _adapter = new OutputStreamInvoker(output); - } + public override bool CanSeek => _adapter.CanSeek; - public override bool CanRead => _adapter.CanRead; + public override bool CanWrite => _adapter.CanWrite; - public override bool CanSeek => _adapter.CanSeek; + public override long Length => _adapter.Length; - public override bool CanWrite => _adapter.CanWrite; + public override long Position + { + get => _adapter.Position; + set => _adapter.Position = value; + } - public override long Length => _adapter.Length; + public override void Flush() + => _adapter.Flush(); - public override long Position + protected override void Dispose(bool disposing) + { + if (_isDisposed) { - get => _adapter.Position; - set => _adapter.Position = value; + // We cannot .Close() the _output multiple times. + return; } + _isDisposed = true; - public override void Flush() - => _adapter.Flush(); - - protected override void Dispose(bool disposing) + if (disposing) { - if (_isDisposed) - { - // We cannot .Close() the _output multiple times. - return; - } - _isDisposed = true; - - if (disposing) - { - _output.Close(); - } - - _adapter.Dispose(); - _output.Dispose(); - base.Dispose(disposing); + _output.Close(); } - public override int Read(byte[] buffer, int offset, int count) - => _adapter.Read(buffer, offset, count); + _adapter.Dispose(); + _output.Dispose(); + base.Dispose(disposing); + } - public override long Seek(long offset, SeekOrigin origin) - => _adapter.Seek(offset, origin); + public override int Read(byte[] buffer, int offset, int count) + => _adapter.Read(buffer, offset, count); - public override void SetLength(long value) - => _adapter.SetLength(value); + public override long Seek(long offset, SeekOrigin origin) + => _adapter.Seek(offset, origin); - public override void Write(byte[] buffer, int offset, int count) - => _adapter.Write(buffer, offset, count); - } - } + public override void SetLength(long value) + => _adapter.SetLength(value); - /// - /// Persister for devices bellow Android level 23. - /// RSA/ECB/PKCS1Padding only is supported and is not considered secure. - /// - private sealed class UnSecureKeyStorePersister : FilePersister - { - private const string _notSupported = @"RSA/ECB/PKCS1Padding with asymetric key is considered not secure and will not be supported for device under API level 23"; + public override void Write(byte[] buffer, int offset, int count) + => _adapter.Write(buffer, offset, count); + } + } - public UnSecureKeyStorePersister(string filePath = null) - : base(filePath) - { - throw new NotSupportedException(_notSupported); - } + /// + /// Persister for devices bellow Android level 23. + /// RSA/ECB/PKCS1Padding only is supported and is not considered secure. + /// + private sealed class UnSecureKeyStorePersister : FilePersister + { + private const string _notSupported = @"RSA/ECB/PKCS1Padding with asymetric key is considered not secure and will not be supported for device under API level 23"; - protected override Stream Decrypt(Stream inputStream) - { - throw new NotSupportedException(_notSupported); - } + public UnSecureKeyStorePersister(string filePath = null) + : base(filePath) + { + throw new NotSupportedException(_notSupported); + } - protected override Stream Encrypt(Stream outputStream) - { - throw new NotSupportedException(_notSupported); - } + protected override Stream Decrypt(Stream inputStream) + { + throw new NotSupportedException(_notSupported); } + protected override Stream Encrypt(Stream outputStream) + { + throw new NotSupportedException(_notSupported); + } } + } diff --git a/src/Uno.UWP/Security/Credentials/PasswordVault.cs b/src/Uno.UWP/Security/Credentials/PasswordVault.cs index cb37ae2b3cbc..d8f813ad8a90 100644 --- a/src/Uno.UWP/Security/Credentials/PasswordVault.cs +++ b/src/Uno.UWP/Security/Credentials/PasswordVault.cs @@ -12,450 +12,449 @@ using Uno.Extensions; using Uno.Foundation.Logging; -namespace Windows.Security.Credentials +namespace Windows.Security.Credentials; + +public partial class PasswordVault { - public partial class PasswordVault + private readonly object _updateGate = new object(); + private readonly IPersister _persister; + + private ImmutableList _credentials; + + protected PasswordVault(IPersister persister) { - private readonly object _updateGate = new object(); - private readonly IPersister _persister; + _persister = persister ?? throw new ArgumentNullException(nameof(persister)); + _credentials = Load(); + } - private ImmutableList _credentials; + public IReadOnlyList RetrieveAll() + => _credentials; - protected PasswordVault(IPersister persister) + public IReadOnlyList FindAllByResource(string resource) + { + // UWP: 'resource' is case sensitive + var result = _credentials.Where(cred => cred.Resource == resource).ToImmutableList(); + if (result.IsEmpty) { - _persister = persister ?? throw new ArgumentNullException(nameof(persister)); - _credentials = Load(); + throw new Exception("No match"); // UWP: Throw 'Exception' if no match } - public IReadOnlyList RetrieveAll() - => _credentials; + return result; + } - public IReadOnlyList FindAllByResource(string resource) + public IReadOnlyList FindAllByUserName(string userName) + { + // UWP: 'userName' is case sensitive + var result = _credentials.Where(cred => cred.UserName == userName).ToImmutableList(); + if (result.IsEmpty) { - // UWP: 'resource' is case sensitive - var result = _credentials.Where(cred => cred.Resource == resource).ToImmutableList(); - if (result.IsEmpty) - { - throw new Exception("No match"); // UWP: Throw 'Exception' if no match - } - - return result; + throw new Exception("No match"); // UWP: Throw 'Exception' if no match } - public IReadOnlyList FindAllByUserName(string userName) - { - // UWP: 'userName' is case sensitive - var result = _credentials.Where(cred => cred.UserName == userName).ToImmutableList(); - if (result.IsEmpty) - { - throw new Exception("No match"); // UWP: Throw 'Exception' if no match - } + return result; + } - return result; + public PasswordCredential Retrieve(string resource, string userName) + { + // UWP: Retrieve is case IN-sensitive for both 'resource' and 'userName' + var result = _credentials.FirstOrDefault(cred => Comparer.Instance.Equals(cred, resource, userName)); + if (result == null) + { + throw new Exception("No match"); // UWP: Throw 'Exception' if no match } - public PasswordCredential Retrieve(string resource, string userName) + return result; + } + + public void Remove(PasswordCredential credential) + { + while (true) { - // UWP: Retrieve is case IN-sensitive for both 'resource' and 'userName' - var result = _credentials.FirstOrDefault(cred => Comparer.Instance.Equals(cred, resource, userName)); - if (result == null) + var capture = _credentials; + var updated = capture.Remove(credential, Comparer.Instance); + + if (capture == updated) { - throw new Exception("No match"); // UWP: Throw 'Exception' if no match + return; } - return result; + lock (_updateGate) + { + if (capture == _credentials) + { + Persist(updated); + _credentials = updated; + + return; + } + } } + } - public void Remove(PasswordCredential credential) + public void Add(PasswordCredential credential) + { + while (true) { - while (true) + var capture = _credentials; + var existing = capture.FirstOrDefault(c => Comparer.Instance.Equals(c, credential)); + + ImmutableList updated; + if (existing == null) + { + updated = capture.Add(credential); + } + else { - var capture = _credentials; - var updated = capture.Remove(credential, Comparer.Instance); + existing.RetrievePassword(); + credential.RetrievePassword(); - if (capture == updated) + if (existing.Password == credential.Password) { + // no change, abort update! return; } - lock (_updateGate) + updated = capture.Replace(existing, credential); + } + + lock (_updateGate) + { + if (capture == _credentials) { - if (capture == _credentials) - { - Persist(updated); - _credentials = updated; + Persist(updated); + _credentials = updated; - return; - } + return; } } } + } - public void Add(PasswordCredential credential) + public ImmutableList Load() + { + try { - while (true) + if (_persister.TryOpenRead(out var src)) { - var capture = _credentials; - var existing = capture.FirstOrDefault(c => Comparer.Instance.Equals(c, credential)); - - ImmutableList updated; - if (existing == null) - { - updated = capture.Add(credential); - } - else + using (src) + using (var reader = new BinaryReader(src)) { - existing.RetrievePassword(); - credential.RetrievePassword(); + var count = reader.ReadInt32(); + var credentials = ImmutableList.CreateBuilder(); - if (existing.Password == credential.Password) + for (var i = 0; i < count; i++) { - // no change, abort update! - return; - } - - updated = capture.Replace(existing, credential); - } - - lock (_updateGate) - { - if (capture == _credentials) - { - Persist(updated); - _credentials = updated; + var res = reader.ReadString(); + var use = reader.ReadString(); + var pwd = reader.ReadString(); - return; + credentials.Add(new PasswordCredential(res, use, pwd)); } + + return credentials.ToImmutable(); } } } - - public ImmutableList Load() + catch (Exception e) { - try - { - if (_persister.TryOpenRead(out var src)) - { - using (src) - using (var reader = new BinaryReader(src)) - { - var count = reader.ReadInt32(); - var credentials = ImmutableList.CreateBuilder(); + this.Log().Warn("Failed to load values from persister, assume empty.", e); + } - for (var i = 0; i < count; i++) - { - var res = reader.ReadString(); - var use = reader.ReadString(); - var pwd = reader.ReadString(); + return ImmutableList.Empty; + } - credentials.Add(new PasswordCredential(res, use, pwd)); - } + public void Persist(ImmutableList credentials) + { + using (var transaction = _persister.OpenWrite(out var dst)) + using (dst) + using (var writer = new BinaryWriter(dst)) + { + writer.Write(credentials.Count); - return credentials.ToImmutable(); - } - } - } - catch (Exception e) + foreach (var credential in credentials) { - this.Log().Warn("Failed to load values from persister, assume empty.", e); + credential.RetrievePassword(); + + writer.Write(credential.Resource); + writer.Write(credential.UserName); + writer.Write(credential.Password); } - return ImmutableList.Empty; + writer.Flush(); + transaction.Commit(); } + } - public void Persist(ImmutableList credentials) - { - using (var transaction = _persister.OpenWrite(out var dst)) - using (dst) - using (var writer = new BinaryWriter(dst)) - { - writer.Write(credentials.Count); + /// + /// A persister responsible to securely persist the credentials managed by a PasswordVault + /// + protected interface IPersister + { + /// + /// Tries to open the source stream from which credentials can be read. + /// + /// The source stream which should be parsed to reload credentials + /// A bool which indicates if the is valid or not. + bool TryOpenRead(out Stream inputStream); - foreach (var credential in credentials) - { - credential.RetrievePassword(); + /// + /// Open the target stream which where credentials should be stored. + /// + /// The target stream where credentials can be stored + /// A which ensure to atomatically update the credentials + WriteTransaction OpenWrite(out Stream outputStream); + } - writer.Write(credential.Resource); - writer.Write(credential.UserName); - writer.Write(credential.Password); - } + /// + /// A transaction used to persist credentials to ensure ACID + /// + protected sealed class WriteTransaction : IDisposable + { + private readonly Action _onCommit; + private readonly Action _onComplete; - writer.Flush(); - transaction.Commit(); - } + private int _state = State.New; + + private static class State + { + public const int New = 0; + public const int Commited = 1; + public const int Disposed = int.MaxValue; } /// - /// A persister responsible to securely persist the credentials managed by a PasswordVault + /// Creates a new transaction /// - protected interface IPersister + /// Callback invoked when this transaction is committed (cf. . + /// Callback invoked when this transaction completes (i.e. Disposed). + public WriteTransaction(Action onCommit = null, Action onComplete = null) { - /// - /// Tries to open the source stream from which credentials can be read. - /// - /// The source stream which should be parsed to reload credentials - /// A bool which indicates if the is valid or not. - bool TryOpenRead(out Stream inputStream); - - /// - /// Open the target stream which where credentials should be stored. - /// - /// The target stream where credentials can be stored - /// A which ensure to atomatically update the credentials - WriteTransaction OpenWrite(out Stream outputStream); + _onCommit = onCommit; + _onComplete = onComplete; } /// - /// A transaction used to persist credentials to ensure ACID + /// A boolean which indicates if this transaction was committed or not (cf. ). /// - protected sealed class WriteTransaction : IDisposable + public bool IsCommited { - private readonly Action _onCommit; - private readonly Action _onComplete; - - private int _state = State.New; - - private static class State - { - public const int New = 0; - public const int Commited = 1; - public const int Disposed = int.MaxValue; - } - - /// - /// Creates a new transaction - /// - /// Callback invoked when this transaction is committed (cf. . - /// Callback invoked when this transaction completes (i.e. Disposed). - public WriteTransaction(Action onCommit = null, Action onComplete = null) - { - _onCommit = onCommit; - _onComplete = onComplete; - } - - /// - /// A boolean which indicates if this transaction was committed or not (cf. ). - /// - public bool IsCommited + get { - get + var state = _state; + if (state == State.Disposed) { - var state = _state; - if (state == State.Disposed) - { - throw new ObjectDisposedException(nameof(WriteTransaction)); - } - - return state == State.Commited; + throw new ObjectDisposedException(nameof(WriteTransaction)); } + + return state == State.Commited; } + } - /// - /// Makes the changes persistent - /// - public void Commit() + /// + /// Makes the changes persistent + /// + public void Commit() + { + switch (Interlocked.CompareExchange(ref _state, State.Commited, State.New)) { - switch (Interlocked.CompareExchange(ref _state, State.Commited, State.New)) - { - case State.New: - _onCommit?.Invoke(); - break; + case State.New: + _onCommit?.Invoke(); + break; - case State.Disposed: - throw new ObjectDisposedException(nameof(WriteTransaction)); - } + case State.Disposed: + throw new ObjectDisposedException(nameof(WriteTransaction)); } + } - /// - public void Dispose() + /// + public void Dispose() + { + var previousState = Interlocked.Exchange(ref _state, State.Disposed); + if (previousState != State.Disposed) { - var previousState = Interlocked.Exchange(ref _state, State.Disposed); - if (previousState != State.Disposed) - { - _onComplete?.Invoke(previousState == State.Commited); - } + _onComplete?.Invoke(previousState == State.Commited); } } + } + + /// + /// A base class to persist a PasswordVault in a file on the disk + /// + protected abstract class FilePersister : IPersister + { + private readonly string _tmp; + private readonly string _dst; /// - /// A base class to persist a PasswordVault in a file on the disk + /// Creates a new instance /// - protected abstract class FilePersister : IPersister + /// The path where the vault should be persisted + protected FilePersister(string filePath = null) { - private readonly string _tmp; - private readonly string _dst; - - /// - /// Creates a new instance - /// - /// The path where the vault should be persisted - protected FilePersister(string filePath = null) - { - _dst = filePath ?? Path.Combine(ApplicationData.Current.LocalFolder.Path, ".vault"); - _tmp = _dst + ".tmp"; - } + _dst = filePath ?? Path.Combine(ApplicationData.Current.LocalFolder.Path, ".vault"); + _tmp = _dst + ".tmp"; + } - /// - /// Wraps a given encrypted stream into a stream which ensure decryption - /// - /// The encrypted stream - /// The decrypted stream - protected abstract Stream Encrypt(Stream outputStream); - - /// - /// Wraps a given raw stream into a stream which ensure encryption - /// - /// The raw stream - /// The encrypted stream - protected abstract Stream Decrypt(Stream inputStream); - - /// - public bool TryOpenRead(out Stream inputStream) - { - var dst = new FileInfo(_dst); + /// + /// Wraps a given encrypted stream into a stream which ensure decryption + /// + /// The encrypted stream + /// The decrypted stream + protected abstract Stream Encrypt(Stream outputStream); - var exists = dst.Exists; + /// + /// Wraps a given raw stream into a stream which ensure encryption + /// + /// The raw stream + /// The encrypted stream + protected abstract Stream Decrypt(Stream inputStream); - if (exists) - { - var length = dst.Length; + /// + public bool TryOpenRead(out Stream inputStream) + { + var dst = new FileInfo(_dst); - inputStream = Decrypt(File.Open(_dst, FileMode.Open, FileAccess.Read, FileShare.Read)); - return true; - } - else - { - inputStream = Stream.Null; - return false; - } - } + var exists = dst.Exists; - /// - public WriteTransaction OpenWrite(out Stream outputStream) + if (exists) { - var fileStream = File.Open(_tmp, FileMode.Create, FileAccess.Write, FileShare.None); - var encryptedStream = Encrypt(fileStream); + var length = dst.Length; - outputStream = encryptedStream; + inputStream = Decrypt(File.Open(_dst, FileMode.Open, FileAccess.Read, FileShare.Read)); + return true; + } + else + { + inputStream = Stream.Null; + return false; + } + } - return new WriteTransaction(onComplete: Complete); + /// + public WriteTransaction OpenWrite(out Stream outputStream) + { + var fileStream = File.Open(_tmp, FileMode.Create, FileAccess.Write, FileShare.None); + var encryptedStream = Encrypt(fileStream); - void Complete(bool isCommitted) - { - // The encryptedStream has been disposed by the "Persist" but make sure to dispose - // the underlying file stream before accessing to the file itself. - //fileStream.Flush(); - //fileStream.Close(); - fileStream.Dispose(); + outputStream = encryptedStream; - if (!isCommitted) - { - return; - } + return new WriteTransaction(onComplete: Complete); - if (File.Exists(_dst)) - { - // Note: We don't use the backup file. We don't want that removed credentials - // can be restored by altering the current '_dst' file. - File.Replace(_tmp, _dst, null, ignoreMetadataErrors: true); - } - else - { - File.Move(_tmp, _dst); - } + void Complete(bool isCommitted) + { + // The encryptedStream has been disposed by the "Persist" but make sure to dispose + // the underlying file stream before accessing to the file itself. + //fileStream.Flush(); + //fileStream.Close(); + fileStream.Dispose(); + + if (!isCommitted) + { + return; + } + + if (File.Exists(_dst)) + { + // Note: We don't use the backup file. We don't want that removed credentials + // can be restored by altering the current '_dst' file. + File.Replace(_tmp, _dst, null, ignoreMetadataErrors: true); + } + else + { + File.Move(_tmp, _dst); } } } + } + + /// + /// A basically encrypted persister which does not provide an acceptable security level for sensitive data like a password. + /// + protected sealed class UnsecuredPersister : FilePersister + { + private readonly byte[] _key; + private readonly byte[] _iv; /// - /// A basically encrypted persister which does not provide an acceptable security level for sensitive data like a password. + /// Creates a new instance providing the secrets for encryption (TripleDES) /// - protected sealed class UnsecuredPersister : FilePersister + /// The key, must be 24 bytes length + /// The IV, must be 8 bytes length + /// The path where the vault should be persisted + public UnsecuredPersister(byte[] key, byte[] iv, string filePath = null) + : base(filePath) { - private readonly byte[] _key; - private readonly byte[] _iv; - - /// - /// Creates a new instance providing the secrets for encryption (TripleDES) - /// - /// The key, must be 24 bytes length - /// The IV, must be 8 bytes length - /// The path where the vault should be persisted - public UnsecuredPersister(byte[] key, byte[] iv, string filePath = null) - : base(filePath) - { - _key = key ?? throw new ArgumentNullException(nameof(key)); - _iv = iv ?? throw new ArgumentNullException(nameof(iv)); + _key = key ?? throw new ArgumentNullException(nameof(key)); + _iv = iv ?? throw new ArgumentNullException(nameof(iv)); - if (_key.Length != 24) - { - throw new InvalidOperationException("The secret must have 24 bytes"); - } - if (_iv.Length != 8) - { - throw new InvalidOperationException("The iv must have 8 bytes"); - } + if (_key.Length != 24) + { + throw new InvalidOperationException("The secret must have 24 bytes"); } - - /// - /// Creates a new instance providing a simple password used to encrypt the file - /// - /// The password used to encrypt the file - /// The path where the vault should be persisted - public UnsecuredPersister(string password = null, string filePath = null) - : base(filePath) + if (_iv.Length != 8) { - (_key, _iv) = GenerateSecrets(password ?? GetEntryPointIdentifier()); + throw new InvalidOperationException("The iv must have 8 bytes"); } + } - private static string GetEntryPointIdentifier() - { - var assembly = Assembly.GetEntryAssembly(); - var method = assembly.EntryPoint.DeclaringType; + /// + /// Creates a new instance providing a simple password used to encrypt the file + /// + /// The password used to encrypt the file + /// The path where the vault should be persisted + public UnsecuredPersister(string password = null, string filePath = null) + : base(filePath) + { + (_key, _iv) = GenerateSecrets(password ?? GetEntryPointIdentifier()); + } - return assembly.GetName().Name + method.DeclaringType.FullName; - } + private static string GetEntryPointIdentifier() + { + var assembly = Assembly.GetEntryAssembly(); + var method = assembly.EntryPoint.DeclaringType; - private static (byte[] key, byte[] iv) GenerateSecrets(string password) + return assembly.GetName().Name + method.DeclaringType.FullName; + } + + private static (byte[] key, byte[] iv) GenerateSecrets(string password) + { + if (string.IsNullOrWhiteSpace(password)) { - if (string.IsNullOrWhiteSpace(password)) - { - throw new ArgumentOutOfRangeException(nameof(password), "Password is empty"); - } + throw new ArgumentOutOfRangeException(nameof(password), "Password is empty"); + } - var key = new byte[24]; - var iv = new byte[8]; + var key = new byte[24]; + var iv = new byte[8]; - var src = SHA256.HashData(Encoding.UTF8.GetBytes(password)); + var src = SHA256.HashData(Encoding.UTF8.GetBytes(password)); - Array.Copy(src, 0, key, 0, 24); - Array.Copy(src, 24, iv, 0, 8); + Array.Copy(src, 0, key, 0, 24); + Array.Copy(src, 24, iv, 0, 8); - return (key, iv); - } + return (key, iv); + } - protected override Stream Decrypt(Stream input) - => new CryptoStream(input, TripleDES.Create().CreateDecryptor(_key, _iv), CryptoStreamMode.Read); + protected override Stream Decrypt(Stream input) + => new CryptoStream(input, TripleDES.Create().CreateDecryptor(_key, _iv), CryptoStreamMode.Read); - protected override Stream Encrypt(Stream output) - => new CryptoStream(output, TripleDES.Create().CreateEncryptor(_key, _iv), CryptoStreamMode.Write); - } + protected override Stream Encrypt(Stream output) + => new CryptoStream(output, TripleDES.Create().CreateEncryptor(_key, _iv), CryptoStreamMode.Write); + } - private class Comparer : EqualityComparer - { - public static readonly Comparer Instance = new Comparer(); + private class Comparer : EqualityComparer + { + public static readonly Comparer Instance = new Comparer(); - public bool Equals(PasswordCredential obj, string resource, string userName) - => StringComparer.OrdinalIgnoreCase.Equals(obj.Resource, resource) - && StringComparer.OrdinalIgnoreCase.Equals(obj.UserName, userName); + public bool Equals(PasswordCredential obj, string resource, string userName) + => StringComparer.OrdinalIgnoreCase.Equals(obj.Resource, resource) + && StringComparer.OrdinalIgnoreCase.Equals(obj.UserName, userName); - public override bool Equals(PasswordCredential left, PasswordCredential right) - => StringComparer.OrdinalIgnoreCase.Equals(left.Resource, right.Resource) - && StringComparer.OrdinalIgnoreCase.Equals(left.UserName, right.UserName); + public override bool Equals(PasswordCredential left, PasswordCredential right) + => StringComparer.OrdinalIgnoreCase.Equals(left.Resource, right.Resource) + && StringComparer.OrdinalIgnoreCase.Equals(left.UserName, right.UserName); - public override int GetHashCode(PasswordCredential obj) - => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Resource) - ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.UserName); - } + public override int GetHashCode(PasswordCredential obj) + => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Resource) + ^ StringComparer.OrdinalIgnoreCase.GetHashCode(obj.UserName); } } diff --git a/src/Uno.UWP/Security/Credentials/PasswordVault.iOSmacOS.cs b/src/Uno.UWP/Security/Credentials/PasswordVault.iOSmacOS.cs index 0e6ba4e074c8..1251e22f6742 100644 --- a/src/Uno.UWP/Security/Credentials/PasswordVault.iOSmacOS.cs +++ b/src/Uno.UWP/Security/Credentials/PasswordVault.iOSmacOS.cs @@ -3,81 +3,80 @@ using Foundation; using Security; -namespace Windows.Security.Credentials +namespace Windows.Security.Credentials; + +sealed partial class PasswordVault { - sealed partial class PasswordVault + public PasswordVault() + : this(new KeyChainPersister()) { - public PasswordVault() - : this(new KeyChainPersister()) - { - } + } - private sealed class KeyChainPersister : IPersister - { - private const string _accountId = "uno_passwordvault"; + private sealed class KeyChainPersister : IPersister + { + private const string _accountId = "uno_passwordvault"; - private readonly SecRecord _query = new SecRecord(SecKind.GenericPassword) { Account = _accountId }; + private readonly SecRecord _query = new SecRecord(SecKind.GenericPassword) { Account = _accountId }; - public bool TryOpenRead(out Stream inputStream) + public bool TryOpenRead(out Stream inputStream) + { + var record = SecKeyChain.QueryAsRecord(_query, out var statusCode); + if (statusCode == SecStatusCode.Success) { - var record = SecKeyChain.QueryAsRecord(_query, out var statusCode); - if (statusCode == SecStatusCode.Success) - { - inputStream = record.ValueData.AsStream(); - return true; - } - else - { - CheckCommonStatusCodes(statusCode); + inputStream = record.ValueData.AsStream(); + return true; + } + else + { + CheckCommonStatusCodes(statusCode); - inputStream = Stream.Null; - return false; - } + inputStream = Stream.Null; + return false; } + } - public WriteTransaction OpenWrite(out Stream outputStream) + public WriteTransaction OpenWrite(out Stream outputStream) + { + var stream = new MemoryStream(); + outputStream = stream; + + return new WriteTransaction(onCommit: () => Commit()); + + void Commit() { - var stream = new MemoryStream(); - outputStream = stream; + stream.Position = 0; + var record = new SecRecord() + { + ValueData = NSData.FromStream(stream) + }; - return new WriteTransaction(onCommit: () => Commit()); - void Commit() + var result = SecKeyChain.Update(_query, record); + if (result == SecStatusCode.ItemNotFound) { stream.Position = 0; - var record = new SecRecord() + record = new SecRecord(SecKind.GenericPassword) { + Account = _accountId, ValueData = NSData.FromStream(stream) }; + result = SecKeyChain.Add(record); + } - var result = SecKeyChain.Update(_query, record); - if (result == SecStatusCode.ItemNotFound) - { - stream.Position = 0; - record = new SecRecord(SecKind.GenericPassword) - { - Account = _accountId, - ValueData = NSData.FromStream(stream) - }; - - result = SecKeyChain.Add(record); - } - - if (result != SecStatusCode.Success) - { - CheckCommonStatusCodes(result); - throw new InvalidOperationException("Failed to persist the vault"); - } + if (result != SecStatusCode.Success) + { + CheckCommonStatusCodes(result); + throw new InvalidOperationException("Failed to persist the vault"); } } + } - private void CheckCommonStatusCodes(SecStatusCode code) + private void CheckCommonStatusCodes(SecStatusCode code) + { + if (code == SecStatusCode.MissingEntitlement) { - if (code == SecStatusCode.MissingEntitlement) - { - throw new InvalidOperationException("Your application is not allowed to use the keychain. Make sure that you have setup the KeyChain in the Entitlepements.plist of your application."); - } + throw new InvalidOperationException("Your application is not allowed to use the keychain. Make sure that you have setup the KeyChain in the Entitlepements.plist of your application."); } } } diff --git a/src/Uno.UWP/Security/Credentials/PasswordVault.others.cs b/src/Uno.UWP/Security/Credentials/PasswordVault.others.cs new file mode 100644 index 000000000000..f8a9f40526f2 --- /dev/null +++ b/src/Uno.UWP/Security/Credentials/PasswordVault.others.cs @@ -0,0 +1,25 @@ +#if !__ANDROID__ && !__IOS__ && !__MACOS__ +using System; +using Uno; + +namespace Windows.Security.Credentials; + +[NotImplemented("IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +// This class is ** NOT ** sealed in order to allow projects for which the security limit described bellow is not +// really a concern (for instance if they are only storing an OAuth token) to inherit and provide they own +// implementation of 'IPersister'. +partial class PasswordVault +{ + [NotImplemented("IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] + public PasswordVault() + { +#if !__WASM__ + throw new NotImplementedException(); +#else + throw new NotSupportedException(@"There is no way to properly persist secured content on WebAssembly. +At the opposite of other platforms, we cannot properly store a secret in a secured enclave which ensure that our secret +won't be accessed by any untrusted code (e.g. cross-site scripting)."); +#endif + } +} +#endif diff --git a/src/Uno.UWP/Security/Credentials/PasswordVault.reference.cs b/src/Uno.UWP/Security/Credentials/PasswordVault.reference.cs deleted file mode 100644 index 34bbf3011c36..000000000000 --- a/src/Uno.UWP/Security/Credentials/PasswordVault.reference.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Uno; - -namespace Windows.Security.Credentials -{ - [NotImplemented] - /* sealed */ - partial class PasswordVault - { - [NotImplemented] - public PasswordVault() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Uno.UWP/Security/Credentials/PasswordVault.skia.cs b/src/Uno.UWP/Security/Credentials/PasswordVault.skia.cs deleted file mode 100644 index 34bbf3011c36..000000000000 --- a/src/Uno.UWP/Security/Credentials/PasswordVault.skia.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Uno; - -namespace Windows.Security.Credentials -{ - [NotImplemented] - /* sealed */ - partial class PasswordVault - { - [NotImplemented] - public PasswordVault() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Uno.UWP/Security/Credentials/PasswordVault.wasm.cs b/src/Uno.UWP/Security/Credentials/PasswordVault.wasm.cs deleted file mode 100644 index 4169d38934ab..000000000000 --- a/src/Uno.UWP/Security/Credentials/PasswordVault.wasm.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using Uno; - -namespace Windows.Security.Credentials -{ - [NotImplemented] // Not really not implemented, but this will display an error directly in the IDE. - /* sealed */ - partial class PasswordVault - { - // This class is ** NOT ** sealed in order to allow projects for which the security limit described bellow is not - // really a concern (for instance if they are only storing an OAuth token) to inherit and provide they own - // implementation of 'IPersister'. - - private const string _notSupported = @"There is no way to properly persist secured content on WebAssembly. -At the opposite of other platforms, we cannot properly store a secret in a secured enclave which ensure that our secret -won't be accessed by any untrusted code (e.g. cross-site scripting)."; - - [NotImplemented] // Not really not implemented, but this will display an error directly in the IDE. - public PasswordVault() - { - throw new NotSupportedException(_notSupported); - } - } -} diff --git a/src/Uno.UWP/Storage/StorageFile.skia.cs b/src/Uno.UWP/Storage/StorageFile.skia.cs index 67bcf2a7338c..2c004139103a 100644 --- a/src/Uno.UWP/Storage/StorageFile.skia.cs +++ b/src/Uno.UWP/Storage/StorageFile.skia.cs @@ -12,6 +12,8 @@ namespace Windows.Storage { partial class StorageFile { + internal static string ResourcePathBase { get; set; } = Package.Current.InstalledPath; + private static async Task GetFileFromApplicationUri(CancellationToken ct, Uri uri) { if (uri.Scheme != "ms-appx") @@ -22,7 +24,7 @@ private static async Task GetFileFromApplicationUri(CancellationTok var path = Uri.UnescapeDataString(uri.PathAndQuery).TrimStart('/'); - var resourcePathname = global::System.IO.Path.Combine(Package.Current.InstalledPath, uri.Host, path); + var resourcePathname = global::System.IO.Path.Combine(ResourcePathBase, uri.Host, path); if (resourcePathname != null) {