Files
ReLibreHardwareMonitor/LibreHardwareMonitor.Windows.WinUI/Services/SensorIconRenderer.cs
T

140 lines
5.8 KiB
C#

// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
using System;
using System.Globalization;
using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Windows.WinUI.ViewModels;
using static LibreHardwareMonitor.Windows.WinUI.Services.TrayIconInterop;
using WinUIColor = Windows.UI.Color;
namespace LibreHardwareMonitor.Windows.WinUI.Services;
/// <summary>
/// Renders a sensor's current value into a 16x16 GDI icon (solid background tinted by the sensor's tray color, with the
/// value drawn on top) for display in the system tray. Extracted from <see cref="TrayIconService" /> to keep the GDI
/// drawing separate from the tray orchestration.
/// </summary>
internal sealed class SensorIconRenderer
{
private const int IconSize = 16;
private readonly AppSettings _settings;
private readonly Func<TemperatureUnit> _temperatureUnitProvider;
public SensorIconRenderer(AppSettings settings, Func<TemperatureUnit> temperatureUnitProvider)
{
_settings = settings;
_temperatureUnitProvider = temperatureUnitProvider;
}
/// <summary>Creates an icon handle for the sensor. The caller owns the handle and must DestroyIcon() it.</summary>
public IntPtr CreateIcon(ISensor sensor)
{
IntPtr hdc = GetDC(IntPtr.Zero);
IntPtr memoryDc = CreateCompatibleDC(hdc);
IntPtr bitmap = IntPtr.Zero;
IntPtr mask = IntPtr.Zero;
IntPtr oldBitmap = IntPtr.Zero;
IntPtr font = IntPtr.Zero;
IntPtr oldFont = IntPtr.Zero;
IntPtr brush = IntPtr.Zero;
try
{
BitmapInfo bitmapInfo = BitmapInfo.Create(IconSize, IconSize);
bitmap = CreateDIBSection(hdc, ref bitmapInfo, 0, out _, IntPtr.Zero, 0);
if (bitmap == IntPtr.Zero)
return IntPtr.Zero; // never return the shared main icon: callers DestroyIcon() the returned handle
oldBitmap = SelectObject(memoryDc, bitmap);
WinUIColor color = GetSensorTrayColor(sensor);
brush = CreateSolidBrush(ToColorRef(color));
Rect fill = new() { Left = 0, Top = 0, Right = IconSize, Bottom = IconSize };
FillRect(memoryDc, ref fill, brush);
font = CreateFont(-11, 0, 0, 0, 700, 0, 0, 0, 0, 0, 0, 0, 0, "Segoe UI");
oldFont = SelectObject(memoryDc, font);
SetBkMode(memoryDc, 1);
SetTextColor(memoryDc, ToColorRef(WinUIColor.FromArgb(255, 255, 255, 255)));
string text = GetSensorIconText(sensor);
Rect textRect = new() { Left = 0, Top = 0, Right = IconSize, Bottom = IconSize };
DrawText(memoryDc, text, text.Length, ref textRect, 0x00000001 | 0x00000004 | 0x00000020);
mask = CreateBitmap(IconSize, IconSize, 1, 1, IntPtr.Zero);
IconInfo iconInfo = new()
{
IsIcon = true,
ColorBitmap = bitmap,
MaskBitmap = mask
};
return CreateIconIndirect(ref iconInfo);
}
finally
{
if (oldFont != IntPtr.Zero)
SelectObject(memoryDc, oldFont);
if (oldBitmap != IntPtr.Zero)
SelectObject(memoryDc, oldBitmap);
if (font != IntPtr.Zero)
DeleteObject(font);
if (brush != IntPtr.Zero)
DeleteObject(brush);
if (bitmap != IntPtr.Zero)
DeleteObject(bitmap);
if (mask != IntPtr.Zero)
DeleteObject(mask);
if (memoryDc != IntPtr.Zero)
DeleteDC(memoryDc);
if (hdc != IntPtr.Zero)
ReleaseDC(IntPtr.Zero, hdc);
}
}
/// <summary>
/// Returns a key identifying the pixels <see cref="CreateIcon" /> would produce (the drawn text and the background
/// color). When it is unchanged the previously created icon can be reused instead of re-rendering a new GDI icon.
/// </summary>
public string GetRenderKey(ISensor sensor)
{
WinUIColor color = GetSensorTrayColor(sensor);
return $"{GetSensorIconText(sensor)}|{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}";
}
private WinUIColor GetSensorTrayColor(ISensor sensor)
{
WinUIColor defaultColor = sensor.SensorType is SensorType.Load or SensorType.Control or SensorType.Level
? WinUIColor.FromArgb(255, 0x70, 0x8c, 0xf1)
: WinUIColor.FromArgb(255, 0x00, 0x78, 0xd4);
return _settings.GetValue(SensorSelectionService.GetSensorSettingName(sensor, "traycolor"), defaultColor);
}
private string GetSensorIconText(ISensor sensor)
{
if (!sensor.Value.HasValue)
return "-";
float value = sensor.Value.Value;
if (sensor.SensorType == SensorType.Temperature && _temperatureUnitProvider() == TemperatureUnit.Fahrenheit)
value = value * 1.8f + 32;
return sensor.SensorType switch
{
SensorType.TimeSpan => TimeSpan.FromSeconds(value).ToString("g", CultureInfo.CurrentCulture),
SensorType.Timing => value.ToString("F1", CultureInfo.CurrentCulture),
SensorType.Clock or SensorType.Fan or SensorType.Flow => (value * 1e-3f).ToString("F1", CultureInfo.CurrentCulture),
SensorType.Voltage or SensorType.Current or SensorType.SmallData or SensorType.Factor or SensorType.Throughput or SensorType.Conductivity => value.ToString("F1", CultureInfo.CurrentCulture),
_ => value.ToString("F0", CultureInfo.CurrentCulture)
};
}
private static uint ToColorRef(WinUIColor color)
{
return (uint)(color.R | (color.G << 8) | (color.B << 16));
}
}