Files
DeskCore/DesktopOverlays.cs
T
2026-10-03 19:51:56 +00:00

262 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace DeskCore;
/// <summary>Gemeinsame Hilfen für klickdurchlässige Overlay-Fenster.</summary>
internal static class Overlay
{
public static Window Create(bool topmost)
{
var w = new Window
{
WindowStyle = WindowStyle.None, AllowsTransparency = true, Background = Brushes.Transparent,
ShowInTaskbar = false, ShowActivated = false, Topmost = topmost, ResizeMode = ResizeMode.NoResize,
Focusable = false, IsHitTestVisible = false,
};
w.SourceInitialized += (_, _) =>
{
var hwnd = new WindowInteropHelper(w).Handle;
long ex = GetWindowLongPtr(hwnd, GWL_EXSTYLE).ToInt64();
SetWindowLongPtr(hwnd, GWL_EXSTYLE, new IntPtr(ex | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE));
};
return w;
}
/// <summary>Wie die Boxen: Besitzer = Desktop, immer ganz unten (über den Desktop-Icons, unter allen Fenstern).</summary>
public static void PinToDesktop(Window w)
{
var hwnd = new WindowInteropHelper(w).Handle;
var progman = FindWindow("Progman", null);
if (progman != IntPtr.Zero) SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, progman);
HwndSource.FromHwnd(hwnd).AddHook((IntPtr h, int msg, IntPtr wp, IntPtr lp, ref bool handled) =>
{
if (msg == WM_WINDOWPOSCHANGING)
{
var pos = Marshal.PtrToStructure<WINDOWPOS>(lp);
pos.hwndInsertAfter = new IntPtr(1); // HWND_BOTTOM
pos.flags &= ~SWP_NOZORDER;
Marshal.StructureToPtr(pos, lp, false);
}
return IntPtr.Zero;
});
}
private const int GWL_EXSTYLE = -20, GWLP_HWNDPARENT = -8, WM_WINDOWPOSCHANGING = 0x46;
private const long WS_EX_TRANSPARENT = 0x20, WS_EX_LAYERED = 0x80000, WS_EX_TOOLWINDOW = 0x80, WS_EX_NOACTIVATE = 0x8000000;
private const uint SWP_NOZORDER = 0x4;
[StructLayout(LayoutKind.Sequential)]
private struct WINDOWPOS { public IntPtr hwnd, hwndInsertAfter; public int x, y, cx, cy; public uint flags; }
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr FindWindow(string? cls, string? title);
[DllImport("user32.dll")] private static extern IntPtr GetWindowLongPtr(IntPtr h, int i);
[DllImport("user32.dll")] private static extern IntPtr SetWindowLongPtr(IntPtr h, int i, IntPtr v);
}
// =========================================================================
// Seitenanzeige beim Wechseln der Desktop-Seite
// =========================================================================
public static class PageIndicator
{
private static Window? _w;
private static TextBlock? _name;
private static StackPanel? _dots;
private static DispatcherTimer? _hide;
public static void Show(string name, int index, int count)
{
if (_w is null)
{
_w = Overlay.Create(topmost: true);
_w.SizeToContent = SizeToContent.WidthAndHeight;
_name = new TextBlock { FontSize = 15, FontWeight = FontWeights.SemiBold, HorizontalAlignment = HorizontalAlignment.Center };
_dots = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center, Margin = new Thickness(0, 8, 0, 0) };
_w.Content = new Border
{
Padding = new Thickness(22, 12, 22, 12), CornerRadius = new CornerRadius(10), Margin = new Thickness(12),
Child = new StackPanel { Children = { _name, _dots } },
};
_hide = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1300) };
_hide.Tick += (_, _) =>
{
_hide.Stop();
var fade = new DoubleAnimation(0, TimeSpan.FromMilliseconds(350));
fade.Completed += (_, _) => { if (_w.Opacity == 0) _w.Hide(); };
_w.BeginAnimation(UIElement.OpacityProperty, fade);
};
}
var res = Application.Current.Resources;
var border = (Border)_w.Content;
border.Background = (Brush)res["FlyoutBrush"];
border.BorderBrush = (Brush)res["FlyoutStrokeBrush"];
border.BorderThickness = new Thickness(1);
border.Effect = (System.Windows.Media.Effects.Effect)res["FlyoutShadow"];
_name!.Text = name;
_name.Foreground = (Brush)res["TextBrush"];
_dots!.Children.Clear();
for (int i = 0; i < count; i++)
_dots.Children.Add(new Ellipse
{
Width = 7, Height = 7, Margin = new Thickness(4, 0, 4, 0),
Fill = (Brush)res[i == index ? "AccentBrush" : "ControlStrongStrokeBrush"],
});
_w.BeginAnimation(UIElement.OpacityProperty, null);
_w.Opacity = 1;
_w.Show();
_w.UpdateLayout();
var wa = Screens.PrimaryWorkArea;
_w.Left = wa.Left + (wa.Width - _w.ActualWidth) / 2;
_w.Top = wa.Bottom - _w.ActualHeight - 40;
_hide!.Stop();
_hide.Start();
}
}
// =========================================================================
// Rahmen beim Aufziehen einer neuen Box (rechte Maustaste auf dem Desktop)
// =========================================================================
public static class DrawBoxOverlay
{
private static Window? _w;
public static void Begin()
{
if (_w is null)
{
_w = Overlay.Create(topmost: true);
var res = Application.Current.Resources;
var accent = ((SolidColorBrush)res["AccentBrush"]).Color;
_w.Content = new Grid
{
Children =
{
new Rectangle
{
RadiusX = 8, RadiusY = 8, StrokeThickness = 2, StrokeDashArray = [4, 3],
Stroke = new SolidColorBrush(accent), Fill = new SolidColorBrush(Color.FromArgb(0x38, accent.R, accent.G, accent.B)),
},
new TextBlock
{
Text = "Neue Box", Foreground = Brushes.White, FontSize = 14, FontWeight = FontWeights.SemiBold,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
Effect = new System.Windows.Media.Effects.DropShadowEffect { BlurRadius = 6, ShadowDepth = 0, Opacity = 0.8 },
},
},
};
}
_w.Width = _w.Height = 1;
_w.Show();
}
/// <summary>Rechteck in Bildschirm-Pixeln.</summary>
public static void Update(Rect px)
{
if (_w is null) return;
double s = Screens.Scale;
_w.Left = px.X / s;
_w.Top = px.Y / s;
_w.Width = Math.Max(1, px.Width / s);
_w.Height = Math.Max(1, px.Height / s);
}
public static void Finish(Rect px)
{
_w?.Hide();
double s = Screens.Scale;
var dip = new Rect(px.X / s, px.Y / s, px.Width / s, px.Height / s);
if (dip.Width < 120 || dip.Height < 60) return; // zu klein – war wohl nur ein Versehen
FenceManager.ShowCreateMenu(dip);
}
}
// =========================================================================
// Tönung der echten Desktop-Icons
// =========================================================================
/// <summary>
/// Legt eine klickdurchlässige Farbschicht exakt über die Bilder der Desktop-Icons. Die Positionen stammen aus der
/// offiziellen Shell-Schnittstelle; am Explorer selbst wird nichts verändert. Icons unter Boxen bleiben ausgespart.
/// </summary>
public static class DesktopTint
{
private static Window? _w;
private static Canvas? _canvas;
private static string _signature = "";
private static AppSettings S => AppSettings.Current;
/// <summary>Schicht neu aufbauen, wenn sich Icons, Boxen oder Einstellungen geändert haben.</summary>
public static void Refresh(bool force = false)
{
bool on = S.DesktopTint is not null && !FenceManager.Hidden && DesktopToggle.IconsVisible();
if (!on)
{
_w?.Hide();
_signature = "";
return;
}
var icons = DesktopIcons.Cached.Where(i => i.Path.Length > 0).ToList();
var fences = FenceManager.OpenRects();
var sig = $"{S.DesktopTint}|{S.DesktopTintStrength}|{DesktopIcons.IconTopOffset}|" +
string.Join(";", icons.Select(i => $"{i.X},{i.Y},{i.IconSize}")) + "|" + string.Join(";", fences.Select(r => r.ToString()));
if (!force && sig == _signature && _w?.IsVisible == true) return;
_signature = sig;
if (_w is null)
{
_w = Overlay.Create(topmost: false);
_canvas = new Canvas();
_w.Content = _canvas;
_w.SourceInitialized += (_, _) => Overlay.PinToDesktop(_w);
}
// Fenster über die gesamte Desktop-Fläche (alle Bildschirme)
double s = Screens.Scale;
var vs = new Rect(SystemParameters.VirtualScreenLeft, SystemParameters.VirtualScreenTop,
SystemParameters.VirtualScreenWidth, SystemParameters.VirtualScreenHeight);
_w.Left = vs.Left;
_w.Top = vs.Top;
_w.Width = vs.Width;
_w.Height = vs.Height;
var tint = CategoryColors.Brush(S.DesktopTint) ?? Brushes.Transparent;
_canvas!.Children.Clear();
foreach (var icon in icons)
{
var (l, t, size) = DesktopIcons.IconRect(icon);
var r = new Rect(l / s, t / s, size / s, size / s);
if (fences.Any(f => f.IntersectsWith(r))) continue; // liegt unter einer Box
var item = new IconItem { Path = icon.Path };
var rect = new Rectangle
{
Width = r.Width, Height = r.Height, Fill = tint, Opacity = Math.Clamp(S.DesktopTintStrength, 0.1, 0.95),
Visibility = Visibility.Hidden, // erst zeigen, wenn die Form (das Icon) geladen ist
};
void Apply()
{
if (item.Icon is null) return;
rect.OpacityMask = new ImageBrush(item.Icon) { Stretch = Stretch.Fill };
rect.Visibility = Visibility.Visible;
}
item.PropertyChanged += (_, _) => Apply();
Canvas.SetLeft(rect, r.X - vs.Left);
Canvas.SetTop(rect, r.Y - vs.Top);
_canvas.Children.Add(rect);
IconLoader.Load(item, size);
Apply();
}
if (!_w.IsVisible) _w.Show();
}
}