Skip to content

Commit

Permalink
Change TextLabelControl to support DirectWrite
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JeremyKuhne committed Feb 11, 2024
1 parent 74f7ad4 commit 698f574
Show file tree
Hide file tree
Showing 24 changed files with 723 additions and 127 deletions.
25 changes: 24 additions & 1 deletion src/samples/Layout/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Drawing;
using Windows;
using Windows.Messages;
using Windows.Win32.Graphics.Gdi;

namespace LayoutSample;

Expand All @@ -20,6 +21,7 @@ private class LayoutWindow : MainWindow
private readonly ButtonControl _buttonControl;
private readonly StaticControl _staticControl;
private readonly TextLabelControl _textLabel;
private readonly HBRUSH _blueBrush;

public LayoutWindow(string title) : base(title: title)
{
Expand All @@ -41,9 +43,16 @@ public LayoutWindow(string title) : base(title: title)
text: "You pushed it!",
parentWindow: this);

_blueBrush = HBRUSH.CreateSolid(Color.Blue);

_textLabel = new TextLabelControl(
text: "Text Label Control",
parentWindow: this);
parentWindow: this,
textColor: Color.White,
backgroundColor: Color.Blue,
features: Features.EnableDirect2d
// features: default
);

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

Expand Down Expand Up @@ -77,5 +86,19 @@ private void Handler_MouseUp(Window window, Point position, MouseButton button,
_staticControl.ShowWindow(ShowWindowCommand.Show);
}
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
_editControl.Dispose();
_buttonControl.Dispose();
_staticControl.Dispose();
_textLabel.Dispose();
_blueBrush.Dispose();
}

base.Dispose(disposing);
}
}
}
3 changes: 2 additions & 1 deletion src/samples/Petzold/5th/Clover/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Drawing;
using Windows;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
Expand All @@ -24,7 +25,7 @@ internal class Clover : MainWindow
private HRGN _hrgnClip;
private const double TWO_PI = Math.PI * 2;

public Clover() : base(title: "Draw a Clover", backgroundBrush: StockBrush.White) { }
public Clover() : base(title: "Draw a Clover", backgroundColor: Color.White) { }

protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
{
Expand Down
3 changes: 2 additions & 1 deletion src/samples/Petzold/5th/HelloWin/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Windows.Win32.Media.Audio;
using System.Runtime.InteropServices;
using Windows;
using System.Drawing;

namespace HelloWin;

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

private class HelloWindow : MainWindow
{
public HelloWindow(string title) : base(title: title, backgroundBrush: StockBrush.White)
public HelloWindow(string title) : base(title: title, backgroundColor: Color.White)
{
}

Expand Down
16 changes: 16 additions & 0 deletions src/thirtytwo/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,22 @@ public static void EnumerateThreadWindows(
using var enumerator = new ThreadWindowEnumerator(threadId, callback);
}

public static string GetUserDefaultLocaleName()
{
Span<char> localeName = stackalloc char[(int)Interop.LOCALE_NAME_MAX_LENGTH];
fixed (char* ln = localeName)
{
int length = Interop.GetUserDefaultLocaleName(ln, (int)Interop.LOCALE_NAME_MAX_LENGTH);

if (length == 0)
{
Error.ThrowLastError();
}

return localeName[..(length - 1)].ToString();
}
}

public static Direct2dFactory Direct2dFactory => s_direct2dFactory ??= new();
public static DirectWriteFactory DirectWriteFactory => s_directWriteFactory ??= new();
public static DirectWriteGdiInterop DirectWriteGdiInterop => s_directWriteGdiInterop ??= new();
Expand Down
14 changes: 13 additions & 1 deletion src/thirtytwo/Controls/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,19 @@ public Control(
Window? parentWindow = default,
WindowClass? windowClass = default,
nint parameters = 0,
HMENU menuHandle = default) : base(bounds, text, style, extendedStyle, parentWindow, windowClass, parameters, menuHandle)
HMENU menuHandle = default,
Color backgroundColor = default,
Features features = default) : base(
bounds,
text,
style,
extendedStyle,
parentWindow,
windowClass,
parameters,
menuHandle,
backgroundColor,
features: features)
{
}
}
101 changes: 88 additions & 13 deletions src/thirtytwo/Controls/TextLabelControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,136 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Drawing;
using Windows.Win32.Graphics.Direct2D;
using Windows.Win32.Graphics.DirectWrite;

namespace Windows;

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

private DrawTextFormat _textFormat;
private DrawTextFormat _drawTextFormat;
private TextFormat? _textFormat;
private SolidColorBrush? _textBrush;
private Color _textColor;

public TextLabelControl(
Rectangle bounds = default,
DrawTextFormat textFormat = DrawTextFormat.Center | DrawTextFormat.VerticallyCenter,
DrawTextFormat textFormat = DrawTextFormat.Center | DrawTextFormat.VerticallyCenter | DrawTextFormat.SingleLine,
string? text = default,
Color textColor = default,
WindowStyles style = WindowStyles.Overlapped | WindowStyles.Visible | WindowStyles.Child,
ExtendedWindowStyles extendedStyle = ExtendedWindowStyles.Default,
Window? parentWindow = default,
nint parameters = default) : base(
nint parameters = default,
Color backgroundColor = default,
Features features = default) : base(
bounds,
text,
style,
extendedStyle,
parentWindow,
s_textLabelClass,
parameters)
parameters,
backgroundColor: backgroundColor,
features: features)
{
_textFormat = textFormat;
_drawTextFormat = textFormat;
TextColor = textColor;
}

public Color TextColor
{
get => _textColor;
set
{
if (value.IsEmpty)
{
value = Color.Black;
}

if (value == _textColor)
{
return;
}

_textColor = value;
if (_textBrush is { } brush)
{
brush.Color = _textColor;
}

this.Invalidate();
}
}

protected override void RenderTargetCreated()
{
_textBrush?.Dispose();
_textBrush = RenderTarget.CreateSolidColorBrush(TextColor);
base.RenderTargetCreated();
}

private TextFormat GetTextFormat()
{
if (_textFormat is null)
{
HFONT hfont = this.GetFontHandle();
_textFormat = new TextFormat(hfont, _drawTextFormat);
}

return _textFormat;
}

protected override void OnPaint()
{
if (IsDirect2dEnabled())
{
Size size = this.GetClientRectangle().Size;
using TextLayout textLayout = new(Text, GetTextFormat(), size);
Debug.Assert(_textBrush is not null);
RenderTarget.DrawTextLayout(default, textLayout, _textBrush!);
}
else
{
using var deviceContext = this.BeginPaint();
Rectangle bounds = this.GetClientRectangle();
deviceContext.DrawText(Text, bounds, _drawTextFormat, this.GetFontHandle(), TextColor);
}

base.OnPaint();
}

protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case MessageType.Paint:
case MessageType.SetFont:
if (_textFormat is not null)
{
using var deviceContext = window.BeginPaint(out Rectangle paintBounds);
using var selectionScope = deviceContext.SelectObject(this.GetFontHandle());
deviceContext.DrawText(Text, paintBounds, _textFormat);
break;
// The font isn't set until we call base, so we need to wait to recreate the text format.
_textFormat.Dispose();
_textFormat = null;
}

break;
}

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

public DrawTextFormat TextFormat
{
get => _textFormat;
get => _drawTextFormat;
set
{
if (value == _textFormat)
if (value == _drawTextFormat)
{
return;
}

_textFormat = value;
_drawTextFormat = value;
this.Invalidate();
}
}
Expand Down
Loading

0 comments on commit 698f574

Please sign in to comment.