using System.Runtime.InteropServices; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; using OneClickRun.App.Controls; using OneClickRun.App.Services; using OneClickRun.Core.Logic; using OneClickRun.Core.Models; using Windows.Graphics; using Windows.UI; namespace OneClickRun.App; /// /// 轮盘悬浮窗:无边框、透明背景、置顶、不进 Alt+Tab。 /// 尺寸/位置一律使用物理像素;内容按 PerMonitorV2 DPI 自动缩放。 /// public sealed partial class WheelWindow : Window { private const double WheelDiameterDips = 560; private int _x; private int _y; private int _size; private double _scale = 1.0; public event Action? SectorClicked; public event Action? DismissRequested; public WheelWindow() { InitializeComponent(); Title = "OneClickRun Wheel"; AppWindow.IsShownInSwitchers = false; AppWindow.SetPresenter(AppWindowPresenterKind.Overlapped); if (AppWindow.Presenter is OverlappedPresenter presenter) { presenter.SetBorderAndTitleBar(false, false); presenter.IsAlwaysOnTop = true; presenter.IsResizable = false; presenter.IsMaximizable = false; presenter.IsMinimizable = false; } SystemBackdrop = new TransparentBackdrop(); // alpha=1 的“透明”背景:肉眼不可见,但可命中鼠标事件(用于点击轮盘外关闭) Root.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)); Wheel.SectorClicked += i => SectorClicked?.Invoke(i); Wheel.DismissRequested += () => DismissRequested?.Invoke(); } public bool IsVisible => AppWindow.IsVisible; public void SetItems(IReadOnlyList items) => Wheel.SetItems(items); public void SetHoverIndex(int index) => Wheel.SetHoverIndex(index); public void SetPendingIndex(int index) => Wheel.SetPendingIndex(index); public void ClearPending() => Wheel.ClearPending(); public void ClearHover() => Wheel.SetHoverIndex(-1); /// 按住模式轮询:光标所在扇区(窗口外也能计算方向)。 public int GetSectorIndexAtCursor() { if (Wheel.Count == 0) return -1; NativeMethods.GetCursorPos(out var pt); double cx = _x + _size / 2.0; double cy = _y + _size / 2.0; double dx = pt.X - cx; double dy = pt.Y - cy; double dist = Math.Sqrt(dx * dx + dy * dy); if (dist < 76 * _scale || dist > 282 * _scale) return -1; return WheelMath.GetSectorIndex(Wheel.Count, WheelMath.AngleFromVector(dx, dy)); } /// 滑动选择:松开时按光标相对轮盘中心方位决定扇区(死区 24 DIP)。 public bool TryGetSwipeIndex(int count, out int index) { index = -1; if (!NativeMethods.GetCursorPos(out var pt)) return false; double cx = _x + _size / 2.0; double cy = _y + _size / 2.0; return WheelMath.TryGetSwipeIndex(count, pt.X - cx, pt.Y - cy, 24 * _scale, out index); } /// 在屏幕指定中心点显示轮盘(自动钳制到工作区并换算 DPI)。 internal void ShowAt(POINT center) { var monitor = NativeMethods.MonitorFromPoint(center, NativeMethods.MONITOR_DEFAULTTONEAREST); _scale = GetMonitorScale(monitor); _size = (int)Math.Round(WheelDiameterDips * _scale); int half = _size / 2; var info = new MONITORINFO { cbSize = Marshal.SizeOf() }; if (NativeMethods.GetMonitorInfo(monitor, ref info)) { int left = Math.Max(info.rcWork.Left + half, Math.Min(info.rcWork.Right - half, center.X)); int top = Math.Max(info.rcWork.Top + half, Math.Min(info.rcWork.Bottom - half, center.Y)); center.X = left; center.Y = top; } _x = center.X - half; _y = center.Y - half; AppWindow.MoveAndResize(new RectInt32(_x, _y, _size, _size)); if (!AppWindow.IsVisible) AppWindow.Show(); Activate(); } public void Hide() => AppWindow.Hide(); private static double GetMonitorScale(IntPtr monitor) { if (NativeMethods.GetDpiForMonitor(monitor, NativeMethods.MDT_EFFECTIVE_DPI, out uint dpiX, out _) == 0 && dpiX > 0) { return dpiX / 96.0; } return 1.0; } }