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); }