diff --git a/.github/workflows/azure-static-web-app.yml b/.github/workflows/azure-static-web-app.yml new file mode 100644 index 00000000..be6570c7 --- /dev/null +++ b/.github/workflows/azure-static-web-app.yml @@ -0,0 +1,123 @@ +name: Azure Static Web Apps CI/CD + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - main + +jobs: + build_and_deploy_job: + env: + DIST_PATH: samples/WinUI.TableView.SampleApp.Uno/bin/Release/net10.0-browserwasm/publish/wwwroot + DotnetVersion: '10.0.100' + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + container: 'unoplatform/wasm-build:3.0' + name: Build and Deploy Job + steps: + - uses: actions/checkout@v3 + with: + submodules: true + lfs: false + + # When running on macos, set the DOTNET_INSTALL_DIR so a workspace local folder + # is used to install the SDK. This is required for the Uno.Sdk.Updater to work + - name: Set DOTNET_INSTALL_DIR + if: runner.os == 'macos' || runner.os == 'linux' + shell: bash + run: echo "DOTNET_INSTALL_DIR=$GITHUB_WORKSPACE/.dotnet" >> $GITHUB_ENV + + - name: 'Set Wasm cache path' + shell: pwsh + run: echo "WasmCachePath=${{ github.workspace }}/.emscripten-cache" >> $env:GITHUB_ENV + + - name: Cache EMSDK + id: cache-emsdk + uses: actions/cache@v4 + env: + cache-name: cache-emsdk-modules + with: + path: ${{ env.WasmCachePath }} + key: ${{ runner.os }}-build-emsdk-${{ env.ValidationUnoCheckVersion }} + restore-keys: | + ${{ runner.os }}-build-emsdk-${{ env.ValidationUnoCheckVersion }} + + - name: Cache .NET + id: cache-dotnet + uses: actions/cache@v4 + env: + cache-name: cache-dotnet-install + with: + path: ${{ github.workspace }}/.dotnet + key: ${{ runner.os }}-build-dotnet-${{ env.DotnetVersion }} + restore-keys: | + ${{ runner.os }}-build-dotnet-${{ env.DotnetVersion }} + + - name: Setup .NET + uses: actions/setup-dotnet@v1.7.2 + with: + dotnet-version: ${{ env.DotnetVersion }} + + - uses: dotnet/nbgv@f088059084cb5d872e9d1a994433ca6440c2bf72 # v0.4.2 + id: nbgv + with: + toolVersion: 3.6.139 + setAllVars: true + + - run: | + cd samples/WinUI.TableView.SampleApp.Uno + dotnet workload restore + name: Setup Workloads + + - run: | + cd samples/WinUI.TableView.SampleApp.Uno + dotnet workload install wasm-tools + name: Setup Workloads + + - run: | + cd samples/WinUI.TableView.SampleApp.Uno + dotnet publish -c Release -f net10.0-browserwasm "/p:PackageVersion=${{ steps.nbgv.outputs.SemVer2 }}" /p:EnableWindowsTargeting=true /bl:../binlogs/output.binlog + name: Build Uno Wasm App + + - uses: actions/upload-artifact@v4 + with: + name: wasm-site + path: ${{ env.DIST_PATH }} + + - uses: actions/upload-artifact@v4 + with: + name: logs + path: binlogs + + - name: Build And Deploy + id: builddeploy + continue-on-error: true + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_LIVELY_GROUND_0D0F6A810 }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "${{ env.DIST_PATH }}" # App source code path + api_location: "" # Api source code path - optional + output_location: "" # Built app content directory - optional + ###### End of Repository/Build Configurations ###### + + close_pull_request_job: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + name: Close Pull Request Job + steps: + - name: Close Pull Request + id: closepullrequest + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_LIVELY_GROUND_0D0F6A810 }} + action: "close" + + diff --git a/.github/workflows/cd-build.yml b/.github/workflows/cd-build.yml index 2192d4c6..4b8fc9ec 100644 --- a/.github/workflows/cd-build.yml +++ b/.github/workflows/cd-build.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: windows-2022 + runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v4.1.4 @@ -18,7 +18,7 @@ jobs: dotnet-version: | 8.0.x 9.0.x - 10.0.x + 10.0.100 - name: Setup MSBuild.exe uses: microsoft/setup-msbuild@v2 diff --git a/.github/workflows/ci-build-samples.yml b/.github/workflows/ci-build-samples.yml new file mode 100644 index 00000000..42f68b4d --- /dev/null +++ b/.github/workflows/ci-build-samples.yml @@ -0,0 +1,41 @@ +name: ci-build-samples + +on: + push: + branches: main + paths-ignore: + - 'docs/**' + + pull_request: + branches: main + paths-ignore: + - 'docs/**' + +jobs: + build: + runs-on: windows-latest + + strategy: + matrix: + architecture: [x86, x64, ARM64] + + steps: + - name: Checkout + uses: actions/checkout@v4.1.4 + with: + submodules: recursive + + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v2 + + - name: Setup .NET Core SDK + uses: actions/setup-dotnet@v5.0.1 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.100 + + - name: Build + run: | + msbuild /restore samples/WinUI.TableView.SampleApp/WinUI.TableView.SampleApp.csproj /p:Configuration=Release /p:Platform=${{ matrix.architecture }} diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 7a1fdc4b..4558aa66 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -5,14 +5,16 @@ on: branches: main paths-ignore: - 'docs/**' + - 'samples/**' pull_request: branches: main paths-ignore: - 'docs/**' + - 'samples/**' jobs: build: - runs-on: windows-2022 + runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v4.1.4 @@ -26,8 +28,7 @@ jobs: dotnet-version: | 8.0.x 9.0.x - 10.0.x - + 10.0.100 - name: Build run: | msbuild /restore ` @@ -58,7 +59,7 @@ jobs: publish: needs: build - runs-on: windows-2022 + runs-on: windows-latest environment: ci-nuget-publish if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 97b405ab..7f81cd43 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -3,10 +3,12 @@ name: ci-docs on: push: branches: main + paths-ignore: + - 'samples/**' jobs: build: - runs-on: windows-2022 + runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v4.1.4 @@ -17,7 +19,7 @@ jobs: dotnet-version: | 8.0.x 9.0.x - 10.0.x + 10.0.100 - name: Install DocFX run: dotnet tool update -g docfx diff --git a/WinUI.TableView.slnx b/WinUI.TableView.slnx index f098337b..64845ab8 100644 --- a/WinUI.TableView.slnx +++ b/WinUI.TableView.slnx @@ -1,4 +1,11 @@ + + + + + + + diff --git a/global.json b/global.json new file mode 100644 index 00000000..38abb8e5 --- /dev/null +++ b/global.json @@ -0,0 +1,10 @@ +{ + "msbuild-sdks": { + "Uno.Sdk": "6.4.53" + }, + "sdk": { + "version": "10.0.100", + "rollForward": "patch", + "allowPrerelease": false + } +} diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props new file mode 100644 index 00000000..550ee33e --- /dev/null +++ b/samples/Directory.Build.props @@ -0,0 +1,18 @@ + + + net10.0 + preview + enable + enable + WinUI.TableView.SampleApp + true + + + $(NoWarn);NU1507;NETSDK1201;PRI257 + + diff --git a/samples/Directory.Packages.props b/samples/Directory.Packages.props new file mode 100644 index 00000000..834cdd1b --- /dev/null +++ b/samples/Directory.Packages.props @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Assets/Icons/icon.svg b/samples/WinUI.TableView.SampleApp.Uno/Assets/Icons/icon.svg new file mode 100644 index 00000000..a15af53a --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Assets/Icons/icon.svg @@ -0,0 +1,42 @@ + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Assets/Icons/icon_foreground.svg b/samples/WinUI.TableView.SampleApp.Uno/Assets/Icons/icon_foreground.svg new file mode 100644 index 00000000..d00de5a7 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Assets/Icons/icon_foreground.svg @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Assets/Splash/splash_screen.svg b/samples/WinUI.TableView.SampleApp.Uno/Assets/Splash/splash_screen.svg new file mode 100644 index 00000000..d00de5a7 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Assets/Splash/splash_screen.svg @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Package.appxmanifest b/samples/WinUI.TableView.SampleApp.Uno/Package.appxmanifest new file mode 100644 index 00000000..9ef38146 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Package.appxmanifest @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/AndroidManifest.xml b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/AndroidManifest.xml new file mode 100644 index 00000000..95ae0753 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Assets/AboutAssets.txt b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Assets/AboutAssets.txt new file mode 100644 index 00000000..89ab409d --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Assets/AboutAssets.txt @@ -0,0 +1,22 @@ +To add cross-platform image assets for your Uno Platform app, use the Assets folder +in the shared project instead. Assets in this folder are Android-only assets. + +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "AndroidAsset". + +These files will be deployed with your package and will be accessible using Android's +AssetManager, like this: + +public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + +Additionally, some Android functions will automatically load asset files: + +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Main.Android.cs b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Main.Android.cs new file mode 100644 index 00000000..65177e10 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Main.Android.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using Microsoft.UI.Xaml.Media; + +namespace WinUI.TableView.SampleApp.Droid; +[global::Android.App.ApplicationAttribute( + Label = "@string/ApplicationName", + Icon = "@mipmap/icon", + LargeHeap = true, + HardwareAccelerated = true, + Theme = "@style/Theme.App.Starting" +)] +public class Application : Microsoft.UI.Xaml.NativeApplication +{ + static Application() + { + App.InitializeLogging(); + } + + public Application(IntPtr javaReference, JniHandleOwnership transfer) + : base(() => new App(), javaReference, transfer) + { + } + +} + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/MainActivity.Android.cs b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/MainActivity.Android.cs new file mode 100644 index 00000000..b60b7817 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/MainActivity.Android.cs @@ -0,0 +1,22 @@ +using Android.App; +using Android.Content.PM; +using Android.OS; +using Android.Views; +using Android.Widget; + +namespace WinUI.TableView.SampleApp.Droid; +[Activity( + MainLauncher = true, + ConfigurationChanges = global::Uno.UI.ActivityHelper.AllConfigChanges, + WindowSoftInputMode = SoftInput.AdjustNothing | SoftInput.StateHidden +)] +public class MainActivity : Microsoft.UI.Xaml.ApplicationActivity +{ + protected override void OnCreate(Bundle? savedInstanceState) + { + global::AndroidX.Core.SplashScreen.SplashScreen.InstallSplashScreen(this); + + base.OnCreate(savedInstanceState); + } + +} diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/AboutResources.txt b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/AboutResources.txt new file mode 100644 index 00000000..17e3b133 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/AboutResources.txt @@ -0,0 +1,47 @@ +To add cross-platform image assets for your Uno Platform app, use the Assets folder +in the shared project instead. Resources in this folder are Android-only. + +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/values/Strings.xml b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/values/Strings.xml new file mode 100644 index 00000000..57866898 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/values/Strings.xml @@ -0,0 +1,5 @@ + + + Hello World, Click Me! + WinUI.TableView.SampleApp + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/values/Styles.xml b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/values/Styles.xml new file mode 100644 index 00000000..f47dcf36 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/Resources/values/Styles.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/environment.conf b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/environment.conf new file mode 100644 index 00000000..d8c10645 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Android/environment.conf @@ -0,0 +1,2 @@ +# See this for more details: http://developer.xamarin.com/guides/android/advanced_topics/garbage_collection/ +MONO_GC_PARAMS=bridge-implementation=new,nursery-size=32m,soft-heap-limit=256m \ No newline at end of file diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/Desktop/Program.cs b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Desktop/Program.cs new file mode 100644 index 00000000..421440b2 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/Desktop/Program.cs @@ -0,0 +1,21 @@ +using Uno.UI.Hosting; +using WinUI.TableView.SampleApp; + +internal class Program +{ + [STAThread] + public static void Main(string[] args) + { + App.InitializeLogging(); + + var host = UnoPlatformHostBuilder.Create() + .App(() => new App()) + .UseX11() + .UseLinuxFrameBuffer() + .UseMacOS() + .UseWin32() + .Build(); + + host.Run(); + } +} diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/LinkerConfig.xml b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/LinkerConfig.xml new file mode 100644 index 00000000..b0dec412 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/LinkerConfig.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/Program.cs b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/Program.cs new file mode 100644 index 00000000..7241f99a --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/Program.cs @@ -0,0 +1,11 @@ +using Uno.UI.Hosting; +using WinUI.TableView.SampleApp; + +App.InitializeLogging(); + +var host = UnoPlatformHostBuilder.Create() + .App(() => new App()) + .UseWebAssembly() + .Build(); + +await host.RunAsync(); diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/WasmCSS/Fonts.css b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/WasmCSS/Fonts.css new file mode 100644 index 00000000..4fdd6055 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/WasmCSS/Fonts.css @@ -0,0 +1,28 @@ +/** + When adding fonts here, make sure to add them using a base64 data uri, otherwise + fonts loading are delayed, and text may get displayed incorrectly. +*/ + +/* https://github.com/unoplatform/uno/issues/3954 */ +@font-face { + font-family: 'Segoe UI'; + src: local('Segoe UI'), local('-apple-system'), local('BlinkMacSystemFont'), local('Inter'), local('Cantarell'), local('Ubuntu'), local('Roboto'), local('Open Sans'), local('Noto Sans'), local('Helvetica Neue'), local('sans-serif'); +} + +@font-face { + font-family: 'Roboto'; + src: url(./Uno.Fonts.Roboto/Fonts/Roboto-Light.ttf) format('truetype'); + font-weight: 300; +} + +@font-face { + font-family: 'Roboto'; + src: url(./Uno.Fonts.Roboto/Fonts/Roboto-Regular.ttf) format('truetype'); + font-weight: 400; +} + +@font-face { + font-family: 'Roboto'; + src: url(./Uno.Fonts.Roboto/Fonts/Roboto-Medium.ttf) format('truetype'); + font-weight: 500; +} diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/WasmScripts/AppManifest.js b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/WasmScripts/AppManifest.js new file mode 100644 index 00000000..b6055f8d --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/WasmScripts/AppManifest.js @@ -0,0 +1,3 @@ +var UnoAppManifest = { + displayName: "WinUI.TableView.SampleApp" +} diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/aot.profile b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/aot.profile new file mode 100644 index 00000000..e5708436 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/aot.profile differ diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/manifest.webmanifest b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/manifest.webmanifest new file mode 100644 index 00000000..87639197 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/manifest.webmanifest @@ -0,0 +1,10 @@ +{ + "background_color": "#ffffff", + "description": "WinUI.TableView.SampleApp", + "display": "standalone", + "name": "WinUI.TableView.SampleApp", + "short_name": "WinUI.TableView.SampleApp", + "start_url": "/index.html", + "theme_color": "#ffffff", + "scope": "/" +} diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/wwwroot/staticwebapp.config.json b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/wwwroot/staticwebapp.config.json new file mode 100644 index 00000000..79c1b17c --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/wwwroot/staticwebapp.config.json @@ -0,0 +1,30 @@ +{ + "navigationFallback": { + "rewrite": "/index.html", + "exclude": [ + "*.{css,js}", + "*.{png}", + "*.{c,h,wasm,clr,pdb,dat,txt}" + ] + }, + "routes": [ + { + "route": "/package_*", + "headers": { + "cache-control": "public, immutable, max-age=31536000" + } + }, + { + "route": "/*.ttf", + "headers": { + "cache-control": "public, immutable, max-age=31536000" + } + }, + { + "route": "/*", + "headers": { + "cache-control": "must-revalidate, max-age=3600" + } + } + ] +} diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/wwwroot/web.config b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/wwwroot/web.config new file mode 100644 index 00000000..8f5a860f --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/WebAssembly/wwwroot/web.config @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Entitlements.plist b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Entitlements.plist new file mode 100644 index 00000000..24c31036 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Info.plist b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Info.plist new file mode 100644 index 00000000..ea3dcb4b --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Info.plist @@ -0,0 +1,43 @@ + + + + + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + XSAppIconAssets + Assets.xcassets/icon.appiconset + UIApplicationSupportsIndirectInputEvents + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Main.iOS.cs b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Main.iOS.cs new file mode 100644 index 00000000..14fa0c1b --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Main.iOS.cs @@ -0,0 +1,12 @@ +using UIKit; +using Uno.UI.Hosting; +using WinUI.TableView.SampleApp; + +App.InitializeLogging(); + +var host = UnoPlatformHostBuilder.Create() + .App(() => new App()) + .UseAppleUIKit() + .Build(); + +host.Run(); diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Media.xcassets/LaunchImages.launchimage/Contents.json b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Media.xcassets/LaunchImages.launchimage/Contents.json new file mode 100644 index 00000000..69555e44 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/Media.xcassets/LaunchImages.launchimage/Contents.json @@ -0,0 +1,58 @@ +{ + "images": [ + { + "orientation": "portrait", + "extent": "full-screen", + "minimum-system-version": "7.0", + "scale": "2x", + "size": "640x960", + "idiom": "iphone" + }, + { + "orientation": "portrait", + "extent": "full-screen", + "minimum-system-version": "7.0", + "subtype": "retina4", + "scale": "2x", + "size": "640x1136", + "idiom": "iphone" + }, + { + "orientation": "portrait", + "extent": "full-screen", + "minimum-system-version": "7.0", + "scale": "1x", + "size": "768x1024", + "idiom": "ipad" + }, + { + "orientation": "landscape", + "extent": "full-screen", + "minimum-system-version": "7.0", + "scale": "1x", + "size": "1024x768", + "idiom": "ipad" + }, + { + "orientation": "portrait", + "extent": "full-screen", + "minimum-system-version": "7.0", + "scale": "2x", + "size": "1536x2048", + "idiom": "ipad" + }, + { + "orientation": "landscape", + "extent": "full-screen", + "minimum-system-version": "7.0", + "scale": "2x", + "size": "2048x1536", + "idiom": "ipad" + } + ], + "properties": {}, + "info": { + "version": 1, + "author": "" + } +} \ No newline at end of file diff --git a/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/PrivacyInfo.xcprivacy b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..902abb05 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Platforms/iOS/PrivacyInfo.xcprivacy @@ -0,0 +1,41 @@ + + + + + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/Properties/launchSettings.json b/samples/WinUI.TableView.SampleApp.Uno/Properties/launchSettings.json new file mode 100644 index 00000000..330903d5 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:8080", + "sslPort": 0 + } + }, + "profiles": { + // This profile is first in order for dotnet run to pick it up by default + "WinUI.TableView.SampleApp (WebAssembly)": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WinUI.TableView.SampleApp (WebAssembly IIS Express)": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WinUI.TableView.SampleApp (Desktop)": { + "commandName": "Project", + "compatibleTargetFramework": "desktop" + }, + "WinUI.TableView.SampleApp (Desktop WSL2)": { + "commandName": "WSL2", + "commandLineArgs": "{ProjectDir}/bin/Debug/net9.0-desktop/WinUI.TableView.SampleApp.dll", + "distributionName": "", + "compatibleTargetFramework": "desktop" + } + } +} diff --git a/samples/WinUI.TableView.SampleApp.Uno/Services/Endpoints/DebugHandler.cs b/samples/WinUI.TableView.SampleApp.Uno/Services/Endpoints/DebugHandler.cs new file mode 100644 index 00000000..7e37be10 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/Services/Endpoints/DebugHandler.cs @@ -0,0 +1,41 @@ +namespace WinUI.TableView.SampleApp.Services.Endpoints; + +internal class DebugHttpHandler : DelegatingHandler +{ + public DebugHttpHandler(HttpMessageHandler? innerHandler = null) + : base(innerHandler ?? new HttpClientHandler()) + { + } + + protected async override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + var response = await base.SendAsync(request, cancellationToken); +#if DEBUG + if (!response.IsSuccessStatusCode) + { + Console.Error.WriteLine("Unsuccessful API Call"); + if (request.RequestUri is not null) + { + Console.Error.WriteLine($"{request.RequestUri} ({request.Method})"); + } + + foreach ((var key, var values) in request.Headers.ToDictionary(x => x.Key, x => string.Join(", ", x.Value))) + { + Console.Error.WriteLine($" {key}: {values}"); + } + + var content = request.Content is not null ? await request.Content.ReadAsStringAsync() : null; + if (!string.IsNullOrEmpty(content)) + { + Console.Error.WriteLine(content); + } + + // Uncomment to automatically break when an API call fails while debugging + // System.Diagnostics.Debugger.Break(); + } +#endif + return response; + } +} diff --git a/samples/WinUI.TableView.SampleApp.Uno/WinUI.TableView.SampleApp.Uno.csproj b/samples/WinUI.TableView.SampleApp.Uno/WinUI.TableView.SampleApp.Uno.csproj new file mode 100644 index 00000000..b85b8f0d --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/WinUI.TableView.SampleApp.Uno.csproj @@ -0,0 +1,93 @@ + + + + $(NetVersion)-desktop; + $(NetVersion)-browserwasm; + $(NetVersion)-android; + $(NetVersion)-ios; + + + Exe + true + WinUI.TableView.SampleApp + + + WinUI.TableView.SampleApp + + com.W.Ahmad.WinUI.TableView.SampleApp + + 1.0 + 1 + + W.Ahmad + + WinUI.TableView.SampleApp powered by Uno Platform. + + + + Mvvm; + SkiaRenderer; + + + + + InterpreterAndAOT + false + true + + + + + + + %(RecursiveDir)%(Filename)%(Extension) + MSBuild:Compile + + + + %(RecursiveDir)%(Filename)%(Extension) + %(RecursiveDir)%(Filename) + + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp.Uno/app.manifest b/samples/WinUI.TableView.SampleApp.Uno/app.manifest new file mode 100644 index 00000000..462a37ea --- /dev/null +++ b/samples/WinUI.TableView.SampleApp.Uno/app.manifest @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + + diff --git a/samples/WinUI.TableView.SampleApp/App.xaml b/samples/WinUI.TableView.SampleApp/App.xaml new file mode 100644 index 00000000..d507e1a7 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/App.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + M8.00662 0C3.57917 0 0 3.66665 0 8.2028C0 11.8288 2.29329 14.8981 5.4747 15.9844C5.87246 16.0661 6.01816 15.8079 6.01816 15.5908C6.01816 15.4006 6.00505 14.7488 6.00505 14.0696C3.7778 14.5586 3.31399 13.0918 3.31399 13.0918C2.95606 12.1411 2.42572 11.8968 2.42572 11.8968C1.69674 11.3943 2.47882 11.3943 2.47882 11.3943C3.28744 11.4486 3.71175 12.2363 3.71175 12.2363C4.42745 13.4856 5.58074 13.1326 6.04471 12.9153C6.11092 12.3856 6.32315 12.0189 6.5485 11.8153C4.77211 11.6251 2.90312 10.919 2.90312 7.76813C2.90312 6.8718 3.22107 6.13847 3.72486 5.56814C3.64538 5.36448 3.36693 4.52231 3.80451 3.39515C3.80451 3.39515 4.48055 3.17782 6.00488 4.23715C6.6575 4.05759 7.33054 3.96625 8.00662 3.96548C8.68266 3.96548 9.37181 4.06065 10.0082 4.23715C11.5327 3.17782 12.2087 3.39515 12.2087 3.39515C12.6463 4.52231 12.3677 5.36448 12.2882 5.56814C12.8053 6.13847 13.1101 6.8718 13.1101 7.76813C13.1101 10.919 11.2411 11.6115 9.45146 11.8153C9.74318 12.0733 9.99492 12.5621 9.99492 13.3363C9.99492 14.4363 9.98181 15.3191 9.98181 15.5906C9.98181 15.8079 10.1277 16.0661 10.5253 15.9846C13.7067 14.8979 16 11.8288 16 8.2028C16.0131 3.66665 12.4208 0 8.00662 0Z + + + diff --git a/samples/WinUI.TableView.SampleApp/App.xaml.cs b/samples/WinUI.TableView.SampleApp/App.xaml.cs new file mode 100644 index 00000000..47fb37d1 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/App.xaml.cs @@ -0,0 +1,69 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using Microsoft.Extensions.Logging; +#if !WINDOWS +using Uno.Resizetizer; +#endif + +namespace WinUI.TableView.SampleApp; + +public partial class App : Application +{ + private readonly Lazy _mainWindow = new(() => new Window()); + private readonly Lazy _mainPage = new(() => new MainPage()); + + public App() + { + InitializeComponent(); + } + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { +#if WINDOWS + MainWindow.ExtendsContentIntoTitleBar = true; + MainWindow.SystemBackdrop = new MicaBackdrop(); +#endif + MainWindow.Content = MainPage; +#if DEBUG && !WINDOWS + MainWindow.UseStudio(); + MainWindow.SetWindowIcon(); +#endif + MainWindow.Activate(); + } + + public static void InitializeLogging() + { +#if DEBUG + var factory = LoggerFactory.Create(builder => + { +#if __WASM__ + builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider()); + // Note: DebugSettings.EnableFrameRateCounter requires an Application instance +#elif !WINDOWS + builder.AddConsole(); +#else + builder.AddDebug(); +#endif + + // Exclude logs below this level + builder.SetMinimumLevel(LogLevel.Information); + + // Default filters for Uno Platform namespaces + builder.AddFilter("Uno", LogLevel.Warning); + builder.AddFilter("Windows", LogLevel.Warning); + builder.AddFilter("Microsoft", LogLevel.Warning); + }); + + +#if !WINDOWS + global::Uno.Extensions.LogExtensionPoint.AmbientLoggerFactory = factory; + global::Uno.UI.Adapter.Microsoft.Extensions.Logging.LoggingAdapter.Initialize(); +#endif +#endif + } + + + public MainPage MainPage => _mainPage.Value; + public Window MainWindow => _mainWindow.Value; + public static new App Current => (App)Application.Current; +} diff --git a/samples/WinUI.TableView.SampleApp/Assets/LockScreenLogo.scale-200.png b/samples/WinUI.TableView.SampleApp/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 00000000..00997679 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/LockScreenLogo.scale-200.png differ diff --git a/samples/WinUI.TableView.SampleApp/Assets/SplashScreen.scale-200.png b/samples/WinUI.TableView.SampleApp/Assets/SplashScreen.scale-200.png new file mode 100644 index 00000000..a05eb806 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/SplashScreen.scale-200.png differ diff --git a/samples/WinUI.TableView.SampleApp/Assets/Square150x150Logo.scale-200.png b/samples/WinUI.TableView.SampleApp/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 00000000..25807740 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/Square150x150Logo.scale-200.png differ diff --git a/samples/WinUI.TableView.SampleApp/Assets/Square44x44Logo.scale-200.png b/samples/WinUI.TableView.SampleApp/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 00000000..6084fe38 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/Square44x44Logo.scale-200.png differ diff --git a/samples/WinUI.TableView.SampleApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/samples/WinUI.TableView.SampleApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 00000000..087e8911 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/samples/WinUI.TableView.SampleApp/Assets/StoreLogo.png b/samples/WinUI.TableView.SampleApp/Assets/StoreLogo.png new file mode 100644 index 00000000..a6afcac7 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/StoreLogo.png differ diff --git a/samples/WinUI.TableView.SampleApp/Assets/TableView.ico b/samples/WinUI.TableView.SampleApp/Assets/TableView.ico new file mode 100644 index 00000000..4feef2f8 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/TableView.ico differ diff --git a/samples/WinUI.TableView.SampleApp/Assets/TitlebarLogo.png b/samples/WinUI.TableView.SampleApp/Assets/TitlebarLogo.png new file mode 100644 index 00000000..b357042f Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/TitlebarLogo.png differ diff --git a/samples/WinUI.TableView.SampleApp/Assets/Wide310x150Logo.scale-200.png b/samples/WinUI.TableView.SampleApp/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 00000000..4c6cb327 Binary files /dev/null and b/samples/WinUI.TableView.SampleApp/Assets/Wide310x150Logo.scale-200.png differ diff --git a/samples/WinUI.TableView.SampleApp/Controls/CodeSubstitution.cs b/samples/WinUI.TableView.SampleApp/Controls/CodeSubstitution.cs new file mode 100644 index 00000000..6e2dd643 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Controls/CodeSubstitution.cs @@ -0,0 +1,55 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using Windows.Foundation; + +namespace WinUI.TableView.SampleApp.Controls; + +/// +/// Describes a textual substitution in sample content. +/// If enabled (default), then $(Key) is replaced with the stringified value. +/// If disabled, then $(Key) is replaced with the empty string. +/// +public sealed partial class CodeSubstitution : DependencyObject +{ + public event TypedEventHandler? ValueChanged; + + public string? Key { get; set; } + + public object? Value + { + get; + set + { + field = value; + ValueChanged?.Invoke(this, null); + } + } + + public bool IsEnabled + { + get; + set + { + field = value; + ValueChanged?.Invoke(this, null); + } + } = true; + + public string? ValueAsString() + { + if (!IsEnabled) + { + return string.Empty; + } + + var value = Value; + + // For solid color brushes, use the underlying color. + if (value is SolidColorBrush brush) + { + value = brush.Color; + } + + return value?.ToString() ?? string.Empty; + } +} \ No newline at end of file diff --git a/samples/WinUI.TableView.SampleApp/Controls/SamplePresenter.xaml b/samples/WinUI.TableView.SampleApp/Controls/SamplePresenter.xaml new file mode 100644 index 00000000..b11a9cf3 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Controls/SamplePresenter.xaml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14 + + + + + diff --git a/samples/WinUI.TableView.SampleApp/Controls/SamplePresenter.xaml.cs b/samples/WinUI.TableView.SampleApp/Controls/SamplePresenter.xaml.cs new file mode 100644 index 00000000..39eaf933 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Controls/SamplePresenter.xaml.cs @@ -0,0 +1,252 @@ +#if WINDOWS +using ColorCode; +#endif +using CommunityToolkit.WinUI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using System.Text.RegularExpressions; +using WinUI.TableView.SampleApp.Helpers; + +namespace WinUI.TableView.SampleApp.Controls +{ + public sealed partial class SamplePresenter : UserControl + { + private const string _baseUri = "https://GitHub.com/w-ahmad/WinUI.TableView.SampleApp/tree/main/src/WinUI.TableView.SampleApp/Pages/"; + private static readonly Regex _substitutionPattern = SubstitutionPattern(); + + public SamplePresenter() + { + InitializeComponent(); + + Substitutions = []; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + if (this.FindParent() is { } page) + { + var pageName = page.GetType().Name; + + PageMarkupGitHubLink.NavigateUri = new Uri($"{_baseUri}{pageName}.xaml", UriKind.Absolute); + PageCodeGitHubLink.NavigateUri = new Uri($"{_baseUri}{pageName}.xaml.cs", UriKind.Absolute); + + } + + if (Substitutions is not null) + { + foreach (var substitution in Substitutions) + { + substitution.ValueChanged += OnSubstitutionValueChanged; + } + } + + GenerateSyntaxHighlightedContent(); + } + + private void OnSubstitutionValueChanged(CodeSubstitution sender, object? args) + { + GenerateSyntaxHighlightedContent(); + } + + private void GenerateSyntaxHighlightedContent() + { + OnXamlChanged(); + OnCSharpChanged(); + } + + private void OnToggleThemeButtonClicked(object sender, RoutedEventArgs e) + { +#if WINDOWS + exampleContainer.RequestedTheme = exampleContainer.ActualTheme == ElementTheme.Light ? ElementTheme.Dark : ElementTheme.Light; + themeBackground.Visibility = exampleContainer.ActualTheme != ThemeHelper.ActualTheme ? Visibility.Visible : Visibility.Collapsed; +#else + if (App.Current.MainWindow.Content is FrameworkElement root) + { + root.RequestedTheme = root.ActualTheme == ElementTheme.Light ? ElementTheme.Dark : ElementTheme.Light; + } +#endif + } + + private void OnSourceExpanderExpanded(Expander sender, ExpanderExpandingEventArgs args) + { + sourceRow.Height = new GridLength(1, GridUnitType.Star); + } + + private void OnSourceExpanderCollapsed(Expander sender, ExpanderCollapsedEventArgs args) + { + sourceRow.Height = GridLength.Auto; + } + + private void OnXamlChanged() + { + if (!IsLoaded) return; + + ToggleSourceExpanderVisibility(); + + if (!string.IsNullOrWhiteSpace(Xaml)) + { + AddFormattedCode(Xaml, "XAML"); + } + } + + private void OnCSharpChanged() + { + if (!IsLoaded) return; + + ToggleSourceExpanderVisibility(); + + if (!string.IsNullOrWhiteSpace(CSharp)) + { + AddFormattedCode(CSharp, "C#"); + } + } + + private void AddFormattedCode(string code, string lang) + { + code = ApplySubstitutions(code); + +#if WINDOWS + var formatter = new RichTextBlockFormatter(ThemeHelper.ActualTheme); + var textBlock = new RichTextBlock + { + Margin = new Thickness(0, 8, 0, 0), + FontFamily = new FontFamily("Consolas"), + IsTextSelectionEnabled = true + }; + + formatter.FormatRichTextBlock(code, lang == "XAML" ? Languages.Xml : Languages.CSharp, textBlock); +#else + var textBlock = new TextBlock + { + Text = code, + Margin = new Thickness(0, 8, 0, 0), + FontFamily = new FontFamily("Consolas"), + }; +#endif + + var pivotItem = sourcePivot.Items.OfType().FirstOrDefault(x => x.Header?.Equals(lang) is true); + + if (pivotItem is not null) + { + var scrollViewer = (ScrollViewer)pivotItem.Content; + scrollViewer.Content = textBlock; + } + else + { + sourcePivot.Items.Insert(lang == "XAML" ? 0 : sourcePivot.Items.Count, new PivotItem + { + Header = lang, + Content = new ScrollViewer + { + VerticalScrollMode = ScrollMode.Auto, + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + Content = textBlock + } + }); + } + } + + private string ApplySubstitutions(string code) + { + // Trim out stray blank lines at start and end. + code = code.TrimStart('\n').TrimEnd(); + + // Also trim out spaces at the end of each line + code = string.Join('\n', code.Split('\n').Select(s => s.TrimEnd())); + + if (Substitutions != null) + { + // Perform any applicable substitutions. + code = _substitutionPattern.Replace(code, match => + { + foreach (var substitution in Substitutions) + { + if (substitution.Key == match.Groups[1].Value) + { + return substitution.ValueAsString()!; + } + } + throw new KeyNotFoundException(match.Groups[1].Value); + }); + } + + return code; + } + + private void ToggleSourceExpanderVisibility() + { + sourceExpander.Visibility = !string.IsNullOrWhiteSpace(Xaml) || !string.IsNullOrWhiteSpace(CSharp) + ? Visibility.Visible : Visibility.Collapsed; + } + + private static void OnXamlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is SamplePresenter presenter) + { + presenter.OnXamlChanged(); + } + } + + private static void OnCSharpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is SamplePresenter presenter) + { + presenter.OnCSharpChanged(); + } + } + + public string? Header + { + get => (string?)GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + public string? Description + { + get => (string?)GetValue(DescriptionProperty); + set => SetValue(DescriptionProperty, value); + } + + public object? Example + { + get => GetValue(ExampleProperty); + set => SetValue(ExampleProperty, value); + } + + public object? Options + { + get => GetValue(OptionsProperty); + set => SetValue(OptionsProperty, value); + } + + public string? Xaml + { + get => (string?)GetValue(XamlCodeProperty); + set => SetValue(XamlCodeProperty, value); + } + + public string? CSharp + { + get => (string?)GetValue(CSharpCodeProperty); + set => SetValue(CSharpCodeProperty, value); + } + + public IList Substitutions + { + get => (IList)GetValue(SubstitutionsProperty); + set => SetValue(SubstitutionsProperty, value); + } + + public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(nameof(Header), typeof(string), typeof(SamplePresenter), new PropertyMetadata(null)); + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(nameof(Description), typeof(string), typeof(SamplePresenter), new PropertyMetadata(null)); + public static readonly DependencyProperty ExampleProperty = DependencyProperty.Register(nameof(Example), typeof(object), typeof(SamplePresenter), new PropertyMetadata(null)); + public static readonly DependencyProperty OptionsProperty = DependencyProperty.Register(nameof(Options), typeof(object), typeof(SamplePresenter), new PropertyMetadata(null)); + public static readonly DependencyProperty XamlCodeProperty = DependencyProperty.Register(nameof(Xaml), typeof(string), typeof(SamplePresenter), new PropertyMetadata(null, OnXamlChanged)); + public static readonly DependencyProperty CSharpCodeProperty = DependencyProperty.Register(nameof(CSharp), typeof(string), typeof(SamplePresenter), new PropertyMetadata(null, OnCSharpChanged)); + public static readonly DependencyProperty SubstitutionsProperty = DependencyProperty.Register(nameof(Substitutions), typeof(IList), typeof(SamplePresenter), new PropertyMetadata(null)); + + [GeneratedRegex(@"\$\(([^\)]+)\)")] + private static partial Regex SubstitutionPattern(); + } +} diff --git a/samples/WinUI.TableView.SampleApp/Converters/ColorToBrushConverter.cs b/samples/WinUI.TableView.SampleApp/Converters/ColorToBrushConverter.cs new file mode 100644 index 00000000..65834046 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Converters/ColorToBrushConverter.cs @@ -0,0 +1,18 @@ +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; +using Windows.UI; + +namespace WinUI.TableView.SampleApp.Converters; + +public class ColorToBrushConverter : IValueConverter +{ + public object? Convert(object value, Type targetType, object parameter, string language) + { + return value is SolidColorBrush brush ? brush.Color : default; + } + + public object? ConvertBack(object value, Type targetType, object parameter, string language) + { + return value is Color color ? new SolidColorBrush(color) : default; + } +} diff --git a/samples/WinUI.TableView.SampleApp/Converters/EnumToStringConverter.cs b/samples/WinUI.TableView.SampleApp/Converters/EnumToStringConverter.cs new file mode 100644 index 00000000..1a24b2f3 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Converters/EnumToStringConverter.cs @@ -0,0 +1,40 @@ +using Microsoft.UI.Xaml.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static CommunityToolkit.WinUI.Animations.Expressions.ExpressionValues; + +namespace WinUI.TableView.SampleApp.Converters; + +public class EnumToStringConverter : IValueConverter +{ + public object? Convert(object value, Type targetType, object parameter, string language) + { + return value?.ToString(); + } + + public object? ConvertBack(object value, Type targetType, object parameter, string language) + { + if (value == null || targetType == null) + return null; + + if (!targetType.IsEnum) + throw new ArgumentException("Target type must be an Enum."); + + if (value is string stringValue) + { + try + { + return Enum.Parse(targetType, stringValue, ignoreCase: true); + } + catch (ArgumentException) + { + return null; + } + } + + return null; + } +} diff --git a/samples/WinUI.TableView.SampleApp/Converters/NullToVisibilityConverter.cs b/samples/WinUI.TableView.SampleApp/Converters/NullToVisibilityConverter.cs new file mode 100644 index 00000000..bb83df7b --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Converters/NullToVisibilityConverter.cs @@ -0,0 +1,20 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; + +namespace WinUI.TableView.SampleApp.Converters; +public class NullToVisibilityConverter : IValueConverter +{ + public Visibility NullValue { get; set; } = Visibility.Collapsed; + public Visibility NonNullValue { get; set; } = Visibility.Visible; + + + public object Convert(object value, Type targetType, object parameter, string language) + { + return value is null ? NullValue : NonNullValue; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} diff --git a/samples/WinUI.TableView.SampleApp/Converters/VisibilityToBoolConverter.cs b/samples/WinUI.TableView.SampleApp/Converters/VisibilityToBoolConverter.cs new file mode 100644 index 00000000..b4afef40 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Converters/VisibilityToBoolConverter.cs @@ -0,0 +1,21 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; + +namespace WinUI.TableView.SampleApp.Converters; + +public partial class VisibilityToBoolConverter : IValueConverter +{ + public bool VisibleValue { get; set; } = true; + + public object Convert(object value, Type targetType, object parameter, string language) + { + var collapesValue = !VisibleValue; + + return value is Visibility.Visible ? VisibleValue : collapesValue; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} diff --git a/samples/WinUI.TableView.SampleApp/DataFaker.cs b/samples/WinUI.TableView.SampleApp/DataFaker.cs new file mode 100644 index 00000000..86613405 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/DataFaker.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; + +namespace WinUI.TableView.SampleApp; + +/// +/// A native AOT-compatible data faker for generating sample data. +/// +public static class DataFaker +{ + private static readonly Random _random = new(); + + // First names + private static readonly string[] FirstNames = + [ + "James", "Mary", "Robert", "Patricia", "Michael", "Jennifer", "William", "Linda", "David", "Barbara", + "Richard", "Elizabeth", "Joseph", "Susan", "Thomas", "Jessica", "Charles", "Sarah", "Christopher", "Karen", + "Daniel", "Nancy", "Matthew", "Lisa", "Anthony", "Betty", "Mark", "Margaret", "Donald", "Sandra", + "Steven", "Ashley", "Paul", "Kimberly", "Andrew", "Donna", "Joshua", "Carol", "Kenneth", "Michelle", + "Kevin", "Emily", "Brian", "Melissa", "George", "Deborah", "Edward", "Stephanie", "Ronald", "Rebecca", + "Timothy", "Sharon", "Jason", "Brenda", "Jeffrey", "Amber", "Ryan", "Anna", "Jacob", "Pamela", + "Gary", "Nicole", "Nicholas", "Emma", "Eric", "Helen", "Jonathan", "Samantha", "Stephen", "Katherine" + ]; + + // Last names + private static readonly string[] LastNames = + [ + "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", + "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", + "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", + "Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Peterson", "Phillips", "Campbell", + "Parker", "Evans", "Edwards", "Collins", "Reyes", "Stewart", "Morris", "Morales", "Murphy", "Cook", + "Rogers", "Morgan", "Peterson", "Cooper", "Reed", "Bell", "Gomez", "Murray", "Freeman", "Wells", + "Webb", "Simpson", "Stevens", "Tucker", "Porter", "Hunter", "Hicks", "Crawford", "Henry", "Boyd" + ]; + + // Job titles + private static readonly string[] JobTitles = + [ + "Software Developer", "Manager", "Sales Representative", "Accountant", "Analyst", + "Engineer", "Designer", "Teacher", "Consultant", "Executive", + "Administrator", "Coordinator", "Director", "Supervisor", "Specialist", + "Technician", "Operator", "Clerk", "Assistant", "Officer", + "Agent", "Associate", "Architect", "Planner", "Scientist", + "Researcher", "Programmer", "Nurse", "Doctor", "Lawyer", + "Marketing Manager", "Product Manager", "Business Analyst", "Data Scientist", "DevOps Engineer" + ]; + + // Department names + private static readonly string[] Departments = + [ + "Sales", "Marketing", "Engineering", "Finance", "Human Resources", + "Operations", "IT", "Legal", "Research", "Development", + "Quality Assurance", "Customer Service", "Production", "Logistics", "Planning", + "Maintenance", "Administration", "Strategic Planning", "Corporate Communications", "Treasury" + ]; + + // Street suffixes + private static readonly string[] StreetSuffixes = + [ + "Street", "Avenue", "Road", "Boulevard", "Drive", "Lane", "Court", "Circle", + "Way", "Parkway", "Plaza", "Square", "Trail", "Ridge", "Hill", "Oak" + ]; + + // Cities + private static readonly string[] Cities = + [ + "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", + "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose", + "Austin", "Jacksonville", "Seattle", "Denver", "Boston", + "Portland", "Miami", "Atlanta", "Las Vegas", "Detroit" + ]; + + // States + private static readonly string[] States = + [ + "AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA", + "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", + "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", + "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", + "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY" + ]; + + private static readonly string[] Regions = [ + "East", "West", "North", "South" + ]; + + // Genders + private static readonly string[] Genders = ["Male", "Female", "Non-binary", "Genderfluid", "Agender", "Bigender", "Genderqueer", "Two-Spirit", "Prefer not to say"]; + + // Avatar images (using placeholder service URLs) + private static readonly string[] AvatarSeeds = + [ + "avatar1", "avatar2", "avatar3", "avatar4", "avatar5", + "avatar6", "avatar7", "avatar8", "avatar9", "avatar10" + ]; + + public static string FirstName() + { + return FirstNames[_random.Next(FirstNames.Length)]; + } + + public static string LastName() + { + return LastNames[_random.Next(LastNames.Length)]; + } + + public static string FullName() + { + return $"{FirstName()} {LastName()}"; + } + + public static string Email(string? firstName = null, string? lastName = null) + { + firstName ??= FirstName(); + lastName ??= LastName(); + var domains = new[] { "example.com", "test.com", "sample.com", "data.com" }; + var domain = domains[_random.Next(domains.Length)]; + return $"{firstName.ToLower()}.{lastName.ToLower()}@{domain}"; + } + + public static string Gender() + { + return Genders[_random.Next(Genders.Length)]; + } + + public static DateOnly PastDate(int yearsBack = 50, DateOnly? maxDate = null) + { + maxDate ??= DateOnly.FromDateTime(DateTime.Now); + var startDate = maxDate.Value.AddYears(-yearsBack); + var daysRange = maxDate.Value.DayNumber - startDate.DayNumber; + var randomDays = _random.Next(daysRange); + return startDate.AddDays(randomDays); + } + + public static TimeOnly TimeOfDay() + { + var hours = _random.Next(0, 24); + var minutes = _random.Next(0, 60); + var seconds = _random.Next(0, 60); + return new TimeOnly(hours, minutes, seconds); + } + + public static bool Boolean(float truePercentage = 0.5f) + { + return _random.NextSingle() < truePercentage; + } + + public static int Integer(int min = 0, int max = int.MaxValue) + { + return _random.Next(min, max); + } + + public static decimal Decimal(decimal min = 0, decimal max = 1000) + { + return ((decimal)_random.NextDouble() * (max - min)) + min; + } + + public static string Department() + { + return Departments[_random.Next(Departments.Length)]; + } + + public static string JobTitle() + { + return JobTitles[_random.Next(JobTitles.Length)]; + } + + public static string Address() + { + var streetNumber = _random.Next(1, 9999); + var streetName = FirstName(); + var suffix = StreetSuffixes[_random.Next(StreetSuffixes.Length)]; + var city = Cities[_random.Next(Cities.Length)]; + var state = States[_random.Next(States.Length)]; + var zip = _random.Next(10000, 99999); + return $"{streetNumber} {streetName} {suffix}, {city}, {state} {zip}"; + } + + public static string Avatar() + { + var seed = AvatarSeeds[_random.Next(AvatarSeeds.Length)]; + var id = _random.Next(1, 100); + return $"https://api.dicebear.com/7.x/avataaars/svg?seed={seed}{id}"; + } + + public static string ZipCode() + { + return _random.Next(10000, 99999).ToString(); + } + + public static string State() + { + return States[_random.Next(States.Length)]; + } + + public static string Region() + { + return Regions[_random.Next(Regions.Length)]; + } + + public static string City() + { + return Cities[_random.Next(Cities.Length)]; + } +} diff --git a/samples/WinUI.TableView.SampleApp/ExampleModel.cs b/samples/WinUI.TableView.SampleApp/ExampleModel.cs new file mode 100644 index 00000000..fc7bd8f8 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/ExampleModel.cs @@ -0,0 +1,52 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +namespace WinUI.TableView.SampleApp; + +public partial class ExampleModel : ObservableObject +{ + [ObservableProperty] + public partial int Id { get; set; } + + [ObservableProperty] + [Display(ShortName = "First Name")] + public partial string? FirstName { get; set; } + + [ObservableProperty] + [Display(ShortName = "Last Name")] + public partial string? LastName { get; set; } + + [ObservableProperty] + public partial string? Email { get; set; } + + [ObservableProperty] + public partial string? Gender { get; set; } + + [ObservableProperty] + public partial DateOnly Dob { get; set; } + + [ObservableProperty] + [Display(ShortName = "Active At")] + public partial TimeOnly ActiveAt { get; set; } + + [ObservableProperty] + [Display(ShortName = "Is Active")] + public partial bool IsActive { get; set; } + + [ObservableProperty] + public partial string? Department { get; set; } + + [ObservableProperty] + public partial string? Designation { get; set; } + + [ObservableProperty] + public partial string? Address { get; set; } + + [ObservableProperty] + [Display(AutoGenerateField = false)] + public partial string? Avatar { get; set; } + + public Uri AvatarUrl => new Uri(Avatar ?? string.Empty); +} diff --git a/samples/WinUI.TableView.SampleApp/ExampleModelColumnsHelper.cs b/samples/WinUI.TableView.SampleApp/ExampleModelColumnsHelper.cs new file mode 100644 index 00000000..3ee13585 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/ExampleModelColumnsHelper.cs @@ -0,0 +1,69 @@ +using Microsoft.UI.Xaml; + +namespace WinUI.TableView.SampleApp; + +public static class ExampleModelColumnsHelper +{ + public static void OnAutoGeneratingColumns(object sender, TableViewAutoGeneratingColumnEventArgs e) + { + var viewModel = (ExampleViewModel)((TableView)sender).DataContext; + var boundColumn = (TableViewBoundColumn)e.Column; + + switch (e.PropertyName) + { + case nameof(ExampleModel.Id): + e.Column.Width = new GridLength(60); + break; + case nameof(ExampleModel.FirstName): + e.Column.Width = new GridLength(110); + break; + case nameof(ExampleModel.LastName): + e.Column.Width = new GridLength(110); + break; + case nameof(ExampleModel.Email): + e.Column.Width = new GridLength(270); + break; + case nameof(ExampleModel.Gender): + e.Column = new TableViewComboBoxColumn + { + Binding = boundColumn.Binding, + Header = boundColumn.Header, + Width = new GridLength(120), + ItemsSource = viewModel.Genders + }; + break; + case nameof(ExampleModel.Dob): + e.Column.Width = new GridLength(110); + break; + case nameof(ExampleModel.ActiveAt): + e.Column.Width = new GridLength(110); + break; + case nameof(ExampleModel.IsActive): + e.Column.Width = new GridLength(100); + break; + case nameof(ExampleModel.Department): + e.Column = new TableViewComboBoxColumn + { + Binding = boundColumn.Binding, + Header = boundColumn.Header, + Width = new GridLength(200), + ItemsSource = viewModel.Departments + }; + break; + case nameof(ExampleModel.Designation): + e.Column = new TableViewComboBoxColumn + { + Binding = boundColumn.Binding, + Header = boundColumn.Header, + Width = new GridLength(200), + ItemsSource = viewModel.Designations + }; + break; + case nameof(ExampleModel.Address): + e.Column.Width = new GridLength(200); + break; + default: + break; + } + } +} diff --git a/samples/WinUI.TableView.SampleApp/ExampleViewModel.cs b/samples/WinUI.TableView.SampleApp/ExampleViewModel.cs new file mode 100644 index 00000000..2834556d --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/ExampleViewModel.cs @@ -0,0 +1,64 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System.Collections.ObjectModel; + +namespace WinUI.TableView.SampleApp; + +public partial class ExampleViewModel : ObservableObject +{ + public ExampleViewModel() + { + foreach (var item in ItemsList) + { + Items.Add(item); + Genders.Add(item.Gender); + Departments.Add(item.Department); + Designations.Add(item.Designation); + } + } + + public async static Task InitializeItemsAsync() + { + await Task.Run(() => + { + var startId = 1; + var startDate = new DateOnly(1970, 1, 1); + + ItemsList.Clear(); + + for (var i = 0; i < 1_000; i++) + { + var firstName = DataFaker.FirstName(); + var lastName = DataFaker.LastName(); + ItemsList.Add(new ExampleModel + { + Id = startId++, + FirstName = firstName, + LastName = lastName, + Email = DataFaker.Email(firstName, lastName), + Gender = DataFaker.Gender(), + Dob = DataFaker.PastDate(50, startDate), + IsActive = DataFaker.Boolean(), + ActiveAt = DataFaker.TimeOfDay(), + Department = DataFaker.Department(), + Designation = DataFaker.JobTitle(), + Address = DataFaker.Address(), + Avatar = DataFaker.Avatar() + }); + } + }); + } + + public static List ItemsList { get; } = []; + + [ObservableProperty] + public partial ObservableCollection Items { get; set; } = []; + + public SortedSet Genders { get; set; } = []; + + public SortedSet Departments { get; set; } = []; + + public SortedSet Designations { get; set; } = []; + + [ObservableProperty] + public partial ExampleModel? SelectedItem { get; set; } +} diff --git a/samples/WinUI.TableView.SampleApp/Helpers/NativeHelper.cs b/samples/WinUI.TableView.SampleApp/Helpers/NativeHelper.cs new file mode 100644 index 00000000..58f9b476 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Helpers/NativeHelper.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace WinUI.TableView.SampleApp.Helpers; +internal class NativeHelper +{ + public const int ERROR_SUCCESS = 0; + public const int ERROR_INSUFFICIENT_BUFFER = 122; + public const int APPMODEL_ERROR_NO_PACKAGE = 15700; + + [DllImport("api-ms-win-appmodel-runtime-l1-1-1", SetLastError = true)] + [return: MarshalAs(UnmanagedType.U4)] + internal static extern uint GetCurrentPackageId(ref int pBufferLength, out byte pBuffer); + + public static bool IsAppPackaged + { + get + { + var bufferSize = 0; + var lastError = GetCurrentPackageId(ref bufferSize, out var byteBuffer); + return lastError != APPMODEL_ERROR_NO_PACKAGE; + } + } +} \ No newline at end of file diff --git a/samples/WinUI.TableView.SampleApp/Helpers/ThemeHelper.cs b/samples/WinUI.TableView.SampleApp/Helpers/ThemeHelper.cs new file mode 100644 index 00000000..f06813a0 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Helpers/ThemeHelper.cs @@ -0,0 +1,90 @@ +using Microsoft.UI.Xaml; +using Windows.Storage; + +namespace WinUI.TableView.SampleApp.Helpers; + +/// +/// Class providing functionality around switching and restoring theme settings +/// +internal static class ThemeHelper +{ + private const string SelectedAppThemeKey = "SelectedAppTheme"; + + /// + /// Gets the current actual theme of the app based on the requested theme of the + /// root element, or if that value is Default, the requested theme of the Application. + /// + public static ElementTheme ActualTheme + { + get + { + if (App.Current.MainWindow.Content is FrameworkElement rootElement) + { + if (rootElement.RequestedTheme != ElementTheme.Default) + { + return rootElement.RequestedTheme; + } + } + + return App.Current.RequestedTheme.ToElementTheme(); + } + } + + /// + /// Gets or sets (with LocalSettings persistence) the RequestedTheme of the root element. + /// + public static ElementTheme RootTheme + { + get => App.Current.MainWindow.Content is FrameworkElement rootElement ? rootElement.RequestedTheme : ElementTheme.Default; + set + { + if (App.Current.MainWindow.Content is FrameworkElement rootElement) + { + rootElement.RequestedTheme = value; + } + + if (NativeHelper.IsAppPackaged) + { + ApplicationData.Current.LocalSettings.Values[SelectedAppThemeKey] = value.ToString(); + } + } + } + + public static void Initialize() + { + if (NativeHelper.IsAppPackaged) + { + var savedTheme = ApplicationData.Current.LocalSettings.Values[SelectedAppThemeKey]?.ToString(); + + if (savedTheme != null) + { + RootTheme = GetElementTheme(savedTheme); + } + } + } + + public static bool IsDarkTheme() + { + return RootTheme == ElementTheme.Default ? App.Current.RequestedTheme == ApplicationTheme.Dark : RootTheme == ElementTheme.Dark; + } + + internal static ElementTheme ToElementTheme(this ApplicationTheme applicationTheme) + { + return applicationTheme switch + { + ApplicationTheme.Light => ElementTheme.Light, + ApplicationTheme.Dark => ElementTheme.Dark, + _ => ElementTheme.Default, + }; + } + + public static ElementTheme GetElementTheme(string applicationTheme) + { + return applicationTheme switch + { + "Light" => ElementTheme.Light, + "Dark" => ElementTheme.Dark, + _ => ElementTheme.Default, + }; + } +} \ No newline at end of file diff --git a/samples/WinUI.TableView.SampleApp/Helpers/TitleBarHelper.cs b/samples/WinUI.TableView.SampleApp/Helpers/TitleBarHelper.cs new file mode 100644 index 00000000..301a597f --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Helpers/TitleBarHelper.cs @@ -0,0 +1,27 @@ +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Windows.UI; + +namespace WinUI.TableView.SampleApp.Helpers; + +internal class TitleBarHelper +{ + public static Color ApplySystemThemeToCaptionButtons(Window window) + { + if (App.Current.MainWindow.Content is FrameworkElement element) + { + var color = element.ActualTheme == ElementTheme.Dark ? Colors.White : Colors.Black; + SetCaptionButtonColors(window, color); + return color; + } + + return default; + } + + public static void SetCaptionButtonColors(Window window, Color color) + { + var res = Application.Current.Resources; + res["WindowCaptionForeground"] = color; + window.AppWindow.TitleBar.ButtonForegroundColor = color; + } +} \ No newline at end of file diff --git a/samples/WinUI.TableView.SampleApp/Helpers/UIHelper.cs b/samples/WinUI.TableView.SampleApp/Helpers/UIHelper.cs new file mode 100644 index 00000000..d474b178 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Helpers/UIHelper.cs @@ -0,0 +1,14 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation.Peers; + +namespace WinUI.TableView.SampleApp.Helpers; +internal static class UIHelper +{ + // Confirmation of Action + static public void AnnounceActionForAccessibility(UIElement ue, string announcement, string activityID) + { + var peer = FrameworkElementAutomationPeer.FromElement(ue); + peer.RaiseNotificationEvent(AutomationNotificationKind.ActionCompleted, + AutomationNotificationProcessing.ImportantMostRecent, announcement, activityID); + } +} diff --git a/samples/WinUI.TableView.SampleApp/MainPage.xaml b/samples/WinUI.TableView.SampleApp/MainPage.xaml new file mode 100644 index 00000000..701278a9 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/MainPage.xaml @@ -0,0 +1,207 @@ + + + + 0,48,0,0 + 0,48,0,0 + 1,1,0,0 + 8,0,0,0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs b/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs new file mode 100644 index 00000000..ef811570 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/MainPage.xaml.cs @@ -0,0 +1,150 @@ +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using Windows.UI.ViewManagement; +using WinUI.TableView.SampleApp.Helpers; +using WinUI.TableView.SampleApp.Pages; + +namespace WinUI.TableView.SampleApp; + +public sealed partial class MainPage : Page +{ + private readonly DispatcherQueue _dispatcherQueue; + private readonly UISettings _settings = new(); + private bool _canNavigate = true; + + public MainPage() + { + InitializeComponent(); + + App.Current.MainWindow.Activated += OnMainWindowActivated; + App.Current.MainWindow.SetTitleBar(AppTitleBar); + + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + _settings.ColorValuesChanged += delegate { OnSettingsColorValuesChanged(); }; + } + + private async void OnPageLoaded(object sender, RoutedEventArgs e) + { + OnSettingsColorValuesChanged(); + + await ExampleViewModel.InitializeItemsAsync(); + + SetLoading(false); + +#if DEBUG + navigationView.SelectedItem = navigationView.MenuItems[2]; +#else + navigationView.SelectedItem = overViewNavItem; +#endif + } + + internal void SetLoading(bool isLoading) + { + loadingIndicator.Visibility = isLoading ? Visibility.Visible : Visibility.Collapsed; + } + + private void OnMainWindowActivated(object sender, WindowActivatedEventArgs args) + { +#if WINDOWS + if (args.WindowActivationState == WindowActivationState.Deactivated) + { + VisualStateManager.GoToState(this, "Deactivated", true); + } + else + { + VisualStateManager.GoToState(this, "Activated", true); + } +#endif + } + + private void OnPaneDisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args) + { + if (sender.PaneDisplayMode == NavigationViewPaneDisplayMode.Top) + { + VisualStateManager.GoToState(this, "Top", true); + } + else + { + if (args.DisplayMode == NavigationViewDisplayMode.Minimal) + { + VisualStateManager.GoToState(this, "Compact", true); + } + else + { + VisualStateManager.GoToState(this, "Default", true); + } + } + } + + // this handles updating the caption button colors correctly when windows system theme is changed + // while the app is open + private void OnSettingsColorValuesChanged() + { + // This calls comes off-thread, hence we will need to dispatch it to current app's thread + _dispatcherQueue.TryEnqueue(() => TitleBarHelper.ApplySystemThemeToCaptionButtons(App.Current.MainWindow)); + } + + private void OnNavigationSelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (!_canNavigate) + { + return; + } + + if (args.SelectedItem is NavigationViewItem { Content: string } selectedItem) + { + var pageType = selectedItem.Content.ToString() switch + { + "Settings" => typeof(SettingsPage), + "Overview" => typeof(OverviewPage), + "Grid Lines" => typeof(GridLinesPage), + "Selection" => typeof(SelectionPage), + "Corner Button" => typeof(CornerButtonPage), + "Alternate Row Color" => typeof(AlternateRowColorPage), + "Context Flyouts" => typeof(ContextFlyoutsPage), + "Row Reorder" => typeof(ReorderRowsPage), + "Pagination" => typeof(PaginationPage), + "Filtering" => typeof(FilteringPage), + "Customize Filter Flyout" => typeof(CustomizeFilterPage), + "External Filtering" => typeof(ExternalFilteringPage), + "Editing" => typeof(EditingPage), + "Sorting" => typeof(SortingPage), + "Custom Sorting" => typeof(CustomizeSortingPage), + "Data Export" => typeof(ExportPage), + "Large Dataset" => typeof(LargeDataPage), + "Conditional Cell Styling" => typeof(ConditionalStylingPage), + _ => typeof(BlankPage) + }; + + rootFrame.Navigate(pageType, selectedItem); + } + } + + private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) + { + if (rootFrame.CanGoBack) + { + rootFrame.GoBack(); + } + } + + private async void OnRootFrameNavigated(object sender, NavigationEventArgs e) + { + _canNavigate = false; + + if (e.NavigationMode == NavigationMode.Back && e.Parameter is NavigationViewItem navItem && !navItem.Equals(navigationView.SelectedItem)) + { + navigationView.SelectedItem = navItem; + } + + if (e.Content is Page { DataContext: null } page) + { + await Task.Delay(10); + page.DataContext = new ExampleViewModel(); + } + + _canNavigate = true; + } +} \ No newline at end of file diff --git a/samples/WinUI.TableView.SampleApp/Package.appxmanifest b/samples/WinUI.TableView.SampleApp/Package.appxmanifest new file mode 100644 index 00000000..b8dcf742 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Package.appxmanifest @@ -0,0 +1,51 @@ + + + + + + + + + + TableView Samples + Waheed Ahmad + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/WinUI.TableView.SampleApp/Pages/AlternateRowColorPage.xaml b/samples/WinUI.TableView.SampleApp/Pages/AlternateRowColorPage.xaml new file mode 100644 index 00000000..0103a397 --- /dev/null +++ b/samples/WinUI.TableView.SampleApp/Pages/AlternateRowColorPage.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + +