diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/Assets/Design/Geometry.dark.png b/source/iNKORE.UI.WPF.Modern.Gallery/Assets/Design/Geometry.dark.png new file mode 100644 index 00000000..65371088 Binary files /dev/null and b/source/iNKORE.UI.WPF.Modern.Gallery/Assets/Design/Geometry.dark.png differ diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/Assets/Design/Geometry.light.png b/source/iNKORE.UI.WPF.Modern.Gallery/Assets/Design/Geometry.light.png new file mode 100644 index 00000000..5e7d95f3 Binary files /dev/null and b/source/iNKORE.UI.WPF.Modern.Gallery/Assets/Design/Geometry.light.png differ diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/DataModel/Data/Controls.Foundation.json b/source/iNKORE.UI.WPF.Modern.Gallery/DataModel/Data/Controls.Foundation.json index 45a7ec64..bd1177b3 100644 --- a/source/iNKORE.UI.WPF.Modern.Gallery/DataModel/Data/Controls.Foundation.json +++ b/source/iNKORE.UI.WPF.Modern.Gallery/DataModel/Data/Controls.Foundation.json @@ -24,6 +24,29 @@ "Description": "Design guidance and resources", "Items": [ + { + "UniqueId": "Geometry", + "Title": "Geometry", + "Subtitle": "Corner radius and geometry values", + "ImagePath": "ms-appx:///Assets/ControlIcons/DefaultIcon.png", + "ImageIconPath": "ms-appx:///Assets/ControlIcons/DefaultIcon.png", + "Description": "", + "Content": "", + "IncludedInBuild": true, + "IsNew": false, + "IsUpdated": false, + "Docs": [ + { + "Title": "Geometry in Windows 11", + "Uri": "https://learn.microsoft.com/windows/apps/design/signature-experiences/geometry" + }, + { + "Title": "WinUI Theme Resources (GitHub)", + "Uri": "https://github.com/microsoft/microsoft-ui-xaml/blob/main/src/controls/dev/CommonStyles/Common_themeresources_any.xaml" + } + ], + "RelatedControls": [] + }, { "UniqueId": "Typography", "Title": "Typography", @@ -68,4 +91,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/Navigation/NavigationRootPage.xaml b/source/iNKORE.UI.WPF.Modern.Gallery/Navigation/NavigationRootPage.xaml index 61e9a267..b637fa60 100644 --- a/source/iNKORE.UI.WPF.Modern.Gallery/Navigation/NavigationRootPage.xaml +++ b/source/iNKORE.UI.WPF.Modern.Gallery/Navigation/NavigationRootPage.xaml @@ -121,6 +121,14 @@ + + + + + r.Groups) + .SelectMany(g => g.Items) + .FirstOrDefault(i => i.UniqueId == "Geometry"); + if (geometryItem != null) + { + rootFrame.Navigate(ItemPage.Create(geometryItem)); + } + } else if (selectedItem?.Tag?.ToString() == "Typography") { // Handle Typography navigation var typographyId = "Typography"; if (_lastItem?.ToString() == typographyId) return; _lastItem = typographyId; - + // Find Typography item from the data source var typographyItem = ControlInfoDataSource.Instance.Realms .SelectMany(r => r.Groups) .SelectMany(g => g.Items) .FirstOrDefault(i => i.UniqueId == "Typography"); - + if (typographyItem != null) { rootFrame.Navigate(ItemPage.Create(typographyItem)); diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/GeometryPage.xaml b/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/GeometryPage.xaml new file mode 100644 index 00000000..965d3e36 --- /dev/null +++ b/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/GeometryPage.xaml @@ -0,0 +1,423 @@ + + + + + + + + + + Geometry describes the shape, size and position of UI elements on screen. These fundamental design elements help experiences feel coherent across the entire design system. WinUI uses three levels of rounding depending on what UI component is being rounded and how that component is arranged relative to neighboring elements. + You can reference built-in corner radii styles using: + CornerRadius="{StaticResource ControlCornerRadius}". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/GeometryPage.xaml.cs b/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/GeometryPage.xaml.cs new file mode 100644 index 00000000..634ee086 --- /dev/null +++ b/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/GeometryPage.xaml.cs @@ -0,0 +1,331 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using iNKORE.UI.WPF.Modern.Controls; +using System; +using System.Windows; +using iNKORE.UI.WPF.Modern.Gallery; +using iNKORE.UI.WPF.Modern; +using System.Windows.Controls; +using Page = iNKORE.UI.WPF.Modern.Controls.Page; +using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox; +using System.Threading.Tasks; + +namespace iNKORE.UI.WPF.Modern.Gallery.Pages.Controls.Foundation +{ + /// + /// Geometry page showcasing Windows geometry values and layout examples. + /// + public partial class GeometryPage : Page + { + private ElementTheme _lastKnownTheme = ElementTheme.Default; + + public GeometryPage() + { + this.InitializeComponent(); + + Loaded += GeometryPage_Loaded; + + iNKORE.UI.WPF.Modern.ThemeManager.Current.ActualApplicationThemeChanged += OnThemeChanged; + iNKORE.UI.WPF.Modern.ThemeManager.AddActualThemeChangedHandler(this, OnElementThemeChanged); + + System.ComponentModel.DependencyPropertyDescriptor.FromProperty(iNKORE.UI.WPF.Modern.ThemeManager.RequestedThemeProperty, typeof(FrameworkElement)) + ?.AddValueChanged(this, OnRequestedThemeChanged); + + var _themeMonitorTimer = new System.Windows.Threading.DispatcherTimer + { + Interval = TimeSpan.FromMilliseconds(200) + }; + _themeMonitorTimer.Tick += ThemeMonitorTimer_Tick; + _themeMonitorTimer.Start(); + + UpdateExampleCode(); + } + + private void GeometryPage_Loaded(object sender, RoutedEventArgs e) + { + UpdateGeometryImage(); + } + + private void UpdateGeometryImage() + { + if (GeometryImage == null) return; + + var pageTheme = ThemeManager.GetActualTheme(this); + var parentTheme = ElementTheme.Default; + var controlExampleTheme = ElementTheme.Default; + + var parentElement = this.Parent as FrameworkElement; + while (parentElement != null) + { + var currentParentTheme = ThemeManager.GetActualTheme(parentElement); + if (currentParentTheme != ElementTheme.Default) + { + parentTheme = currentParentTheme; + break; + } + parentElement = parentElement.Parent as FrameworkElement; + } + + if (Example1 != null) + { + try + { + var exampleTheme = ThemeManager.GetActualTheme(Example1); + if (exampleTheme != ElementTheme.Default) + { + controlExampleTheme = exampleTheme; + } + else if (Example1.ExampleContainer != null) + { + var containerTheme = ThemeManager.GetActualTheme(Example1.ExampleContainer); + if (containerTheme != ElementTheme.Default) + { + controlExampleTheme = containerTheme; + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Theme detection error: {ex.Message}"); + } + } + + var effectiveTheme = controlExampleTheme != ElementTheme.Default ? controlExampleTheme : + pageTheme != ElementTheme.Default ? pageTheme : parentTheme; + var isDarkTheme = effectiveTheme == ElementTheme.Dark || + (effectiveTheme == ElementTheme.Default && iNKORE.UI.WPF.Modern.Gallery.Helpers.ThemeHelper.IsDarkTheme()); + + var imageName = isDarkTheme ? "Geometry.dark.png" : "Geometry.light.png"; + var uri = new System.Uri($"pack://application:,,,/iNKORE.UI.WPF.Modern.Gallery;component/Assets/Design/{imageName}"); + + try + { + var bitmapImage = new System.Windows.Media.Imaging.BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.UriSource = uri; + bitmapImage.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad; + bitmapImage.CreateOptions = System.Windows.Media.Imaging.BitmapCreateOptions.IgnoreImageCache; + bitmapImage.EndInit(); + bitmapImage.Freeze(); + GeometryImage.Source = bitmapImage; + System.Diagnostics.Debug.WriteLine($"Geometry image updated to: {imageName}"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to load geometry image: {ex.Message}"); + var fallbackUri = new System.Uri("pack://application:,,,/iNKORE.UI.WPF.Modern.Gallery;component/Assets/Design/Geometry.dark.png"); + GeometryImage.Source = new System.Windows.Media.Imaging.BitmapImage(fallbackUri); + } + } + + private void OnThemeChanged(iNKORE.UI.WPF.Modern.ThemeManager sender, object args) + { + UpdateGeometryImage(); + } + + private void OnElementThemeChanged(object sender, RoutedEventArgs e) + { + UpdateGeometryImage(); + } + + private void ThemeMonitorTimer_Tick(object sender, EventArgs e) + { + var currentTheme = iNKORE.UI.WPF.Modern.ThemeManager.GetActualTheme(this); + if (currentTheme != _lastKnownTheme) + { + _lastKnownTheme = currentTheme; + UpdateGeometryImage(); + System.Diagnostics.Debug.WriteLine($"Theme change detected: {currentTheme}"); + } + + var parentElement = this.Parent as FrameworkElement; + while (parentElement != null) + { + var parentTheme = ThemeManager.GetActualTheme(parentElement); + if (parentTheme != currentTheme) + { + UpdateGeometryImage(); + System.Diagnostics.Debug.WriteLine($"Element-level theme change detected: Parent={parentTheme}, Current={currentTheme}"); + break; + } + parentElement = parentElement.Parent as FrameworkElement; + } + + if (Example1 != null) + { + try + { + var controlExampleTheme = ThemeManager.GetActualTheme(Example1); + var containerTheme = ElementTheme.Default; + + if (Example1.ExampleContainer != null) + { + containerTheme = ThemeManager.GetActualTheme(Example1.ExampleContainer); + } + + if (controlExampleTheme != currentTheme || containerTheme != currentTheme) + { + UpdateGeometryImage(); + System.Diagnostics.Debug.WriteLine($"ControlExample theme change detected: ControlExample={controlExampleTheme}, Container={containerTheme}, Page={currentTheme}"); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Theme monitor error: {ex.Message}"); + } + } + } + + private void OnRequestedThemeChanged(object sender, EventArgs e) + { + Dispatcher.BeginInvoke(new System.Action(() => { + UpdateGeometryImage(); + }), System.Windows.Threading.DispatcherPriority.ApplicationIdle); + + var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; + timer.Tick += (s, args) => { + timer.Stop(); + UpdateGeometryImage(); + }; + timer.Start(); + } + + // kept for backward compatibility if we want to programmatically set usage + public string Usage { get; set; } + + /// + /// Unified handler for resource-copy buttons that use the Tag property to store copy text. + /// (OverlayCornerRadiusCopyButton and ControlCornerRadiusCopyButton call this.) + /// + private async void CopyControlCornerRadiusButton_Click(object sender, RoutedEventArgs e) + { + try + { + // Use GoToElementState for FrameworkElement-based VSM. + bool started = VisualStateManager.GoToElementState(LayoutRoot, "ControlCornerRadiusCopyButtonVisible", true); + if (!started) + { + // MessageBox.Show("Could not find visual state 'ControlCornerRadiusCopyButtonVisible' on LayoutRoot.", "VSM", MessageBoxButton.OK, MessageBoxImage.Warning); + } + if (sender is Button btn && btn.Tag is string tagText) + { + Clipboard.SetText(tagText); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), "Unable to Perform Copy", MessageBoxButton.OK, MessageBoxImage.Error); + } + + await Task.Delay(1000); + + try + { + VisualStateManager.GoToElementState(LayoutRoot, "ControlCornerRadiusCopyButtonHidden", true); + } + catch + { + // swallow + } + } + + /// + /// Helper method: if we want resource-copy buttons to also show the same animation, + /// call this. Left public for optional reuse. + /// + public async Task ShowCopyConfirmationAnimationAsync() + { + try + { + VisualStateManager.GoToElementState(LayoutRoot, "ConfirmationDialogVisible", true); + } + catch { } + + await Task.Delay(1000); + + try + { + VisualStateManager.GoToElementState(LayoutRoot, "ConfirmationDialogHidden", true); + } + catch { } + } + + private async void OverlayCornerRadiusCopyButton_Click(object sender, RoutedEventArgs e) + { + try + { + bool started = VisualStateManager.GoToElementState(LayoutRoot, "OverlayCornerRadiusCopyButtonVisible", true); + if (!started) + { + // MessageBox.Show("Could not find visual state 'OverlayCornerRadiusCopyButtonVisible' on LayoutRoot.", "VSM", MessageBoxButton.OK, MessageBoxImage.Warning); + } + if (sender is Button btn && btn.Tag is string tagText) + { + Clipboard.SetText(tagText); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), "Unable to Perform Copy", MessageBoxButton.OK, MessageBoxImage.Error); + } + + await Task.Delay(1000); + + try + { + VisualStateManager.GoToElementState(LayoutRoot, "OverlayCornerRadiusCopyButtonHidden", true); + } + catch + { + // swallow + } + } + + private void ShowGeometryButtonClick1(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + var flyout = FlyoutService.GetFlyout(button); + if (flyout != null) + { + flyout.ShowAt(button); + } + } + } + + private void ShowGeometryButtonClick2(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + var flyout = FlyoutService.GetFlyout(button); + if (flyout != null) + { + flyout.ShowAt(button); + } + } + } + + private void ShowGeometryButtonClick3(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + var flyout = FlyoutService.GetFlyout(button); + if (flyout != null) + { + flyout.ShowAt(button); + } + } + } + + private void UpdateExampleCode() + { + Example1.Xaml = Example1Xaml; + } + + string Example1Xaml => @" + +"; + + } +}