mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
0.1.4
This commit is contained in:
8
LanMontainDesktop/Models/MonetPalette.cs
Normal file
8
LanMontainDesktop/Models/MonetPalette.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace LanMontainDesktop.Models;
|
||||
|
||||
public sealed record MonetPalette(
|
||||
IReadOnlyList<Color> RecommendedColors,
|
||||
IReadOnlyList<Color> MonetColors);
|
||||
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);
|
||||
}
|
||||
}
|
||||
54
LanMontainDesktop/Styles/GlassModule.axaml
Normal file
54
LanMontainDesktop/Styles/GlassModule.axaml
Normal file
@@ -0,0 +1,54 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls">
|
||||
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonHoverBackgroundBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pressed">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonPressedBackgroundBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonHoverBackgroundBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ui|NumberBox">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ToggleSwitch">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.glass-panel">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveGlassPanelBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveGlassPanelBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.glass-strong">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveGlassStrongBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveGlassStrongBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
</Styles>
|
||||
14
LanMontainDesktop/Theme/AppThemePalette.cs
Normal file
14
LanMontainDesktop/Theme/AppThemePalette.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace LanMontainDesktop.Theme;
|
||||
|
||||
public sealed record AppThemePalette(
|
||||
Color TextPrimary,
|
||||
Color TextSecondary,
|
||||
Color TextMuted,
|
||||
Color TextAccent,
|
||||
Color NavText,
|
||||
Color NavSelectedText,
|
||||
Color NavItemBackground,
|
||||
Color NavItemHoverBackground,
|
||||
Color NavItemSelectedBackground);
|
||||
9
LanMontainDesktop/Theme/ThemeColorContext.cs
Normal file
9
LanMontainDesktop/Theme/ThemeColorContext.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace LanMontainDesktop.Theme;
|
||||
|
||||
public sealed record ThemeColorContext(
|
||||
Color AccentColor,
|
||||
bool IsLightBackground,
|
||||
bool IsLightNavBackground,
|
||||
bool IsNightMode);
|
||||
@@ -9,17 +9,17 @@
|
||||
|
||||
<Border x:Name="RootBorder"
|
||||
Padding="8"
|
||||
CornerRadius="8"
|
||||
BorderBrush="#80A5B4FC"
|
||||
BorderThickness="1"
|
||||
Background="#CC0F172A">
|
||||
CornerRadius="0"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
Background="Transparent">
|
||||
<TextBlock x:Name="TimeTextBlock"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
FontSize="26"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="White" />
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
WindowState="FullScreen"
|
||||
SystemDecorations="None"
|
||||
CanResize="False"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Background="#FF020617"
|
||||
Title="LanMontainDesktop">
|
||||
|
||||
@@ -21,71 +22,563 @@
|
||||
<vm:MainWindowViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Window.Resources>
|
||||
<SolidColorBrush x:Key="AdaptiveTextPrimaryBrush" Color="#FFF8FAFC" />
|
||||
<SolidColorBrush x:Key="AdaptiveTextSecondaryBrush" Color="#FFE2E8F0" />
|
||||
<SolidColorBrush x:Key="AdaptiveTextMutedBrush" Color="#FF94A3B8" />
|
||||
<SolidColorBrush x:Key="AdaptiveTextAccentBrush" Color="#FF93C5FD" />
|
||||
<SolidColorBrush x:Key="AdaptiveButtonBackgroundBrush" Color="#66334155" />
|
||||
<SolidColorBrush x:Key="AdaptiveButtonBorderBrush" Color="#80E2E8F0" />
|
||||
<SolidColorBrush x:Key="AdaptiveButtonHoverBackgroundBrush" Color="#88475A74" />
|
||||
<SolidColorBrush x:Key="AdaptiveButtonPressedBackgroundBrush" Color="#AA2A3B55" />
|
||||
<SolidColorBrush x:Key="AdaptiveGlassPanelBackgroundBrush" Color="#70233448" />
|
||||
<SolidColorBrush x:Key="AdaptiveGlassPanelBorderBrush" Color="#70475569" />
|
||||
<SolidColorBrush x:Key="AdaptiveGlassStrongBackgroundBrush" Color="#A01E293B" />
|
||||
<SolidColorBrush x:Key="AdaptiveGlassStrongBorderBrush" Color="#80475569" />
|
||||
<SolidColorBrush x:Key="AdaptiveNavTextBrush" Color="#FFF8FAFC" />
|
||||
<SolidColorBrush x:Key="AdaptiveNavSelectedTextBrush" Color="#FFFFFFFF" />
|
||||
<SolidColorBrush x:Key="AdaptiveNavItemBackgroundBrush" Color="#220F172A" />
|
||||
<SolidColorBrush x:Key="AdaptiveNavItemHoverBackgroundBrush" Color="#40334155" />
|
||||
<SolidColorBrush x:Key="AdaptiveNavItemSelectedBackgroundBrush" Color="#CC1D4ED8" />
|
||||
</Window.Resources>
|
||||
|
||||
<Window.Styles>
|
||||
<StyleInclude Source="avares://LanMontainDesktop/Styles/GlassModule.axaml" />
|
||||
</Window.Styles>
|
||||
|
||||
<Grid>
|
||||
<Border x:Name="DesktopHost"
|
||||
ClipToBounds="True"
|
||||
Background="#FF020617">
|
||||
<Grid x:Name="DesktopGrid"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
ShowGridLines="False">
|
||||
<comp:ClockWidget x:Name="ClockWidget"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="4" />
|
||||
<Grid x:Name="DesktopPage">
|
||||
<Grid.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.24" />
|
||||
</Transitions>
|
||||
</Grid.Transitions>
|
||||
|
||||
<Button x:Name="BackToWindowsButton"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="4"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Click="OnMinimizeClick"
|
||||
Content="回到Windows" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border x:Name="DesktopHost"
|
||||
ClipToBounds="True"
|
||||
Background="#FF020617">
|
||||
<Grid x:Name="DesktopGrid"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
ShowGridLines="False">
|
||||
<comp:ClockWidget x:Name="ClockWidget"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="4" />
|
||||
|
||||
<Border HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="10"
|
||||
Padding="10"
|
||||
CornerRadius="10"
|
||||
Background="#BF111827"
|
||||
BorderBrush="#80334155"
|
||||
BorderThickness="1">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto"
|
||||
RowDefinitions="Auto,Auto"
|
||||
ColumnSpacing="8"
|
||||
RowSpacing="6">
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#FFE5E7EB"
|
||||
Text="短边格数" />
|
||||
<ui:NumberBox x:Name="GridSizeNumberBox"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="100"
|
||||
Minimum="6"
|
||||
Maximum="96"
|
||||
Value="12" />
|
||||
<Button Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Padding="12,6"
|
||||
Click="OnApplyGridSizeClick"
|
||||
Content="应用" />
|
||||
<Border x:Name="BackToWindowsContainer"
|
||||
Classes="glass-panel"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="4"
|
||||
CornerRadius="10">
|
||||
<Button x:Name="BackToWindowsButton"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Click="OnMinimizeClick"
|
||||
Content="回到Windows" />
|
||||
</Border>
|
||||
|
||||
<TextBlock x:Name="GridInfoTextBlock"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Foreground="#FF93C5FD"
|
||||
Text="Grid: - cols x - rows (1:1)" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Button x:Name="OpenSettingsButton"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="4"
|
||||
Padding="16,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Click="OnOpenSettingsClick"
|
||||
Content="设置" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid x:Name="SettingsPage"
|
||||
IsVisible="False"
|
||||
Opacity="0">
|
||||
<Grid.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.24" />
|
||||
</Transitions>
|
||||
</Grid.Transitions>
|
||||
|
||||
<Border x:Name="SettingsBackdropOverlay"
|
||||
Classes="glass-strong"
|
||||
BorderThickness="0" />
|
||||
|
||||
<Border x:Name="SettingsContentPanel"
|
||||
Classes="glass-strong"
|
||||
Margin="24"
|
||||
Padding="24"
|
||||
MaxWidth="1240"
|
||||
CornerRadius="16">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform Y="30">
|
||||
<TranslateTransform.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Y" Duration="0:0:0.24" />
|
||||
</Transitions>
|
||||
</TranslateTransform.Transitions>
|
||||
</TranslateTransform>
|
||||
</Border.RenderTransform>
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto"
|
||||
RowSpacing="16">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Text="设置" />
|
||||
<Button Grid.Column="1"
|
||||
Padding="14,8"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Click="OnCloseSettingsClick"
|
||||
Content="返回桌面" />
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Classes="glass-strong"
|
||||
CornerRadius="14"
|
||||
Padding="18">
|
||||
<Grid ColumnDefinitions="220,*"
|
||||
ColumnSpacing="16">
|
||||
<Border x:Name="SettingsNavPanelBorder"
|
||||
Classes="glass-panel"
|
||||
Grid.Column="0"
|
||||
CornerRadius="10"
|
||||
Padding="10">
|
||||
<Border.Styles>
|
||||
<Style Selector="ListBox#SettingsNavListBox ListBoxItem">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveNavTextBrush}" />
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveNavItemBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Padding" Value="10,8" />
|
||||
<Setter Property="Margin" Value="0,2" />
|
||||
</Style>
|
||||
<Style Selector="ListBox#SettingsNavListBox ListBoxItem:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveNavItemHoverBackgroundBrush}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox#SettingsNavListBox ListBoxItem:selected">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveNavSelectedTextBrush}" />
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveNavItemSelectedBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
|
||||
</Style>
|
||||
</Border.Styles>
|
||||
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="设置选项" />
|
||||
<ListBox x:Name="SettingsNavListBox"
|
||||
SelectionChanged="OnSettingsNavSelectionChanged">
|
||||
<ListBoxItem Content="壁纸" />
|
||||
<ListBoxItem Content="网格" />
|
||||
<ListBoxItem Content="颜色" />
|
||||
</ListBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="1"
|
||||
Classes="glass-panel"
|
||||
CornerRadius="10"
|
||||
Padding="14">
|
||||
<Grid>
|
||||
<StackPanel x:Name="WallpaperSettingsPanel"
|
||||
IsVisible="True"
|
||||
Spacing="14">
|
||||
<TextBlock FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="壁纸" />
|
||||
<TextBlock Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="选择图片后可立即设为应用窗口壁纸。" />
|
||||
|
||||
<Border x:Name="WallpaperPreviewHost"
|
||||
Classes="glass-panel"
|
||||
CornerRadius="10"
|
||||
Padding="10">
|
||||
<Border x:Name="WallpaperPreviewFrame"
|
||||
HorizontalAlignment="Center"
|
||||
Width="360"
|
||||
Height="220"
|
||||
CornerRadius="14"
|
||||
BorderBrush="{DynamicResource AdaptiveButtonBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Background="#22000000">
|
||||
<Border x:Name="WallpaperPreviewViewport"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="13"
|
||||
Background="#30111827">
|
||||
<Grid x:Name="WallpaperPreviewGrid"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
ShowGridLines="False">
|
||||
<Border x:Name="WallpaperPreviewClockContainer"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
CornerRadius="0"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Margin="3">
|
||||
<TextBlock x:Name="WallpaperPreviewClockTextBlock"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Text="12:34" />
|
||||
</Border>
|
||||
|
||||
<Border x:Name="WallpaperPreviewBackButtonContainer"
|
||||
Classes="glass-panel"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="3"
|
||||
CornerRadius="7">
|
||||
<TextBlock x:Name="WallpaperPreviewBackButtonTextBlock"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Text="回到Windows" />
|
||||
</Border>
|
||||
|
||||
<Border x:Name="WallpaperPreviewSettingsButtonContainer"
|
||||
Classes="glass-panel"
|
||||
Grid.Row="1"
|
||||
Grid.Column="4"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="3"
|
||||
CornerRadius="7">
|
||||
<TextBlock x:Name="WallpaperPreviewSettingsButtonTextBlock"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Text="设置" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
RowDefinitions="Auto,Auto,Auto"
|
||||
ColumnSpacing="10"
|
||||
RowSpacing="10">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="当前壁纸" />
|
||||
<TextBlock x:Name="WallpaperPathTextBlock"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Foreground="{DynamicResource AdaptiveTextAccentBrush}"
|
||||
Text="未选择壁纸" />
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="显示方式" />
|
||||
<ComboBox x:Name="WallpaperPlacementComboBox"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
SelectionChanged="OnWallpaperPlacementSelectionChanged">
|
||||
<ComboBoxItem Content="Fill" />
|
||||
<ComboBoxItem Content="Fit" />
|
||||
<ComboBoxItem Content="Stretch" />
|
||||
<ComboBoxItem Content="Center" />
|
||||
<ComboBoxItem Content="Tile" />
|
||||
</ComboBox>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button Padding="12,6"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Click="OnPickWallpaperClick"
|
||||
Content="浏览图片" />
|
||||
<Button Padding="12,6"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Click="OnClearWallpaperClick"
|
||||
Content="恢复纯色" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<TextBlock x:Name="WallpaperStatusTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextMutedBrush}"
|
||||
Text="当前使用纯色背景。" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel x:Name="GridSettingsPanel"
|
||||
IsVisible="False"
|
||||
Spacing="14">
|
||||
<TextBlock FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="Grid layout" />
|
||||
<TextBlock Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="Every component must occupy at least one cell (minimum 1x1)." />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto"
|
||||
RowDefinitions="Auto,Auto"
|
||||
ColumnSpacing="8"
|
||||
RowSpacing="8">
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="短边格数" />
|
||||
<ui:NumberBox x:Name="GridSizeNumberBox"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="120"
|
||||
Minimum="6"
|
||||
Maximum="96"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Value="12" />
|
||||
<Button Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Padding="12,6"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Click="OnApplyGridSizeClick"
|
||||
Content="应用" />
|
||||
|
||||
<TextBlock x:Name="GridInfoTextBlock"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Foreground="{DynamicResource AdaptiveTextAccentBrush}"
|
||||
Text="Grid: - cols x - rows (1:1)" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel x:Name="ColorSettingsPanel"
|
||||
IsVisible="False"
|
||||
Spacing="14">
|
||||
<TextBlock FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="Color" />
|
||||
<TextBlock Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="Switch day/night mode and pick app accent colors." />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,Auto,*"
|
||||
ColumnSpacing="12">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="日夜模式" />
|
||||
<ToggleSwitch x:Name="NightModeToggleSwitch"
|
||||
Grid.Column="1"
|
||||
OffContent="Day"
|
||||
OnContent="Night"
|
||||
Checked="OnNightModeChecked"
|
||||
Unchecked="OnNightModeUnchecked" />
|
||||
<TextBlock x:Name="ThemeModeStatusTextBlock"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextMutedBrush}"
|
||||
Text="Night mode enabled" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="Recommended Colors" />
|
||||
<WrapPanel ItemWidth="72"
|
||||
ItemHeight="56"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="RecommendedColorButton1"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnRecommendedColorClick">
|
||||
<Border x:Name="RecommendedColorSwatch1"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="RecommendedColorButton2"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnRecommendedColorClick">
|
||||
<Border x:Name="RecommendedColorSwatch2"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="RecommendedColorButton3"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnRecommendedColorClick">
|
||||
<Border x:Name="RecommendedColorSwatch3"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="RecommendedColorButton4"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnRecommendedColorClick">
|
||||
<Border x:Name="RecommendedColorSwatch4"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="RecommendedColorButton5"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnRecommendedColorClick">
|
||||
<Border x:Name="RecommendedColorSwatch5"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="RecommendedColorButton6"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnRecommendedColorClick">
|
||||
<Border x:Name="RecommendedColorSwatch6"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
</WrapPanel>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto"
|
||||
ColumnSpacing="10">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="System Monet Colors" />
|
||||
<Button Grid.Column="1"
|
||||
Padding="10,6"
|
||||
Click="OnRefreshMonetColorsClick"
|
||||
Content="Refresh" />
|
||||
</Grid>
|
||||
|
||||
<WrapPanel ItemWidth="72"
|
||||
ItemHeight="56"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="MonetColorButton1"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnMonetColorClick">
|
||||
<Border x:Name="MonetColorSwatch1"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="MonetColorButton2"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnMonetColorClick">
|
||||
<Border x:Name="MonetColorSwatch2"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="MonetColorButton3"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnMonetColorClick">
|
||||
<Border x:Name="MonetColorSwatch3"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="MonetColorButton4"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnMonetColorClick">
|
||||
<Border x:Name="MonetColorSwatch4"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="MonetColorButton5"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnMonetColorClick">
|
||||
<Border x:Name="MonetColorSwatch5"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
<Button x:Name="MonetColorButton6"
|
||||
Width="68"
|
||||
Height="50"
|
||||
Padding="8"
|
||||
Click="OnMonetColorClick">
|
||||
<Border x:Name="MonetColorSwatch6"
|
||||
Width="26"
|
||||
Height="26"
|
||||
CornerRadius="6"
|
||||
BorderBrush="#A0FFFFFF"
|
||||
BorderThickness="1" />
|
||||
</Button>
|
||||
</WrapPanel>
|
||||
|
||||
<TextBlock x:Name="ThemeColorStatusTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextMutedBrush}"
|
||||
Text="Theme color is ready." />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="2"
|
||||
Classes="glass-panel"
|
||||
CornerRadius="10"
|
||||
Padding="8,6">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextMutedBrush}"
|
||||
Text="LanMontainDesktop Settings" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
|
||||
@@ -1,16 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using LanMontainDesktop.Models;
|
||||
using LanMontainDesktop.Services;
|
||||
using LanMontainDesktop.Theme;
|
||||
|
||||
namespace LanMontainDesktop.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private enum WallpaperPlacement
|
||||
{
|
||||
Fill,
|
||||
Fit,
|
||||
Stretch,
|
||||
Center,
|
||||
Tile
|
||||
}
|
||||
|
||||
private const int StatusBarRowIndex = 0;
|
||||
private const int MinShortSideCells = 6;
|
||||
private const int MaxShortSideCells = 96;
|
||||
private const int SettingsTransitionDurationMs = 240;
|
||||
private const double LightBackgroundLuminanceThreshold = 0.57;
|
||||
private const double WallpaperPreviewMinWidth = 220;
|
||||
private const double WallpaperPreviewMinHeight = 140;
|
||||
private const double WallpaperPreviewMaxHeight = 280;
|
||||
private readonly record struct GridMetrics(int ColumnCount, int RowCount, double CellSize);
|
||||
private readonly MonetColorService _monetColorService = new();
|
||||
private int _targetShortSideCells;
|
||||
private bool _isSettingsOpen;
|
||||
private bool _isNightMode;
|
||||
private bool _suppressThemeToggleEvents;
|
||||
private TranslateTransform? _settingsContentPanelTransform;
|
||||
private IBrush? _defaultDesktopBackground;
|
||||
private Bitmap? _wallpaperBitmap;
|
||||
private string? _wallpaperPath;
|
||||
private string _wallpaperStatus = "Current background uses solid color.";
|
||||
private IReadOnlyList<Color> _recommendedColors = Array.Empty<Color>();
|
||||
private IReadOnlyList<Color> _monetColors = Array.Empty<Color>();
|
||||
private Color _selectedThemeColor = Color.Parse("#FF3B82F6");
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@@ -24,14 +62,31 @@ public partial class MainWindow : Window
|
||||
|
||||
_targetShortSideCells = CalculateDefaultShortSideCellCountFromDpi();
|
||||
GridSizeNumberBox.Value = _targetShortSideCells;
|
||||
SettingsNavListBox.SelectedIndex = 0;
|
||||
UpdateSettingsTabContent();
|
||||
WallpaperPlacementComboBox.SelectedIndex = 0;
|
||||
_defaultDesktopBackground = DesktopHost.Background;
|
||||
UpdateWallpaperDisplay();
|
||||
_isNightMode = CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold;
|
||||
ApplyNightModeState(_isNightMode, refreshPalettes: false);
|
||||
RefreshColorPalettes();
|
||||
EnsureSelectedThemeColor();
|
||||
UpdateThemeColorSelectionState();
|
||||
ThemeColorStatusTextBlock.Text = $"Theme color ready: {_selectedThemeColor}.";
|
||||
UpdateAdaptiveTextSystem();
|
||||
_settingsContentPanelTransform = SettingsContentPanel.RenderTransform as TranslateTransform;
|
||||
DesktopHost.SizeChanged += OnDesktopHostSizeChanged;
|
||||
WallpaperPreviewHost.SizeChanged += OnWallpaperPreviewHostSizeChanged;
|
||||
RebuildDesktopGrid();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
_wallpaperBitmap?.Dispose();
|
||||
_wallpaperBitmap = null;
|
||||
PropertyChanged -= OnWindowPropertyChanged;
|
||||
DesktopHost.SizeChanged -= OnDesktopHostSizeChanged;
|
||||
WallpaperPreviewHost.SizeChanged -= OnWallpaperPreviewHostSizeChanged;
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
@@ -47,6 +102,11 @@ public partial class MainWindow : Window
|
||||
RebuildDesktopGrid();
|
||||
}
|
||||
|
||||
private void OnWallpaperPreviewHostSizeChanged(object? sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateWallpaperPreviewLayout();
|
||||
}
|
||||
|
||||
private void OnApplyGridSizeClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var requested = (int)Math.Round(GridSizeNumberBox.Value);
|
||||
@@ -67,60 +127,114 @@ public partial class MainWindow : Window
|
||||
|
||||
private void RebuildDesktopGrid()
|
||||
{
|
||||
var hostWidth = DesktopHost.Bounds.Width;
|
||||
var hostHeight = DesktopHost.Bounds.Height;
|
||||
if (hostWidth <= 1 || hostHeight <= 1)
|
||||
var gridMetrics = CalculateGridMetrics(
|
||||
DesktopHost.Bounds.Width,
|
||||
DesktopHost.Bounds.Height,
|
||||
_targetShortSideCells);
|
||||
if (gridMetrics.CellSize <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var shortSideCells = Math.Max(1, _targetShortSideCells);
|
||||
double cellSize;
|
||||
int columnCount;
|
||||
int rowCount;
|
||||
DesktopGrid.RowDefinitions.Clear();
|
||||
DesktopGrid.ColumnDefinitions.Clear();
|
||||
DesktopGrid.Width = gridMetrics.ColumnCount * gridMetrics.CellSize;
|
||||
DesktopGrid.Height = gridMetrics.RowCount * gridMetrics.CellSize;
|
||||
|
||||
if (hostWidth >= hostHeight)
|
||||
for (var row = 0; row < gridMetrics.RowCount; row++)
|
||||
{
|
||||
rowCount = shortSideCells;
|
||||
cellSize = hostHeight / rowCount;
|
||||
columnCount = Math.Max(1, (int)Math.Ceiling(hostWidth / cellSize));
|
||||
DesktopGrid.RowDefinitions.Add(new RowDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
|
||||
}
|
||||
|
||||
for (var col = 0; col < gridMetrics.ColumnCount; col++)
|
||||
{
|
||||
DesktopGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
|
||||
}
|
||||
|
||||
PlaceStatusBarComponent(ClockWidget, column: 0, requestedColumnSpan: 3, totalColumns: gridMetrics.ColumnCount);
|
||||
|
||||
var firstDesktopRow = Math.Min(gridMetrics.RowCount - 1, StatusBarRowIndex + 1);
|
||||
|
||||
var settingsColumnSpan = ClampComponentSpan(2, gridMetrics.ColumnCount);
|
||||
var settingsRowSpan = ClampComponentSpan(1, gridMetrics.RowCount);
|
||||
var settingsRow = Math.Max(firstDesktopRow, gridMetrics.RowCount - 1);
|
||||
var settingsColumn = Math.Max(0, gridMetrics.ColumnCount - settingsColumnSpan);
|
||||
|
||||
var backButtonRow = settingsRow;
|
||||
var backButtonMaxColumnsWithoutOverlap = settingsColumn;
|
||||
int backButtonColumnSpan;
|
||||
if (backButtonMaxColumnsWithoutOverlap >= 1)
|
||||
{
|
||||
backButtonColumnSpan = ClampComponentSpan(Math.Min(4, backButtonMaxColumnsWithoutOverlap), gridMetrics.ColumnCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
columnCount = shortSideCells;
|
||||
cellSize = hostWidth / columnCount;
|
||||
rowCount = Math.Max(1, (int)Math.Ceiling(hostHeight / cellSize));
|
||||
backButtonRow = Math.Max(firstDesktopRow, gridMetrics.RowCount - 2);
|
||||
backButtonColumnSpan = ClampComponentSpan(Math.Min(4, gridMetrics.ColumnCount), gridMetrics.ColumnCount);
|
||||
}
|
||||
|
||||
DesktopGrid.RowDefinitions.Clear();
|
||||
DesktopGrid.ColumnDefinitions.Clear();
|
||||
DesktopGrid.Width = columnCount * cellSize;
|
||||
DesktopGrid.Height = rowCount * cellSize;
|
||||
Grid.SetRow(BackToWindowsContainer, backButtonRow);
|
||||
Grid.SetColumn(BackToWindowsContainer, 0);
|
||||
Grid.SetRowSpan(BackToWindowsContainer, ClampComponentSpan(1, gridMetrics.RowCount));
|
||||
Grid.SetColumnSpan(BackToWindowsContainer, backButtonColumnSpan);
|
||||
|
||||
for (var row = 0; row < rowCount; row++)
|
||||
{
|
||||
DesktopGrid.RowDefinitions.Add(new RowDefinition(new GridLength(cellSize, GridUnitType.Pixel)));
|
||||
}
|
||||
Grid.SetRow(OpenSettingsButton, settingsRow);
|
||||
Grid.SetColumn(OpenSettingsButton, settingsColumn);
|
||||
Grid.SetRowSpan(OpenSettingsButton, settingsRowSpan);
|
||||
Grid.SetColumnSpan(OpenSettingsButton, settingsColumnSpan);
|
||||
|
||||
for (var col = 0; col < columnCount; col++)
|
||||
{
|
||||
DesktopGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(cellSize, GridUnitType.Pixel)));
|
||||
}
|
||||
|
||||
Grid.SetRow(ClockWidget, 0);
|
||||
Grid.SetColumn(ClockWidget, 0);
|
||||
Grid.SetRowSpan(ClockWidget, 1);
|
||||
Grid.SetColumnSpan(ClockWidget, Math.Min(3, columnCount));
|
||||
|
||||
Grid.SetRow(BackToWindowsButton, rowCount - 1);
|
||||
Grid.SetColumn(BackToWindowsButton, 0);
|
||||
Grid.SetRowSpan(BackToWindowsButton, 1);
|
||||
Grid.SetColumnSpan(BackToWindowsButton, Math.Min(4, columnCount));
|
||||
|
||||
ApplyWidgetSizing(cellSize);
|
||||
ApplyWidgetSizing(gridMetrics.CellSize);
|
||||
|
||||
GridInfoTextBlock.Text =
|
||||
$"Grid: {columnCount} cols x {rowCount} rows | cell {cellSize:F1}px (1:1)";
|
||||
$"Grid: {gridMetrics.ColumnCount} cols x {gridMetrics.RowCount} rows | cell {gridMetrics.CellSize:F1}px (1:1)";
|
||||
|
||||
UpdateWallpaperPreviewLayout();
|
||||
}
|
||||
|
||||
private static GridMetrics CalculateGridMetrics(double hostWidth, double hostHeight, int targetShortSideCells)
|
||||
{
|
||||
if (hostWidth <= 1 || hostHeight <= 1)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var shortSideCells = Math.Max(1, targetShortSideCells);
|
||||
if (hostWidth >= hostHeight)
|
||||
{
|
||||
var rowCount = shortSideCells;
|
||||
var cellSize = hostHeight / rowCount;
|
||||
var columnCount = Math.Max(1, (int)Math.Floor(hostWidth / cellSize));
|
||||
return new GridMetrics(columnCount, rowCount, cellSize);
|
||||
}
|
||||
|
||||
var columns = shortSideCells;
|
||||
var size = hostWidth / columns;
|
||||
var rows = Math.Max(1, (int)Math.Floor(hostHeight / size));
|
||||
return new GridMetrics(columns, rows, size);
|
||||
}
|
||||
|
||||
private static int ClampComponentSpan(int requestedSpan, int axisCellCount)
|
||||
{
|
||||
return Math.Clamp(requestedSpan, 1, Math.Max(1, axisCellCount));
|
||||
}
|
||||
|
||||
private static int ClampGridIndex(int requestedIndex, int axisCellCount)
|
||||
{
|
||||
return Math.Clamp(requestedIndex, 0, Math.Max(0, axisCellCount - 1));
|
||||
}
|
||||
|
||||
private static void PlaceStatusBarComponent(
|
||||
Control component,
|
||||
int column,
|
||||
int requestedColumnSpan,
|
||||
int totalColumns)
|
||||
{
|
||||
var clampedColumn = ClampGridIndex(column, totalColumns);
|
||||
var availableColumns = Math.Max(1, totalColumns - clampedColumn);
|
||||
Grid.SetRow(component, StatusBarRowIndex);
|
||||
Grid.SetColumn(component, clampedColumn);
|
||||
Grid.SetRowSpan(component, 1);
|
||||
Grid.SetColumnSpan(component, ClampComponentSpan(requestedColumnSpan, availableColumns));
|
||||
}
|
||||
|
||||
private void ApplyWidgetSizing(double cellSize)
|
||||
@@ -132,9 +246,129 @@ public partial class MainWindow : Window
|
||||
ClockWidget.Margin = new Thickness(margin);
|
||||
ClockWidget.ApplyCellSize(cellSize);
|
||||
|
||||
BackToWindowsButton.Margin = new Thickness(margin);
|
||||
BackToWindowsContainer.Margin = new Thickness(margin);
|
||||
BackToWindowsContainer.CornerRadius = new CornerRadius(Math.Clamp(cellSize * 0.12, 5, 14));
|
||||
BackToWindowsButton.Padding = new Thickness(horizontalPadding, verticalPadding);
|
||||
BackToWindowsButton.FontSize = Math.Clamp(cellSize * 0.30, 8, 30);
|
||||
|
||||
OpenSettingsButton.Margin = new Thickness(Math.Clamp(cellSize * 0.12, 6, 16));
|
||||
OpenSettingsButton.Padding = new Thickness(
|
||||
Math.Clamp(horizontalPadding + 2, 8, 26),
|
||||
Math.Clamp(verticalPadding, 4, 12));
|
||||
OpenSettingsButton.FontSize = Math.Clamp(cellSize * 0.22, 9, 22);
|
||||
}
|
||||
|
||||
private void UpdateWallpaperPreviewLayout()
|
||||
{
|
||||
if (WallpaperPreviewFrame is null ||
|
||||
WallpaperPreviewHost is null ||
|
||||
WallpaperPreviewGrid is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var desktopWidth = Math.Max(1, DesktopHost.Bounds.Width);
|
||||
var desktopHeight = Math.Max(1, DesktopHost.Bounds.Height);
|
||||
var aspectRatio = desktopWidth / desktopHeight;
|
||||
var availableWidth = WallpaperPreviewHost.Bounds.Width - 24;
|
||||
if (availableWidth <= 1)
|
||||
{
|
||||
availableWidth = WallpaperPreviewFrame.Width;
|
||||
}
|
||||
|
||||
var previewWidth = Math.Max(WallpaperPreviewMinWidth, availableWidth);
|
||||
var previewHeight = previewWidth / aspectRatio;
|
||||
|
||||
if (previewHeight > WallpaperPreviewMaxHeight)
|
||||
{
|
||||
previewHeight = WallpaperPreviewMaxHeight;
|
||||
previewWidth = previewHeight * aspectRatio;
|
||||
}
|
||||
|
||||
if (previewHeight < WallpaperPreviewMinHeight)
|
||||
{
|
||||
previewHeight = WallpaperPreviewMinHeight;
|
||||
previewWidth = previewHeight * aspectRatio;
|
||||
}
|
||||
|
||||
WallpaperPreviewFrame.Width = previewWidth;
|
||||
WallpaperPreviewFrame.Height = previewHeight;
|
||||
WallpaperPreviewClockTextBlock.Text = DateTime.Now.ToString("HH:mm");
|
||||
|
||||
var gridMetrics = CalculateGridMetrics(previewWidth, previewHeight, _targetShortSideCells);
|
||||
if (gridMetrics.CellSize <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WallpaperPreviewGrid.RowDefinitions.Clear();
|
||||
WallpaperPreviewGrid.ColumnDefinitions.Clear();
|
||||
WallpaperPreviewGrid.Width = gridMetrics.ColumnCount * gridMetrics.CellSize;
|
||||
WallpaperPreviewGrid.Height = gridMetrics.RowCount * gridMetrics.CellSize;
|
||||
|
||||
for (var row = 0; row < gridMetrics.RowCount; row++)
|
||||
{
|
||||
WallpaperPreviewGrid.RowDefinitions.Add(
|
||||
new RowDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
|
||||
}
|
||||
|
||||
for (var col = 0; col < gridMetrics.ColumnCount; col++)
|
||||
{
|
||||
WallpaperPreviewGrid.ColumnDefinitions.Add(
|
||||
new ColumnDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
|
||||
}
|
||||
|
||||
PlaceStatusBarComponent(
|
||||
WallpaperPreviewClockContainer,
|
||||
column: 0,
|
||||
requestedColumnSpan: 3,
|
||||
totalColumns: gridMetrics.ColumnCount);
|
||||
|
||||
var firstDesktopRow = Math.Min(gridMetrics.RowCount - 1, StatusBarRowIndex + 1);
|
||||
var settingsColumnSpan = ClampComponentSpan(2, gridMetrics.ColumnCount);
|
||||
var settingsRow = Math.Max(firstDesktopRow, gridMetrics.RowCount - 1);
|
||||
var settingsColumn = Math.Max(0, gridMetrics.ColumnCount - settingsColumnSpan);
|
||||
|
||||
var backButtonRow = settingsRow;
|
||||
var backButtonMaxColumnsWithoutOverlap = settingsColumn;
|
||||
int backButtonColumnSpan;
|
||||
if (backButtonMaxColumnsWithoutOverlap >= 1)
|
||||
{
|
||||
backButtonColumnSpan = ClampComponentSpan(Math.Min(4, backButtonMaxColumnsWithoutOverlap), gridMetrics.ColumnCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
backButtonRow = Math.Max(firstDesktopRow, gridMetrics.RowCount - 2);
|
||||
backButtonColumnSpan = ClampComponentSpan(Math.Min(4, gridMetrics.ColumnCount), gridMetrics.ColumnCount);
|
||||
}
|
||||
|
||||
Grid.SetRow(WallpaperPreviewBackButtonContainer, backButtonRow);
|
||||
Grid.SetColumn(WallpaperPreviewBackButtonContainer, 0);
|
||||
Grid.SetRowSpan(WallpaperPreviewBackButtonContainer, 1);
|
||||
Grid.SetColumnSpan(WallpaperPreviewBackButtonContainer, backButtonColumnSpan);
|
||||
|
||||
Grid.SetRow(WallpaperPreviewSettingsButtonContainer, settingsRow);
|
||||
Grid.SetColumn(WallpaperPreviewSettingsButtonContainer, settingsColumn);
|
||||
Grid.SetRowSpan(WallpaperPreviewSettingsButtonContainer, 1);
|
||||
Grid.SetColumnSpan(WallpaperPreviewSettingsButtonContainer, settingsColumnSpan);
|
||||
|
||||
ApplyPreviewWidgetSizing(gridMetrics.CellSize);
|
||||
}
|
||||
|
||||
private void ApplyPreviewWidgetSizing(double cellSize)
|
||||
{
|
||||
var margin = Math.Clamp(cellSize * 0.08, 1, 6);
|
||||
WallpaperPreviewClockContainer.Margin = new Thickness(margin);
|
||||
WallpaperPreviewBackButtonContainer.Margin = new Thickness(margin);
|
||||
WallpaperPreviewSettingsButtonContainer.Margin = new Thickness(margin);
|
||||
|
||||
WallpaperPreviewClockTextBlock.FontSize = Math.Clamp(cellSize * 0.30, 6, 18);
|
||||
WallpaperPreviewBackButtonTextBlock.FontSize = Math.Clamp(cellSize * 0.19, 5, 13);
|
||||
WallpaperPreviewSettingsButtonTextBlock.FontSize = Math.Clamp(cellSize * 0.19, 5, 13);
|
||||
|
||||
var cornerRadius = new CornerRadius(Math.Clamp(cellSize * 0.12, 3, 10));
|
||||
WallpaperPreviewBackButtonContainer.CornerRadius = cornerRadius;
|
||||
WallpaperPreviewSettingsButtonContainer.CornerRadius = cornerRadius;
|
||||
}
|
||||
|
||||
private void OnMinimizeClick(object? sender, RoutedEventArgs e)
|
||||
@@ -142,6 +376,617 @@ public partial class MainWindow : Window
|
||||
WindowState = WindowState.Minimized;
|
||||
}
|
||||
|
||||
private void OnOpenSettingsClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenSettingsPage();
|
||||
}
|
||||
|
||||
private void OnCloseSettingsClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
CloseSettingsPage();
|
||||
}
|
||||
|
||||
private void OnSettingsNavSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
UpdateSettingsTabContent();
|
||||
}
|
||||
|
||||
private void UpdateSettingsTabContent()
|
||||
{
|
||||
// SelectionChanged can fire during XAML initialization before all named controls are assigned.
|
||||
if (SettingsNavListBox is null ||
|
||||
GridSettingsPanel is null ||
|
||||
WallpaperSettingsPanel is null ||
|
||||
ColorSettingsPanel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedIndex = SettingsNavListBox.SelectedIndex;
|
||||
WallpaperSettingsPanel.IsVisible = selectedIndex == 0;
|
||||
GridSettingsPanel.IsVisible = selectedIndex == 1;
|
||||
ColorSettingsPanel.IsVisible = selectedIndex == 2;
|
||||
}
|
||||
|
||||
private void OnNightModeChecked(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressThemeToggleEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyNightModeState(true, refreshPalettes: true);
|
||||
}
|
||||
|
||||
private void OnNightModeUnchecked(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressThemeToggleEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyNightModeState(false, refreshPalettes: true);
|
||||
}
|
||||
|
||||
private void OnRecommendedColorClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ApplyThemeColorFromButton(sender as Button, "Recommended");
|
||||
}
|
||||
|
||||
private void OnMonetColorClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ApplyThemeColorFromButton(sender as Button, "Monet");
|
||||
}
|
||||
|
||||
private void OnRefreshMonetColorsClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
RefreshColorPalettes();
|
||||
EnsureSelectedThemeColor();
|
||||
UpdateThemeColorSelectionState();
|
||||
ThemeColorStatusTextBlock.Text = "Monet colors refreshed.";
|
||||
UpdateAdaptiveTextSystem();
|
||||
}
|
||||
|
||||
private async void OnPickWallpaperClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (StorageProvider is null)
|
||||
{
|
||||
_wallpaperStatus = "Storage provider is unavailable.";
|
||||
UpdateWallpaperDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
var options = new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Select wallpaper",
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter =
|
||||
[
|
||||
new FilePickerFileType("Image files")
|
||||
{
|
||||
Patterns = ["*.png", "*.jpg", "*.jpeg", "*.bmp", "*.gif", "*.webp"]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var files = await StorageProvider.OpenFilePickerAsync(options);
|
||||
if (files.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var file = files[0];
|
||||
try
|
||||
{
|
||||
Bitmap bitmap;
|
||||
var localPath = file.TryGetLocalPath();
|
||||
if (!string.IsNullOrWhiteSpace(localPath))
|
||||
{
|
||||
bitmap = new Bitmap(localPath);
|
||||
_wallpaperPath = localPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
await using var stream = await file.OpenReadAsync();
|
||||
bitmap = new Bitmap(stream);
|
||||
_wallpaperPath = file.Name;
|
||||
}
|
||||
|
||||
_wallpaperBitmap?.Dispose();
|
||||
_wallpaperBitmap = bitmap;
|
||||
_wallpaperStatus = "Wallpaper applied.";
|
||||
ApplyWallpaperBrush();
|
||||
UpdateWallpaperDisplay();
|
||||
RefreshColorPalettes();
|
||||
EnsureSelectedThemeColor();
|
||||
UpdateThemeColorSelectionState();
|
||||
ThemeColorStatusTextBlock.Text = "Wallpaper updated. Monet colors refreshed.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_wallpaperStatus = $"Failed to apply wallpaper: {ex.Message}";
|
||||
UpdateWallpaperDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClearWallpaperClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
_wallpaperBitmap?.Dispose();
|
||||
_wallpaperBitmap = null;
|
||||
_wallpaperPath = null;
|
||||
_wallpaperStatus = "Background reset to solid color.";
|
||||
ApplyWallpaperBrush();
|
||||
UpdateWallpaperDisplay();
|
||||
RefreshColorPalettes();
|
||||
EnsureSelectedThemeColor();
|
||||
UpdateThemeColorSelectionState();
|
||||
ThemeColorStatusTextBlock.Text = "Wallpaper cleared. Monet colors refreshed.";
|
||||
}
|
||||
|
||||
private void OnWallpaperPlacementSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
ApplyWallpaperBrush();
|
||||
if (_wallpaperBitmap is not null)
|
||||
{
|
||||
_wallpaperStatus = $"Wallpaper mode: {GetPlacementDisplayName(GetSelectedWallpaperPlacement())}.";
|
||||
}
|
||||
|
||||
UpdateWallpaperDisplay();
|
||||
}
|
||||
|
||||
private void ApplyWallpaperBrush()
|
||||
{
|
||||
if (_wallpaperBitmap is null)
|
||||
{
|
||||
DesktopHost.Background = _defaultDesktopBackground ?? new SolidColorBrush(Color.Parse("#FF020617"));
|
||||
WallpaperPreviewViewport.Background = _defaultDesktopBackground ?? new SolidColorBrush(Color.Parse("#30111827"));
|
||||
UpdateAdaptiveTextSystem();
|
||||
return;
|
||||
}
|
||||
|
||||
var placement = GetSelectedWallpaperPlacement();
|
||||
DesktopHost.Background = CreateWallpaperBrush(_wallpaperBitmap, placement, false);
|
||||
WallpaperPreviewViewport.Background = CreateWallpaperBrush(_wallpaperBitmap, placement, true);
|
||||
UpdateAdaptiveTextSystem();
|
||||
}
|
||||
|
||||
private void UpdateWallpaperDisplay()
|
||||
{
|
||||
if (WallpaperPathTextBlock is null ||
|
||||
WallpaperStatusTextBlock is null ||
|
||||
WallpaperPreviewViewport is null ||
|
||||
WallpaperPlacementComboBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WallpaperPathTextBlock.Text = string.IsNullOrWhiteSpace(_wallpaperPath)
|
||||
? "No wallpaper selected."
|
||||
: _wallpaperPath;
|
||||
WallpaperStatusTextBlock.Text = _wallpaperStatus;
|
||||
|
||||
if (_wallpaperBitmap is null)
|
||||
{
|
||||
WallpaperPreviewViewport.Background = _defaultDesktopBackground ?? new SolidColorBrush(Color.Parse("#30111827"));
|
||||
return;
|
||||
}
|
||||
|
||||
WallpaperPreviewViewport.Background = CreateWallpaperBrush(
|
||||
_wallpaperBitmap,
|
||||
GetSelectedWallpaperPlacement(),
|
||||
true);
|
||||
}
|
||||
|
||||
private ImageBrush CreateWallpaperBrush(Bitmap bitmap, WallpaperPlacement placement, bool forPreview)
|
||||
{
|
||||
var brush = new ImageBrush
|
||||
{
|
||||
Source = bitmap,
|
||||
Stretch = Stretch.UniformToFill,
|
||||
AlignmentX = AlignmentX.Center,
|
||||
AlignmentY = AlignmentY.Center,
|
||||
TileMode = TileMode.None
|
||||
};
|
||||
|
||||
switch (placement)
|
||||
{
|
||||
case WallpaperPlacement.Fill:
|
||||
brush.Stretch = Stretch.UniformToFill;
|
||||
break;
|
||||
case WallpaperPlacement.Fit:
|
||||
brush.Stretch = Stretch.Uniform;
|
||||
break;
|
||||
case WallpaperPlacement.Stretch:
|
||||
brush.Stretch = Stretch.Fill;
|
||||
break;
|
||||
case WallpaperPlacement.Center:
|
||||
brush.Stretch = Stretch.None;
|
||||
break;
|
||||
case WallpaperPlacement.Tile:
|
||||
brush.Stretch = Stretch.None;
|
||||
brush.TileMode = TileMode.Tile;
|
||||
var tileSize = forPreview ? 96d : 220d;
|
||||
brush.DestinationRect = new RelativeRect(0, 0, tileSize, tileSize, RelativeUnit.Absolute);
|
||||
break;
|
||||
}
|
||||
|
||||
return brush;
|
||||
}
|
||||
|
||||
private WallpaperPlacement GetSelectedWallpaperPlacement()
|
||||
{
|
||||
return WallpaperPlacementComboBox?.SelectedIndex switch
|
||||
{
|
||||
1 => WallpaperPlacement.Fit,
|
||||
2 => WallpaperPlacement.Stretch,
|
||||
3 => WallpaperPlacement.Center,
|
||||
4 => WallpaperPlacement.Tile,
|
||||
_ => WallpaperPlacement.Fill
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetPlacementDisplayName(WallpaperPlacement placement)
|
||||
{
|
||||
return placement switch
|
||||
{
|
||||
WallpaperPlacement.Fill => "Fill",
|
||||
WallpaperPlacement.Fit => "Fit",
|
||||
WallpaperPlacement.Stretch => "Stretch",
|
||||
WallpaperPlacement.Center => "Center",
|
||||
WallpaperPlacement.Tile => "Tile",
|
||||
_ => "Fill"
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateAdaptiveTextSystem()
|
||||
{
|
||||
var luminance = CalculateCurrentBackgroundLuminance();
|
||||
var isLightBackground = luminance >= LightBackgroundLuminanceThreshold;
|
||||
var navBackground = SettingsNavPanelBorder?.Background;
|
||||
var isLightNavBackground = CalculateBrushLuminance(navBackground) >= LightBackgroundLuminanceThreshold;
|
||||
var context = new ThemeColorContext(
|
||||
_selectedThemeColor,
|
||||
isLightBackground,
|
||||
isLightNavBackground,
|
||||
_isNightMode);
|
||||
|
||||
ThemeColorSystemService.ApplyThemeResources(Resources, context);
|
||||
GlassEffectService.ApplyGlassResources(Resources, context.IsLightBackground);
|
||||
}
|
||||
|
||||
private double CalculateCurrentBackgroundLuminance()
|
||||
{
|
||||
if (_wallpaperBitmap is not null)
|
||||
{
|
||||
return CalculateBitmapAverageLuminance(_wallpaperBitmap);
|
||||
}
|
||||
|
||||
return CalculateBrushLuminance(DesktopHost.Background ?? _defaultDesktopBackground);
|
||||
}
|
||||
|
||||
private void ApplyNightModeState(bool enabled, bool refreshPalettes)
|
||||
{
|
||||
_isNightMode = enabled;
|
||||
RequestedThemeVariant = enabled ? ThemeVariant.Dark : ThemeVariant.Light;
|
||||
|
||||
_suppressThemeToggleEvents = true;
|
||||
NightModeToggleSwitch.IsChecked = enabled;
|
||||
_suppressThemeToggleEvents = false;
|
||||
ThemeModeStatusTextBlock.Text = enabled ? "Night mode enabled" : "Day mode enabled";
|
||||
|
||||
if (refreshPalettes)
|
||||
{
|
||||
RefreshColorPalettes();
|
||||
EnsureSelectedThemeColor();
|
||||
}
|
||||
|
||||
UpdateThemeColorSelectionState();
|
||||
ThemeColorStatusTextBlock.Text = $"Theme mode: {(enabled ? "Night" : "Day")}.";
|
||||
UpdateAdaptiveTextSystem();
|
||||
}
|
||||
|
||||
private void RefreshColorPalettes()
|
||||
{
|
||||
var palette = _monetColorService.BuildPalette(_wallpaperBitmap, _isNightMode);
|
||||
_recommendedColors = palette.RecommendedColors;
|
||||
_monetColors = palette.MonetColors;
|
||||
ApplyColorPaletteToButtons(_recommendedColors, GetRecommendedColorTargets());
|
||||
ApplyColorPaletteToButtons(_monetColors, GetMonetColorTargets());
|
||||
}
|
||||
|
||||
private void ApplyColorPaletteToButtons(
|
||||
IReadOnlyList<Color> colors,
|
||||
IReadOnlyList<(Button Button, Border Swatch)> targets)
|
||||
{
|
||||
for (var i = 0; i < targets.Count; i++)
|
||||
{
|
||||
var color = i < colors.Count
|
||||
? colors[i]
|
||||
: Color.Parse("#00000000");
|
||||
var (button, swatch) = targets[i];
|
||||
button.Tag = color.ToString();
|
||||
button.IsEnabled = i < colors.Count;
|
||||
swatch.Background = i < colors.Count
|
||||
? new SolidColorBrush(color)
|
||||
: new SolidColorBrush(Color.Parse("#00000000"));
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<(Button Button, Border Swatch)> GetRecommendedColorTargets()
|
||||
{
|
||||
return
|
||||
[
|
||||
(RecommendedColorButton1, RecommendedColorSwatch1),
|
||||
(RecommendedColorButton2, RecommendedColorSwatch2),
|
||||
(RecommendedColorButton3, RecommendedColorSwatch3),
|
||||
(RecommendedColorButton4, RecommendedColorSwatch4),
|
||||
(RecommendedColorButton5, RecommendedColorSwatch5),
|
||||
(RecommendedColorButton6, RecommendedColorSwatch6)
|
||||
];
|
||||
}
|
||||
|
||||
private IReadOnlyList<(Button Button, Border Swatch)> GetMonetColorTargets()
|
||||
{
|
||||
return
|
||||
[
|
||||
(MonetColorButton1, MonetColorSwatch1),
|
||||
(MonetColorButton2, MonetColorSwatch2),
|
||||
(MonetColorButton3, MonetColorSwatch3),
|
||||
(MonetColorButton4, MonetColorSwatch4),
|
||||
(MonetColorButton5, MonetColorSwatch5),
|
||||
(MonetColorButton6, MonetColorSwatch6)
|
||||
];
|
||||
}
|
||||
|
||||
private void EnsureSelectedThemeColor()
|
||||
{
|
||||
if (ContainsColor(_recommendedColors, _selectedThemeColor) ||
|
||||
ContainsColor(_monetColors, _selectedThemeColor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_recommendedColors.Count > 0)
|
||||
{
|
||||
_selectedThemeColor = _recommendedColors[0];
|
||||
return;
|
||||
}
|
||||
|
||||
if (_monetColors.Count > 0)
|
||||
{
|
||||
_selectedThemeColor = _monetColors[0];
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyThemeColorFromButton(Button? button, string sourceLabel)
|
||||
{
|
||||
if (!TryGetButtonColor(button, out var color))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedThemeColor = color;
|
||||
UpdateThemeColorSelectionState();
|
||||
ThemeColorStatusTextBlock.Text = $"{sourceLabel} color applied: {_selectedThemeColor}.";
|
||||
UpdateAdaptiveTextSystem();
|
||||
}
|
||||
|
||||
private void UpdateThemeColorSelectionState()
|
||||
{
|
||||
UpdateColorSelectionVisuals(GetRecommendedColorTargets());
|
||||
UpdateColorSelectionVisuals(GetMonetColorTargets());
|
||||
}
|
||||
|
||||
private void UpdateColorSelectionVisuals(IReadOnlyList<(Button Button, Border Swatch)> targets)
|
||||
{
|
||||
foreach (var (button, swatch) in targets)
|
||||
{
|
||||
var isSelected = TryGetButtonColor(button, out var color) && AreSameColor(color, _selectedThemeColor);
|
||||
swatch.BorderBrush = isSelected
|
||||
? new SolidColorBrush(Color.Parse("#FFFFFFFF"))
|
||||
: new SolidColorBrush(Color.Parse("#A0FFFFFF"));
|
||||
swatch.BorderThickness = new Thickness(isSelected ? 2 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetButtonColor(Button? button, out Color color)
|
||||
{
|
||||
color = default;
|
||||
if (button?.Tag is not string colorText || string.IsNullOrWhiteSpace(colorText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
color = Color.Parse(colorText);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool ContainsColor(IReadOnlyList<Color> colors, Color target)
|
||||
{
|
||||
for (var i = 0; i < colors.Count; i++)
|
||||
{
|
||||
if (AreSameColor(colors[i], target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool AreSameColor(Color left, Color right)
|
||||
{
|
||||
return left.R == right.R && left.G == right.G && left.B == right.B;
|
||||
}
|
||||
|
||||
|
||||
private static double CalculateBrushLuminance(IBrush? brush)
|
||||
{
|
||||
if (brush is ISolidColorBrush solidBrush)
|
||||
{
|
||||
return CalculateRelativeLuminance(solidBrush.Color);
|
||||
}
|
||||
|
||||
return CalculateRelativeLuminance(Color.Parse("#FF020617"));
|
||||
}
|
||||
|
||||
private static double CalculateBitmapAverageLuminance(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sampleWidth = Math.Clamp(bitmap.PixelSize.Width, 1, 48);
|
||||
var sampleHeight = Math.Clamp(bitmap.PixelSize.Height, 1, 48);
|
||||
|
||||
using var scaledBitmap = bitmap.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 rowBytes = framebuffer.RowBytes;
|
||||
var byteCount = rowBytes * framebuffer.Size.Height;
|
||||
if (byteCount <= 0 || framebuffer.Address == IntPtr.Zero)
|
||||
{
|
||||
return CalculateRelativeLuminance(Color.Parse("#FF020617"));
|
||||
}
|
||||
|
||||
var pixelBuffer = new byte[byteCount];
|
||||
Marshal.Copy(framebuffer.Address, pixelBuffer, 0, byteCount);
|
||||
|
||||
double luminanceSum = 0;
|
||||
var pixelCount = 0;
|
||||
for (var y = 0; y < framebuffer.Size.Height; y++)
|
||||
{
|
||||
var rowOffset = y * rowBytes;
|
||||
for (var x = 0; x < framebuffer.Size.Width; x++)
|
||||
{
|
||||
var index = rowOffset + (x * 4);
|
||||
var alpha = pixelBuffer[index + 3] / 255d;
|
||||
if (alpha <= 0.01)
|
||||
{
|
||||
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);
|
||||
|
||||
luminanceSum += CalculateRelativeLuminance(red, green, blue);
|
||||
pixelCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return pixelCount > 0
|
||||
? luminanceSum / pixelCount
|
||||
: CalculateRelativeLuminance(Color.Parse("#FF020617"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return CalculateRelativeLuminance(Color.Parse("#FF020617"));
|
||||
}
|
||||
}
|
||||
|
||||
private static double CalculateRelativeLuminance(Color color)
|
||||
{
|
||||
return CalculateRelativeLuminance(color.R / 255d, color.G / 255d, color.B / 255d);
|
||||
}
|
||||
|
||||
private static double CalculateRelativeLuminance(double red, double green, double blue)
|
||||
{
|
||||
var linearRed = ToLinearRgb(red);
|
||||
var linearGreen = ToLinearRgb(green);
|
||||
var linearBlue = ToLinearRgb(blue);
|
||||
return (0.2126 * linearRed) + (0.7152 * linearGreen) + (0.0722 * linearBlue);
|
||||
}
|
||||
|
||||
private static double ToLinearRgb(double value)
|
||||
{
|
||||
return value <= 0.04045
|
||||
? value / 12.92
|
||||
: Math.Pow((value + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
private void OpenSettingsPage()
|
||||
{
|
||||
if (_isSettingsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isSettingsOpen = true;
|
||||
UpdateAdaptiveTextSystem();
|
||||
SettingsPage.IsVisible = true;
|
||||
SettingsPage.Opacity = 0;
|
||||
if (_settingsContentPanelTransform is not null)
|
||||
{
|
||||
_settingsContentPanelTransform.Y = 30;
|
||||
}
|
||||
|
||||
DesktopPage.IsHitTestVisible = false;
|
||||
UpdateWallpaperPreviewLayout();
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (!_isSettingsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsPage.Opacity = 1;
|
||||
if (_settingsContentPanelTransform is not null)
|
||||
{
|
||||
_settingsContentPanelTransform.Y = 0;
|
||||
}
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void CloseSettingsPage()
|
||||
{
|
||||
if (!_isSettingsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isSettingsOpen = false;
|
||||
UpdateAdaptiveTextSystem();
|
||||
|
||||
DesktopPage.IsHitTestVisible = true;
|
||||
|
||||
SettingsPage.Opacity = 0;
|
||||
if (_settingsContentPanelTransform is not null)
|
||||
{
|
||||
_settingsContentPanelTransform.Y = 30;
|
||||
}
|
||||
|
||||
DispatcherTimer.RunOnce(() =>
|
||||
{
|
||||
if (_isSettingsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsPage.IsVisible = false;
|
||||
}, TimeSpan.FromMilliseconds(SettingsTransitionDurationMs));
|
||||
}
|
||||
|
||||
private void OnWindowPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property != WindowStateProperty)
|
||||
|
||||
Reference in New Issue
Block a user