Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/EasyWindowsApplication/Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ internal void AddWindow(WindowModel window)

public void Initialize()
{
// 1. Registrar defaults PRIMERO (antes de cualquier control o HFONT)
try { UiDefaultsProvider.Set(new Win32ControlsModule.Backend.Win32UiDefaults()); } catch { }

var icc = new INITCOMMONCONTROLSEX
{
dwSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf<INITCOMMONCONTROLSEX>(),
Expand Down
8 changes: 8 additions & 0 deletions src/EasyWindowsApplication/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ internal static class WM
internal const uint MOUSEWHEEL = 0x020A;
internal const uint ENTERSIZEMOVE = 0x0231;
internal const uint EXITSIZEMOVE = 0x0232;
internal const uint SETTINGCHANGE = 0x001A;
internal const uint DPICHANGED = 0x02E0;
}

internal static class SPI
{
internal const uint GETNONCLIENTMETRICS = 0x0029;
internal const uint SETNONCLIENTMETRICS = 0x002A;
}

internal static class WMSIZE
Expand Down
18 changes: 12 additions & 6 deletions src/EasyWindowsApplication/Core/ControlBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using EasyWindowsApplication.Core.LayoutEngine;
using EasyWindowsApplication.Share;
using EasyWindowsApplication.Win32ControlsModule.Backend;
using EasyWindowsApplication.Win32ControlsModule.Frontend;
using static EasyWindowsApplication.Core.Win32;

Expand Down Expand Up @@ -210,14 +211,19 @@ protected string GetWindowText()
}
}

private static nint GetFallbackFont()
{
try { return ControlProcedures.GetDefaultFont(); } catch { return GetStockObject(17); }
}

protected (int Width, int Height) MeasureText(string text)
{
if (string.IsNullOrEmpty(text))
return (20, 24);

nint hdc = GetDC(Hwnd);
nint hfont = SendMessageW(Hwnd, WM.GETFONT, 0, 0);
nint oldFont = SelectObject(hdc, hfont != 0 ? hfont : GetStockObject(17));
nint hfont = Hwnd != 0 ? SendMessageW(Hwnd, WM.GETFONT, 0, 0) : 0;
nint oldFont = SelectObject(hdc, hfont != 0 ? hfont : GetFallbackFont());
RECT rect = default;
DrawTextW(hdc, text, -1, ref rect, DT.CALCRECT | DT.SINGLELINE);
SelectObject(hdc, oldFont);
Expand All @@ -231,8 +237,8 @@ protected string GetWindowText()
return (20, 24);

nint hdc = GetDC(Hwnd);
nint hfont = SendMessageW(Hwnd, WM.GETFONT, 0, 0);
nint oldFont = SelectObject(hdc, hfont != 0 ? hfont : GetStockObject(17));
nint hfont = Hwnd != 0 ? SendMessageW(Hwnd, WM.GETFONT, 0, 0) : 0;
nint oldFont = SelectObject(hdc, hfont != 0 ? hfont : GetFallbackFont());

GetTextExtentPoint32W(hdc, text, text.Length, out SIZE size);

Expand All @@ -247,8 +253,8 @@ protected string GetWindowText()
return (20, 24);

nint hdc = GetDC(Hwnd);
nint hfont = SendMessageW(Hwnd, WM.GETFONT, 0, 0);
nint oldFont = SelectObject(hdc, hfont != 0 ? hfont : GetStockObject(17));
nint hfont = Hwnd != 0 ? SendMessageW(Hwnd, WM.GETFONT, 0, 0) : 0;
nint oldFont = SelectObject(hdc, hfont != 0 ? hfont : GetFallbackFont());

int totalWidth = 0;
int maxHeight = 0;
Expand Down
6 changes: 6 additions & 0 deletions src/EasyWindowsApplication/Core/HandleRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,10 @@ internal void UnregisterWindowByHwnd(nint hwnd)

internal IBaseWindow? GetWindow(string name)
=> _windowByName.TryGetValue(name, out var w) ? w : null;

internal IEnumerable<nint> AllControlHandles()
{
foreach (var kv in _hwndToControl)
yield return kv.Key;
}
}
30 changes: 30 additions & 0 deletions src/EasyWindowsApplication/Core/MasterRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,36 @@ internal nint WndProc(nint hwnd, uint msg, nint wParam, nint lParam)
return 0;
}
}
else if (msg == WM.SETTINGCHANGE)
{
// SPI_SETNONCLIENTMETRICS sent when system font/theme changes
if ((uint)wParam == SPI.SETNONCLIENTMETRICS)
{
try { Win32ControlsModule.Backend.ControlProcedures.InvalidateDefaultFont(); } catch { }
nint newFont = 0;
try { newFont = Win32ControlsModule.Backend.ControlProcedures.GetDefaultFont(); } catch { }
if (newFont != 0)
{
foreach (var ch in _registry.AllControlHandles())
{
try { Win32.SendMessageW(ch, WM.SETFONT, newFont, 1); } catch { }
}
}
}
}
else if (msg == WM.DPICHANGED)
{
try { Win32ControlsModule.Backend.ControlProcedures.InvalidateDefaultFont(); } catch { }
nint newFont = 0;
try { newFont = Win32ControlsModule.Backend.ControlProcedures.GetDefaultFont(); } catch { }
if (newFont != 0)
{
foreach (var ch in _registry.AllControlHandles())
{
try { Win32.SendMessageW(ch, WM.SETFONT, newFont, 1); } catch { }
}
}
}

if (msg == WM.DESTROY)
{
Expand Down
10 changes: 10 additions & 0 deletions src/EasyWindowsApplication/Core/UiDefaults/ControlUiDefaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using EasyWindowsApplication.Share;

namespace EasyWindowsApplication.Core;

internal sealed record ControlUiDefaults
{
public int? PreferredHeight { get; init; }
public Thickness? Padding { get; init; }
public Color? Background { get; init; }
}
52 changes: 52 additions & 0 deletions src/EasyWindowsApplication/Core/UiDefaults/CoreUiDefaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using EasyWindowsApplication.Share;

namespace EasyWindowsApplication.Core;

internal abstract class CoreUiDefaults : IDefaultUiValues
{
public virtual WindowUiDefaults MainWindow => new()
{
Width = 420,
Height = 280,
Position = WindowPositionOnScreen.Center,
Background = null
};

public virtual WindowUiDefaults AlternativeWindow => new()
{
Width = 400,
Height = 300,
Position = WindowPositionOnScreen.Center,
Background = null
};

public abstract FontSpec DefaultFont { get; }

public virtual Color DefaultForeground
=> ColorFromSysColor(Win32.GetSysColor(COLOR_WINDOWTEXT));

public virtual Color DefaultBackground
=> ColorFromSysColor(Win32.GetSysColor(COLOR_WINDOW));

public virtual Thickness DefaultPadding => new(3);
public virtual int DefaultControlSpacing => 6;
public virtual bool EnableVisualStyles => true;

public virtual IReadOnlyDictionary<Type, ControlUiDefaults> ControlDefaults
=> _empty;

private static readonly IReadOnlyDictionary<Type, ControlUiDefaults> _empty
= new Dictionary<Type, ControlUiDefaults>();

protected static Color ColorFromSysColor(int colorRef)
=> Color.FromRgb(
(byte)(colorRef & 0xFF),
(byte)((colorRef >> 8) & 0xFF),
(byte)((colorRef >> 16) & 0xFF));

// SysColor indices (winuser.h)
private const int COLOR_WINDOW = 5;
private const int COLOR_WINDOWTEXT = 8;
private const int COLOR_BTNFACE = 15;
private const int COLOR_BTNTEXT = 18;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace EasyWindowsApplication.Core;

internal static class DefaultUiValuesExtensions
{
public static ControlUiDefaults? GetFor(this IDefaultUiValues d, Type t)
=> d.ControlDefaults.TryGetValue(t, out var v) ? v : null;

public static ControlUiDefaults? GetFor<T>(this IDefaultUiValues d)
=> d.GetFor(typeof(T));
}
15 changes: 15 additions & 0 deletions src/EasyWindowsApplication/Core/UiDefaults/FontSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace EasyWindowsApplication.Core;

internal enum FontWeight { Normal, Bold }
internal enum FontStyle { Regular, Italic }

internal sealed record FontSpec
{
public static readonly FontSpec SystemTheme = new() { IsSystemTheme = true };

public bool IsSystemTheme { get; init; }
public string Family { get; init; } = "Segoe UI";
public float Size { get; init; } = 9f;
public FontWeight Weight { get; init; } = FontWeight.Normal;
public FontStyle Style { get; init; } = FontStyle.Regular;
}
18 changes: 18 additions & 0 deletions src/EasyWindowsApplication/Core/UiDefaults/IDefaultUiValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using EasyWindowsApplication.Share;

namespace EasyWindowsApplication.Core;

internal interface IDefaultUiValues
{
WindowUiDefaults MainWindow { get; }
WindowUiDefaults AlternativeWindow { get; }

FontSpec DefaultFont { get; }
Color DefaultForeground { get; }
Color DefaultBackground { get; }
Thickness DefaultPadding { get; }
int DefaultControlSpacing { get; }
bool EnableVisualStyles { get; }

IReadOnlyDictionary<Type, ControlUiDefaults> ControlDefaults { get; }
}
16 changes: 16 additions & 0 deletions src/EasyWindowsApplication/Core/UiDefaults/UiDefaultsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace EasyWindowsApplication.Core;

internal static class UiDefaultsProvider
{
private sealed class CoreFallbackDefaults : CoreUiDefaults
{
public override FontSpec DefaultFont => FontSpec.SystemTheme;
}

private static IDefaultUiValues _current = new CoreFallbackDefaults();

public static IDefaultUiValues Current => _current;

public static void Set(IDefaultUiValues defaults)
=> _current = defaults ?? throw new ArgumentNullException(nameof(defaults));
}
13 changes: 13 additions & 0 deletions src/EasyWindowsApplication/Core/UiDefaults/WindowUiDefaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using EasyWindowsApplication.Share;

namespace EasyWindowsApplication.Core;

internal sealed record WindowUiDefaults
{
public int Width { get; init; } = 420;
public int Height { get; init; } = 280;
public WindowPositionOnScreen Position { get; init; } = WindowPositionOnScreen.Center;
public Color? Background { get; init; } = null;
public string Title { get; init; } = "";
public string Name { get; init; } = "";
}
23 changes: 23 additions & 0 deletions src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@ public string Text
public bool Enabled { get; set; } = true;
public void Click() => OnClick(() => { });
public void SetStyle(uint style, bool redraw = true) { }

protected override (float Width, float Height) MeasureContent(float availableWidth, float availableHeight)
{
string text = GetWindowText();
var (tw, th) = string.IsNullOrEmpty(text) ? (20, 24) : MeasureTextByGlyphs(text);
var defaults = UiDefaultsProvider.Current.GetFor<IButton>();
if (defaults == null)
defaults = UiDefaultsProvider.Current.GetFor<Button>();
float preferredH = defaults?.PreferredHeight ?? 24f;
// DPI scaling: defaults are at 96 DPI, scale to current DPI if possible
try
{
uint dpi = 0;
if (Hwnd != 0)
dpi = Win32Controls.GetDpiForWindow(Hwnd);
if (dpi == 0) dpi = Win32Controls.GetDpiForSystem();
if (dpi != 0 && dpi != 96)
preferredH = preferredH * dpi / 96f;
}
catch { }
// Height is fixed by defaults, width by text
return (tw, preferredH);
}
}

public sealed class Label : ControlBase, ILabel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,81 @@ internal static class ControlProcedures

internal static void SetInstance(nint hInstance) => _hInstance = hInstance;

internal static void InvalidateDefaultFont()
{
if (_defaultFont == 0) return;
Win32Controls.DeleteObject(_defaultFont);
_defaultFont = 0;
}

internal static nint GetDefaultFont()
{
if (_defaultFont == 0)
_defaultFont = Win32.GetStockObject(17); // DEFAULT_GUI_FONT
if (_defaultFont != 0) return _defaultFont;

var spec = UiDefaultsProvider.Current.DefaultFont;

if (spec.IsSystemTheme)
{
var ncm = new NONCLIENTMETRICS
{
cbSize = (uint)Marshal.SizeOf<NONCLIENTMETRICS>()
};
if (Win32Controls.SystemParametersInfoW(SPI.GETNONCLIENTMETRICS, ncm.cbSize, ref ncm, 0))
{
_defaultFont = Win32Controls.CreateFontIndirectW(ref ncm.lfMessageFont);
if (_defaultFont != 0) return _defaultFont;
}
}

_defaultFont = CreateFontFromSpec(spec);
return _defaultFont;
}

private static nint CreateFontFromSpec(FontSpec spec)
{
var lf = new LOGFONT
{
lfHeight = -(int)(spec.Size * GetDpiForSystemSafe() / 72f),
lfWeight = spec.Weight == FontWeight.Bold ? 700 : 400,
lfItalic = spec.Style == FontStyle.Italic ? (byte)1 : (byte)0,
lfCharSet = 1, // DEFAULT_CHARSET
lfQuality = 5, // CLEARTYPE_QUALITY
lfFaceName = spec.Family ?? "Segoe UI"
};
var h = Win32Controls.CreateFontIndirectW(ref lf);
if (h != 0) return h;
// Ultimate fallback: GetStockObject DEFAULT_GUI_FONT (17)
return Win32Controls.GetStockObject(17);
}

private static int GetDpiForSystemSafe()
{
try { return (int)Win32Controls.GetDpiForSystem(); } catch { }
try
{
nint hdc = Win32Controls.GetDC(0);
if (hdc != 0)
{
int dpi = Win32Controls.GetDeviceCaps(hdc, LOGPIXELS.LOGPIXELSY);
Win32Controls.ReleaseDC(0, hdc);
if (dpi > 0) return dpi;
}
}
catch { }
return 96;
}

private static int GetDpiForWindowSafe(nint hwnd)
{
try
{
uint dpi = Win32Controls.GetDpiForWindow(hwnd);
if (dpi != 0) return (int)dpi;
}
catch { }
return GetDpiForSystemSafe();
}

internal static nint CreateControl(
string windowClass,
nint parentHwnd,
Expand Down
Loading
Loading