mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 09:14:25 +08:00
0.1.4
This commit is contained in:
32
LanMontainDesktop/Services/GlassEffectService.cs
Normal file
32
LanMontainDesktop/Services/GlassEffectService.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace LanMontainDesktop.Services;
|
||||
|
||||
public static class GlassEffectService
|
||||
{
|
||||
public static void ApplyGlassResources(IResourceDictionary resources, bool isLightBackground)
|
||||
{
|
||||
if (isLightBackground)
|
||||
{
|
||||
resources["AdaptiveButtonBackgroundBrush"] = new SolidColorBrush(Color.Parse("#80FFFFFF"));
|
||||
resources["AdaptiveButtonBorderBrush"] = new SolidColorBrush(Color.Parse("#80475569"));
|
||||
resources["AdaptiveButtonHoverBackgroundBrush"] = new SolidColorBrush(Color.Parse("#B3FFFFFF"));
|
||||
resources["AdaptiveButtonPressedBackgroundBrush"] = new SolidColorBrush(Color.Parse("#D9F8FAFC"));
|
||||
resources["AdaptiveGlassPanelBackgroundBrush"] = new SolidColorBrush(Color.Parse("#A6FFFFFF"));
|
||||
resources["AdaptiveGlassPanelBorderBrush"] = new SolidColorBrush(Color.Parse("#80475569"));
|
||||
resources["AdaptiveGlassStrongBackgroundBrush"] = new SolidColorBrush(Color.Parse("#CCFFFFFF"));
|
||||
resources["AdaptiveGlassStrongBorderBrush"] = new SolidColorBrush(Color.Parse("#80475569"));
|
||||
return;
|
||||
}
|
||||
|
||||
resources["AdaptiveButtonBackgroundBrush"] = new SolidColorBrush(Color.Parse("#66334155"));
|
||||
resources["AdaptiveButtonBorderBrush"] = new SolidColorBrush(Color.Parse("#80E2E8F0"));
|
||||
resources["AdaptiveButtonHoverBackgroundBrush"] = new SolidColorBrush(Color.Parse("#88475A74"));
|
||||
resources["AdaptiveButtonPressedBackgroundBrush"] = new SolidColorBrush(Color.Parse("#AA2A3B55"));
|
||||
resources["AdaptiveGlassPanelBackgroundBrush"] = new SolidColorBrush(Color.Parse("#70233448"));
|
||||
resources["AdaptiveGlassPanelBorderBrush"] = new SolidColorBrush(Color.Parse("#70475569"));
|
||||
resources["AdaptiveGlassStrongBackgroundBrush"] = new SolidColorBrush(Color.Parse("#A01E293B"));
|
||||
resources["AdaptiveGlassStrongBorderBrush"] = new SolidColorBrush(Color.Parse("#80475569"));
|
||||
}
|
||||
}
|
||||
250
LanMontainDesktop/Services/MonetColorService.cs
Normal file
250
LanMontainDesktop/Services/MonetColorService.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using LanMontainDesktop.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LanMontainDesktop.Services;
|
||||
|
||||
public sealed class MonetColorService
|
||||
{
|
||||
public MonetPalette BuildPalette(Bitmap? wallpaper, bool nightMode)
|
||||
{
|
||||
var recommended = BuildRecommendedPalette(nightMode);
|
||||
var seed = TryExtractSeedColor(wallpaper) ?? TryGetSystemMonetSeedColor() ?? Color.Parse("#FF3B82F6");
|
||||
var monet = BuildMonetPalette(seed, nightMode);
|
||||
return new MonetPalette(recommended, monet);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Color> BuildRecommendedPalette(bool nightMode)
|
||||
{
|
||||
if (nightMode)
|
||||
{
|
||||
return
|
||||
[
|
||||
Color.Parse("#FF3B82F6"),
|
||||
Color.Parse("#FF22C55E"),
|
||||
Color.Parse("#FFF59E0B"),
|
||||
Color.Parse("#FFF97316"),
|
||||
Color.Parse("#FFA855F7"),
|
||||
Color.Parse("#FFEF4444")
|
||||
];
|
||||
}
|
||||
|
||||
return
|
||||
[
|
||||
Color.Parse("#FF1D4ED8"),
|
||||
Color.Parse("#FF15803D"),
|
||||
Color.Parse("#FFB45309"),
|
||||
Color.Parse("#FFC2410C"),
|
||||
Color.Parse("#FF7E22CE"),
|
||||
Color.Parse("#FFB91C1C")
|
||||
];
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Color> BuildMonetPalette(Color seed, bool nightMode)
|
||||
{
|
||||
var (hue, saturation, value) = ToHsv(seed);
|
||||
var valueBase = nightMode ? Math.Max(0.70, value) : Math.Min(0.72, Math.Max(0.35, value));
|
||||
var saturationBase = Math.Clamp(saturation, 0.22, 0.74);
|
||||
var offsets = new[] { 0d, 16d, -16d, 36d, -36d, 180d };
|
||||
var palette = new List<Color>(offsets.Length);
|
||||
|
||||
for (var i = 0; i < offsets.Length; i++)
|
||||
{
|
||||
var hueShift = NormalizeHue(hue + offsets[i]);
|
||||
var sat = Math.Clamp(saturationBase + ((i % 2 == 0) ? 0.05 : -0.05), 0.18, 0.86);
|
||||
var val = Math.Clamp(valueBase + ((i < 3) ? 0.06 : -0.04), 0.32, 0.92);
|
||||
palette.Add(FromHsv(hueShift, sat, val));
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
private static Color? TryExtractSeedColor(Bitmap? wallpaper)
|
||||
{
|
||||
if (wallpaper is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sampleWidth = Math.Clamp(wallpaper.PixelSize.Width, 1, 48);
|
||||
var sampleHeight = Math.Clamp(wallpaper.PixelSize.Height, 1, 48);
|
||||
|
||||
using var scaledBitmap = wallpaper.CreateScaledBitmap(
|
||||
new PixelSize(sampleWidth, sampleHeight),
|
||||
BitmapInterpolationMode.MediumQuality);
|
||||
using var writeable = new WriteableBitmap(
|
||||
scaledBitmap.PixelSize,
|
||||
new Vector(96, 96),
|
||||
PixelFormat.Bgra8888,
|
||||
AlphaFormat.Premul);
|
||||
using var framebuffer = writeable.Lock();
|
||||
scaledBitmap.CopyPixels(framebuffer, AlphaFormat.Premul);
|
||||
|
||||
var byteCount = framebuffer.RowBytes * framebuffer.Size.Height;
|
||||
if (byteCount <= 0 || framebuffer.Address == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var pixelBuffer = new byte[byteCount];
|
||||
Marshal.Copy(framebuffer.Address, pixelBuffer, 0, byteCount);
|
||||
|
||||
double bestScore = double.MinValue;
|
||||
Color? bestColor = null;
|
||||
|
||||
for (var y = 0; y < framebuffer.Size.Height; y++)
|
||||
{
|
||||
var rowOffset = y * framebuffer.RowBytes;
|
||||
for (var x = 0; x < framebuffer.Size.Width; x++)
|
||||
{
|
||||
var index = rowOffset + (x * 4);
|
||||
var alpha = pixelBuffer[index + 3] / 255d;
|
||||
if (alpha <= 0.15)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var blue = (pixelBuffer[index] / 255d) / alpha;
|
||||
var green = (pixelBuffer[index + 1] / 255d) / alpha;
|
||||
var red = (pixelBuffer[index + 2] / 255d) / alpha;
|
||||
red = Math.Clamp(red, 0, 1);
|
||||
green = Math.Clamp(green, 0, 1);
|
||||
blue = Math.Clamp(blue, 0, 1);
|
||||
|
||||
var color = Color.FromRgb(
|
||||
(byte)Math.Round(red * 255),
|
||||
(byte)Math.Round(green * 255),
|
||||
(byte)Math.Round(blue * 255));
|
||||
var (_, saturation, value) = ToHsv(color);
|
||||
var score = (saturation * 1.8) + (value * 0.6);
|
||||
if (score <= bestScore)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestScore = score;
|
||||
bestColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
return bestColor;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color? TryGetSystemMonetSeedColor()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var value = Registry.GetValue(
|
||||
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM",
|
||||
"AccentColor",
|
||||
null);
|
||||
if (value is not int accentDword)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bytes = BitConverter.GetBytes(accentDword);
|
||||
var blue = bytes[0];
|
||||
var green = bytes[1];
|
||||
var red = bytes[2];
|
||||
return Color.FromRgb(red, green, blue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static (double Hue, double Saturation, double Value) ToHsv(Color color)
|
||||
{
|
||||
var red = color.R / 255d;
|
||||
var green = color.G / 255d;
|
||||
var blue = color.B / 255d;
|
||||
var max = Math.Max(red, Math.Max(green, blue));
|
||||
var min = Math.Min(red, Math.Min(green, blue));
|
||||
var delta = max - min;
|
||||
|
||||
double hue;
|
||||
if (delta < 0.0001)
|
||||
{
|
||||
hue = 0;
|
||||
}
|
||||
else if (Math.Abs(max - red) < 0.0001)
|
||||
{
|
||||
hue = 60 * (((green - blue) / delta) % 6);
|
||||
}
|
||||
else if (Math.Abs(max - green) < 0.0001)
|
||||
{
|
||||
hue = 60 * (((blue - red) / delta) + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
hue = 60 * (((red - green) / delta) + 4);
|
||||
}
|
||||
|
||||
hue = NormalizeHue(hue);
|
||||
var saturation = max <= 0.0001 ? 0 : delta / max;
|
||||
return (hue, saturation, max);
|
||||
}
|
||||
|
||||
private static Color FromHsv(double hue, double saturation, double value)
|
||||
{
|
||||
hue = NormalizeHue(hue);
|
||||
saturation = Math.Clamp(saturation, 0, 1);
|
||||
value = Math.Clamp(value, 0, 1);
|
||||
|
||||
if (saturation <= 0.0001)
|
||||
{
|
||||
var gray = (byte)Math.Round(value * 255);
|
||||
return Color.FromRgb(gray, gray, gray);
|
||||
}
|
||||
|
||||
var chroma = value * saturation;
|
||||
var x = chroma * (1 - Math.Abs(((hue / 60d) % 2) - 1));
|
||||
var m = value - chroma;
|
||||
|
||||
(double r, double g, double b) = hue switch
|
||||
{
|
||||
>= 0 and < 60 => (chroma, x, 0d),
|
||||
>= 60 and < 120 => (x, chroma, 0d),
|
||||
>= 120 and < 180 => (0d, chroma, x),
|
||||
>= 180 and < 240 => (0d, x, chroma),
|
||||
>= 240 and < 300 => (x, 0d, chroma),
|
||||
_ => (chroma, 0d, x)
|
||||
};
|
||||
|
||||
var red = (byte)Math.Round((r + m) * 255);
|
||||
var green = (byte)Math.Round((g + m) * 255);
|
||||
var blue = (byte)Math.Round((b + m) * 255);
|
||||
return Color.FromRgb(red, green, blue);
|
||||
}
|
||||
|
||||
private static double NormalizeHue(double hue)
|
||||
{
|
||||
hue %= 360;
|
||||
if (hue < 0)
|
||||
{
|
||||
hue += 360;
|
||||
}
|
||||
|
||||
return hue;
|
||||
}
|
||||
}
|
||||
80
LanMontainDesktop/Services/ThemeColorSystemService.cs
Normal file
80
LanMontainDesktop/Services/ThemeColorSystemService.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using LanMontainDesktop.Theme;
|
||||
|
||||
namespace LanMontainDesktop.Services;
|
||||
|
||||
public static class ThemeColorSystemService
|
||||
{
|
||||
public static void ApplyThemeResources(
|
||||
IResourceDictionary resources,
|
||||
ThemeColorContext context)
|
||||
{
|
||||
var palette = BuildPalette(context);
|
||||
|
||||
resources["AdaptiveTextPrimaryBrush"] = new SolidColorBrush(palette.TextPrimary);
|
||||
resources["AdaptiveTextSecondaryBrush"] = new SolidColorBrush(palette.TextSecondary);
|
||||
resources["AdaptiveTextMutedBrush"] = new SolidColorBrush(palette.TextMuted);
|
||||
resources["AdaptiveTextAccentBrush"] = new SolidColorBrush(palette.TextAccent);
|
||||
resources["AdaptiveNavTextBrush"] = new SolidColorBrush(palette.NavText);
|
||||
resources["AdaptiveNavSelectedTextBrush"] = new SolidColorBrush(palette.NavSelectedText);
|
||||
resources["AdaptiveNavItemBackgroundBrush"] = new SolidColorBrush(palette.NavItemBackground);
|
||||
resources["AdaptiveNavItemHoverBackgroundBrush"] = new SolidColorBrush(palette.NavItemHoverBackground);
|
||||
resources["AdaptiveNavItemSelectedBackgroundBrush"] = new SolidColorBrush(palette.NavItemSelectedBackground);
|
||||
}
|
||||
|
||||
public static void ApplyThemeResources(
|
||||
IResourceDictionary resources,
|
||||
Color accentColor,
|
||||
bool isLightBackground,
|
||||
bool isLightNavBackground)
|
||||
{
|
||||
ApplyThemeResources(resources, new ThemeColorContext(
|
||||
accentColor,
|
||||
isLightBackground,
|
||||
isLightNavBackground,
|
||||
!isLightBackground));
|
||||
}
|
||||
|
||||
private static AppThemePalette BuildPalette(ThemeColorContext context)
|
||||
{
|
||||
var textPrimary = context.IsLightBackground ? Color.Parse("#FF0B1220") : Color.Parse("#FFF8FAFC");
|
||||
var textSecondary = context.IsLightBackground ? Color.Parse("#FF1E293B") : Color.Parse("#FFE2E8F0");
|
||||
var textMuted = context.IsLightBackground ? Color.Parse("#FF475569") : Color.Parse("#FF94A3B8");
|
||||
var textAccent = context.IsLightBackground
|
||||
? BlendColor(context.AccentColor, Color.Parse("#FF0B1220"), 0.20)
|
||||
: BlendColor(context.AccentColor, Color.Parse("#FFFFFFFF"), 0.16);
|
||||
|
||||
var navText = context.IsLightNavBackground ? Color.Parse("#FF0B1220") : Color.Parse("#FFF8FAFC");
|
||||
var navSelectedText = Color.Parse("#FFFFFFFF");
|
||||
var navItemBackground = context.IsLightNavBackground ? Color.Parse("#40FFFFFF") : Color.Parse("#220F172A");
|
||||
var navItemHoverBackground = context.IsLightNavBackground ? Color.Parse("#66E2E8F0") : Color.Parse("#40334155");
|
||||
var navItemSelectedBackground = Color.FromArgb(
|
||||
0xCC,
|
||||
context.AccentColor.R,
|
||||
context.AccentColor.G,
|
||||
context.AccentColor.B);
|
||||
|
||||
return new AppThemePalette(
|
||||
textPrimary,
|
||||
textSecondary,
|
||||
textMuted,
|
||||
textAccent,
|
||||
navText,
|
||||
navSelectedText,
|
||||
navItemBackground,
|
||||
navItemHoverBackground,
|
||||
navItemSelectedBackground);
|
||||
}
|
||||
|
||||
private static Color BlendColor(Color from, Color to, double ratio)
|
||||
{
|
||||
ratio = Math.Clamp(ratio, 0, 1);
|
||||
var inverse = 1 - ratio;
|
||||
var red = (byte)Math.Round((from.R * inverse) + (to.R * ratio));
|
||||
var green = (byte)Math.Round((from.G * inverse) + (to.G * ratio));
|
||||
var blue = (byte)Math.Round((from.B * inverse) + (to.B * ratio));
|
||||
return Color.FromRgb(red, green, blue);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user