diff --git a/src/samples/Petzold/5th/Clock/Clock.csproj b/src/samples/Petzold/5th/Clock/Clock.csproj new file mode 100644 index 0000000..6629cf4 --- /dev/null +++ b/src/samples/Petzold/5th/Clock/Clock.csproj @@ -0,0 +1,11 @@ + + + + WinExe + True + app1.manifest + + + + + \ No newline at end of file diff --git a/src/samples/Petzold/5th/Clock/Program.cs b/src/samples/Petzold/5th/Clock/Program.cs new file mode 100644 index 0000000..7f2b7fe --- /dev/null +++ b/src/samples/Petzold/5th/Clock/Program.cs @@ -0,0 +1,173 @@ +// Copyright (c) Jeremy W. Kuhne. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// The GDI+ variant works, but is far from optimized. Creating and disposing a Graphics object every second +// is clearly not optimal. Keeping things aligned with the sample to show the direct mapping. +// +#define GDIPLUS + +#if GDIPLUS +using Windows.Win32.Graphics.GdiPlus; +using GdiPlusPen = Windows.Win32.Graphics.GdiPlus.Pen; +using GdiPlusBrush = Windows.Win32.Graphics.GdiPlus.Brush; +#endif + +using Windows; +using System.Drawing; +using Windows.Win32.Foundation; +using Windows.Win32; +using Point = System.Drawing.Point; + +namespace Clock; + +/// +/// Sample from Programming Windows, 5th Edition. +/// Original (c) Charles Petzold, 1998 +/// Figure 8-5, Pages 346-350. +/// +internal static class Program +{ + [STAThread] + private static void Main() => Application.Run(new Clock("Analog Clock")); +} + +internal class Clock : MainWindow +{ +#if GDIPLUS + private readonly GdiPlusPen _blackPen = new(Color.Black); + private static readonly GdiPlusBrush s_blackBrush = new SolidBrush(Color.Black); + private readonly GdiPlusBrush _whiteBrush = new SolidBrush(Color.White); +#endif + + public Clock(string title) : base(title) { } + + private void SetIsotropic(DeviceContext hdc) + { + hdc.SetMappingMode(MappingMode.Isotropic); + hdc.SetWindowExtents(new Size(1000, 1000)); + hdc.SetViewportExtents(new Size(_clientSize.Width / 2, -_clientSize.Height / 2)); + hdc.SetViewportOrigin(new Point(_clientSize.Width / 2, _clientSize.Height / 2)); + } + + private static void RotatePoint(Point[] pt, int iNum, int iAngle) + { + for (int i = 0; i < iNum; i++) + { + pt[i] = new Point + ( + (int)(pt[i].X * Math.Cos(TWOPI * iAngle / 360) + pt[i].Y * Math.Sin(TWOPI * iAngle / 360)), + (int)(pt[i].Y * Math.Cos(TWOPI * iAngle / 360) - pt[i].X * Math.Sin(TWOPI * iAngle / 360)) + ); + } + } + + private static void DrawClock(DeviceContext dc) + { + int iAngle; + Point[] pt = new Point[3]; + +#if GDIPLUS + using var graphics = new Graphics(dc); + graphics.SetSmoothingMode(SmoothingMode.SmoothingModeHighQuality); +#else + dc.SelectObject(StockBrush.Black); +#endif + for (iAngle = 0; iAngle < 360; iAngle += 6) + { + pt[0].X = 0; + pt[0].Y = 900; + RotatePoint(pt, 1, iAngle); + pt[2].X = pt[2].Y = iAngle % 5 != 0 ? 33 : 100; + pt[0].X -= pt[2].X / 2; + pt[0].Y -= pt[2].Y / 2; + pt[1].X = pt[0].X + pt[2].X; + pt[1].Y = pt[0].Y + pt[2].Y; +#if GDIPLUS + graphics.FillEllipse(s_blackBrush, pt[0].X, pt[0].Y, pt[1].X - pt[0].X, pt[1].Y - pt[0].Y); +#else + dc.Ellipse(Rectangle.FromLTRB(pt[0].X, pt[0].Y, pt[1].X, pt[1].Y)); +#endif + } + } + + private void DrawHands(DeviceContext dc, SYSTEMTIME time, bool erase = false, bool drawHourAndMinuteHands = true) + { + int[] handAngles = + [ + (time.wHour * 30) % 360 + time.wMinute / 2, + time.wMinute * (360 / 60), + time.wSecond * (360 / 60) + ]; + + Point[][] handPoints = + [ + [new(0, -150), new(100, 0), new(0, 600), new(-100, 0), new(0, -150)], + [new(0, -200), new(50, 0), new(0, 800), new(-50, 0), new(0, -200)], + [new(0, 0), new(0, 0), new(0, 0), new(0, 0), new(0, 800)] + ]; + +#if GDIPLUS + using var graphics = new Graphics(dc); + if (erase) + { + graphics.FillEllipse(_whiteBrush, -830, -830, 1660, 1660); + return; + } + + graphics.SetSmoothingMode(SmoothingMode.SmoothingModeHighQuality); +#else + dc.SelectObject(erase ? StockPen.White : StockPen.Black); +#endif + + for (int i = drawHourAndMinuteHands ? 0 : 2; i < 3; i++) + { + RotatePoint(handPoints[i], 5, handAngles[i]); + +#if GDIPLUS + graphics.DrawLines(_blackPen, handPoints[i]); +#else + dc.Polyline(handPoints[i]); +#endif + } + } + + private const int ID_TIMER = 1; + private const double TWOPI = Math.PI * 2; + private Size _clientSize; + private SYSTEMTIME _previousTime; + + protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam) + { + switch (message) + { + case MessageType.Create: + window.SetTimer(ID_TIMER, 1000); + Interop.GetLocalTime(out _previousTime); + return (LRESULT)0; + case MessageType.Size: + _clientSize = new Message.Size(wParam, lParam).NewSize; + return (LRESULT)0; + case MessageType.Timer: + Interop.GetLocalTime(out SYSTEMTIME time); + bool drawAllHands = time.wHour != _previousTime.wHour || time.wMinute != _previousTime.wMinute; + using (DeviceContext dc = window.GetDeviceContext()) + { + SetIsotropic(dc); + DrawHands(dc, _previousTime, erase: true, drawAllHands); + DrawHands(dc, time); + } + _previousTime = time; + return (LRESULT)0; + case MessageType.Paint: + using (DeviceContext dc = window.BeginPaint()) + { + SetIsotropic(dc); + DrawClock(dc); + DrawHands(dc, _previousTime); + } + return (LRESULT)0; + } + + return base.WindowProcedure(window, message, wParam, lParam); + } +} diff --git a/src/samples/Petzold/5th/Clock/app1.manifest b/src/samples/Petzold/5th/Clock/app1.manifest new file mode 100644 index 0000000..203d152 --- /dev/null +++ b/src/samples/Petzold/5th/Clock/app1.manifest @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + PerMonitorV2 + true + + + + + + + + + + + diff --git a/src/thirtytwo/DeviceContextExtensions.cs b/src/thirtytwo/DeviceContextExtensions.cs index 2f74749..9e3a353 100644 --- a/src/thirtytwo/DeviceContextExtensions.cs +++ b/src/thirtytwo/DeviceContextExtensions.cs @@ -110,6 +110,20 @@ public static bool Polygon(this T context, ReadOnlySpan points) } } + public static bool Polyline(this T context, params Point[] points) where T : IHandle => + Polyline(context, points.AsSpan()); + + public static bool Polyline(this T context, ReadOnlySpan points) + where T : IHandle + { + fixed (Point* p = points) + { + bool result = Interop.Polyline(context.Handle, p, points.Length); + GC.KeepAlive(context.Wrapper); + return result; + } + } + public static unsafe (int Height, uint LengthDrawn, Rectangle Bounds) DrawText( this T context, ReadOnlySpan text, @@ -229,6 +243,71 @@ public static Bitmap CreateCompatibleBitmap(this T context, Size size) where return Bitmap.Create(hbitmap, ownsHandle: true); } + public static unsafe bool OffsetWindowOrigin(this T context, int x, int y) where T : IHandle + { + bool success = Interop.OffsetWindowOrgEx(context.Handle, x, y, null); + GC.KeepAlive(context.Wrapper); + return success; + } + + public static unsafe bool OffsetViewportOrigin(this T context, int x, int y) where T : IHandle + { + bool success = Interop.OffsetViewportOrgEx(context.Handle, x, y, null); + GC.KeepAlive(context.Wrapper); + return success; + } + + public static unsafe bool GetWindowExtents(this T context, out Size size) where T : IHandle + { + fixed (Size* s = &size) + { + bool success = Interop.GetWindowExtEx(context.Handle, (SIZE*)s); + GC.KeepAlive(context.Wrapper); + return success; + } + } + + /// + /// Sets the logical ("window") dimensions of the device context. + /// + public static unsafe bool SetWindowExtents(this T context, Size size) where T : IHandle + { + bool success = Interop.SetWindowExtEx(context.Handle, size.Width, size.Height, null); + GC.KeepAlive(context.Wrapper); + return success; + } + + public static unsafe bool GetViewportExtents(this T context, out Size size) where T : IHandle + { + fixed (Size* s = &size) + { + bool success = Interop.GetViewportExtEx(context.Handle, (SIZE*)s); + GC.KeepAlive(context.Wrapper); + return success; + } + } + + public static unsafe bool SetViewportExtents(this T context, Size size) where T : IHandle + { + bool success = Interop.SetViewportExtEx(context.Handle, size.Width, size.Height, null); + GC.KeepAlive(context.Wrapper); + return success; + } + + public static MappingMode GetMappingMode(this T context) where T : IHandle + { + MappingMode result = (MappingMode)Interop.GetMapMode(context.Handle); + GC.KeepAlive(context.Wrapper); + return result; + } + + public static MappingMode SetMappingMode(this T context, MappingMode mapMode) where T : IHandle + { + MappingMode result = (MappingMode)Interop.SetMapMode(context.Handle, (HDC_MAP_MODE)mapMode); + GC.KeepAlive(context.Wrapper); + return result; + } + public static unsafe Point GetViewportOrigin(this T context, out bool success) where T : IHandle { diff --git a/src/thirtytwo/NativeMethods.txt b/src/thirtytwo/NativeMethods.txt index 4db2435..48accd5 100644 --- a/src/thirtytwo/NativeMethods.txt +++ b/src/thirtytwo/NativeMethods.txt @@ -31,6 +31,7 @@ CreateEllipticRgn CreateErrorInfo CreateFont CreateFontIndirect +CreatePen CreateRectRgn CreateSolidBrush CreateStdAccessibleObject @@ -89,6 +90,18 @@ FONT_WEIGHT FormatMessage FrameRect FVE_E_LOCKED_VOLUME +GdipCreateFromHDC +GdipCreatePen1 +GdipCreateSolidFill +GdipDeleteBrush +GdipDeleteGraphics +GdipDeletePen +GdipDrawLines +GdipDrawLinesI +GdipFillEllipse +GdiplusShutdown +GdiplusStartup +GdipSetSmoothingMode GET_MODULE_HANDLE_EX_FLAG* GET_STOCK_OBJECT_FLAGS GetActiveWindow @@ -102,15 +115,19 @@ GetClipboardOwner GetClipRgn GetCurrentThread GetCurrentThreadId +GetCursorPos GetDC GetDeviceCaps GetDialogBaseUnits GetDialogBaseUnits +GetDlgCtrlID +GetDlgItem GetDpiForWindow GetFocus GetGraphicsMode GetIconInfo GetIconInfoEx +GetLocalTime GetMapMode GetMessage GetModuleHandleEx @@ -129,7 +146,11 @@ GetThreadLocale GetUpdatedClipboardFormats GetUpdateRgn GetUserDefaultLCID +GetViewportExtEx GetViewportOrgEx +GetWindowExtEx +GetWindowLong +GetWindowLongPtr GetWindowRect GetWindowRgn GetWindowText @@ -223,6 +244,8 @@ NtQuerySystemInformation NTSTATUS OBJ_TYPE OBJECT_IDENTIFIER +OffsetViewportOrgEx +OffsetWindowOrgEx OLECONTF OLEIVERB* OLEIVERB_* @@ -233,6 +256,7 @@ PlaySound POINTS PolyBezier Polygon +Polyline PostMessage PostQuitMessage PROPVARIANT @@ -269,6 +293,7 @@ SetClassLongPtr SetClipboardData SetCoalescableTimer SetCursor +SetCursorPos SetFocus SetGraphicsMode SetLayeredWindowAttributes @@ -276,7 +301,9 @@ SetMapMode SetPolyFillMode SetROP2 SetThreadDpiAwarenessContext +SetViewportExtEx SetViewportOrgEx +SetWindowExtEx SetWindowLong SetWindowRgn SetWindowText @@ -320,11 +347,4 @@ VIRTUAL_KEY WIN32_ERROR WINDOWPOS WM_* -XFORMCOORDS -GetCursorPos -SetCursorPos -GetWindowLong -GetWindowLongPtr -GetDlgItem -GetDlgCtrlID -CreatePen \ No newline at end of file +XFORMCOORDS \ No newline at end of file diff --git a/src/thirtytwo/Support/IPointer.cs b/src/thirtytwo/Support/IPointer.cs new file mode 100644 index 0000000..5a0f312 --- /dev/null +++ b/src/thirtytwo/Support/IPointer.cs @@ -0,0 +1,17 @@ +// Copyright (c) Jeremy W. Kuhne. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Windows.Support; + +/// +/// Used to indicate ownership of a native resource pointer. +/// +/// +/// +/// This should never be put on a struct. +/// +/// +public unsafe interface IPointer where TPointer : unmanaged +{ + TPointer* Pointer { get; } +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/ARGB.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/ARGB.cs new file mode 100644 index 0000000..cb0a3ea --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/ARGB.cs @@ -0,0 +1,51 @@ +// 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 System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Windows.Win32.Graphics.GdiPlus; + +[StructLayout(LayoutKind.Explicit)] +public readonly struct ARGB +{ + [FieldOffset(0)] + public readonly byte B; + [FieldOffset(1)] + public readonly byte G; + [FieldOffset(2)] + public readonly byte R; + [FieldOffset(3)] + public readonly byte A; + + [FieldOffset(0)] + public readonly uint Value; + + public ARGB(uint value) + { + Unsafe.SkipInit(out this); + Value = value; + } + + public ARGB(byte red, byte green, byte blue) + : this(255, red, green, blue) + { + } + + [SkipLocalsInit] + public ARGB(byte alpha, byte red, byte green, byte blue) + { + Unsafe.SkipInit(out this); + A = alpha; + R = red; + G = green; + B = blue; + } + + public static implicit operator ARGB(COLORREF color) => new(color.R, color.G, color.B); + public static explicit operator COLORREF(ARGB color) => (COLORREF)((Color)color); + public static implicit operator ARGB(Color color) => new(color.A, color.R, color.G, color.B); + public static implicit operator Color(ARGB color) => Color.FromArgb((int)color.Value); + public static implicit operator uint(ARGB color) => color.Value; +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/Brush.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/Brush.cs new file mode 100644 index 0000000..6fffa8c --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/Brush.cs @@ -0,0 +1,26 @@ +// 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 Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public unsafe class Brush : DisposableBase.Finalizable, IPointer +{ + private GpBrush* _pointer; + + public GpBrush* Pointer => _pointer; + + public Brush(GpBrush* pointer) => _pointer = pointer; + + protected override void Dispose(bool disposing) + { + Status status = Interop.GdipDeleteBrush(_pointer); + if (disposing) + { + status.ThrowIfFailed(); + } + + _pointer = null; + } +} diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/GdiPlus.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/GdiPlus.cs new file mode 100644 index 0000000..5b9af5a --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/GdiPlus.cs @@ -0,0 +1,51 @@ +// 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.Runtime.CompilerServices; +using Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public static unsafe class GdiPlus +{ + internal static Session Init() => s_session; + private static readonly Session s_session = new(); + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void Initialize() => Init(); + + public static nuint Startup(uint version = 2) + { + GdiplusStartupInput input = new() { GdiplusVersion = version }; + GdiplusStartupOutput output; + nuint token; + ThrowIfFailed(Interop.GdiplusStartup( + &token, + &input, + &output)); + + return token; + } + + public static void Shutdown(nuint token) => Interop.GdiplusShutdown(token); + + public static void ThrowIfFailed(this Status status) + { + if (status != Status.Ok) + throw GetExceptionForStatus(status); + } + + public static Exception GetExceptionForStatus(Status status) + { + switch (status) + { + case Status.Win32Error: + WIN32_ERROR error = Error.GetLastError(); + if (error != WIN32_ERROR.ERROR_SUCCESS) + return error.GetException(); + goto default; + default: + return new GdiPlusException(status); + } + } +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/GdiPlusException.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/GdiPlusException.cs new file mode 100644 index 0000000..53108b6 --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/GdiPlusException.cs @@ -0,0 +1,12 @@ +// 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 Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public class GdiPlusException : ThirtyTwoException +{ + public GdiPlusException(Status status) : base(status.ToString()) => Status = status; + public Status Status { get; private set; } +} diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/Graphics.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/Graphics.cs new file mode 100644 index 0000000..35e511f --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/Graphics.cs @@ -0,0 +1,34 @@ +// 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 Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public unsafe class Graphics : DisposableBase.Finalizable, IPointer +{ + private GpGraphics* _pointer; + + public GpGraphics* Pointer => _pointer; + + public Graphics(GpGraphics* pointer) => _pointer = pointer; + + public Graphics(HDC hdc) + { + GdiPlus.Init(); + GpGraphics* pointer; + Interop.GdipCreateFromHDC(hdc, &pointer).ThrowIfFailed(); + _pointer = pointer; + } + + protected override void Dispose(bool disposing) + { + Status status = Interop.GdipDeleteGraphics(_pointer); + if (disposing) + { + status.ThrowIfFailed(); + } + + _pointer = null; + } +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/GraphicsExtensions.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/GraphicsExtensions.cs new file mode 100644 index 0000000..adef471 --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/GraphicsExtensions.cs @@ -0,0 +1,77 @@ +// 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.Runtime.InteropServices; +using Windows.Support; +using Drawing = System.Drawing; + +namespace Windows.Win32.Graphics.GdiPlus; + +public static unsafe class GraphicsExtensions +{ + public static void SetSmoothingMode(this T graphics, SmoothingMode smoothingMode) where T : IPointer + { + Interop.GdipSetSmoothingMode(graphics.Pointer, smoothingMode).ThrowIfFailed(); + GC.KeepAlive(graphics); + } + + public static unsafe void DrawLines(this TGraphics graphics, TPen pen, ReadOnlySpan points) + where TGraphics : IPointer + where TPen : IPointer => + DrawLines(graphics, pen, MemoryMarshal.Cast(points)); + + public static unsafe void DrawLines(this TGraphics graphics, TPen pen, ReadOnlySpan points) + where TGraphics : IPointer + where TPen : IPointer + { + fixed (Point* p = points) + { + Interop.GdipDrawLinesI(graphics.Pointer, pen.Pointer, p, points.Length).ThrowIfFailed(); + } + + GC.KeepAlive(graphics); + GC.KeepAlive(pen); + } + + public static unsafe void DrawLines(this TGraphics graphics, TPen pen, ReadOnlySpan points) + where TGraphics : IPointer + where TPen : IPointer => + DrawLines(graphics, pen, MemoryMarshal.Cast(points)); + + public static unsafe void DrawLines(this TGraphics graphics, TPen pen, ReadOnlySpan points) + where TGraphics : IPointer + where TPen : IPointer + { + fixed (PointF* p = points) + { + Interop.GdipDrawLines(graphics.Pointer, pen.Pointer, p, points.Length).ThrowIfFailed(); + } + + GC.KeepAlive(graphics); + GC.KeepAlive(pen); + } + + public static void FillEllipse(this TGraphics graphics, TBrush brush, Drawing.Rectangle bounds) + where TGraphics : IPointer + where TBrush : IPointer + => FillEllipse(graphics, brush, (float)bounds.X, bounds.Y, bounds.Width, bounds.Height); + + public static void FillEllipse(this TGraphics graphics, TBrush brush, Drawing.RectangleF bounds) + where TGraphics : IPointer + where TBrush : IPointer + => FillEllipse(graphics, brush, bounds.X, bounds.Y, bounds.Width, bounds.Height); + + public static void FillEllipse(this TGraphics graphics, TBrush brush, int x, int y, int width, int height) + where TGraphics : IPointer + where TBrush : IPointer + => FillEllipse(graphics, brush, (float)x, y, width, height); + + public static void FillEllipse(this TGraphics graphics, TBrush brush, float x, float y, float width, float height) + where TGraphics : IPointer + where TBrush : IPointer + { + Interop.GdipFillEllipse(graphics.Pointer, brush.Pointer, x, y, width, height).ThrowIfFailed(); + GC.KeepAlive(graphics); + GC.KeepAlive(brush); + } +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/Pen.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/Pen.cs new file mode 100644 index 0000000..772133c --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/Pen.cs @@ -0,0 +1,32 @@ +// 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 Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public unsafe class Pen : DisposableBase.Finalizable, IPointer +{ + private GpPen* _pointer; + + public GpPen* Pointer => _pointer; + + public Pen(ARGB color, float width = 1.0f) + { + GdiPlus.Init(); + GpPen* pointer; + Interop.GdipCreatePen1(color, width, Unit.UnitPixel, &pointer).ThrowIfFailed(); + _pointer = pointer; + } + + protected override void Dispose(bool disposing) + { + Status status = Interop.GdipDeletePen(_pointer); + if (disposing) + { + status.ThrowIfFailed(); + } + + _pointer = null; + } +} diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/Session.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/Session.cs new file mode 100644 index 0000000..4d3bd23 --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/Session.cs @@ -0,0 +1,26 @@ +// 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.Runtime.CompilerServices; +using Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public class Session : IDisposable +{ + private UIntPtr _token; + + public Session(uint version = 2) + { + _token = GdiPlus.Startup(version); + } + + ~Session() => Dispose(); + + public void Dispose() + { + GC.SuppressFinalize(this); + GdiPlus.Shutdown(_token); + _token = UIntPtr.Zero; + } +} \ No newline at end of file diff --git a/src/thirtytwo/Win32/Graphics/GdiPlus/SolidBrush.cs b/src/thirtytwo/Win32/Graphics/GdiPlus/SolidBrush.cs new file mode 100644 index 0000000..1597912 --- /dev/null +++ b/src/thirtytwo/Win32/Graphics/GdiPlus/SolidBrush.cs @@ -0,0 +1,22 @@ +// 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 Windows.Support; + +namespace Windows.Win32.Graphics.GdiPlus; + +public unsafe class SolidBrush : Brush, IPointer +{ + public new GpSolidFill* Pointer => (GpSolidFill*)base.Pointer; + + public SolidBrush(ARGB color) : base(CreateBrush(color)) + { + } + + private static GpBrush* CreateBrush(ARGB color) + { + GpBrush* brush; + Interop.GdipCreateSolidFill(color, (GpSolidFill**)&brush).ThrowIfFailed(); + return brush; + } +} diff --git a/src/thirtytwo/WrapperEnums/MappingMode.cs b/src/thirtytwo/WrapperEnums/MappingMode.cs new file mode 100644 index 0000000..717c335 --- /dev/null +++ b/src/thirtytwo/WrapperEnums/MappingMode.cs @@ -0,0 +1,65 @@ +// Copyright (c) Jeremy W. Kuhne. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Windows; + +/// +/// Page-space to device-space transformation. +/// +/// +/// +/// +/// Mapping Modes and Translations +/// +/// +/// +public enum MappingMode +{ + /// + /// Pixel mapping. Each unit in page space is one pixel in device space. + /// X increases left to right. Y increases from top to bottom. (MM_TEXT) + /// + Text = HDC_MAP_MODE.MM_TEXT, + + /// + /// Each unit in page space is 0.1 mm in device space. + /// X increases left to right. Y increases from bottom to top. (MM_LOMETRIC) + /// + LowMetric = HDC_MAP_MODE.MM_LOMETRIC, + + /// + /// Each unit in page space is 0.01 mm in device space. + /// X increases left to right. Y increases from bottom to top. (MM_HIMETRIC) + /// + HighMetric = HDC_MAP_MODE.MM_HIMETRIC, + + /// + /// Each unit in page space is 0.01 inch in device space. + /// X increases left to right. Y increases from bottom to top. (MM_LOENGLISH) + /// + LowEnglish = HDC_MAP_MODE.MM_LOENGLISH, + + /// + /// Each unit in page space is 0.001 inch in device space. + /// X increases left to right. Y increases from bottom to top. (MM_HIENGLISH) + /// + HighEnglish = HDC_MAP_MODE.MM_HIENGLISH, + + /// + /// Each unit in page space is a twip in device space (1/1440 inch). + /// X increases left to right. Y increases from bottom to top. (MM_TWIPS) + /// + Twips = HDC_MAP_MODE.MM_TWIPS, + + /// + /// Application defined via SetWindow/ViewPortExtents. + /// Both axis are equally scaled. (MM_ISOTROPIC) + /// + Isotropic = HDC_MAP_MODE.MM_ISOTROPIC, + + /// + /// Application defined via SetWindow/ViewPortExtents. + /// Axis may not be equally scaled. (MM_ANISOTROPIC) + /// + Anisotropic = HDC_MAP_MODE.MM_ANISOTROPIC +} \ No newline at end of file diff --git a/src/thirtytwo/thirtytwo.csproj b/src/thirtytwo/thirtytwo.csproj index d471a4d..9fd524d 100644 --- a/src/thirtytwo/thirtytwo.csproj +++ b/src/thirtytwo/thirtytwo.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/thirtytwo.sln b/thirtytwo.sln index 0d3e66e..d11c483 100644 --- a/thirtytwo.sln +++ b/thirtytwo.sln @@ -51,6 +51,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blokout2", "src\samples\Pet EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BtnLook", "src\samples\Petzold\5th\BtnLook\BtnLook.csproj", "{22E8534C-628A-4A9C-9EDB-7BE4DAC12544}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Checker", "src\samples\Petzold\5th\Checker1\Checker.csproj", "{7DB41BCA-E497-4EE4-9A2F-A529E5306ABC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clock", "src\samples\Petzold\5th\Clock\Clock.csproj", "{D4EA97FD-45D3-4A26-843F-63427F0F1BFE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -125,6 +129,14 @@ Global {22E8534C-628A-4A9C-9EDB-7BE4DAC12544}.Debug|x64.Build.0 = Debug|x64 {22E8534C-628A-4A9C-9EDB-7BE4DAC12544}.Release|x64.ActiveCfg = Release|x64 {22E8534C-628A-4A9C-9EDB-7BE4DAC12544}.Release|x64.Build.0 = Release|x64 + {7DB41BCA-E497-4EE4-9A2F-A529E5306ABC}.Debug|x64.ActiveCfg = Debug|x64 + {7DB41BCA-E497-4EE4-9A2F-A529E5306ABC}.Debug|x64.Build.0 = Debug|x64 + {7DB41BCA-E497-4EE4-9A2F-A529E5306ABC}.Release|x64.ActiveCfg = Release|x64 + {7DB41BCA-E497-4EE4-9A2F-A529E5306ABC}.Release|x64.Build.0 = Release|x64 + {D4EA97FD-45D3-4A26-843F-63427F0F1BFE}.Debug|x64.ActiveCfg = Debug|x64 + {D4EA97FD-45D3-4A26-843F-63427F0F1BFE}.Debug|x64.Build.0 = Debug|x64 + {D4EA97FD-45D3-4A26-843F-63427F0F1BFE}.Release|x64.ActiveCfg = Release|x64 + {D4EA97FD-45D3-4A26-843F-63427F0F1BFE}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -146,6 +158,8 @@ Global {751D8704-4AF9-46E4-AD24-B43DD011ED11} = {13110246-EBE1-441B-B721-B0614D62B13B} {F52C0159-AFFE-431D-BC46-1AA6C8381400} = {13110246-EBE1-441B-B721-B0614D62B13B} {22E8534C-628A-4A9C-9EDB-7BE4DAC12544} = {13110246-EBE1-441B-B721-B0614D62B13B} + {7DB41BCA-E497-4EE4-9A2F-A529E5306ABC} = {13110246-EBE1-441B-B721-B0614D62B13B} + {D4EA97FD-45D3-4A26-843F-63427F0F1BFE} = {13110246-EBE1-441B-B721-B0614D62B13B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3761BFC9-DBEF-4186-BB8B-BC0D84ED9AE5}