using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace DeskCore;
///
/// Auswahl nach dem Aufziehen einer Box: Welche Kategorie (oder welcher Ordner) soll hinein?
/// Ein eigenes Fenster statt eines Kontextmenüs – ein Menü ohne aktives Fenster schließt sich sonst gelegentlich von selbst.
///
public sealed class BoxPicker : Window
{
private static BoxPicker? _open;
private readonly long _shownAt = Environment.TickCount64;
private bool _done;
private static AppSettings S => AppSettings.Current;
/// Fläche der neuen Box in WPF-Einheiten.
/// Legt die Box mit dem gewählten Tab an.
public static void Show(Rect area, Action create)
{
_open?.Finish(null);
_open = new BoxPicker(area, create);
_open.Show();
}
private readonly Action _create;
private BoxPicker(Rect area, Action create)
{
_create = create;
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
Background = Brushes.Transparent;
ResizeMode = ResizeMode.NoResize;
ShowInTaskbar = false;
Topmost = true;
SizeToContent = SizeToContent.WidthAndHeight;
Title = "Neue Box";
var res = Application.Current.Resources;
var list = new StackPanel();
list.Children.Add(new TextBlock
{
Text = "Neue Box hier", FontSize = 14, FontWeight = FontWeights.SemiBold, Margin = new Thickness(12, 8, 12, 0),
Foreground = (Brush)res["TextBrush"],
});
list.Children.Add(new TextBlock
{
Text = "Was soll hinein?", FontSize = 12, Margin = new Thickness(12, 2, 12, 6),
Foreground = (Brush)res["TextSecondaryBrush"],
});
var cats = Storage.Categories();
var catPanel = new StackPanel();
foreach (var c in cats)
{
var meta = S.Categories.GetValueOrDefault(c);
// Pro Seite steht eine Kategorie nur in einer Box – andere Seiten behalten ihre
string? hint = S.HasFence(c) ? "wird aus ihrer Box auf dieser Seite hierher geholt" : null;
catPanel.Children.Add(Row(meta?.Glyph ?? "", CategoryColors.Brush(meta?.Color), c, hint,
() => Pick(new FenceTab { Value = c })));
}
if (cats.Count > 0)
{
list.Children.Add(new ScrollViewer
{
Content = catPanel, MaxHeight = 360, VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
});
list.Children.Add(Divider());
}
list.Children.Add(Row("", null, "Zuletzt benutzt", "zeigt, was du zuletzt gestartet hast",
() => Pick(new FenceTab { Kind = FenceTab.RecentKind })));
list.Children.Add(Row("", null, "Neue Kategorie …", null, () => PickLater(() =>
App.Win?.CreateCategoryInteractive() is { } name ? new FenceTab { Value = name } : null)));
list.Children.Add(Row("", null, "Ordner-Portal …", null, () => PickLater(() =>
FenceManager.PickFolder() is { } folder ? new FenceTab { Kind = FenceTab.FolderKind, Value = folder } : null)));
list.Children.Add(Divider());
list.Children.Add(Row("", null, "Abbrechen", null, () => Finish(null)));
Content = new Border
{
Margin = new Thickness(14), Padding = new Thickness(4), MinWidth = 240, CornerRadius = new CornerRadius(8),
Background = (Brush)res["FlyoutBrush"], BorderBrush = (Brush)res["FlyoutStrokeBrush"], BorderThickness = new Thickness(1),
Effect = (System.Windows.Media.Effects.Effect)res["FlyoutShadow"],
Child = list,
};
// Neben dem Mauszeiger öffnen, aber immer ganz auf dem Bildschirm
Loaded += (_, _) =>
{
var mouse = GetCursorPos(out var p) ? new Point(p.X / Screens.Scale, p.Y / Screens.Scale) : area.BottomRight;
var wa = Screens.WorkAreaAt(mouse);
Left = Math.Clamp(mouse.X - 14, wa.Left, Math.Max(wa.Left, wa.Right - ActualWidth));
Top = Math.Clamp(mouse.Y - 14, wa.Top, Math.Max(wa.Top, wa.Bottom - ActualHeight));
BringToFront();
};
KeyDown += (_, e) => { if (e.Key == Key.Escape) Finish(null); };
// Klick daneben schließt – aber nicht durch ein kurzes Flackern direkt nach dem Öffnen
Deactivated += (_, _) =>
{
if (_done) return;
if (Environment.TickCount64 - _shownAt < 400) BringToFront();
else Finish(null);
};
Closed += (_, _) => { if (_open == this) _open = null; DrawBoxOverlay.Hide(); };
}
private Button Row(string glyph, Brush? glyphBrush, string text, string? hint, Action click)
{
var res = Application.Current.Resources;
var content = new DockPanel();
var icon = new TextBlock
{
Text = glyph, FontFamily = (FontFamily)res["IconFont"], FontSize = 15, Width = 20, Margin = new Thickness(0, 0, 12, 0),
VerticalAlignment = VerticalAlignment.Center, Foreground = glyphBrush ?? (Brush)res["TextBrush"],
};
DockPanel.SetDock(icon, Dock.Left);
content.Children.Add(icon);
var texts = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
texts.Children.Add(new TextBlock { Text = text, Foreground = (Brush)res["TextBrush"] });
if (hint is not null)
texts.Children.Add(new TextBlock { Text = hint, FontSize = 11, Foreground = (Brush)res["TextTertiaryBrush"] });
content.Children.Add(texts);
var b = new Button
{
Style = (Style)res["SubtleButton"], Content = content, HorizontalContentAlignment = HorizontalAlignment.Stretch,
Padding = new Thickness(10, 7, 10, 7), Focusable = true,
};
b.Click += (_, _) => click();
return b;
}
private static Border Divider() => new()
{
Height = 1, Margin = new Thickness(4, 4, 4, 4), Background = (Brush)Application.Current.Resources["ControlStrokeBrush"],
};
private void Pick(FenceTab tab) => Finish(tab);
/// Für Auswahlen mit eigenem Dialog: erst die Auswahl schließen, Rahmen aber stehen lassen.
private void PickLater(Func ask)
{
_done = true;
Hide();
var tab = ask();
if (tab is not null) _create(tab);
Close();
}
private void Finish(FenceTab? tab)
{
if (_done) return;
_done = true;
Close();
if (tab is not null) _create(tab);
}
///
/// DeskCore läuft im Hintergrund – Windows lässt es nicht ohne Weiteres nach vorne. Ein kurzer Alt-Tastendruck
/// gibt die Erlaubnis (bewährter Weg, der auch von Windows selbst so akzeptiert wird).
///
private void BringToFront()
{
var hwnd = new WindowInteropHelper(this).Handle;
if (GetForegroundWindow() != hwnd)
{
// Loslassen erst nach dem Wechsel – so landet es bei uns und öffnet nirgends ein Menü
keybd_event(VK_MENU, 0, 0, UIntPtr.Zero);
SetForegroundWindow(hwnd);
keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
}
Activate();
Focus();
}
private const byte VK_MENU = 0x12;
private const uint KEYEVENTF_KEYUP = 0x2;
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int X, Y; }
[DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT p);
[DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll")] private static extern void keybd_event(byte vk, byte scan, uint flags, UIntPtr extra);
}