diff --git a/src/EasyWindowsApplication/Core/Application.cs b/src/EasyWindowsApplication/Core/Application.cs index 0089381..de0e00b 100644 --- a/src/EasyWindowsApplication/Core/Application.cs +++ b/src/EasyWindowsApplication/Core/Application.cs @@ -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(), diff --git a/src/EasyWindowsApplication/Core/Constants.cs b/src/EasyWindowsApplication/Core/Constants.cs index 2eab93a..bc1b04e 100644 --- a/src/EasyWindowsApplication/Core/Constants.cs +++ b/src/EasyWindowsApplication/Core/Constants.cs @@ -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 diff --git a/src/EasyWindowsApplication/Core/ControlBase.cs b/src/EasyWindowsApplication/Core/ControlBase.cs index 945648c..393144e 100644 --- a/src/EasyWindowsApplication/Core/ControlBase.cs +++ b/src/EasyWindowsApplication/Core/ControlBase.cs @@ -3,6 +3,7 @@ using EasyWindowsApplication.Core.LayoutEngine; using EasyWindowsApplication.Share; +using EasyWindowsApplication.Win32ControlsModule.Backend; using EasyWindowsApplication.Win32ControlsModule.Frontend; using static EasyWindowsApplication.Core.Win32; @@ -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); @@ -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); @@ -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; diff --git a/src/EasyWindowsApplication/Core/HandleRegistry.cs b/src/EasyWindowsApplication/Core/HandleRegistry.cs index 175c6ef..a684009 100644 --- a/src/EasyWindowsApplication/Core/HandleRegistry.cs +++ b/src/EasyWindowsApplication/Core/HandleRegistry.cs @@ -145,4 +145,10 @@ internal void UnregisterWindowByHwnd(nint hwnd) internal IBaseWindow? GetWindow(string name) => _windowByName.TryGetValue(name, out var w) ? w : null; + + internal IEnumerable AllControlHandles() + { + foreach (var kv in _hwndToControl) + yield return kv.Key; + } } diff --git a/src/EasyWindowsApplication/Core/MasterRouter.cs b/src/EasyWindowsApplication/Core/MasterRouter.cs index ce6eead..7f162f3 100644 --- a/src/EasyWindowsApplication/Core/MasterRouter.cs +++ b/src/EasyWindowsApplication/Core/MasterRouter.cs @@ -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) { diff --git a/src/EasyWindowsApplication/Core/UiDefaults/ControlUiDefaults.cs b/src/EasyWindowsApplication/Core/UiDefaults/ControlUiDefaults.cs new file mode 100644 index 0000000..0ff3ac6 --- /dev/null +++ b/src/EasyWindowsApplication/Core/UiDefaults/ControlUiDefaults.cs @@ -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; } +} diff --git a/src/EasyWindowsApplication/Core/UiDefaults/CoreUiDefaults.cs b/src/EasyWindowsApplication/Core/UiDefaults/CoreUiDefaults.cs new file mode 100644 index 0000000..ac02d89 --- /dev/null +++ b/src/EasyWindowsApplication/Core/UiDefaults/CoreUiDefaults.cs @@ -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 ControlDefaults + => _empty; + + private static readonly IReadOnlyDictionary _empty + = new Dictionary(); + + 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; +} diff --git a/src/EasyWindowsApplication/Core/UiDefaults/DefaultUiValuesExtensions.cs b/src/EasyWindowsApplication/Core/UiDefaults/DefaultUiValuesExtensions.cs new file mode 100644 index 0000000..1d7ada1 --- /dev/null +++ b/src/EasyWindowsApplication/Core/UiDefaults/DefaultUiValuesExtensions.cs @@ -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(this IDefaultUiValues d) + => d.GetFor(typeof(T)); +} diff --git a/src/EasyWindowsApplication/Core/UiDefaults/FontSpec.cs b/src/EasyWindowsApplication/Core/UiDefaults/FontSpec.cs new file mode 100644 index 0000000..7414c18 --- /dev/null +++ b/src/EasyWindowsApplication/Core/UiDefaults/FontSpec.cs @@ -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; +} diff --git a/src/EasyWindowsApplication/Core/UiDefaults/IDefaultUiValues.cs b/src/EasyWindowsApplication/Core/UiDefaults/IDefaultUiValues.cs new file mode 100644 index 0000000..3dfacbf --- /dev/null +++ b/src/EasyWindowsApplication/Core/UiDefaults/IDefaultUiValues.cs @@ -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 ControlDefaults { get; } +} diff --git a/src/EasyWindowsApplication/Core/UiDefaults/UiDefaultsProvider.cs b/src/EasyWindowsApplication/Core/UiDefaults/UiDefaultsProvider.cs new file mode 100644 index 0000000..c8fac77 --- /dev/null +++ b/src/EasyWindowsApplication/Core/UiDefaults/UiDefaultsProvider.cs @@ -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)); +} diff --git a/src/EasyWindowsApplication/Core/UiDefaults/WindowUiDefaults.cs b/src/EasyWindowsApplication/Core/UiDefaults/WindowUiDefaults.cs new file mode 100644 index 0000000..ae548fc --- /dev/null +++ b/src/EasyWindowsApplication/Core/UiDefaults/WindowUiDefaults.cs @@ -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; } = ""; +} diff --git a/src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs b/src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs index c68ced6..3993daa 100644 --- a/src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs +++ b/src/EasyWindowsApplication/Win32ControlsModule/Backend/Controls.cs @@ -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(); + if (defaults == null) + defaults = UiDefaultsProvider.Current.GetFor