Upload via GUI (180 Dateien)
This commit is contained in:
+634
-346
@@ -1,346 +1,634 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace DeskCore;
|
||||
|
||||
/// <summary>Eine "Box" auf dem Desktop, die den Inhalt einer Kategorie zeigt.</summary>
|
||||
public partial class FenceWindow : Window
|
||||
{
|
||||
private const double CollapsedHeight = 38;
|
||||
private readonly DispatcherTimer _saveTimer;
|
||||
private bool _applying;
|
||||
|
||||
public string Category { get; private set; }
|
||||
|
||||
public FenceWindow(string category)
|
||||
{
|
||||
InitializeComponent();
|
||||
Category = category;
|
||||
_saveTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(700), DispatcherPriority.Background, (_, _) =>
|
||||
{
|
||||
_saveTimer!.Stop();
|
||||
AppSettings.Current.Save();
|
||||
}, Dispatcher);
|
||||
_saveTimer.Stop();
|
||||
|
||||
ApplyBounds();
|
||||
LocationChanged += (_, _) => RememberBounds();
|
||||
SizeChanged += (_, _) => RememberBounds();
|
||||
SourceInitialized += (_, _) => PinToDesktop();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private CategoryMeta Meta => AppSettings.Current.Meta(Category);
|
||||
|
||||
public void Rename(string newName)
|
||||
{
|
||||
Category = newName;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// ---------- Position/Größe ----------
|
||||
|
||||
private void ApplyBounds()
|
||||
{
|
||||
_applying = true;
|
||||
var m = Meta;
|
||||
Width = Math.Max(MinWidth, m.FenceW);
|
||||
Height = m.FenceCollapsed ? CollapsedHeight : Math.Max(120, m.FenceH);
|
||||
var wa = Screens.PrimaryWorkArea;
|
||||
double left = m.FenceX ?? wa.Right - Width - 24, top = m.FenceY ?? wa.Top + 24;
|
||||
// Bildschirm abgesteckt? → zurück auf den Hauptbildschirm (oben rechts)
|
||||
if (!Screens.IsReachable(new Rect(left, top, Width, Height)))
|
||||
{
|
||||
left = wa.Right - Width - 24;
|
||||
top = wa.Top + 24;
|
||||
m.FenceX = left;
|
||||
m.FenceY = top;
|
||||
}
|
||||
Left = left;
|
||||
Top = top;
|
||||
Body.Visibility = m.FenceCollapsed ? Visibility.Collapsed : Visibility.Visible;
|
||||
_applying = false;
|
||||
}
|
||||
|
||||
/// <summary>Nach Bildschirm-Änderungen: Box wieder sichtbar machen.</summary>
|
||||
public void EnsureOnScreen()
|
||||
{
|
||||
if (Screens.IsReachable(new Rect(Left, Top, ActualWidth, ActualHeight))) return;
|
||||
Meta.FenceX = null;
|
||||
Meta.FenceY = null;
|
||||
ApplyBounds();
|
||||
AppSettings.Current.Save();
|
||||
}
|
||||
|
||||
private void RememberBounds()
|
||||
{
|
||||
if (_applying || !IsLoaded) return;
|
||||
var m = Meta;
|
||||
m.FenceX = Left;
|
||||
m.FenceY = Top;
|
||||
m.FenceW = Width;
|
||||
if (!m.FenceCollapsed) m.FenceH = Height;
|
||||
_saveTimer.Stop();
|
||||
_saveTimer.Start();
|
||||
}
|
||||
|
||||
private void ToggleCollapsed()
|
||||
{
|
||||
Meta.FenceCollapsed = !Meta.FenceCollapsed;
|
||||
ApplyBounds();
|
||||
AppSettings.Current.Save();
|
||||
}
|
||||
|
||||
// ---------- Inhalt ----------
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
var meta = Meta;
|
||||
TitleText.Text = Category;
|
||||
GlyphText.Text = meta.Glyph ?? "";
|
||||
GlyphText.Foreground = CategoryColors.Brush(meta.Color) ?? (Brush)FindResource("TextBrush");
|
||||
|
||||
var items = Storage.Entries(Storage.CategoryPath(Category))
|
||||
.Select(p => new IconItem { Path = p })
|
||||
.OrderBy(i => !Directory.Exists(i.Path)).ThenBy(i => i.Name, StringComparer.CurrentCultureIgnoreCase)
|
||||
.ToList();
|
||||
int px = (int)Math.Round(32 * VisualTreeHelper.GetDpi(this).DpiScaleX);
|
||||
foreach (var i in items) IconLoader.Load(i, px);
|
||||
Items.ItemsSource = items;
|
||||
CountText.Text = items.Count.ToString();
|
||||
EmptyText.Visibility = items.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private List<IconItem> Selected => Items.SelectedItems.Cast<IconItem>().ToList();
|
||||
|
||||
private static void Open(string path)
|
||||
{
|
||||
try { Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void Items_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (MainWindow.FindParent<ListBoxItem>(e.OriginalSource as DependencyObject)?.DataContext is IconItem i) Open(i.Path);
|
||||
}
|
||||
|
||||
private void Items_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
var sel = Selected;
|
||||
if (sel.Count == 0 || App.Win is not { } win) return;
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter: sel.ForEach(i => Open(i.Path)); e.Handled = true; break;
|
||||
case Key.Delete: win.DeleteIcons(sel); e.Handled = true; break;
|
||||
case Key.F2 when sel.Count == 1: win.RenameIcon(sel[0]); e.Handled = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Items_ContextMenuOpening(object sender, ContextMenuEventArgs e)
|
||||
{
|
||||
var sel = Selected;
|
||||
if (sel.Count == 0 || App.Win is not { } win) { e.Handled = true; return; }
|
||||
ItemMenu.Items.Clear();
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Öffnen", "\uE8A7", () => sel.ForEach(i => Open(i.Path)), bold: true, gesture: "Enter"));
|
||||
if (sel.Count == 1)
|
||||
{
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Dateispeicherort öffnen", "\uED25", () =>
|
||||
Process.Start("explorer.exe", $"/select,\"{sel[0].Path}\"")));
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Umbenennen", "\uE70F", () => win.RenameIcon(sel[0]), gesture: "F2"));
|
||||
}
|
||||
ItemMenu.Items.Add(new Separator());
|
||||
|
||||
var moveTo = MainWindow.MenuItem("Verschieben nach", "\uE8DE", null);
|
||||
foreach (var c in Storage.Categories().Where(c => !string.Equals(c, Category, StringComparison.OrdinalIgnoreCase)))
|
||||
moveTo.Items.Add(MainWindow.MenuItem(c, AppSettings.Current.Categories.GetValueOrDefault(c)?.Glyph ?? "\uF12B",
|
||||
() => win.TransferAll(sel.Select(i => i.Path), Storage.CategoryPath(c))));
|
||||
if (moveTo.Items.Count > 0) ItemMenu.Items.Add(moveTo);
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Zurück auf den Desktop legen", "\uE7F4",
|
||||
() => win.TransferAll(sel.Select(i => i.Path), Storage.UserDesktop)));
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Löschen", "\uE74D", () => win.DeleteIcons(sel), gesture: "Entf"));
|
||||
ItemMenu.Items.Add(new Separator());
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("In DeskCore zeigen", "\uE8A7", ShowInApp));
|
||||
}
|
||||
|
||||
private void MenuButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FenceMenu.Items.Clear();
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem("In DeskCore öffnen", "", ShowInApp));
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem(Meta.FenceCollapsed ? "Ausklappen" : "Einklappen",
|
||||
Meta.FenceCollapsed ? "" : "", ToggleCollapsed));
|
||||
FenceMenu.Items.Add(new Separator());
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem("Box ausblenden", "", () =>
|
||||
{
|
||||
Meta.Fence = false;
|
||||
AppSettings.Current.Save();
|
||||
FenceManager.Sync();
|
||||
}));
|
||||
FenceMenu.PlacementTarget = MenuButton;
|
||||
FenceMenu.IsOpen = true;
|
||||
}
|
||||
|
||||
private void ShowInApp() => App.Win?.ShowCategory(Category);
|
||||
|
||||
private Point _dragStart;
|
||||
private bool _dragging;
|
||||
|
||||
private void Items_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) => _dragStart = e.GetPosition(null);
|
||||
|
||||
private void Items_PreviewMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton != MouseButtonState.Pressed || _dragging) return;
|
||||
var d = e.GetPosition(null) - _dragStart;
|
||||
if (Math.Abs(d.X) < SystemParameters.MinimumHorizontalDragDistance &&
|
||||
Math.Abs(d.Y) < SystemParameters.MinimumVerticalDragDistance) return;
|
||||
if (MainWindow.FindParent<ListBoxItem>(e.OriginalSource as DependencyObject) is null) return;
|
||||
var paths = Selected.Select(i => i.Path).ToArray();
|
||||
if (paths.Length == 0) return;
|
||||
_dragging = true;
|
||||
try
|
||||
{
|
||||
// nur "Kopieren" erlauben: der Explorer soll die Datei nicht aus der Kategorie entfernen.
|
||||
// In eine andere Box/Kategorie gezogen, verschiebt DeskCore selbst.
|
||||
DragDrop.DoDragDrop(Items, new DataObject(DataFormats.FileDrop, paths), DragDropEffects.Copy);
|
||||
}
|
||||
finally { _dragging = false; }
|
||||
}
|
||||
|
||||
private void Items_DragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) && !_dragging
|
||||
? (e.AllowedEffects.HasFlag(DragDropEffects.Copy) ? DragDropEffects.Copy : DragDropEffects.Move)
|
||||
: DragDropEffects.None;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Items_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
if (!_dragging && e.Data.GetData(DataFormats.FileDrop) is string[] files)
|
||||
App.Win?.TransferAll(files, Storage.CategoryPath(Category));
|
||||
}
|
||||
|
||||
// ---------- Auf dem Desktop festhalten ----------
|
||||
|
||||
private void PinToDesktop()
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
// Besitzer = Desktop (Progman): bleibt bei Win+D sichtbar und liegt über den Desktop-Icons
|
||||
var progman = FindWindow("Progman", null);
|
||||
if (progman != IntPtr.Zero) SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, progman);
|
||||
// Nicht in Alt+Tab
|
||||
var ex = GetWindowLongPtr(hwnd, GWL_EXSTYLE).ToInt64();
|
||||
SetWindowLongPtr(hwnd, GWL_EXSTYLE, new IntPtr((ex | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW));
|
||||
HwndSource.FromHwnd(hwnd).AddHook(Hook);
|
||||
SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case WM_WINDOWPOSCHANGING:
|
||||
// immer ganz unten bleiben, nie über anderen Fenstern
|
||||
var wp = Marshal.PtrToStructure<WINDOWPOS>(lParam);
|
||||
wp.hwndInsertAfter = HWND_BOTTOM;
|
||||
wp.flags &= ~SWP_NOZORDER;
|
||||
Marshal.StructureToPtr(wp, lParam, false);
|
||||
break;
|
||||
case WM_NCLBUTTONDBLCLK:
|
||||
ToggleCollapsed();
|
||||
handled = true;
|
||||
break;
|
||||
case WM_SYSCOMMAND when (wParam.ToInt32() & 0xFFF0) == SC_MAXIMIZE:
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private const int GWL_EXSTYLE = -20, GWLP_HWNDPARENT = -8;
|
||||
private const long WS_EX_TOOLWINDOW = 0x80, WS_EX_APPWINDOW = 0x40000;
|
||||
private const int WM_WINDOWPOSCHANGING = 0x46, WM_NCLBUTTONDBLCLK = 0xA3, WM_SYSCOMMAND = 0x112, SC_MAXIMIZE = 0xF030;
|
||||
private const uint SWP_NOSIZE = 0x1, SWP_NOMOVE = 0x2, SWP_NOZORDER = 0x4, SWP_NOACTIVATE = 0x10;
|
||||
private static readonly IntPtr HWND_BOTTOM = new(1);
|
||||
|
||||
[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);
|
||||
[DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int cx, int cy, uint flags);
|
||||
}
|
||||
|
||||
/// <summary>Hält die Desktop-Boxen passend zu den Einstellungen.</summary>
|
||||
public static class FenceManager
|
||||
{
|
||||
private static readonly List<FenceWindow> _open = [];
|
||||
|
||||
public static void Sync()
|
||||
{
|
||||
var s = AppSettings.Current;
|
||||
var wanted = s.FencesEnabled
|
||||
? Storage.Categories().Where(c => s.Categories.GetValueOrDefault(c)?.Fence == true).ToList()
|
||||
: [];
|
||||
|
||||
foreach (var f in _open.Where(f => !wanted.Contains(f.Category, StringComparer.OrdinalIgnoreCase)).ToList())
|
||||
{
|
||||
f.Close();
|
||||
_open.Remove(f);
|
||||
}
|
||||
|
||||
var wa = SystemParameters.WorkArea;
|
||||
foreach (var c in wanted)
|
||||
{
|
||||
var f = _open.FirstOrDefault(x => string.Equals(x.Category, c, StringComparison.OrdinalIgnoreCase));
|
||||
if (f is not null) { f.Refresh(); continue; }
|
||||
|
||||
// Neue Box: neben die vorhandenen setzen
|
||||
var meta = s.Meta(c);
|
||||
if (meta.FenceX is null)
|
||||
{
|
||||
// oben rechts beginnen – links liegen meist die Desktop-Icons
|
||||
int perRow = Math.Max(1, (int)((wa.Width - 48) / (meta.FenceW + 16)));
|
||||
int i = _open.Count;
|
||||
meta.FenceX = wa.Right - 24 - meta.FenceW - (i % perRow) * (meta.FenceW + 16);
|
||||
meta.FenceY = wa.Top + 24 + (i / perRow) * (meta.FenceH + 16);
|
||||
}
|
||||
f = new FenceWindow(c);
|
||||
_open.Add(f);
|
||||
f.Show();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RefreshAll()
|
||||
{
|
||||
foreach (var f in _open) f.Refresh();
|
||||
}
|
||||
|
||||
public static void EnsureOnScreen()
|
||||
{
|
||||
foreach (var f in _open) f.EnsureOnScreen();
|
||||
}
|
||||
|
||||
public static void Renamed(string oldName, string newName)
|
||||
{
|
||||
_open.FirstOrDefault(x => string.Equals(x.Category, oldName, StringComparison.OrdinalIgnoreCase))?.Rename(newName);
|
||||
}
|
||||
|
||||
public static void CloseAll()
|
||||
{
|
||||
foreach (var f in _open) f.Close();
|
||||
_open.Clear();
|
||||
}
|
||||
}
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shell;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace DeskCore;
|
||||
|
||||
/// <summary>
|
||||
/// Eine Box auf dem Desktop: zeigt eine oder mehrere Kategorien bzw. Ordner (Tabs),
|
||||
/// mit eigenem Aussehen, Einklappen, Sperren, Chamäleon und Peek.
|
||||
/// </summary>
|
||||
public partial class FenceWindow : Window
|
||||
{
|
||||
private const double CollapsedHeight = 38;
|
||||
private readonly DispatcherTimer _saveTimer;
|
||||
private readonly DispatcherTimer _collapseTimer;
|
||||
private FileSystemWatcher? _portalWatcher;
|
||||
private DispatcherTimer? _portalRefresh;
|
||||
private bool _applying;
|
||||
private bool _hoverExpanded;
|
||||
private bool _mouseInside;
|
||||
/// <summary>Unterordner, in den im Ordner-Portal navigiert wurde (null = Wurzel).</summary>
|
||||
private string? _portalSub;
|
||||
|
||||
public FenceDef Def { get; }
|
||||
private static AppSettings S => AppSettings.Current;
|
||||
|
||||
public FenceWindow(FenceDef def)
|
||||
{
|
||||
InitializeComponent();
|
||||
Def = def;
|
||||
_saveTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(700), DispatcherPriority.Background, (_, _) =>
|
||||
{
|
||||
_saveTimer!.Stop();
|
||||
S.Save();
|
||||
}, Dispatcher);
|
||||
_saveTimer.Stop();
|
||||
_collapseTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
|
||||
_collapseTimer.Tick += (_, _) =>
|
||||
{
|
||||
_collapseTimer.Stop();
|
||||
if (!_mouseInside && _hoverExpanded && !ItemMenu.IsOpen && !FenceMenu.IsOpen) CollapseAfterHover();
|
||||
};
|
||||
|
||||
ApplyBounds();
|
||||
LocationChanged += (_, _) => RememberBounds();
|
||||
SizeChanged += (_, _) => RememberBounds();
|
||||
SourceInitialized += (_, _) => PinToDesktop();
|
||||
MouseEnter += (_, _) => OnHover(true);
|
||||
MouseLeave += (_, _) => OnHover(false);
|
||||
Closed += (_, _) => _portalWatcher?.Dispose();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Position, Größe, Einklappen
|
||||
// =====================================================================
|
||||
|
||||
private void ApplyBounds()
|
||||
{
|
||||
_applying = true;
|
||||
Width = Math.Max(MinWidth, Def.W);
|
||||
Height = Def.Collapsed && !_hoverExpanded ? CollapsedHeight : Math.Max(120, Def.H);
|
||||
var wa = Screens.PrimaryWorkArea;
|
||||
double left = Def.X ?? wa.Right - Width - 24, top = Def.Y ?? wa.Top + 24;
|
||||
// Bildschirm abgesteckt? → zurück auf den Hauptbildschirm (oben rechts)
|
||||
if (!Screens.IsReachable(new Rect(left, top, Width, Height)))
|
||||
{
|
||||
left = wa.Right - Width - 24;
|
||||
top = wa.Top + 24;
|
||||
Def.X = left;
|
||||
Def.Y = top;
|
||||
}
|
||||
Left = left;
|
||||
Top = top;
|
||||
Body.Visibility = Def.Collapsed && !_hoverExpanded ? Visibility.Collapsed : Visibility.Visible;
|
||||
_applying = false;
|
||||
}
|
||||
|
||||
/// <summary>Position/Größe aus den Einstellungen neu übernehmen (z. B. nach Snapshot).</summary>
|
||||
public void Reapply()
|
||||
{
|
||||
ApplyBounds();
|
||||
ApplyLook();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public void EnsureOnScreen()
|
||||
{
|
||||
if (Screens.IsReachable(new Rect(Left, Top, ActualWidth, ActualHeight))) return;
|
||||
Def.X = null;
|
||||
Def.Y = null;
|
||||
ApplyBounds();
|
||||
S.Save();
|
||||
}
|
||||
|
||||
private void RememberBounds()
|
||||
{
|
||||
if (_applying || !IsLoaded || _hoverExpanded) return;
|
||||
Def.X = Left;
|
||||
Def.Y = Top;
|
||||
Def.W = Width;
|
||||
if (!Def.Collapsed) Def.H = Height;
|
||||
_saveTimer.Stop();
|
||||
_saveTimer.Start();
|
||||
}
|
||||
|
||||
private void ToggleCollapsed()
|
||||
{
|
||||
_hoverExpanded = false;
|
||||
Def.Collapsed = !Def.Collapsed;
|
||||
ApplyBounds();
|
||||
S.Save();
|
||||
}
|
||||
|
||||
/// <summary>Eingeklappte Box klappt beim Überfahren kurz auf; Chamäleon blendet ein/aus.</summary>
|
||||
private void OnHover(bool inside)
|
||||
{
|
||||
_mouseInside = inside;
|
||||
if (inside)
|
||||
{
|
||||
_collapseTimer.Stop();
|
||||
if (Def.Collapsed && !_hoverExpanded)
|
||||
{
|
||||
_hoverExpanded = true;
|
||||
ApplyBounds();
|
||||
}
|
||||
}
|
||||
else if (_hoverExpanded) _collapseTimer.Start();
|
||||
ApplyChameleon();
|
||||
}
|
||||
|
||||
private void CollapseAfterHover()
|
||||
{
|
||||
_hoverExpanded = false;
|
||||
ApplyBounds();
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Aussehen
|
||||
// =====================================================================
|
||||
|
||||
public void ApplyLook()
|
||||
{
|
||||
// Hintergrund: eigene Farbe oder Design-Farbe, mit eingestellter Deckkraft
|
||||
var baseColor = Def.Background is { } hex && CategoryColors.Brush(hex) is SolidColorBrush custom
|
||||
? custom.Color
|
||||
: ((SolidColorBrush)FindResource("FenceBrush")).Color;
|
||||
double opacity = Math.Clamp(Def.Opacity, 0.2, 1);
|
||||
if (Def.Blur) opacity = Math.Min(opacity, 0.75); // sonst sieht man vom Milchglas nichts
|
||||
var bg = new SolidColorBrush(Color.FromArgb((byte)(opacity * 255), baseColor.R, baseColor.G, baseColor.B));
|
||||
bg.Freeze();
|
||||
Root.Background = bg;
|
||||
Root.CornerRadius = new CornerRadius(Def.Blur ? 0 : 8); // Milchglas wirkt nur eckig sauber
|
||||
|
||||
// Icon-Größe
|
||||
var (w, h, icon) = Def.IconSize switch
|
||||
{
|
||||
ViewMode.Small => (64.0, 66.0, 24.0),
|
||||
ViewMode.Large => (98.0, 104.0, 48.0),
|
||||
_ => (78.0, 80.0, 32.0),
|
||||
};
|
||||
Resources["FTileW"] = w;
|
||||
Resources["FTileH"] = h;
|
||||
Resources["FIcon"] = icon;
|
||||
Resources["FTint"] = CategoryColors.Brush(Def.Tint) ?? Brushes.Transparent;
|
||||
|
||||
// Sperren: nicht verschieb-/vergrößerbar
|
||||
Chrome.CaptionHeight = Def.Locked ? 0 : 36;
|
||||
Chrome.ResizeBorderThickness = Def.Locked ? new Thickness(0) : new Thickness(6);
|
||||
LockGlyph.Visibility = Def.Locked ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
if (IsLoaded) SetBlur(Def.Blur);
|
||||
ApplyChameleon();
|
||||
}
|
||||
|
||||
/// <summary>Chamäleon: Box ist fast unsichtbar, bis die Maus darüberfährt (nicht während Peek).</summary>
|
||||
private void ApplyChameleon()
|
||||
{
|
||||
double target = S.FenceChameleon && !_mouseInside && !FenceManager.Peeking ? 0.08 : 1;
|
||||
Root.BeginAnimation(OpacityProperty, new DoubleAnimation(target, TimeSpan.FromMilliseconds(target < 1 ? 600 : 150)));
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Inhalt & Tabs
|
||||
// =====================================================================
|
||||
|
||||
private FenceTab? Tab => Def.Active;
|
||||
|
||||
/// <summary>Ordner des aktiven Tabs (bei Ordner-Portalen ggf. ein Unterordner).</summary>
|
||||
private string? CurrentFolder => Tab switch
|
||||
{
|
||||
null => null,
|
||||
{ IsFolder: true } t => _portalSub ?? t.Value,
|
||||
{ } t => Storage.CategoryPath(t.Value),
|
||||
};
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
ApplyLook();
|
||||
BuildTabs();
|
||||
|
||||
var tab = Tab;
|
||||
var folder = CurrentFolder;
|
||||
bool portal = tab?.IsFolder == true;
|
||||
PortalBar.Visibility = portal && _portalSub is not null ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (portal && _portalSub is not null) PortalPath.Text = Path.GetRelativePath(tab!.Value, _portalSub);
|
||||
WatchPortal(portal ? folder : null);
|
||||
|
||||
List<IconItem> items = [];
|
||||
if (folder is not null && Directory.Exists(folder))
|
||||
items = (portal ? PortalEntries(folder) : Storage.Entries(folder)).Select(p => new IconItem { Path = p }).ToList();
|
||||
items = Sort(items);
|
||||
|
||||
int px = (int)Math.Round((double)Resources["FIcon"] * VisualTreeHelper.GetDpi(this).DpiScaleX);
|
||||
foreach (var i in items) IconLoader.Load(i, px);
|
||||
Items.ItemsSource = items;
|
||||
CountText.Text = items.Count.ToString();
|
||||
EmptyText.Text = folder is not null && !Directory.Exists(folder) ? "Ordner nicht gefunden" : "Leer – Icons hierher ziehen";
|
||||
EmptyText.Visibility = items.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>Ordner-Portal zeigt alles außer versteckten/System-Dateien.</summary>
|
||||
private static IEnumerable<string> PortalEntries(string dir) => Storage.Entries(dir);
|
||||
|
||||
private List<IconItem> Sort(List<IconItem> items)
|
||||
{
|
||||
var folders = items.Where(i => Directory.Exists(i.Path));
|
||||
var files = items.Where(i => !Directory.Exists(i.Path));
|
||||
IEnumerable<IconItem> Order(IEnumerable<IconItem> x) => Def.Sort switch
|
||||
{
|
||||
"Typ" => x.OrderBy(i => Path.GetExtension(i.Path), StringComparer.OrdinalIgnoreCase).ThenBy(i => i.Name, StringComparer.CurrentCultureIgnoreCase),
|
||||
"Datum" => x.OrderByDescending(i => { try { return File.GetLastWriteTime(i.Path); } catch { return DateTime.MinValue; } }),
|
||||
_ => x.OrderBy(i => i.Name, StringComparer.CurrentCultureIgnoreCase),
|
||||
};
|
||||
return Order(folders).Concat(Order(files)).ToList();
|
||||
}
|
||||
|
||||
private void BuildTabs()
|
||||
{
|
||||
TabStrip.Children.Clear();
|
||||
for (int i = 0; i < Def.Tabs.Count; i++)
|
||||
{
|
||||
var t = Def.Tabs[i];
|
||||
bool active = i == Math.Clamp(Def.ActiveTab, 0, Def.Tabs.Count - 1);
|
||||
var meta = t.IsFolder ? null : S.Categories.GetValueOrDefault(t.Value);
|
||||
var glyph = new TextBlock
|
||||
{
|
||||
Text = t.IsFolder ? "" : meta?.Glyph ?? "",
|
||||
FontFamily = (FontFamily)FindResource("IconFont"), FontSize = 13, VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 7, 0),
|
||||
Foreground = CategoryColors.Brush(meta?.Color) ?? (Brush)FindResource(t.IsFolder ? "AccentBrush" : "TextBrush"),
|
||||
};
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = t.Title, VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis,
|
||||
FontSize = 13, MaxWidth = Def.Tabs.Count == 1 ? 220 : 120,
|
||||
FontWeight = active ? FontWeights.SemiBold : FontWeights.Normal,
|
||||
Foreground = (Brush)FindResource(active ? "TextBrush" : "TextSecondaryBrush"),
|
||||
};
|
||||
var content = new StackPanel { Orientation = Orientation.Horizontal, Children = { glyph, label } };
|
||||
var underline = new Border
|
||||
{
|
||||
Height = 2, CornerRadius = new CornerRadius(1), Margin = new Thickness(4, 0, 4, 0), VerticalAlignment = VerticalAlignment.Bottom,
|
||||
Background = active && Def.Tabs.Count > 1 ? (Brush)FindResource("AccentBrush") : Brushes.Transparent,
|
||||
};
|
||||
var btn = new Button
|
||||
{
|
||||
Style = (Style)FindResource("SubtleButton"), Padding = new Thickness(6, 4, 6, 4), MinHeight = 0, Tag = i,
|
||||
Content = new Grid { Children = { content, underline } }, Margin = new Thickness(0, 0, 2, 0),
|
||||
ToolTip = t.IsFolder ? t.Value : $"Kategorie „{t.Value}“",
|
||||
};
|
||||
WindowChrome.SetIsHitTestVisibleInChrome(btn, Def.Tabs.Count > 1); // bei nur einem Tab: Titel = Ziehfläche
|
||||
btn.Click += (_, _) => SwitchTab((int)btn.Tag);
|
||||
btn.MouseRightButtonUp += (_, ev) => { ShowTabMenu((int)btn.Tag); ev.Handled = true; };
|
||||
TabStrip.Children.Add(btn);
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchTab(int index)
|
||||
{
|
||||
if (index == Def.ActiveTab) return;
|
||||
Def.ActiveTab = index;
|
||||
_portalSub = null;
|
||||
S.Save();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// ---------- Ordner-Portal ----------
|
||||
|
||||
private void WatchPortal(string? folder)
|
||||
{
|
||||
if (string.Equals(_portalWatcher?.Path, folder, StringComparison.OrdinalIgnoreCase)) return;
|
||||
_portalWatcher?.Dispose();
|
||||
_portalWatcher = null;
|
||||
if (folder is null || !Directory.Exists(folder)) return;
|
||||
_portalRefresh ??= new DispatcherTimer(TimeSpan.FromMilliseconds(400), DispatcherPriority.Background, (_, _) =>
|
||||
{
|
||||
_portalRefresh!.Stop();
|
||||
Refresh();
|
||||
}, Dispatcher);
|
||||
try
|
||||
{
|
||||
_portalWatcher = new FileSystemWatcher(folder)
|
||||
{
|
||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite,
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
FileSystemEventHandler poke = (_, _) => Dispatcher.BeginInvoke(() => { _portalRefresh.Stop(); _portalRefresh.Start(); });
|
||||
_portalWatcher.Created += poke;
|
||||
_portalWatcher.Deleted += poke;
|
||||
_portalWatcher.Renamed += (s, e) => poke(s, e);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void PortalUp_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Tab is not { IsFolder: true } t || _portalSub is null) return;
|
||||
var parent = Path.GetDirectoryName(_portalSub.TrimEnd('\\'));
|
||||
_portalSub = parent is null || Storage.SamePath(parent, t.Value) || !Storage.IsUnder(parent, t.Value) ? null : parent;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Icons
|
||||
// =====================================================================
|
||||
|
||||
private List<IconItem> Selected => Items.SelectedItems.Cast<IconItem>().ToList();
|
||||
|
||||
private void Open(IconItem i)
|
||||
{
|
||||
// Im Ordner-Portal öffnen Ordner sich innerhalb der Box
|
||||
if (Tab?.IsFolder == true && Directory.Exists(i.Path) && !i.Path.EndsWith(".lnk", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_portalSub = i.Path;
|
||||
Refresh();
|
||||
return;
|
||||
}
|
||||
try { Process.Start(new ProcessStartInfo(i.Path) { UseShellExecute = true }); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void Items_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (MainWindow.FindParent<ListBoxItem>(e.OriginalSource as DependencyObject)?.DataContext is IconItem i) Open(i);
|
||||
}
|
||||
|
||||
private void Items_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape && FenceManager.Peeking) { FenceManager.EndPeek(); e.Handled = true; return; }
|
||||
var sel = Selected;
|
||||
if (sel.Count == 0 || App.Win is not { } win) return;
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter: sel.ForEach(Open); e.Handled = true; break;
|
||||
case Key.Delete: win.DeleteIcons(sel); e.Handled = true; break;
|
||||
case Key.F2 when sel.Count == 1: win.RenameIcon(sel[0]); e.Handled = true; break;
|
||||
case Key.Back when _portalSub is not null: PortalUp_Click(this, e); e.Handled = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Items_ContextMenuOpening(object sender, ContextMenuEventArgs e)
|
||||
{
|
||||
var sel = Selected;
|
||||
if (sel.Count == 0 || App.Win is not { } win) { e.Handled = true; return; }
|
||||
ItemMenu.Items.Clear();
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Öffnen", "", () => sel.ForEach(Open), bold: true, gesture: "Enter"));
|
||||
if (sel.Count == 1)
|
||||
{
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Dateispeicherort öffnen", "", () =>
|
||||
Process.Start("explorer.exe", $"/select,\"{sel[0].Path}\"")));
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Umbenennen", "", () => win.RenameIcon(sel[0]), gesture: "F2"));
|
||||
}
|
||||
ItemMenu.Items.Add(new Separator());
|
||||
|
||||
bool inStorage = sel.All(i => Storage.IsInStorage(i.Path));
|
||||
var moveTo = MainWindow.MenuItem(inStorage ? "Verschieben nach" : "Sichern in", inStorage ? "" : "", null);
|
||||
var ownCategory = Tab is { IsFolder: false } t ? t.Value : null;
|
||||
foreach (var c in Storage.Categories().Where(c => !string.Equals(c, ownCategory, StringComparison.OrdinalIgnoreCase)))
|
||||
moveTo.Items.Add(MainWindow.MenuItem(c, S.Categories.GetValueOrDefault(c)?.Glyph ?? "",
|
||||
() => win.TransferAll(sel.Select(i => i.Path), Storage.CategoryPath(c))));
|
||||
if (moveTo.Items.Count > 0) ItemMenu.Items.Add(moveTo);
|
||||
ItemMenu.Items.Add(win.LaunchGroupSubmenu(sel.Select(i => i.Path).ToList()));
|
||||
if (inStorage)
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Zurück auf den Desktop legen", "",
|
||||
() => win.TransferAll(sel.Select(i => i.Path), Storage.UserDesktop)));
|
||||
ItemMenu.Items.Add(MainWindow.MenuItem("Löschen", "", () => win.DeleteIcons(sel), gesture: "Entf"));
|
||||
}
|
||||
|
||||
// ---------- Ziehen ----------
|
||||
|
||||
private Point _dragStart;
|
||||
private bool _dragging;
|
||||
|
||||
private void Items_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) => _dragStart = e.GetPosition(null);
|
||||
|
||||
private void Items_PreviewMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton != MouseButtonState.Pressed || _dragging) return;
|
||||
var d = e.GetPosition(null) - _dragStart;
|
||||
if (Math.Abs(d.X) < SystemParameters.MinimumHorizontalDragDistance &&
|
||||
Math.Abs(d.Y) < SystemParameters.MinimumVerticalDragDistance) return;
|
||||
if (MainWindow.FindParent<ListBoxItem>(e.OriginalSource as DependencyObject) is null) return;
|
||||
var paths = Selected.Select(i => i.Path).ToArray();
|
||||
if (paths.Length == 0) return;
|
||||
_dragging = true;
|
||||
try
|
||||
{
|
||||
// nur "Kopieren" erlauben: der Explorer soll die Datei nicht aus der Kategorie entfernen.
|
||||
// In eine andere Box/Kategorie gezogen, verschiebt DeskCore selbst.
|
||||
DragDrop.DoDragDrop(Items, new DataObject(DataFormats.FileDrop, paths), DragDropEffects.Copy | DragDropEffects.Link);
|
||||
}
|
||||
finally { _dragging = false; }
|
||||
}
|
||||
|
||||
private void Items_DragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) && !_dragging && CurrentFolder is not null
|
||||
? (e.AllowedEffects.HasFlag(DragDropEffects.Copy) ? DragDropEffects.Copy : DragDropEffects.Move)
|
||||
: DragDropEffects.None;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Items_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
if (!_dragging && CurrentFolder is { } folder && e.Data.GetData(DataFormats.FileDrop) is string[] files)
|
||||
App.Win?.TransferAll(files, folder);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Menüs
|
||||
// =====================================================================
|
||||
|
||||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// gesperrte Box: Titelleiste ist normale Fläche → Doppelklick selbst auswerten
|
||||
if (e.ClickCount == 2) { ToggleCollapsed(); e.Handled = true; }
|
||||
}
|
||||
|
||||
private void MenuButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FenceMenu.Items.Clear();
|
||||
if (Tab is { IsFolder: false } t)
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem("In DeskCore öffnen", "", () => App.Win?.ShowCategory(t.Value)));
|
||||
else if (Tab is { IsFolder: true } pt)
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem("Im Explorer öffnen", "", () => Process.Start("explorer.exe", $"\"{CurrentFolder ?? pt.Value}\"")));
|
||||
|
||||
// Tabs
|
||||
var add = MainWindow.MenuItem("Tab hinzufügen", "", null);
|
||||
foreach (var c in Storage.Categories().Where(c => !Def.Tabs.Any(x => !x.IsFolder && string.Equals(x.Value, c, StringComparison.OrdinalIgnoreCase))))
|
||||
add.Items.Add(MainWindow.MenuItem(c, S.Categories.GetValueOrDefault(c)?.Glyph ?? "", () => AddTab(new FenceTab { Value = c })));
|
||||
if (add.Items.Count > 0) add.Items.Add(new Separator());
|
||||
add.Items.Add(MainWindow.MenuItem("Ordner-Portal…", "", () =>
|
||||
{
|
||||
if (FenceManager.PickFolder() is { } folder) AddTab(new FenceTab { Kind = FenceTab.FolderKind, Value = folder });
|
||||
}));
|
||||
FenceMenu.Items.Add(add);
|
||||
if (Def.Tabs.Count > 1)
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem($"Tab „{Tab?.Title}“ entfernen", "", () => RemoveTab(Def.ActiveTab)));
|
||||
|
||||
FenceMenu.Items.Add(new Separator());
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem("Aussehen…", "", () => FenceManager.EditLook(this)));
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem(Def.Collapsed ? "Ausklappen" : "Einklappen",
|
||||
Def.Collapsed ? "" : "", ToggleCollapsed));
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem(Def.Locked ? "Entsperren" : "Sperren (nicht mehr verschiebbar)",
|
||||
Def.Locked ? "" : "", () => { Def.Locked = !Def.Locked; S.Save(); ApplyLook(); }));
|
||||
if (S.Pages.Count > 1)
|
||||
{
|
||||
var move = MainWindow.MenuItem("Auf Seite verschieben", "\uE8AB", null);
|
||||
for (int p = 0; p < S.Pages.Count; p++)
|
||||
{
|
||||
int page = p;
|
||||
var mi = MainWindow.MenuItem(S.Pages[p], "\uE8A1", () => FenceManager.MoveToPage(Def, page));
|
||||
mi.IsEnabled = page != Def.Page;
|
||||
move.Items.Add(mi);
|
||||
}
|
||||
FenceMenu.Items.Add(move);
|
||||
}
|
||||
FenceMenu.Items.Add(new Separator());
|
||||
FenceMenu.Items.Add(MainWindow.MenuItem("Box schließen", "", () => FenceManager.Remove(Def)));
|
||||
FenceMenu.PlacementTarget = MenuButton;
|
||||
FenceMenu.IsOpen = true;
|
||||
}
|
||||
|
||||
private void ShowTabMenu(int index)
|
||||
{
|
||||
if (index < 0 || index >= Def.Tabs.Count) return;
|
||||
var t = Def.Tabs[index];
|
||||
var menu = new ContextMenu();
|
||||
if (!t.IsFolder) menu.Items.Add(MainWindow.MenuItem("In DeskCore öffnen", "", () => App.Win?.ShowCategory(t.Value)));
|
||||
else menu.Items.Add(MainWindow.MenuItem("Im Explorer öffnen", "", () => Process.Start("explorer.exe", $"\"{t.Value}\"")));
|
||||
if (Def.Tabs.Count > 1)
|
||||
{
|
||||
menu.Items.Add(MainWindow.MenuItem("Als eigene Box lösen", "", () => FenceManager.DetachTab(Def, index)));
|
||||
menu.Items.Add(MainWindow.MenuItem("Tab entfernen", "", () => RemoveTab(index)));
|
||||
}
|
||||
menu.IsOpen = true;
|
||||
}
|
||||
|
||||
private void AddTab(FenceTab tab)
|
||||
{
|
||||
// Kategorie steckt schon in einer anderen Box? Dann dorthin verschieben statt doppelt anzeigen
|
||||
if (!tab.IsFolder) FenceManager.RemoveCategoryTab(tab.Value, except: Def);
|
||||
Def.Tabs.Add(tab);
|
||||
Def.ActiveTab = Def.Tabs.Count - 1;
|
||||
_portalSub = null;
|
||||
S.Save();
|
||||
FenceManager.Sync();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void RemoveTab(int index)
|
||||
{
|
||||
if (index < 0 || index >= Def.Tabs.Count) return;
|
||||
Def.Tabs.RemoveAt(index);
|
||||
Def.ActiveTab = Math.Clamp(Def.ActiveTab, 0, Math.Max(0, Def.Tabs.Count - 1));
|
||||
_portalSub = null;
|
||||
S.Save();
|
||||
if (Def.Tabs.Count == 0) FenceManager.Remove(Def);
|
||||
else Refresh();
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Auf dem Desktop festhalten, Peek, Milchglas
|
||||
// =====================================================================
|
||||
|
||||
private IntPtr Hwnd => new WindowInteropHelper(this).Handle;
|
||||
|
||||
private void PinToDesktop()
|
||||
{
|
||||
var hwnd = Hwnd;
|
||||
// Besitzer = Desktop (Progman): bleibt bei Win+D sichtbar und liegt über den Desktop-Icons
|
||||
var progman = FindWindow("Progman", null);
|
||||
if (progman != IntPtr.Zero) SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, progman);
|
||||
// Nicht in Alt+Tab
|
||||
var ex = GetWindowLongPtr(hwnd, GWL_EXSTYLE).ToInt64();
|
||||
SetWindowLongPtr(hwnd, GWL_EXSTYLE, new IntPtr((ex | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW));
|
||||
HwndSource.FromHwnd(hwnd).AddHook(Hook);
|
||||
SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
SetBlur(Def.Blur);
|
||||
}
|
||||
|
||||
/// <summary>Peek: Box vor alle Fenster holen bzw. wieder ganz nach hinten.</summary>
|
||||
public void SetPeek(bool on)
|
||||
{
|
||||
var hwnd = Hwnd;
|
||||
if (hwnd == IntPtr.Zero) return;
|
||||
SetWindowPos(hwnd, on ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
if (!on) SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
ApplyChameleon();
|
||||
}
|
||||
|
||||
private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case WM_WINDOWPOSCHANGING when !FenceManager.Peeking:
|
||||
// immer ganz unten bleiben, nie über anderen Fenstern (außer während Peek)
|
||||
var wp = Marshal.PtrToStructure<WINDOWPOS>(lParam);
|
||||
wp.hwndInsertAfter = HWND_BOTTOM;
|
||||
wp.flags &= ~SWP_NOZORDER;
|
||||
Marshal.StructureToPtr(wp, lParam, false);
|
||||
break;
|
||||
case WM_NCLBUTTONDBLCLK:
|
||||
ToggleCollapsed();
|
||||
handled = true;
|
||||
break;
|
||||
case WM_SYSCOMMAND when (wParam.ToInt32() & 0xFFF0) == SC_MAXIMIZE:
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>Milchglas (verschwommener Hintergrund) über die Windows-Composition-Schnittstelle.</summary>
|
||||
private void SetBlur(bool on)
|
||||
{
|
||||
var hwnd = Hwnd;
|
||||
if (hwnd == IntPtr.Zero) return;
|
||||
var accent = new AccentPolicy { AccentState = on ? 3 : 0 }; // 3 = ACCENT_ENABLE_BLURBEHIND
|
||||
int size = Marshal.SizeOf(accent);
|
||||
var ptr = Marshal.AllocHGlobal(size);
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(accent, ptr, false);
|
||||
var data = new WindowCompositionAttributeData { Attribute = 19, Data = ptr, SizeOfData = size }; // 19 = WCA_ACCENT_POLICY
|
||||
SetWindowCompositionAttribute(hwnd, ref data);
|
||||
}
|
||||
catch { }
|
||||
finally { Marshal.FreeHGlobal(ptr); }
|
||||
}
|
||||
|
||||
private const int GWL_EXSTYLE = -20, GWLP_HWNDPARENT = -8;
|
||||
private const long WS_EX_TOOLWINDOW = 0x80, WS_EX_APPWINDOW = 0x40000;
|
||||
private const int WM_WINDOWPOSCHANGING = 0x46, WM_NCLBUTTONDBLCLK = 0xA3, WM_SYSCOMMAND = 0x112, SC_MAXIMIZE = 0xF030;
|
||||
private const uint SWP_NOSIZE = 0x1, SWP_NOMOVE = 0x2, SWP_NOZORDER = 0x4, SWP_NOACTIVATE = 0x10;
|
||||
private static readonly IntPtr HWND_BOTTOM = new(1), HWND_TOPMOST = new(-1), HWND_NOTOPMOST = new(-2);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WINDOWPOS
|
||||
{
|
||||
public IntPtr hwnd, hwndInsertAfter;
|
||||
public int x, y, cx, cy;
|
||||
public uint flags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct AccentPolicy
|
||||
{
|
||||
public int AccentState, AccentFlags, GradientColor, AnimationId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WindowCompositionAttributeData
|
||||
{
|
||||
public int Attribute;
|
||||
public IntPtr Data;
|
||||
public int SizeOfData;
|
||||
}
|
||||
|
||||
[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);
|
||||
[DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int cx, int cy, uint flags);
|
||||
[DllImport("user32.dll")] private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user