Skip to content

Commit 53203db

Browse files
committed
Fix AppSDK visibility and icon usage
1 parent 9de1d5b commit 53203db

5 files changed

Lines changed: 81 additions & 9 deletions

File tree

Source/Demo/WinUI/HtmlRenderer.Demo.WinUI.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,19 @@
2020
</PropertyGroup>
2121
<PropertyGroup>
2222
<ApplicationManifest>app.manifest</ApplicationManifest>
23+
<ApplicationIcon>html.ico</ApplicationIcon>
2324
</PropertyGroup>
25+
<ItemGroup>
26+
<!-- ApplicationIcon alone only stamps the .exe resource; MainWindow also loads this file at runtime
27+
to set the window/taskbar icon, which a WinUI 3 window does not take from the exe on its own. -->
28+
<Content Include="html.ico" CopyToOutputDirectory="PreserveNewest" />
29+
</ItemGroup>
30+
<Target Name="HideWindowsAppSDKPayloadFiles" AfterTargets="AddMicrosoftWindowsAppSDKPayloadFilesFromComponents">
31+
<ItemGroup>
32+
<None Update="@(None)" Visible="false"
33+
Condition="'%(None.DefiningProjectName)' == 'Microsoft.WindowsAppSDK.SelfContained'" />
34+
</ItemGroup>
35+
</Target>
2436
<ItemGroup>
2537
<ProjectReference Include="..\..\HtmlRenderer.WinUI\HtmlRenderer.WinUI.csproj" />
2638
<ProjectReference Include="..\..\HtmlRenderer\HtmlRenderer.csproj" />

Source/Demo/WinUI/MainWindow.xaml.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ public MainWindow()
5151

5252
Title = "HTML Renderer - WinUI 3 Demo";
5353

54+
// Unlike the WPF/WinForms demo windows, a WinUI 3 window does not pick up the executable
55+
// icon stamped by the csproj ApplicationIcon property, so the title bar and taskbar show the
56+
// generic default until AppWindow is pointed at the .ico explicitly. BaseDirectory rather
57+
// than a relative path: SetIcon resolves against the working directory, which is not the app
58+
// directory when the demo is launched from elsewhere.
59+
AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "html.ico"));
60+
5461
SamplesLoader.Init("WinUI", typeof(TheArtOfDev.HtmlRenderer.WinUI.HtmlRender).Assembly.GetName().Version.ToString());
5562

5663
// Without this, showcase samples' <link rel="Stylesheet" href="StyleSheet"> never resolves, so

Source/Demo/WinUI/html.ico

5.3 KB
Binary file not shown.

Source/HtmlRenderer.WinUI/Adapters/GraphicsAdapter.cs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,34 @@ internal sealed class GraphicsAdapter : RGraphics
5656
/// call - Win2D layers are <see cref="IDisposable"/> and must be disposed in LIFO order, unlike
5757
/// WPF's <see cref="System.Windows.Media.DrawingContext.Pop"/>, which needs no such bookkeeping.
5858
/// </summary>
59-
private readonly Stack<CanvasActiveLayer> _layerStack = new Stack<CanvasActiveLayer>();
59+
private readonly Stack<ClipLayer> _layerStack = new Stack<ClipLayer>();
60+
61+
/// <summary>
62+
/// A single entry of <see cref="_layerStack"/>: the pushed Win2D layer and, for
63+
/// <see cref="PushClipExclude"/>, the geometry masking it. The mask has to outlive the layer - D2D
64+
/// reads it until the layer is popped - so the two are released together in <see cref="PopClip"/>.
65+
/// Both are null on a measure-only instance, which has no session to push a layer on.
66+
/// </summary>
67+
private readonly struct ClipLayer
68+
{
69+
public ClipLayer(CanvasActiveLayer layer, CanvasGeometry mask)
70+
{
71+
Layer = layer;
72+
Mask = mask;
73+
}
74+
75+
private CanvasActiveLayer Layer { get; }
76+
77+
private CanvasGeometry Mask { get; }
78+
79+
public void Dispose()
80+
{
81+
if (Layer != null)
82+
Layer.Dispose();
83+
if (Mask != null)
84+
Mask.Dispose();
85+
}
86+
}
6087

6188
#endregion
6289

@@ -108,17 +135,36 @@ public override void PopClip()
108135
public override void PushClip(RRect rect)
109136
{
110137
_clipStack.Push(rect);
111-
_layerStack.Push(_g.CreateLayer(1f, Utils.Convert(rect)));
138+
139+
// Measure-only instance - the clip is still tracked for layout, but there is no session to
140+
// push it onto (same null-session guard as SetAntiAliasSmoothingMode below).
141+
_layerStack.Push(_g != null
142+
? new ClipLayer(_g.CreateLayer(1f, Utils.Convert(rect)), null)
143+
: new ClipLayer(null, null));
112144
}
113145

114146
public override void PushClipExclude(RRect rect)
115147
{
116-
var full = CanvasGeometry.CreateRectangle(_device, Utils.Convert(_clipStack.Peek()));
117-
var excluded = CanvasGeometry.CreateRectangle(_device, Utils.Convert(rect));
118-
var combined = full.CombineWith(excluded, Matrix3x2.Identity, CanvasGeometryCombine.Exclude);
148+
var current = _clipStack.Peek();
149+
_clipStack.Push(current);
150+
151+
if (_g == null)
152+
{
153+
_layerStack.Push(new ClipLayer(null, null));
154+
return;
155+
}
156+
157+
// full/excluded are only inputs to the combine, so they are released right away; combined is
158+
// handed to the layer and released with it. All three are unmanaged D2D resources created on
159+
// every clip-exclude of every paint, so none of them can be left to the finalizer.
160+
CanvasGeometry combined;
161+
using (var full = CanvasGeometry.CreateRectangle(_device, Utils.Convert(current)))
162+
using (var excluded = CanvasGeometry.CreateRectangle(_device, Utils.Convert(rect)))
163+
{
164+
combined = full.CombineWith(excluded, Matrix3x2.Identity, CanvasGeometryCombine.Exclude);
165+
}
119166

120-
_clipStack.Push(_clipStack.Peek());
121-
_layerStack.Push(_g.CreateLayer(1f, combined));
167+
_layerStack.Push(new ClipLayer(_g.CreateLayer(1f, combined), combined));
122168
}
123169

124170
public override object SetAntiAliasSmoothingMode()

Source/HtmlRenderer.WinUI/Adapters/WinUIAdapter.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ internal sealed class WinUIAdapter : RAdapter
6262
/// </summary>
6363
private readonly CanvasDevice _device;
6464

65+
/// <summary>
66+
/// The system settings object the app-theme subscription in the constructor hangs off - kept in a
67+
/// field for the adapter lifetime because ColorValuesChanged only lives as long as the UISettings
68+
/// instance does, so a local would be collected and the notification would never arrive.
69+
/// </summary>
70+
private readonly UISettings _uiSettings;
71+
6572
// Backs LoadFontFaceFontInt's temp-file registration - CanvasFontSet's documented in-memory story
6673
// is a file Uri, mirroring WPF's own Fonts.GetFontFamilies(Uri) constraint (see that class's own
6774
// remarks) - one directory per process, cleaned up by the OS's normal temp-file housekeeping.
@@ -124,8 +131,8 @@ private WinUIAdapter()
124131
// families upfront); its failure shouldn't prevent the adapter itself from initializing.
125132
}
126133

127-
var uiSettings = new UISettings();
128-
uiSettings.ColorValuesChanged += (sender, e) =>
134+
_uiSettings = new UISettings();
135+
_uiSettings.ColorValuesChanged += (sender, e) =>
129136
{
130137
var previous = _colorScheme;
131138
_colorScheme = null;

0 commit comments

Comments
 (0)