Skip to content

Commit 698f574

Browse files
committed
Change TextLabelControl to support DirectWrite
If Direct2d is enabled TextLabelControl will render using DirectWrite. Supports only some GDI flag conversion (didn't implement everything). I'm not particularly fond of the fact that you can't change vertical justification in GDI when you're using multiline, might just try to handle that by measuring then changing the location of the output rect. Window now takes a Color for the background brush and will only allocate a brush if it really needs to. There is no great way to have Windows use a different background brush without creating a WindowClass for every single Window. If Direct2D is enabled background painting is skipped. I'm considering making the Direct2D handling a MessageHandler which might make the experience tighter. I hadn't fully internalized how HDC state works, but I think I have a better handle on it now. In order to simplify things I've changed DeviceContext for BeginPaint to save the DC state by default. Removed an effectively no-op setting in the Window constructor related to this.
1 parent 74f7ad4 commit 698f574

24 files changed

+723
-127
lines changed

src/samples/Layout/Program.cs

+24-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Drawing;
55
using Windows;
66
using Windows.Messages;
7+
using Windows.Win32.Graphics.Gdi;
78

89
namespace LayoutSample;
910

@@ -20,6 +21,7 @@ private class LayoutWindow : MainWindow
2021
private readonly ButtonControl _buttonControl;
2122
private readonly StaticControl _staticControl;
2223
private readonly TextLabelControl _textLabel;
24+
private readonly HBRUSH _blueBrush;
2325

2426
public LayoutWindow(string title) : base(title: title)
2527
{
@@ -41,9 +43,16 @@ public LayoutWindow(string title) : base(title: title)
4143
text: "You pushed it!",
4244
parentWindow: this);
4345

46+
_blueBrush = HBRUSH.CreateSolid(Color.Blue);
47+
4448
_textLabel = new TextLabelControl(
4549
text: "Text Label Control",
46-
parentWindow: this);
50+
parentWindow: this,
51+
textColor: Color.White,
52+
backgroundColor: Color.Blue,
53+
features: Features.EnableDirect2d
54+
// features: default
55+
);
4756

4857
_textLabel.SetFont("Segoe Print", 20);
4958

@@ -77,5 +86,19 @@ private void Handler_MouseUp(Window window, Point position, MouseButton button,
7786
_staticControl.ShowWindow(ShowWindowCommand.Show);
7887
}
7988
}
89+
90+
protected override void Dispose(bool disposing)
91+
{
92+
if (disposing)
93+
{
94+
_editControl.Dispose();
95+
_buttonControl.Dispose();
96+
_staticControl.Dispose();
97+
_textLabel.Dispose();
98+
_blueBrush.Dispose();
99+
}
100+
101+
base.Dispose(disposing);
102+
}
80103
}
81104
}

src/samples/Petzold/5th/Clover/Program.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Drawing;
45
using Windows;
56
using Windows.Win32.Foundation;
67
using Windows.Win32.Graphics.Gdi;
@@ -24,7 +25,7 @@ internal class Clover : MainWindow
2425
private HRGN _hrgnClip;
2526
private const double TWO_PI = Math.PI * 2;
2627

27-
public Clover() : base(title: "Draw a Clover", backgroundBrush: StockBrush.White) { }
28+
public Clover() : base(title: "Draw a Clover", backgroundColor: Color.White) { }
2829

2930
protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
3031
{

src/samples/Petzold/5th/HelloWin/Program.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Windows.Win32.Media.Audio;
99
using System.Runtime.InteropServices;
1010
using Windows;
11+
using System.Drawing;
1112

1213
namespace HelloWin;
1314

@@ -121,7 +122,7 @@ private static LRESULT WindowProcedure(HWND window, uint message, WPARAM wParam,
121122

122123
private class HelloWindow : MainWindow
123124
{
124-
public HelloWindow(string title) : base(title: title, backgroundBrush: StockBrush.White)
125+
public HelloWindow(string title) : base(title: title, backgroundColor: Color.White)
125126
{
126127
}
127128

src/thirtytwo/Application.cs

+16
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,22 @@ public static void EnumerateThreadWindows(
210210
using var enumerator = new ThreadWindowEnumerator(threadId, callback);
211211
}
212212

213+
public static string GetUserDefaultLocaleName()
214+
{
215+
Span<char> localeName = stackalloc char[(int)Interop.LOCALE_NAME_MAX_LENGTH];
216+
fixed (char* ln = localeName)
217+
{
218+
int length = Interop.GetUserDefaultLocaleName(ln, (int)Interop.LOCALE_NAME_MAX_LENGTH);
219+
220+
if (length == 0)
221+
{
222+
Error.ThrowLastError();
223+
}
224+
225+
return localeName[..(length - 1)].ToString();
226+
}
227+
}
228+
213229
public static Direct2dFactory Direct2dFactory => s_direct2dFactory ??= new();
214230
public static DirectWriteFactory DirectWriteFactory => s_directWriteFactory ??= new();
215231
public static DirectWriteGdiInterop DirectWriteGdiInterop => s_directWriteGdiInterop ??= new();

src/thirtytwo/Controls/Control.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,19 @@ public Control(
1818
Window? parentWindow = default,
1919
WindowClass? windowClass = default,
2020
nint parameters = 0,
21-
HMENU menuHandle = default) : base(bounds, text, style, extendedStyle, parentWindow, windowClass, parameters, menuHandle)
21+
HMENU menuHandle = default,
22+
Color backgroundColor = default,
23+
Features features = default) : base(
24+
bounds,
25+
text,
26+
style,
27+
extendedStyle,
28+
parentWindow,
29+
windowClass,
30+
parameters,
31+
menuHandle,
32+
backgroundColor,
33+
features: features)
2234
{
2335
}
2436
}

src/thirtytwo/Controls/TextLabelControl.cs

+88-13
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,136 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Drawing;
5+
using Windows.Win32.Graphics.Direct2D;
6+
using Windows.Win32.Graphics.DirectWrite;
57

68
namespace Windows;
79

810
public class TextLabelControl : Control
911
{
1012
private static readonly WindowClass s_textLabelClass = new(className: "TextLabelClass");
1113

12-
private DrawTextFormat _textFormat;
14+
private DrawTextFormat _drawTextFormat;
15+
private TextFormat? _textFormat;
16+
private SolidColorBrush? _textBrush;
17+
private Color _textColor;
1318

1419
public TextLabelControl(
1520
Rectangle bounds = default,
16-
DrawTextFormat textFormat = DrawTextFormat.Center | DrawTextFormat.VerticallyCenter,
21+
DrawTextFormat textFormat = DrawTextFormat.Center | DrawTextFormat.VerticallyCenter | DrawTextFormat.SingleLine,
1722
string? text = default,
23+
Color textColor = default,
1824
WindowStyles style = WindowStyles.Overlapped | WindowStyles.Visible | WindowStyles.Child,
1925
ExtendedWindowStyles extendedStyle = ExtendedWindowStyles.Default,
2026
Window? parentWindow = default,
21-
nint parameters = default) : base(
27+
nint parameters = default,
28+
Color backgroundColor = default,
29+
Features features = default) : base(
2230
bounds,
2331
text,
2432
style,
2533
extendedStyle,
2634
parentWindow,
2735
s_textLabelClass,
28-
parameters)
36+
parameters,
37+
backgroundColor: backgroundColor,
38+
features: features)
2939
{
30-
_textFormat = textFormat;
40+
_drawTextFormat = textFormat;
41+
TextColor = textColor;
42+
}
43+
44+
public Color TextColor
45+
{
46+
get => _textColor;
47+
set
48+
{
49+
if (value.IsEmpty)
50+
{
51+
value = Color.Black;
52+
}
53+
54+
if (value == _textColor)
55+
{
56+
return;
57+
}
58+
59+
_textColor = value;
60+
if (_textBrush is { } brush)
61+
{
62+
brush.Color = _textColor;
63+
}
64+
65+
this.Invalidate();
66+
}
67+
}
68+
69+
protected override void RenderTargetCreated()
70+
{
71+
_textBrush?.Dispose();
72+
_textBrush = RenderTarget.CreateSolidColorBrush(TextColor);
73+
base.RenderTargetCreated();
74+
}
75+
76+
private TextFormat GetTextFormat()
77+
{
78+
if (_textFormat is null)
79+
{
80+
HFONT hfont = this.GetFontHandle();
81+
_textFormat = new TextFormat(hfont, _drawTextFormat);
82+
}
83+
84+
return _textFormat;
85+
}
86+
87+
protected override void OnPaint()
88+
{
89+
if (IsDirect2dEnabled())
90+
{
91+
Size size = this.GetClientRectangle().Size;
92+
using TextLayout textLayout = new(Text, GetTextFormat(), size);
93+
Debug.Assert(_textBrush is not null);
94+
RenderTarget.DrawTextLayout(default, textLayout, _textBrush!);
95+
}
96+
else
97+
{
98+
using var deviceContext = this.BeginPaint();
99+
Rectangle bounds = this.GetClientRectangle();
100+
deviceContext.DrawText(Text, bounds, _drawTextFormat, this.GetFontHandle(), TextColor);
101+
}
102+
103+
base.OnPaint();
31104
}
32105

33106
protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
34107
{
35108
switch (message)
36109
{
37-
case MessageType.Paint:
110+
case MessageType.SetFont:
111+
if (_textFormat is not null)
38112
{
39-
using var deviceContext = window.BeginPaint(out Rectangle paintBounds);
40-
using var selectionScope = deviceContext.SelectObject(this.GetFontHandle());
41-
deviceContext.DrawText(Text, paintBounds, _textFormat);
42-
break;
113+
// The font isn't set until we call base, so we need to wait to recreate the text format.
114+
_textFormat.Dispose();
115+
_textFormat = null;
43116
}
117+
118+
break;
44119
}
45120

46121
return base.WindowProcedure(window, message, wParam, lParam);
47122
}
48123

49124
public DrawTextFormat TextFormat
50125
{
51-
get => _textFormat;
126+
get => _drawTextFormat;
52127
set
53128
{
54-
if (value == _textFormat)
129+
if (value == _drawTextFormat)
55130
{
56131
return;
57132
}
58133

59-
_textFormat = value;
134+
_drawTextFormat = value;
60135
this.Invalidate();
61136
}
62137
}

0 commit comments

Comments
 (0)