Skip to content

Commit d8a9f63

Browse files
committed
Expand icon protocol samples
1 parent 10f2449 commit d8a9f63

8 files changed

Lines changed: 332 additions & 40 deletions

File tree

src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.UnitTests/CachedIconSourceProviderTests.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,7 @@ public async Task ThemedSvgProtocolUsesDistinctCacheEntriesAcrossThemes()
234234
{
235235
var loader = new ControllableIconLoader();
236236
var provider = CreateProvider(loader);
237-
var icon = new IconDataViewModel
238-
{
239-
Icon = "|ThemedSvg|<svg xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"{{ThemeColor}}\"/></svg>",
240-
};
237+
var icon = CreateIcon("|ThemedSvg|<svg xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"{{ThemeColor}}\"/></svg>");
241238

242239
var light = provider.GetIconSource(icon, 1.0, theme: ElementTheme.Light);
243240
loader.CompleteNext(null);
@@ -264,10 +261,7 @@ public async Task PlainSvgProtocolSharesCacheEntryAcrossThemes()
264261
{
265262
var loader = new ControllableIconLoader();
266263
var provider = CreateProvider(loader);
267-
var icon = new IconDataViewModel
268-
{
269-
Icon = "|Svg|<svg xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"#0067C0\"/></svg>",
270-
};
264+
var icon = CreateIcon("|Svg|<svg xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"#0067C0\"/></svg>");
271265

272266
var light = provider.GetIconSource(icon, 1.0, theme: ElementTheme.Light);
273267
loader.CompleteNext(null);
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Loading

src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleIconPage.cs

Lines changed: 97 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,42 +30,82 @@ internal sealed partial class SampleIconPage : ListPage
3030

3131
private readonly IListItem[] _items =
3232
[
33-
BuildIconItem(
34-
"|Swatch|#FF0067C0|#FF60CDFF|square|",
35-
"Theme-aware square swatch",
36-
"Uses separate light and dark colors with the square background shape"),
37-
38-
BuildIconItem(
39-
"|Swatch|success|circle|",
40-
"Semantic success swatch",
41-
"Uses a theme-aware semantic color with the circle background shape"),
42-
43-
BuildIconItem(
33+
BuildProtocolIconItem(
4434
"|Initials|A|#FF7A3E9D|circle|",
4535
"Generated circular initials avatar",
4636
"Uses an automatically contrasting foreground"),
4737

48-
BuildIconItem(
38+
BuildProtocolIconItem(
4939
"|Initials|CP|#FF005FB8|#FF60CDFF|square|",
5040
"Theme-aware square initials avatar",
5141
"Uses separate light and dark background colors"),
5242

53-
BuildIconItem(
43+
BuildProtocolIconItem(
44+
"|Initials|Å|info|circle|",
45+
"Accented Latin initials avatar",
46+
"Uses DirectWrite font fallback and vector outlines"),
47+
48+
BuildProtocolIconItem(
49+
"|Initials|ЖП|success|square|",
50+
"Cyrillic initials avatar",
51+
"Shapes non-Latin initials off the UI thread"),
52+
53+
BuildProtocolIconItem(
54+
"|Initials|東京|warning|circle|",
55+
"CJK initials avatar",
56+
"Uses the installed Windows font selected by system fallback"),
57+
58+
BuildProtocolIconItem(
59+
"|Initials|👩‍💻|#FF7A3E9D|square|",
60+
"Multi-code-point initials avatar",
61+
"Treats a joined emoji sequence as one Unicode text element"),
62+
63+
BuildProtocolIconItem(
64+
"|Initials|A%7CB|neutral|circle|",
65+
"Escaped separator initials avatar",
66+
"Uses %7C for a literal protocol separator"),
67+
68+
BuildProtocolIconItem(
69+
"|Initials|%25|subtle|square|",
70+
"Escaped percent initials avatar",
71+
"Uses %25 for a literal percent sign"),
72+
73+
BuildProtocolIconItem(
5474
"|Initials|N|normal|circle|",
55-
"Semantic normal initials avatar",
75+
"Normal-color initials avatar",
5676
"Uses the normal theme foreground as its background color"),
5777

58-
BuildIconItem(
78+
BuildProtocolIconItem(
5979
"|Initials|T|transparent|square|",
60-
"Transparent initials avatar",
61-
"Uses a transparent square background and a theme-aware foreground"),
62-
63-
BuildIconItem(
80+
"Transparent initials icon",
81+
"Omits the visible background while keeping theme-contrasting initials"),
82+
83+
BuildProtocolIconItem(
84+
"|Swatch|#FF7A3E9D|circle|",
85+
"Generated circular color swatch",
86+
"Uses one literal color in both themes"),
87+
88+
BuildProtocolIconItem(
89+
"|Swatch|#FF005FB8|#FF60CDFF|square|",
90+
"Theme-aware square color swatch",
91+
"Uses separate light and dark colors"),
92+
93+
BuildProtocolIconItem("|Swatch|danger|circle|", "Semantic danger swatch", "Uses the theme-aware danger color"),
94+
BuildProtocolIconItem("|Swatch|subtle|square|", "Semantic subtle swatch", "Uses the theme-aware subtle color"),
95+
BuildProtocolIconItem("|Swatch|info|circle|", "Semantic info swatch", "Uses the theme-aware info color"),
96+
BuildProtocolIconItem("|Swatch|warning|square|", "Semantic warning swatch", "Uses the theme-aware warning color"),
97+
BuildProtocolIconItem("|Swatch|success|circle|", "Semantic success swatch", "Uses the theme-aware success color"),
98+
BuildProtocolIconItem("|Swatch|neutral|square|", "Semantic neutral swatch", "Uses the theme-aware neutral color"),
99+
BuildProtocolIconItem("|Swatch|dark|circle|", "Semantic dark swatch", "Uses the same deliberately dark color in both themes"),
100+
BuildProtocolIconItem("|Swatch|normal|square|", "Semantic normal swatch", "Uses the normal foreground color for the current theme"),
101+
BuildProtocolIconItem("|Swatch|transparent|circle|", "Transparent swatch", "Intentionally renders no visible fill; primarily useful as an initials background"),
102+
103+
BuildProtocolIconItem(
64104
"|Svg|" + PlainSvgSample,
65105
"Plain inline SVG",
66106
"Passes SVG content through without theme expansion"),
67107

68-
BuildIconItem(
108+
BuildProtocolIconItem(
69109
"|ThemedSvg|success|" + ThemedSvgSample,
70110
"Themed inline SVG",
71111
"Uses currentColor for the globe and a semantic success accent for the overlay"),
@@ -211,7 +251,42 @@ public SampleIconPage()
211251

212252
public override IListItem[] GetItems() => _items;
213253

214-
private static ListItem BuildIconItem(string icon, string title, string description)
254+
private static ListItem BuildProtocolIconItem(string icon, string title, string description) =>
255+
BuildIconItem(
256+
icon,
257+
title,
258+
description,
259+
new DetailsElement
260+
{
261+
Key = "Icon Protocol",
262+
Data = new DetailsTags
263+
{
264+
Tags = [new Tag(icon)],
265+
},
266+
});
267+
268+
private static ListItem BuildIconItem(string icon, string title, string description) =>
269+
BuildIconItem(
270+
icon,
271+
title,
272+
description,
273+
new DetailsElement
274+
{
275+
Key = "Unicode Code Points",
276+
Data = new DetailsTags
277+
{
278+
Tags = icon.EnumerateRunes()
279+
.Select(rune => rune.Value <= 0xFFFF ? $"\\u{rune.Value:X4}" : $"\\U{rune.Value:X8}")
280+
.Select(t => new Tag(t))
281+
.ToArray<ITag>(),
282+
},
283+
});
284+
285+
private static ListItem BuildIconItem(
286+
string icon,
287+
string title,
288+
string description,
289+
DetailsElement metadata)
215290
{
216291
var iconInfo = new IconInfo(icon);
217292

@@ -229,17 +304,7 @@ private static ListItem BuildIconItem(string icon, string title, string descript
229304
Title = title,
230305
Body = description,
231306
Metadata = [
232-
new DetailsElement
233-
{
234-
Key = "Unicode Code Points",
235-
Data = new DetailsTags
236-
{
237-
Tags = icon.EnumerateRunes()
238-
.Select(rune => rune.Value <= 0xFFFF ? $"\\u{rune.Value:X4}" : $"\\U{rune.Value:X8}")
239-
.Select(t => new Tag(t))
240-
.ToArray<ITag>(),
241-
},
242-
}
307+
metadata,
243308
],
244309
},
245310
};
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) Microsoft Corporation
2+
// The Microsoft Corporation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.IO;
7+
using Microsoft.CommandPalette.Extensions;
8+
using Microsoft.CommandPalette.Extensions.Toolkit;
9+
10+
namespace SamplePagesExtension.Pages;
11+
12+
internal sealed partial class SampleSvgIconPage : ListPage
13+
{
14+
private const string PlainTemplate = """
15+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
16+
<rect x="1" y="1" width="30" height="30" rx="8" fill="#E8DEF8" />
17+
<circle cx="16" cy="16" r="9.5" fill="#7A3E9D" stroke="#33243D" stroke-width="1.5" />
18+
<path d="M11.5 16.2l3 3 6.4-7" fill="none" stroke="#FFFFFF" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" />
19+
</svg>
20+
""";
21+
22+
private const string ThemedTemplate = """
23+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
24+
<rect x="1" y="1" width="30" height="30" rx="8" fill="{{AccentColor}}" fill-opacity="0.18" />
25+
<circle cx="16" cy="16" r="9.5" fill="{{AccentColor}}" stroke="{{ThemeColor}}" stroke-width="1.5" />
26+
<path d="M11.5 16.2l3 3 6.4-7" fill="none" stroke="#FFFFFF" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" />
27+
</svg>
28+
""";
29+
30+
private const string ThemedFluentUploadTemplate = """
31+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" color="{{ThemeColor}}">
32+
<path
33+
fill="currentColor"
34+
d="M10 18q.128 0 .254-.004a5.5 5.5 0 0 1-.698-1.083c-.536-.207-1.098-.793-1.578-1.821A9.3 9.3 0 0 1 7.42 13.5h1.672q.096-.52.284-1h-2.17A15 15 0 0 1 7 10c0-.883.073-1.725.206-2.5h5.588c.092.541.156 1.115.186 1.713q.48-.138.992-.188a16 16 0 0 0-.165-1.525h2.733c.251.656.406 1.36.448 2.094q.543.276 1.008.66A8 8 0 1 0 10 18M10 3c.657 0 1.407.59 2.022 1.908.217.466.406 1.002.559 1.592H7.419c.153-.59.342-1.126.56-1.592C8.592 3.59 9.342 3 10 3M7.072 4.485A10.5 10.5 0 0 0 6.389 6.5H3.936a7.02 7.02 0 0 1 3.778-3.118c-.241.33-.456.704-.642 1.103M6.192 7.5A16 16 0 0 0 6 10c0 .87.067 1.712.193 2.5H3.46A7 7 0 0 1 3 10c0-.88.163-1.724.46-2.5zm.197 6c.176.743.407 1.422.683 2.015.186.399.401.773.642 1.103A7.02 7.02 0 0 1 3.936 13.5zm5.897-10.118A7.02 7.02 0 0 1 16.064 6.5H13.61a10.5 10.5 0 0 0-.683-2.015 6.6 6.6 0 0 0-.642-1.103" />
35+
<path
36+
fill="{{AccentColor}}"
37+
d="M19 14.5a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0m-4.854-2.353-2 2a.5.5 0 0 0 .708.707L14 13.707V16.5a.5.5 0 0 0 1 0v-2.793l1.146 1.147a.5.5 0 0 0 .708-.708l-2-2a.5.5 0 0 0-.351-.146h-.006a.5.5 0 0 0-.348.144z" />
38+
</svg>
39+
""";
40+
41+
private readonly IListItem[] _items;
42+
43+
public SampleSvgIconPage()
44+
{
45+
Icon = new IconInfo("|ThemedSvg|info|" + ThemedFluentUploadTemplate);
46+
Name = "SVG Icon Protocols";
47+
ShowDetails = true;
48+
_items = CreateItems();
49+
}
50+
51+
public override IListItem[] GetItems() => _items;
52+
53+
private static IListItem[] CreateItems()
54+
{
55+
var imageDirectory = Path.Combine(AppContext.BaseDirectory, "Assets", "Images");
56+
var plainFile = Path.Combine(imageDirectory, "PlainSampleIcon.svg");
57+
var themedFile = Path.Combine(imageDirectory, "ThemedSampleIcon.svg");
58+
59+
return
60+
[
61+
BuildIconItem(
62+
"|Svg|" + PlainTemplate,
63+
"Plain inline SVG",
64+
"Passes inline SVG through without placeholder expansion or a theme cache split"),
65+
BuildIconItem(
66+
"|Svg|" + plainFile,
67+
"Plain file SVG",
68+
"Reads the original SVG bytes from a file on an icon-loader worker"),
69+
BuildIconItem(
70+
"|ThemedSvg|" + ThemedTemplate,
71+
"Themed inline SVG",
72+
"Uses the default info accent and expands both theme placeholders"),
73+
BuildIconItem(
74+
"|ThemedSvg|" + themedFile,
75+
"Themed file SVG",
76+
"Reads the template from a file and uses the default info accent"),
77+
BuildIconItem(
78+
"|ThemedSvg|" + ThemedFluentUploadTemplate,
79+
"Fluent upload with default accent",
80+
"Uses inherited currentColor for the globe and the default info accent for the upload badge"),
81+
BuildIconItem(
82+
"|ThemedSvg|success|" + ThemedFluentUploadTemplate,
83+
"Fluent upload with semantic accent",
84+
"Uses inherited currentColor for the globe and the semantic success color for the upload badge"),
85+
BuildIconItem(
86+
"|ThemedSvg|#7A3E9D|" + ThemedFluentUploadTemplate,
87+
"Fluent upload with custom accent",
88+
"Uses inherited currentColor for the globe and custom purple for the upload badge"),
89+
BuildSemanticItem("danger", "Critical actions and errors"),
90+
BuildSemanticItem("subtle", "Secondary, lower-emphasis content"),
91+
BuildSemanticItem("info", "Informational and default accent content"),
92+
BuildSemanticItem("warning", "Cautions that need attention"),
93+
BuildSemanticItem("success", "Successful or completed states"),
94+
BuildSemanticItem("neutral", "Theme-aware neutral gray content"),
95+
BuildSemanticItem("dark", "A deliberately fixed dark accent in both themes"),
96+
BuildSemanticItem("normal", "The current theme foreground color"),
97+
BuildIconItem(
98+
"|ThemedSvg|#7A3E9D|" + ThemedTemplate,
99+
"Custom #7A3E9D accent",
100+
"Uses a custom SVG hex color while ThemeColor still follows the surface theme"),
101+
];
102+
}
103+
104+
private static ListItem BuildSemanticItem(string semanticAccent, string description) =>
105+
BuildIconItem(
106+
$"|ThemedSvg|{semanticAccent}|{ThemedTemplate}",
107+
$"Semantic {semanticAccent} accent",
108+
description);
109+
110+
private static ListItem BuildIconItem(string protocol, string title, string description)
111+
{
112+
var icon = new IconInfo(protocol);
113+
return new ListItem(new CopyTextCommand(protocol) { Name = "Copy SVG protocol" })
114+
{
115+
Title = title,
116+
Subtitle = description,
117+
Icon = icon,
118+
Details = new Details
119+
{
120+
HeroImage = icon,
121+
Title = title,
122+
Body = description,
123+
},
124+
};
125+
}
126+
}

0 commit comments

Comments
 (0)