Files
App-Installer-For-Windows-8…/DataUtils/Theme.cs

363 lines
9.5 KiB
C#

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.Win32;
namespace DataUtils
{
public enum HighContrastTheme
{
None,
Black,
White,
Other
}
public static class UITheme
{
// --- P/Invoke & constants ---
private const int SPI_GETHIGHCONTRAST = 0x0042;
private const uint HCF_HIGHCONTRASTON = 0x00000001;
private const int COLOR_WINDOW = 5;
private const int COLOR_WINDOWTEXT = 8;
private const int LOGPIXELSX = 88; // GetDeviceCaps index for DPI X
// private const int HORZRES = 8; // not used now
private const int SM_CXSCREEN = 0;
private const int SM_CYSCREEN = 1;
[StructLayout (LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct HIGHCONTRAST
{
public int cbSize;
public uint dwFlags;
public IntPtr lpszDefaultScheme;
}
[DllImport ("user32.dll", SetLastError = true)]
private static extern bool SystemParametersInfo (int uiAction, int uiParam, ref HIGHCONTRAST pvParam, int fWinIni);
[DllImport ("user32.dll")]
private static extern int GetSysColor (int nIndex);
[DllImport ("user32.dll")]
private static extern int GetSystemMetrics (int nIndex);
[DllImport ("user32.dll")]
private static extern IntPtr GetDC (IntPtr hWnd);
[DllImport ("user32.dll")]
private static extern int ReleaseDC (IntPtr hWnd, IntPtr hDC);
[DllImport ("gdi32.dll")]
private static extern int GetDeviceCaps (IntPtr hdc, int nIndex);
// DwmGetColorizationColor
[DllImport ("dwmapi.dll", EntryPoint = "DwmGetColorizationColor", SetLastError = false)]
private static extern int DwmGetColorizationColor (out uint pcrColorization, out bool pfOpaqueBlend);
// --- Methods ---
public static bool IsHighContrastEnabled ()
{
try
{
HIGHCONTRAST hc = new HIGHCONTRAST ();
hc.cbSize = Marshal.SizeOf (typeof (HIGHCONTRAST));
if (SystemParametersInfo (SPI_GETHIGHCONTRAST, hc.cbSize, ref hc, 0))
{
return (hc.dwFlags & HCF_HIGHCONTRASTON) != 0;
}
}
catch
{
// ignore errors
}
return false;
}
public static bool IsHighContrast => IsHighContrastEnabled ();
public static HighContrastTheme GetHighContrastTheme ()
{
try
{
HIGHCONTRAST hc = new HIGHCONTRAST ();
hc.cbSize = Marshal.SizeOf (typeof (HIGHCONTRAST));
if (!SystemParametersInfo (SPI_GETHIGHCONTRAST, hc.cbSize, ref hc, 0))
return HighContrastTheme.None;
if ((hc.dwFlags & HCF_HIGHCONTRASTON) == 0)
return HighContrastTheme.None;
int bgColorRef = GetSysColor (COLOR_WINDOW);
int textColorRef = GetSysColor (COLOR_WINDOWTEXT);
int bgR = (bgColorRef & 0x0000FF);
int bgG = (bgColorRef & 0x00FF00) >> 8;
int bgB = (bgColorRef & 0xFF0000) >> 16;
int txtR = (textColorRef & 0x0000FF);
int txtG = (textColorRef & 0x00FF00) >> 8;
int txtB = (textColorRef & 0xFF0000) >> 16;
int brightnessBg = (bgR + bgG + bgB) / 3;
int brightnessText = (txtR + txtG + txtB) / 3;
if (brightnessBg < brightnessText) return HighContrastTheme.Black;
else if (brightnessBg > brightnessText) return HighContrastTheme.White;
else return HighContrastTheme.Other;
}
catch
{
return HighContrastTheme.None;
}
}
public static HighContrastTheme HighContrast => GetHighContrastTheme ();
// Returns DPI as percent (100 = normal 96 DPI)
public static int GetDPI ()
{
IntPtr hdc = IntPtr.Zero;
try
{
hdc = GetDC (IntPtr.Zero);
if (hdc == IntPtr.Zero) return 0;
int dpiX = GetDeviceCaps (hdc, LOGPIXELSX);
if (dpiX <= 0) return 0;
// convert to percentage of 96 DPI baseline
int percent = (int)Math.Round ((dpiX / 96.0) * 100.0);
return percent;
}
catch
{
return 0;
}
finally
{
if (hdc != IntPtr.Zero) ReleaseDC (IntPtr.Zero, hdc);
}
}
public static int DPI => GetDPI ();
public static double DPIDouble => GetDPI () * 0.01;
public static int GetScreenWidth ()
{
try { return GetSystemMetrics (SM_CXSCREEN); }
catch { return 0; }
}
public static int ScreenWidth => GetScreenWidth ();
public static int GetScreenHeight ()
{
try { return GetSystemMetrics (SM_CYSCREEN); }
catch { return 0; }
}
public static int ScreenHeight => GetScreenHeight ();
public static Color GetDwmThemeColor ()
{
try
{
uint color;
bool opaque;
int hr = DwmGetColorizationColor (out color, out opaque);
if (hr == 0) // S_OK
{
byte r = (byte)((color & 0x00FF0000) >> 16);
byte g = (byte)((color & 0x0000FF00) >> 8);
byte b = (byte)(color & 0x000000FF);
return Color.FromArgb (r, g, b);
}
}
catch
{
// ignored
}
// fallback default (matches original C++ fallback)
return Color.FromArgb (0, 120, 215);
}
public static Color ThemeColor => GetDwmThemeColor ();
public static string ColorToHtml (Color color)
{
// Return #RRGGBB
return string.Format ("#{0:X2}{1:X2}{2:X2}", color.R, color.G, color.B);
}
public static Color StringToColor (string colorStr)
{
if (string.IsNullOrWhiteSpace (colorStr)) return Color.Transparent;
string s = colorStr.Trim ();
// Hex: #RGB, #RRGGBB, #AARRGGBB
if (s.StartsWith ("#"))
{
string hex = s.Substring (1);
// Expand short forms (#RGB or #RGBA)
if (hex.Length == 3 || hex.Length == 4)
{
string expanded = "";
for (int i = 0; i < hex.Length; i++)
{
expanded += hex [i].ToString () + hex [i].ToString ();
}
hex = expanded;
}
uint argb;
try
{
argb = Convert.ToUInt32 (hex, 16);
}
catch
{
return Color.Transparent;
}
if (hex.Length == 6)
{
int r = (int)((argb >> 16) & 0xFF);
int g = (int)((argb >> 8) & 0xFF);
int b = (int)(argb & 0xFF);
return Color.FromArgb (r, g, b);
}
else if (hex.Length == 8)
{
int a = (int)((argb >> 24) & 0xFF);
int r = (int)((argb >> 16) & 0xFF);
int g = (int)((argb >> 8) & 0xFF);
int b = (int)(argb & 0xFF);
return Color.FromArgb (a, r, g, b);
}
else
{
return Color.Transparent;
}
}
// rgb()/rgba() functional notation
// Accept forms like: rgb(255,0,0) or rgba(255,0,0,0.5) or rgb(100%,0%,0%)
var m = Regex.Match (s, @"^(rgba?)\s*\(\s*([^\)]+)\s*\)$", RegexOptions.IgnoreCase);
if (m.Success)
{
string func = m.Groups [1].Value.ToLowerInvariant ();
string inside = m.Groups [2].Value;
string [] parts = inside.Split (new char [] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 3)
{
try
{
Func<string, int> comp = (string v) => {
v = v.Trim ();
if (v.EndsWith ("%"))
{
// percentage
string num = v.TrimEnd ('%');
float p;
if (float.TryParse (num, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out p))
{
return (int)Math.Round (Math.Max (0, Math.Min (100, p)) * 255.0 / 100.0);
}
return 0;
}
else
{
int iv;
if (int.TryParse (v, out iv))
{
return Math.Max (0, Math.Min (255, iv));
}
// fallback parse float
float fv;
if (float.TryParse (v, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out fv))
{
return Math.Max (0, Math.Min (255, (int)Math.Round (fv)));
}
return 0;
}
};
int r = comp (parts [0]);
int g = comp (parts [1]);
int b = comp (parts [2]);
int a = 255;
if (func == "rgba" && parts.Length >= 4)
{
string av = parts [3].Trim ();
if (av.EndsWith ("%"))
{
string num = av.TrimEnd ('%');
float p;
if (float.TryParse (num, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out p))
{
a = (int)Math.Round (Math.Max (0, Math.Min (100, p)) * 255.0 / 100.0);
}
}
else
{
// alpha may be 0..1 or 0..255
float af;
if (float.TryParse (av, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out af))
{
if (af <= 1.0f) a = (int)Math.Round (Math.Max (0.0f, Math.Min (1.0f, af)) * 255.0f);
else a = (int)Math.Round (Math.Max (0.0f, Math.Min (255.0f, af)));
}
}
}
return Color.FromArgb (a, r, g, b);
}
catch
{
return Color.Transparent;
}
}
}
// Named color
try
{
Color byName = Color.FromName (s);
if (byName.IsKnownColor || byName.IsNamedColor)
{
return byName;
}
}
catch { /* ignore */ }
// fallback: try parse as known color again (case-insensitive)
try
{
Color named = Color.FromName (s);
if (named.IsKnownColor || named.IsNamedColor) return named;
}
catch { }
return Color.Transparent;
}
public static bool IsAppInDarkMode ()
{
try
{
// HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme
object val = Registry.GetValue (
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
"AppsUseLightTheme",
null);
if (val == null) return false; // default to light? original returned false
int intVal;
if (val is int) intVal = (int)val;
else
{
if (!int.TryParse (val.ToString (), out intVal)) return false;
}
// 0 => dark, 1 => light
return intVal == 0;
}
catch
{
return false;
}
}
public static bool AppDarkMode => IsAppInDarkMode ();
}
}