diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/App.xaml b/source/iNKORE.UI.WPF.Modern.Gallery/App.xaml
index 02d35662..e5b116a1 100644
--- a/source/iNKORE.UI.WPF.Modern.Gallery/App.xaml
+++ b/source/iNKORE.UI.WPF.Modern.Gallery/App.xaml
@@ -30,6 +30,7 @@
+
@@ -47,6 +48,7 @@
+
@@ -60,6 +62,7 @@
+
diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/Data/IconsDataSource.cs b/source/iNKORE.UI.WPF.Modern.Gallery/Data/IconsDataSource.cs
new file mode 100644
index 00000000..e5a777dd
--- /dev/null
+++ b/source/iNKORE.UI.WPF.Modern.Gallery/Data/IconsDataSource.cs
@@ -0,0 +1,158 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using iNKORE.UI.WPF.Modern.Gallery.DataModel;
+
+namespace iNKORE.UI.WPF.Modern.Gallery.Helpers;
+
+internal class IconsDataSource
+{
+ public static IconsDataSource Instance { get; } = new();
+
+ public static List Icons => Instance.icons;
+
+ // Public list of available icon sets discovered via reflection
+ public List AvailableSets { get; } = new();
+
+ // Current active set name (null = all)
+ public string ActiveSet { get; private set; }
+
+ private List icons = new();
+
+ private IconsDataSource() { }
+
+ public object _lock = new();
+
+ public async Task> LoadIcons()
+ {
+ // Yield once to keep this method truly asynchronous without changing logic.
+ await Task.Yield();
+ // If already loaded, return current list
+ lock (_lock)
+ {
+ if (icons.Count != 0)
+ {
+ return icons;
+ }
+ }
+
+ // Try reflection-first: enumerate types in Common.IconKeys namespace
+ try
+ {
+ var assembly = typeof(iNKORE.UI.WPF.Modern.Common.IconKeys.FontDictionary).Assembly;
+ var types = assembly.GetTypes().Where(t => t.IsClass && t.IsSealed && t.IsAbstract && t.Namespace == "iNKORE.UI.WPF.Modern.Common.IconKeys");
+ var discovered = new List();
+
+ foreach (var type in types)
+ {
+ // collect a set name for the class
+ var setName = type.Name;
+ // Try public static fields and properties of type FontIconData
+ var fields = type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
+ foreach (var f in fields)
+ {
+ if (f.FieldType.FullName == "iNKORE.UI.WPF.Modern.Common.IconKeys.FontIconData")
+ {
+ try
+ {
+ var value = f.GetValue(null);
+ var glyphProp = value?.GetType().GetProperty("Glyph");
+ var glyph = glyphProp?.GetValue(value) as string;
+ var familyProp = value?.GetType().GetProperty("FontFamily");
+ var family = familyProp?.GetValue(value) as System.Windows.Media.FontFamily;
+ var name = f.Name;
+ var data = new IconData { Name = name, Glyph = glyph, Set = setName, FontFamily = family };
+ discovered.Add(data);
+ }
+ catch { }
+ }
+ }
+
+ var props = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
+ foreach (var p in props)
+ {
+ if (p.PropertyType.FullName == "iNKORE.UI.WPF.Modern.Common.IconKeys.FontIconData")
+ {
+ try
+ {
+ var value = p.GetValue(null);
+ var glyphProp = value?.GetType().GetProperty("Glyph");
+ var glyph = glyphProp?.GetValue(value) as string;
+ var familyProp = value?.GetType().GetProperty("FontFamily");
+ var family = familyProp?.GetValue(value) as System.Windows.Media.FontFamily;
+ var name = p.Name;
+ var data = new IconData { Name = name, Glyph = glyph, Set = setName, FontFamily = family };
+ discovered.Add(data);
+ }
+ catch { }
+ }
+ }
+
+ if (discovered.Any(d => d.Set == setName))
+ {
+ AvailableSets.Add(setName);
+ }
+ }
+
+ if (discovered.Count > 0)
+ {
+ lock (_lock)
+ {
+ icons = discovered.OrderBy(i => i.Name).ToList();
+ }
+ // Ensure legacy/alias sets are present so the UI can show them
+ EnsureLegacySets();
+ return icons;
+ }
+ }
+ catch
+ {
+ // reflection failed; no fallback
+ }
+
+ return icons;
+ }
+
+ //private static string ToCode(string glyph)
+ //{
+ // if (string.IsNullOrEmpty(glyph)) return string.Empty;
+ // // glyph is a single-character string; convert to hex code (without leading 0x)
+ // var ch = glyph[0];
+ // return ((int)ch).ToString("X4");
+ //}
+
+ // Set active set and return filtered icons
+ public List SetActiveSet(string setName)
+ {
+ // Normalize legacy aliases to concrete set names when possible
+ //if (string.Equals(setName, "SegoeMDL2Assets", StringComparison.OrdinalIgnoreCase) ||
+ // string.Equals(setName, "Segoe MDL2 Assets", StringComparison.OrdinalIgnoreCase))
+ //{
+ // // These glyphs generally live in the JSON data under empty Set (or specific set names).
+ // // Treat this alias as a request to show all non-Fluent-only icons.
+ // ActiveSet = setName;
+ // return icons.Where(i => !i.IsSegoeFluentOnly).ToList();
+ //}
+
+ //if (string.Equals(setName, "SegoeIcons", StringComparison.OrdinalIgnoreCase) ||
+ // string.Equals(setName, "Segoe Icons", StringComparison.OrdinalIgnoreCase))
+ //{
+ // // No dedicated SegoeIcons set in the built-in keys; treat as all icons (fallback).
+ // ActiveSet = setName;
+ // return icons;
+ //}
+
+ ActiveSet = setName;
+ if (string.IsNullOrEmpty(setName)) return icons;
+ return icons.Where(i => i.Set == setName).ToList();
+ }
+
+ private void EnsureLegacySets()
+ {
+ // No-op: legacy set aliases are handled in SetActiveSet().
+ }
+}
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..edcc9291 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,30 @@
"Description": "Design guidance and resources",
"Items":
[
+ {
+ "UniqueId": "Iconography",
+ "Title": "Iconography",
+ "Subtitle": "Segoe Fluent Icons and MDL2 Assets",
+ "ImagePath": "ms-appx:///Assets/ControlIcons/DefaultIcon.png",
+ "ImageIconPath": "ms-appx:///Assets/ControlIcons/DefaultIcon.png",
+ "Description": "",
+ "Content": "",
+ "IncludedInBuild": true,
+ "IsNew": false,
+ "IsUpdated": false,
+ "Docs":
+ [
+ {
+ "Title": "Iconography in Windows 11",
+ "Uri": "https://learn.microsoft.com/windows/apps/design/signature-experiences/iconography#system-icons"
+ },
+ {
+ "Title": "Segoe Fluent Icons font",
+ "Uri": "https://learn.microsoft.com/windows/apps/design/style/segoe-fluent-icons-font"
+ }
+ ],
+ "RelatedControls": []
+ },
{
"UniqueId": "Typography",
"Title": "Typography",
@@ -68,4 +92,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/DataModel/IconData.cs b/source/iNKORE.UI.WPF.Modern.Gallery/DataModel/IconData.cs
new file mode 100644
index 00000000..8d62757e
--- /dev/null
+++ b/source/iNKORE.UI.WPF.Modern.Gallery/DataModel/IconData.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Windows.Media;
+using iNKORE.UI.WPF.Modern.Common.IconKeys;
+
+namespace iNKORE.UI.WPF.Modern.Gallery.DataModel
+{
+ public class IconData
+ {
+ public string Name { get; set; }
+ // Which icon set this icon came from (e.g. "SegoeFluentIcons", "FluentSystemIcons.Regular")
+ public string Set { get; set; }
+ public string[] Tags { get; set; } = Array.Empty();
+ // The actual font to use for rendering this glyph (important for Fluent System Icons)
+ public FontFamily FontFamily { get; set; }
+
+ public string Code { get; protected set; }
+
+
+ private string p_glyph;
+ public string Glyph
+ {
+ get => this.p_glyph;
+ set
+ {
+ this.p_glyph = value;
+ this.Code = ToCode(this.p_glyph);
+ }
+ }
+
+ public string CodeGlyph => string.IsNullOrWhiteSpace(Code) ? string.Empty : "\\u" + Code;
+ public string TextGlyph => string.IsNullOrWhiteSpace(Code) ? string.Empty : "" + Code + ";";
+
+ // WPF doesn't have Symbol enum like WinUI
+ public string SymbolName => null;
+
+
+ public static string ToCode(string glyph)
+ {
+ var codepoint = FontIconData.ToUtf32(glyph);
+ return $"{codepoint:X}";
+ }
+ }
+}
diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/Navigation/NavigationRootPage.xaml b/source/iNKORE.UI.WPF.Modern.Gallery/Navigation/NavigationRootPage.xaml
index 61e9a267..32a6a7f9 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 == "Iconography");
+
+ if (iconographyItem != null)
+ {
+ rootFrame.Navigate(ItemPage.Create(iconographyItem));
+ }
+ }
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/IconographyPage.xaml b/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/IconographyPage.xaml
new file mode 100644
index 00000000..7f9d3970
--- /dev/null
+++ b/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/IconographyPage.xaml
@@ -0,0 +1,910 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ iUWM provides a unified icon system with icon keys for SegoeFluentIcons and FluentSystemIcons. Segoe MDL2 is available via SymbolIcon or as a fallback through SymbolThemeFontFamily. We recommend Fluent System Icons as your primary choice.
+
+
+ Learn more about the Iconography in iUWM by visiting the
+
+
+
+
+ .
+
+
+
+
+
+
+
+
+
+ Use ui:FontIcon with icon keys, such as SegoeFluentIcons.Delete or FluentSystemIcons.Edit_24_Regular (match FontSize to the icon’s designed size). When you set the Icon property, the correct FontFamily is applied automatically for the selected icon set.
+
+
+ If you don't specify a FontFamily, or you specify a FontFamily that isn't available on the system at runtime, FontIcon falls back to the default font family defined by the SymbolThemeFontFamily resource.
+
+
+ The default FontSize is 16 (roughly a 16x16 epx bitmap). For multi-size sets like Fluent System Icons, set FontSize to the icon’s designed size (e.g., 12, 16, 20, 24, 28, 32, 48) to keep pixel-perfect rendering.
+
+
+ All glyphs in Segoe Fluent Icons use a fixed width with a consistent height and left origin point. You can layer and colorize multiple glyphs by drawing them directly on top of each other.
+
+
+
+ Avoid using raw Glyph strings directly; prefer icon keys for clarity, theme compatibility, and future-proofing.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/IconographyPage.xaml.cs b/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/IconographyPage.xaml.cs
new file mode 100644
index 00000000..fc6bc286
--- /dev/null
+++ b/source/iNKORE.UI.WPF.Modern.Gallery/Pages/Controls/Foundation/Design/IconographyPage.xaml.cs
@@ -0,0 +1,660 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using iNKORE.UI.WPF.Modern.Controls;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Threading;
+using iNKORE.UI.WPF.Modern.Gallery.Helpers;
+using iNKORE.UI.WPF.Modern.Gallery.DataModel;
+using iNKORE.UI.WPF.Modern;
+using System.Windows.Media.Animation;
+using System.Collections.ObjectModel;
+using System.Windows.Media;
+using System.Diagnostics;
+using System.Windows.Navigation;
+
+namespace iNKORE.UI.WPF.Modern.Gallery.Pages.Controls.Foundation
+{
+ public partial class IconographyPage : iNKORE.UI.WPF.Modern.Controls.Page
+ {
+ public List FontSizes { get; } = new()
+ {
+ 16,
+ 24,
+ 32,
+ 48
+ };
+
+ private string currentSearch = null;
+
+ public IconData SelectedItem
+ {
+ get { return (IconData)GetValue(SelectedItemProperty); }
+ set
+ {
+ SetValue(SelectedItemProperty, value);
+ SetSampleCodePresenterCode(value);
+ }
+ }
+
+ private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ try
+ {
+ var uri = e.Uri?.AbsoluteUri ?? (sender as System.Windows.Documents.Hyperlink)?.NavigateUri?.AbsoluteUri;
+ if (!string.IsNullOrEmpty(uri))
+ {
+ var psi = new ProcessStartInfo(uri) { UseShellExecute = true };
+ Process.Start(psi);
+ }
+ }
+ catch
+ {
+ }
+ e.Handled = true;
+ }
+
+ public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(IconData), typeof(IconographyPage), new PropertyMetadata(null, OnSelectedItemChanged));
+
+ private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is IconographyPage page)
+ {
+ try
+ {
+ page.UpdateSelectionVisuals(e.NewValue as IconData);
+ }
+ catch
+ {
+ }
+ }
+ }
+
+ public IconographyPage()
+ {
+ InitializeComponent();
+ Loaded += IconographyPage_Loaded;
+ DataContext = this;
+ }
+
+ private void TogglePageTheme()
+ {
+ // Toggle requested theme only on the example container Border so
+ // only the interior region re-evaluates DynamicResource brushes.
+ try
+ {
+ var exampleBorder = FindName("ExampleContainerBorder") as FrameworkElement;
+ if (exampleBorder != null)
+ {
+ exampleBorder.ToggleTheme();
+ return;
+ }
+
+ // Fallback to toggling the whole page if the named Border isn't found
+ this.LayoutRoot.ToggleTheme();
+ }
+ catch { }
+ }
+
+ // Dependency properties for side panel sample text
+ public string FontIconXaml
+ {
+ get => (string)GetValue(FontIconXamlProperty);
+ set => SetValue(FontIconXamlProperty, value);
+ }
+ public static readonly DependencyProperty FontIconXamlProperty = DependencyProperty.Register(nameof(FontIconXaml), typeof(string), typeof(IconographyPage), new PropertyMetadata(string.Empty));
+
+ public string FontIconCSharp
+ {
+ get => (string)GetValue(FontIconCSharpProperty);
+ set => SetValue(FontIconCSharpProperty, value);
+ }
+ public static readonly DependencyProperty FontIconCSharpProperty = DependencyProperty.Register(nameof(FontIconCSharp), typeof(string), typeof(IconographyPage), new PropertyMetadata(string.Empty));
+
+ public string SymbolIconXaml
+ {
+ get => (string)GetValue(SymbolIconXamlProperty);
+ set => SetValue(SymbolIconXamlProperty, value);
+ }
+ public static readonly DependencyProperty SymbolIconXamlProperty = DependencyProperty.Register(nameof(SymbolIconXaml), typeof(string), typeof(IconographyPage), new PropertyMetadata(string.Empty));
+
+ public string SymbolIconCSharp
+ {
+ get => (string)GetValue(SymbolIconCSharpProperty);
+ set => SetValue(SymbolIconCSharpProperty, value);
+ }
+ public static readonly DependencyProperty SymbolIconCSharpProperty = DependencyProperty.Register(nameof(SymbolIconCSharp), typeof(string), typeof(IconographyPage), new PropertyMetadata(string.Empty));
+
+ private void IconographyPage_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (NavigationRootPage.Current?.NavigationView != null)
+ {
+ NavigationRootPage.Current.NavigationView.Header = "Iconography";
+ }
+
+ // Register toggle theme action on page header so Theme toggle also affects this page
+ try
+ {
+ if (NavigationRootPage.Current?.PageHeader != null)
+ {
+ NavigationRootPage.Current.PageHeader.ToggleThemeAction = TogglePageTheme;
+ }
+ }
+ catch { }
+
+ // Load icons on a background thread and assign them immediately to the repeater.
+ Task.Run(async delegate
+ {
+ var icons = await IconsDataSource.Instance.LoadIcons();
+
+ // Assign the full list to the ItemsRepeater on the UI thread.
+ await Dispatcher.BeginInvoke(new Action(() =>
+ {
+ var iconsItemsView = FindName("IconsItemsView") as iNKORE.UI.WPF.Modern.Controls.ItemsRepeater;
+ if (iconsItemsView != null)
+ {
+ iconsItemsView.ItemsSource = icons;
+
+ // Populate icon set ComboBox if present
+ var setCombo = FindName("IconSetComboBox") as ComboBox;
+ try
+ {
+ if (setCombo != null)
+ {
+ setCombo.ItemsSource = IconsDataSource.Instance.AvailableSets;
+ setCombo.SelectionChanged += IconSetComboBox_SelectionChanged;
+ if (IconsDataSource.Instance.AvailableSets.Count > 0)
+ {
+ // Prefer SegoeFluentIcons when present
+ var preferred = IconsDataSource.Instance.AvailableSets.FirstOrDefault(s => string.Equals(s, "SegoeFluentIcons", StringComparison.OrdinalIgnoreCase));
+ if (!string.IsNullOrEmpty(preferred))
+ {
+ setCombo.SelectedItem = preferred;
+ }
+ else
+ {
+ setCombo.SelectedIndex = 0;
+ }
+ }
+ }
+ }
+ catch { }
+
+ // Select the first item by default and show side panel
+ if (icons != null && icons.Count > 0)
+ {
+ var first = icons[0];
+ SelectedItem = first;
+ var sidePanel = FindName("SidePanel") as Border;
+ if (sidePanel != null)
+ {
+ sidePanel.Visibility = Visibility.Visible;
+ }
+ UpdateSelectionVisuals(first);
+ }
+ }
+ }));
+ });
+ }
+
+ private void SetSampleCodePresenterCode(IconData value)
+ {
+ // Update presenters and side panel when selection changes
+ var sidePanel = FindName("SidePanel") as Border;
+ if (value == null)
+ {
+ if (sidePanel != null)
+ {
+ sidePanel.Visibility = Visibility.Collapsed;
+ }
+ return;
+ }
+
+ // Ensure side panel visible
+ if (sidePanel != null)
+ {
+ sidePanel.Visibility = Visibility.Visible;
+ }
+
+ // Populate code strings for TextBoxes
+ if (!string.IsNullOrEmpty(value.Set))
+ {
+ // Use static icon class reference when available
+ // Example XAML:
+ FontIconXaml = $"";
+ // Use fully-qualified C# reference to the static property in Common.IconKeys
+ FontIconCSharp = "using iNKORE.UI.WPF.Modern.Common.IconKeys;" + Environment.NewLine + Environment.NewLine + $"FontIcon icon = new FontIcon();" + Environment.NewLine + $"icon.Icon = {value.Set}.{value.Name};";
+ }
+ else
+ {
+ FontIconXaml = $"";
+ FontIconCSharp = "FontIcon icon = new FontIcon();" + Environment.NewLine + $"icon.Glyph = \"{value.CodeGlyph}\";";
+ }
+
+ if (!string.IsNullOrEmpty(value.SymbolName))
+ {
+ SymbolIconXaml = $"";
+ SymbolIconCSharp = "SymbolIcon icon = new SymbolIcon();" + Environment.NewLine + $"icon.Symbol = Symbol.{value.SymbolName};";
+ var symbolPanel = FindName("SymbolPanel") as StackPanel;
+ if (symbolPanel != null)
+ {
+ symbolPanel.Visibility = Visibility.Visible;
+ }
+ }
+ else
+ {
+ SymbolIconXaml = string.Empty;
+ SymbolIconCSharp = string.Empty;
+ var symbolPanel = FindName("SymbolPanel") as StackPanel;
+ if (symbolPanel != null)
+ {
+ symbolPanel.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ // Tags visibility
+ try
+ {
+ var tagsView = FindName("TagsItemsView") as iNKORE.UI.WPF.Modern.Controls.ItemsRepeater;
+ var noTags = FindName("NoTagsTextBlock") as TextBlock;
+ if (value.Tags == null || value.Tags.Length == 0 || value.Tags.All(t => string.IsNullOrWhiteSpace(t)))
+ {
+ if (tagsView != null) tagsView.Visibility = Visibility.Collapsed;
+ if (noTags != null) noTags.Visibility = Visibility.Collapsed;
+ TagsLabel.Visibility = Visibility.Collapsed;
+ }
+ else
+ {
+ if (tagsView != null) tagsView.Visibility = Visibility.Visible;
+ if (noTags != null) noTags.Visibility = Visibility.Collapsed;
+ TagsLabel.Visibility = Visibility.Visible;
+ }
+ }
+ catch { }
+ }
+
+ private void SearchTextBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
+ {
+ var tb = FindName("IconsSearchBox");
+ var text = tb?.GetType().GetProperty("Text")?.GetValue(tb) as string;
+ Filter(text);
+ }
+
+ public void Filter(string search)
+ {
+ currentSearch = search;
+ string[] filter = search?.Split(' ');
+
+ Task.Run(() =>
+ {
+ var newItems = new List();
+ foreach (var item in IconsDataSource.Icons)
+ {
+ var fitsFilter = filter == null || filter.All(entry =>
+ item.Code.Contains(entry, StringComparison.CurrentCultureIgnoreCase) ||
+ item.Name.Contains(entry, StringComparison.CurrentCultureIgnoreCase) ||
+ item.Tags.Any(tag => !string.IsNullOrEmpty(tag) && tag.Contains(entry, StringComparison.CurrentCultureIgnoreCase)));
+
+ if (fitsFilter)
+ {
+ newItems.Add(item);
+ }
+ }
+
+ // Assign filtered list to repeater immediately on UI thread
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ var iconsItemsView = FindName("IconsItemsView") as iNKORE.UI.WPF.Modern.Controls.ItemsRepeater;
+ if (iconsItemsView != null)
+ {
+ iconsItemsView.ItemsSource = newItems;
+ if (newItems.Count > 0)
+ {
+ SelectedItem = newItems[0];
+ UpdateSelectionVisuals(newItems[0]);
+ }
+ }
+ }));
+ });
+ }
+
+ private void IconsItemsView_SelectionChanged(object sender, EventArgs e)
+ {
+ try
+ {
+ // Try to read a SelectedItem property from the sender (GridView exposes this)
+ var view = FindName("IconsItemsView");
+ if (view != null)
+ {
+ var selProp = view.GetType().GetProperty("SelectedItem");
+ if (selProp != null)
+ {
+ var sel = selProp.GetValue(view) as IconData;
+ if (sel != null) SelectedItem = sel;
+ }
+ }
+ }
+ catch { }
+
+ try
+ {
+ var argsType = e.GetType();
+ var invokedProp = argsType.GetProperty("InvokedItem");
+ if (invokedProp != null)
+ {
+ var tag = invokedProp.GetValue(e) as string;
+ if (!string.IsNullOrEmpty(tag))
+ {
+ // Set text on the named search box if present
+ var searchBox = FindName("IconsSearchBox");
+ if (searchBox != null)
+ {
+ var textProp = searchBox.GetType().GetProperty("Text");
+ textProp?.SetValue(searchBox, tag);
+ }
+ }
+ }
+ }
+ catch { }
+ }
+
+ private void IconsItemsView_ItemClicked(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ if (sender is Button btn && btn.DataContext is IconData data)
+ {
+ SelectedItem = data;
+ UpdateSelectionVisuals(data);
+ }
+ }
+ catch { }
+ }
+
+ private void IconSetComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ try
+ {
+ if (sender is ComboBox cb && cb.SelectedItem is string setName)
+ {
+ var items = IconsDataSource.Instance.SetActiveSet(setName);
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ var iconsItemsView = FindName("IconsItemsView") as iNKORE.UI.WPF.Modern.Controls.ItemsRepeater;
+ if (iconsItemsView != null)
+ {
+ iconsItemsView.ItemsSource = items;
+ if (items.Count > 0)
+ {
+ SelectedItem = items[0];
+ UpdateSelectionVisuals(items[0]);
+ }
+ }
+ }));
+ }
+ }
+ catch { }
+ }
+
+ private void TagButton_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ if (sender is Button btn)
+ {
+ // The TextBlock is inside the Button's visual tree; find it
+ var tb = FindDescendants(btn).FirstOrDefault();
+ var tag = tb?.Text;
+ if (!string.IsNullOrEmpty(tag))
+ {
+ // Update search box text via the named control if available
+ var searchBoxField = this.GetType().GetField("IconsSearchBox", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
+ if (searchBoxField != null)
+ {
+ var searchBox = searchBoxField.GetValue(this);
+ if (searchBox != null)
+ {
+ var textProp = searchBox.GetType().GetProperty("Text");
+ textProp?.SetValue(searchBox, tag);
+ var focusMethod = searchBox.GetType().GetMethod("Focus", System.Type.EmptyTypes);
+ focusMethod?.Invoke(searchBox, null);
+
+ // Try to open suggestion list if property exists
+ var isOpenProp = searchBox.GetType().GetProperty("IsSuggestionListOpen");
+ if (isOpenProp != null) isOpenProp.SetValue(searchBox, true);
+ }
+ }
+
+ // Also call Filter to update items immediately
+ Filter(tag);
+ }
+ }
+ }
+ catch { }
+ }
+
+ // handler for Border-based tag chips. Extracts the tag text and
+ // performs the same actions as TagButton_Click (populate search box,
+ // focus, open suggestions, and filter).
+ private void TagChip_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ try
+ {
+ if (sender is Border outerBorder)
+ {
+ // The inner TextBlock is a descendant; find it
+ var tb = FindDescendants(outerBorder).FirstOrDefault();
+ var tag = tb?.Text;
+ if (!string.IsNullOrEmpty(tag))
+ {
+ // Reuse the same reflection-based logic to set the search box
+ var searchBoxField = this.GetType().GetField("IconsSearchBox", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
+ if (searchBoxField != null)
+ {
+ var searchBox = searchBoxField.GetValue(this);
+ if (searchBox != null)
+ {
+ var textProp = searchBox.GetType().GetProperty("Text");
+ textProp?.SetValue(searchBox, tag);
+ var focusMethod = searchBox.GetType().GetMethod("Focus", System.Type.EmptyTypes);
+ focusMethod?.Invoke(searchBox, null);
+
+ // Try to open suggestion list if property exists
+ var isOpenProp = searchBox.GetType().GetProperty("IsSuggestionListOpen");
+ if (isOpenProp != null) isOpenProp.SetValue(searchBox, true);
+ }
+ }
+
+ // Also call Filter to update items immediately
+ Filter(tag);
+ }
+ }
+ }
+ catch { }
+ }
+
+ private async void CopyValueButton_Click(object sender, RoutedEventArgs e)
+ {
+ string textToCopy = null;
+ try
+ {
+ if (sender is Button btn)
+ {
+ // Primary path: Tag is bound to the value to copy
+ textToCopy = btn.Tag as string;
+
+ // Fallbacks for cases where the Tag binding isn't available or was modified
+ if (string.IsNullOrEmpty(textToCopy))
+ {
+ switch (btn.Name)
+ {
+ case "CopyInlineExampleButton":
+ textToCopy = (FindName("InlineExampleBox") as TextBox)?.Text;
+ break;
+ case "CopyNameButton":
+ textToCopy = SelectedItem?.Name;
+ break;
+ case "CopyTextGlyphButton":
+ textToCopy = SelectedItem?.TextGlyph;
+ break;
+ case "CopyCodeGlyphButton":
+ textToCopy = SelectedItem?.CodeGlyph;
+ break;
+ case "CopyXamlButton":
+ textToCopy = FontIconXaml;
+ break;
+ case "CopyCSharpButton":
+ textToCopy = FontIconCSharp;
+ break;
+ case "CopySymbolXamlButton":
+ textToCopy = SymbolIconXaml;
+ break;
+ case "CopySymbolCSharpButton":
+ textToCopy = SymbolIconCSharp;
+ break;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(textToCopy))
+ {
+ var copied = TrySetClipboardText(textToCopy, 3, TimeSpan.FromMilliseconds(150));
+ if (!copied)
+ {
+ throw new InvalidOperationException("Unable to open clipboard after multiple attempts.");
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ iNKORE.UI.WPF.Modern.Controls.MessageBox.Show(ex.ToString(), "Unable to Perform Copy", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ // Trigger the Geometry-style copy confirmation animation.
+ // The VisualStateGroups are declared on LayoutRoot; use GoToElementState
+ // so the states are found in the same namescope.
+ if (sender is Button)
+ {
+ try
+ {
+ var layoutRoot = FindName("LayoutRoot") as FrameworkElement;
+ if (layoutRoot != null)
+ {
+ // If the Button has a name like "CopyXamlButton" derive a per-button
+ // state name like "CopyXamlVisible" and "CopyXamlHidden" and try
+ // to trigger it. Fall back to the shared Control/Overlay groups.
+ if (sender is Button btn && !string.IsNullOrEmpty(btn.Name))
+ {
+ // Strip trailing "Button" if present
+ var baseName = btn.Name.EndsWith("Button") ? btn.Name.Substring(0, btn.Name.Length - 6) : btn.Name;
+ var visibleState = baseName + "Visible";
+ var hiddenState = baseName + "Hidden";
+
+ var started = VisualStateManager.GoToElementState(layoutRoot, visibleState, true);
+ if (!started)
+ {
+ // fall back to existing Control/Overlay groups
+ started = VisualStateManager.GoToElementState(layoutRoot, "ControlCornerRadiusCopyButtonVisible", true);
+ if (!started)
+ {
+ VisualStateManager.GoToElementState(layoutRoot, "OverlayCornerRadiusCopyButtonVisible", true);
+ }
+ }
+
+ // Revert after a short delay so the success checkmark animates back.
+ await Task.Delay(900);
+
+ // Try to revert per-button state first, then the shared states.
+ VisualStateManager.GoToElementState(layoutRoot, hiddenState, true);
+ VisualStateManager.GoToElementState(layoutRoot, "ControlCornerRadiusCopyButtonHidden", true);
+ VisualStateManager.GoToElementState(layoutRoot, "OverlayCornerRadiusCopyButtonHidden", true);
+ }
+ else
+ {
+ var started = VisualStateManager.GoToElementState(layoutRoot, "ControlCornerRadiusCopyButtonVisible", true);
+ if (!started)
+ {
+ VisualStateManager.GoToElementState(layoutRoot, "OverlayCornerRadiusCopyButtonVisible", true);
+ }
+ await Task.Delay(900);
+ VisualStateManager.GoToElementState(layoutRoot, "ControlCornerRadiusCopyButtonHidden", true);
+ VisualStateManager.GoToElementState(layoutRoot, "OverlayCornerRadiusCopyButtonHidden", true);
+ }
+ }
+ }
+ catch
+ {
+ // Swallow any VSM exceptions; copy already occurred.
+ }
+ }
+ }
+
+ // Recursive descendant finder
+ private static IEnumerable FindDescendants(DependencyObject root) where T : DependencyObject
+ {
+ if (root == null) yield break;
+
+ var queue = new Queue();
+ queue.Enqueue(root);
+
+ while (queue.Count > 0)
+ {
+ var current = queue.Dequeue();
+ int count = VisualTreeHelper.GetChildrenCount(current);
+ for (int i = 0; i < count; i++)
+ {
+ var child = VisualTreeHelper.GetChild(current, i);
+ if (child is T t) yield return t;
+ queue.Enqueue(child);
+ }
+ }
+ }
+
+ // Robust clipboard setter: runs SetText on a new STA thread and retries when clipboard is busy.
+ private static bool TrySetClipboardText(string text, int maxAttempts, TimeSpan delayBetweenAttempts)
+ {
+ if (string.IsNullOrEmpty(text)) return false;
+
+ for (int attempt = 0; attempt < maxAttempts; attempt++)
+ {
+ var success = false;
+ var thread = new Thread(() =>
+ {
+ try
+ {
+ // Use System.Windows.Clipboard on STA thread
+ System.Windows.Clipboard.SetText(text);
+ success = true;
+ }
+ catch
+ {
+ success = false;
+ }
+ });
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.IsBackground = true;
+ thread.Start();
+ thread.Join();
+
+ if (success) return true;
+
+ Thread.Sleep(delayBetweenAttempts);
+ }
+
+ return false;
+ }
+
+ //Keep for reference in the future
+ private void UpdateSelectionVisuals(IconData selected)
+ {
+
+ }
+ }
+}
diff --git a/source/iNKORE.UI.WPF.Modern.Gallery/iNKORE.UI.WPF.Modern.Gallery.csproj b/source/iNKORE.UI.WPF.Modern.Gallery/iNKORE.UI.WPF.Modern.Gallery.csproj
index b26db0e3..565a6fc5 100644
--- a/source/iNKORE.UI.WPF.Modern.Gallery/iNKORE.UI.WPF.Modern.Gallery.csproj
+++ b/source/iNKORE.UI.WPF.Modern.Gallery/iNKORE.UI.WPF.Modern.Gallery.csproj
@@ -1,4 +1,4 @@
-
+
WinExe
diff --git a/source/iNKORE.UI.WPF.Modern/Common/IconKeys/FluentSystemIcons.cs b/source/iNKORE.UI.WPF.Modern/Common/IconKeys/FluentSystemIcons.cs
index 99a272db..b3a9abd1 100644
--- a/source/iNKORE.UI.WPF.Modern/Common/IconKeys/FluentSystemIcons.cs
+++ b/source/iNKORE.UI.WPF.Modern/Common/IconKeys/FluentSystemIcons.cs
@@ -29,15 +29,8 @@ public static FontIconData CreateIcon(string glyph, FluentSystemIconVariants var
public static FontIconData CreateIcon(int chara, FluentSystemIconVariants variant)
{
- return CreateIcon(ToGlyph(chara), variant);
+ return CreateIcon(FontIconData.ToGlyph(chara), variant);
}
-
- public static string ToGlyph(int chara)
- {
- return char.ConvertFromUtf32(chara);
- }
-
-
}
public enum FluentSystemIconVariants
diff --git a/source/iNKORE.UI.WPF.Modern/Common/IconKeys/FontDictionary.cs b/source/iNKORE.UI.WPF.Modern/Common/IconKeys/FontDictionary.cs
index 7fe64a49..cfde05f0 100644
--- a/source/iNKORE.UI.WPF.Modern/Common/IconKeys/FontDictionary.cs
+++ b/source/iNKORE.UI.WPF.Modern/Common/IconKeys/FontDictionary.cs
@@ -51,5 +51,30 @@ public FontIconData(string glyph, FontFamily family = null)
_glyph = glyph;
_fontFamily = family;
}
+
+ public static string ToGlyph(int chara)
+ {
+ return char.ConvertFromUtf32(chara);
+ }
+
+ public static int ToUtf32(string glyph)
+ {
+ if (string.IsNullOrEmpty(glyph))
+ throw new ArgumentException("Input glyph cannot be null or empty.");
+
+ if (glyph.Length == 1)
+ {
+ return char.ConvertToUtf32(glyph, 0);
+ }
+ else if (glyph.Length == 2 && char.IsSurrogatePair(glyph[0], glyph[1]))
+ {
+ return char.ConvertToUtf32(glyph, 0);
+ }
+ else
+ {
+ throw new ArgumentException("Input glyph must be a single character or a valid surrogate pair.");
+ }
+ }
+
}
}
diff --git a/source/iNKORE.UI.WPF.Modern/Common/IconKeys/SegoeFluentIcons.cs b/source/iNKORE.UI.WPF.Modern/Common/IconKeys/SegoeFluentIcons.cs
index 5b274057..613dc362 100644
--- a/source/iNKORE.UI.WPF.Modern/Common/IconKeys/SegoeFluentIcons.cs
+++ b/source/iNKORE.UI.WPF.Modern/Common/IconKeys/SegoeFluentIcons.cs
@@ -19,12 +19,7 @@ public static FontIconData CreateIcon(string glyph, bool forceFluent = false)
public static FontIconData CreateIcon(int chara, bool forceFluent = false)
{
- return CreateIcon(ToGlyph(chara), forceFluent);
- }
-
- public static string ToGlyph(int chara)
- {
- return char.ConvertFromUtf32(chara);
+ return CreateIcon(FontIconData.ToGlyph(chara), forceFluent);
}
}
}