Skip to content

Commit c3af9ec

Browse files
GID0317NotYoojun
andauthored
Gallery Revamp: Design Iconography Page (#343)
* Add Iconography navigation handling * Add Iconography navigation item to NavigationRootPage * Add IconsDataSource class for loading icons * Port all icon data list available from WinUi3 gallery * Add IconsDataSchema.json for glyph metadata * Add Iconography section to Controls.Foundation.json * Add EmbeddedResource for IconsData.json * Change namespace import from Models to DataModel * Add IconData class for icon representation * Add the xaml side of iconograpy Include IconTemplate, and temporarily use ItemRepeater due to the limitation of the absence of ItemView controls in the current version. * Add the cs side for the iconography page including logic to handle the icon loading. temporarily use item repeater until item view implemented * Enhance clipboard copy functionality for the code example under settings expander Added robust clipboard handling with retries. * Update hyperlink for FontIcon documentation * Refactor IconographyPage layout and controls for consistency * Add 'Set' property to IconData class * Implement icon set selection and population logic * Update placeholder text in IconographyPage.xaml since tags and code can't be search anymore * Enhance icon loading with reflection and fallback * Delete source/iNKORE.UI.WPF.Modern.Gallery/DataModel/IconsData.json * Delete source/iNKORE.UI.WPF.Modern.Gallery/DataModel/IconsDataSchema.json * Remove EmbeddedResource for IconsData.json * Refactor IconData class structure and properties * Refactor IconsDataSource by removing fallback loading * fix: (gallery/IconographyPage) re-format XAML code * feat: (gallery/IconographyPage) hide tags & update sample code * feat: (gallery/IconographyPage) list item hover visuals * Enhance IconData and simplify legacy set logic * Add FontFamily property to IconData class * Update IconographyPage.xaml for font family binding * refactor: (gallery/IconsDataSource) glyph handling and update UI bindings * refactor: (gallery/IconographyPage) example control border * Update iconography descriptions --------- Co-authored-by: Yoojun Zhou <[email protected]>
1 parent ac76a24 commit c3af9ec

File tree

12 files changed

+1858
-18
lines changed

12 files changed

+1858
-18
lines changed

source/iNKORE.UI.WPF.Modern.Gallery/App.xaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<SolidColorBrush x:Key="NavigationViewExpandedPaneBackground" Color="Transparent" />
3131
<SolidColorBrush x:Key="AppContentBackgroundBrush" Color="{StaticResource SystemAltHighColor}" />
3232
<StaticResource x:Key="ControlExampleDisplayBrush" ResourceKey="SolidBackgroundFillColorBaseBrush" />
33+
<StaticResource x:Key="GalleryBorderBrush" ResourceKey="CardStrokeColorDefaultBrush" />
3334
</ResourceDictionary>
3435

3536
<ResourceDictionary x:Key="Dark" ui:ThemeDictionary.Key="Dark">
@@ -47,6 +48,7 @@
4748
<SolidColorBrush x:Key="AppContentBackgroundBrush" Color="#FF282828" />
4849
<SolidColorBrush x:Key="NavigationViewExpandedPaneBackground" Color="Transparent" />
4950
<StaticResource x:Key="ControlExampleDisplayBrush" ResourceKey="SolidBackgroundFillColorBaseBrush" />
51+
<StaticResource x:Key="GalleryBorderBrush" ResourceKey="CardStrokeColorDefaultBrush" />
5052
</ResourceDictionary>
5153

5254
<ResourceDictionary x:Key="HighContrast" ui:ThemeDictionary.Key="HighContrast">
@@ -60,6 +62,7 @@
6062
<SolidColorBrush x:Key="SearchBoxBorderBrush" Color="{DynamicResource SystemColorWindowTextColor}" />
6163
<SolidColorBrush x:Key="ControlExampleDisplayBrush" Color="{DynamicResource SystemColorWindowColor}" />
6264
<SolidColorBrush x:Key="PageHeaderForegroundBrush" Color="{DynamicResource SystemColorWindowTextColor}" />
65+
<SolidColorBrush x:Key="GalleryBorderBrush" Color="{DynamicResource SystemColorWindowColor}" />
6366
</ResourceDictionary>
6467
</ui:ThemeResources.ThemeDictionaries>
6568
</ui:ThemeResources>
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Threading.Tasks;
8+
using iNKORE.UI.WPF.Modern.Gallery.DataModel;
9+
10+
namespace iNKORE.UI.WPF.Modern.Gallery.Helpers;
11+
12+
internal class IconsDataSource
13+
{
14+
public static IconsDataSource Instance { get; } = new();
15+
16+
public static List<IconData> Icons => Instance.icons;
17+
18+
// Public list of available icon sets discovered via reflection
19+
public List<string> AvailableSets { get; } = new();
20+
21+
// Current active set name (null = all)
22+
public string ActiveSet { get; private set; }
23+
24+
private List<IconData> icons = new();
25+
26+
private IconsDataSource() { }
27+
28+
public object _lock = new();
29+
30+
public async Task<List<IconData>> LoadIcons()
31+
{
32+
// Yield once to keep this method truly asynchronous without changing logic.
33+
await Task.Yield();
34+
// If already loaded, return current list
35+
lock (_lock)
36+
{
37+
if (icons.Count != 0)
38+
{
39+
return icons;
40+
}
41+
}
42+
43+
// Try reflection-first: enumerate types in Common.IconKeys namespace
44+
try
45+
{
46+
var assembly = typeof(iNKORE.UI.WPF.Modern.Common.IconKeys.FontDictionary).Assembly;
47+
var types = assembly.GetTypes().Where(t => t.IsClass && t.IsSealed && t.IsAbstract && t.Namespace == "iNKORE.UI.WPF.Modern.Common.IconKeys");
48+
var discovered = new List<IconData>();
49+
50+
foreach (var type in types)
51+
{
52+
// collect a set name for the class
53+
var setName = type.Name;
54+
// Try public static fields and properties of type FontIconData
55+
var fields = type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
56+
foreach (var f in fields)
57+
{
58+
if (f.FieldType.FullName == "iNKORE.UI.WPF.Modern.Common.IconKeys.FontIconData")
59+
{
60+
try
61+
{
62+
var value = f.GetValue(null);
63+
var glyphProp = value?.GetType().GetProperty("Glyph");
64+
var glyph = glyphProp?.GetValue(value) as string;
65+
var familyProp = value?.GetType().GetProperty("FontFamily");
66+
var family = familyProp?.GetValue(value) as System.Windows.Media.FontFamily;
67+
var name = f.Name;
68+
var data = new IconData { Name = name, Glyph = glyph, Set = setName, FontFamily = family };
69+
discovered.Add(data);
70+
}
71+
catch { }
72+
}
73+
}
74+
75+
var props = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
76+
foreach (var p in props)
77+
{
78+
if (p.PropertyType.FullName == "iNKORE.UI.WPF.Modern.Common.IconKeys.FontIconData")
79+
{
80+
try
81+
{
82+
var value = p.GetValue(null);
83+
var glyphProp = value?.GetType().GetProperty("Glyph");
84+
var glyph = glyphProp?.GetValue(value) as string;
85+
var familyProp = value?.GetType().GetProperty("FontFamily");
86+
var family = familyProp?.GetValue(value) as System.Windows.Media.FontFamily;
87+
var name = p.Name;
88+
var data = new IconData { Name = name, Glyph = glyph, Set = setName, FontFamily = family };
89+
discovered.Add(data);
90+
}
91+
catch { }
92+
}
93+
}
94+
95+
if (discovered.Any(d => d.Set == setName))
96+
{
97+
AvailableSets.Add(setName);
98+
}
99+
}
100+
101+
if (discovered.Count > 0)
102+
{
103+
lock (_lock)
104+
{
105+
icons = discovered.OrderBy(i => i.Name).ToList();
106+
}
107+
// Ensure legacy/alias sets are present so the UI can show them
108+
EnsureLegacySets();
109+
return icons;
110+
}
111+
}
112+
catch
113+
{
114+
// reflection failed; no fallback
115+
}
116+
117+
return icons;
118+
}
119+
120+
//private static string ToCode(string glyph)
121+
//{
122+
// if (string.IsNullOrEmpty(glyph)) return string.Empty;
123+
// // glyph is a single-character string; convert to hex code (without leading 0x)
124+
// var ch = glyph[0];
125+
// return ((int)ch).ToString("X4");
126+
//}
127+
128+
// Set active set and return filtered icons
129+
public List<IconData> SetActiveSet(string setName)
130+
{
131+
// Normalize legacy aliases to concrete set names when possible
132+
//if (string.Equals(setName, "SegoeMDL2Assets", StringComparison.OrdinalIgnoreCase) ||
133+
// string.Equals(setName, "Segoe MDL2 Assets", StringComparison.OrdinalIgnoreCase))
134+
//{
135+
// // These glyphs generally live in the JSON data under empty Set (or specific set names).
136+
// // Treat this alias as a request to show all non-Fluent-only icons.
137+
// ActiveSet = setName;
138+
// return icons.Where(i => !i.IsSegoeFluentOnly).ToList();
139+
//}
140+
141+
//if (string.Equals(setName, "SegoeIcons", StringComparison.OrdinalIgnoreCase) ||
142+
// string.Equals(setName, "Segoe Icons", StringComparison.OrdinalIgnoreCase))
143+
//{
144+
// // No dedicated SegoeIcons set in the built-in keys; treat as all icons (fallback).
145+
// ActiveSet = setName;
146+
// return icons;
147+
//}
148+
149+
ActiveSet = setName;
150+
if (string.IsNullOrEmpty(setName)) return icons;
151+
return icons.Where(i => i.Set == setName).ToList();
152+
}
153+
154+
private void EnsureLegacySets()
155+
{
156+
// No-op: legacy set aliases are handled in SetActiveSet().
157+
}
158+
}

source/iNKORE.UI.WPF.Modern.Gallery/DataModel/Data/Controls.Foundation.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@
2525
"Description": "Design guidance and resources",
2626
"Items":
2727
[
28+
{
29+
"UniqueId": "Iconography",
30+
"Title": "Iconography",
31+
"Subtitle": "Segoe Fluent Icons and MDL2 Assets",
32+
"ImagePath": "ms-appx:///Assets/ControlIcons/DefaultIcon.png",
33+
"ImageIconPath": "ms-appx:///Assets/ControlIcons/DefaultIcon.png",
34+
"Description": "",
35+
"Content": "",
36+
"IncludedInBuild": true,
37+
"IsNew": false,
38+
"IsUpdated": false,
39+
"Docs":
40+
[
41+
{
42+
"Title": "Iconography in Windows 11",
43+
"Uri": "https://learn.microsoft.com/windows/apps/design/signature-experiences/iconography#system-icons"
44+
},
45+
{
46+
"Title": "Segoe Fluent Icons font",
47+
"Uri": "https://learn.microsoft.com/windows/apps/design/style/segoe-fluent-icons-font"
48+
}
49+
],
50+
"RelatedControls": []
51+
},
2852
{
2953
"UniqueId": "Typography",
3054
"Title": "Typography",
@@ -69,4 +93,4 @@
6993
]
7094
}
7195
]
72-
}
96+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Windows.Media;
7+
using iNKORE.UI.WPF.Modern.Common.IconKeys;
8+
9+
namespace iNKORE.UI.WPF.Modern.Gallery.DataModel
10+
{
11+
public class IconData
12+
{
13+
public string Name { get; set; }
14+
// Which icon set this icon came from (e.g. "SegoeFluentIcons", "FluentSystemIcons.Regular")
15+
public string Set { get; set; }
16+
public string[] Tags { get; set; } = Array.Empty<string>();
17+
// The actual font to use for rendering this glyph (important for Fluent System Icons)
18+
public FontFamily FontFamily { get; set; }
19+
20+
public string Code { get; protected set; }
21+
22+
23+
private string p_glyph;
24+
public string Glyph
25+
{
26+
get => this.p_glyph;
27+
set
28+
{
29+
this.p_glyph = value;
30+
this.Code = ToCode(this.p_glyph);
31+
}
32+
}
33+
34+
public string CodeGlyph => string.IsNullOrWhiteSpace(Code) ? string.Empty : "\\u" + Code;
35+
public string TextGlyph => string.IsNullOrWhiteSpace(Code) ? string.Empty : "&#x" + Code + ";";
36+
37+
// WPF doesn't have Symbol enum like WinUI
38+
public string SymbolName => null;
39+
40+
41+
public static string ToCode(string glyph)
42+
{
43+
var codepoint = FontIconData.ToUtf32(glyph);
44+
return $"{codepoint:X}";
45+
}
46+
}
47+
}

source/iNKORE.UI.WPF.Modern.Gallery/Navigation/NavigationRootPage.xaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@
121121
<ui:FontIcon Glyph="&#xEB3C;" />
122122
</ui:NavigationViewItem.Icon>
123123
<ui:NavigationViewItem.MenuItems>
124+
<ui:NavigationViewItem
125+
x:Name="IconographyItem"
126+
Content="Iconography"
127+
Tag="Iconography">
128+
<ui:NavigationViewItem.Icon>
129+
<ui:FontIcon Glyph="&#xED58;" />
130+
</ui:NavigationViewItem.Icon>
131+
</ui:NavigationViewItem>
124132
<ui:NavigationViewItem
125133
x:Name="TypographyItem"
126134
Content="Typography"

source/iNKORE.UI.WPF.Modern.Gallery/Navigation/NavigationRootPage.xaml.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,19 +291,36 @@ private void OnNavigationViewSelectionChanged(NavigationView sender, NavigationV
291291
_lastItem = item;
292292
rootFrame.Navigate(item);
293293
}
294+
else if (selectedItem?.Tag?.ToString() == "Iconography")
295+
{
296+
var iconographyId = "Iconography";
297+
if (_lastItem?.ToString() == iconographyId) return;
298+
_lastItem = iconographyId;
299+
300+
// Find Iconography item from the data source
301+
var iconographyItem = ControlInfoDataSource.Instance.Realms
302+
.SelectMany(r => r.Groups)
303+
.SelectMany(g => g.Items)
304+
.FirstOrDefault(i => i.UniqueId == "Iconography");
305+
306+
if (iconographyItem != null)
307+
{
308+
rootFrame.Navigate(ItemPage.Create(iconographyItem));
309+
}
310+
}
294311
else if (selectedItem?.Tag?.ToString() == "Typography")
295312
{
296313
// Handle Typography navigation
297314
var typographyId = "Typography";
298315
if (_lastItem?.ToString() == typographyId) return;
299316
_lastItem = typographyId;
300-
317+
301318
// Find Typography item from the data source
302319
var typographyItem = ControlInfoDataSource.Instance.Realms
303320
.SelectMany(r => r.Groups)
304321
.SelectMany(g => g.Items)
305322
.FirstOrDefault(i => i.UniqueId == "Typography");
306-
323+
307324
if (typographyItem != null)
308325
{
309326
rootFrame.Navigate(ItemPage.Create(typographyItem));

0 commit comments

Comments
 (0)