windowcapture
исходный код / UI/LauncherForm.cs

LauncherForm.cs

192 строк · 8,277 байт · модуль UI
  1using System;
  2using System.Drawing;
  3using System.Drawing.Drawing2D;
  4using System.Runtime.InteropServices;
  5using System.Windows.Forms;
  6using WindowCapture.App;
  7
  8namespace WindowCapture.UI
  9{
 10    /// <summary>Game-style radial (pie) launcher menu. Painted over a dimmed frozen desktop
 11    /// (same idea as the capture overlay). Move the cursor toward a slice and click — or press
 12    /// the slice's number, or Enter on the hovered slice — to launch that app. Esc / right-click /
 13    /// the center hub cancels.</summary>
 14    public class LauncherForm : Form
 15    {
 16        public event Action<AppMode> ItemChosen;
 17
 18        private static readonly AppMode[] Items =
 19        {
 20            AppMode.Screenshot, AppMode.TextAssist, AppMode.Clipboard, AppMode.Soundpad, AppMode.Search
 21        };
 22
 23        private Bitmap backdrop;     // dimmed frozen desktop captured before the form is shown
 24        private Point center;
 25        private int hovered = -1;
 26        private const int RIn = 66;
 27        private const int ROut = 170;
 28        private readonly Font labelFont = new Font("Segoe UI", 11f, FontStyle.Bold);
 29        private readonly Font glyphFont = new Font("Segoe UI", 22f, FontStyle.Regular);
 30        private readonly Font hubFont = new Font("Segoe UI", 9f, FontStyle.Regular);
 31
 32        [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd);
 33
 34        public LauncherForm()
 35        {
 36            FormBorderStyle = FormBorderStyle.None;
 37            ShowInTaskbar = false;
 38            TopMost = true;
 39            StartPosition = FormStartPosition.Manual;
 40            DoubleBuffered = true;
 41            SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
 42            KeyPreview = true;
 43            var vs = SystemInformation.VirtualScreen;
 44            Bounds = vs;
 45
 46            // Capture the desktop BEFORE the form paints over it, then dim it as the backdrop.
 47            backdrop = new Bitmap(vs.Width, vs.Height);
 48            using (var g = Graphics.FromImage(backdrop))
 49            {
 50                try { g.CopyFromScreen(vs.Location, Point.Empty, vs.Size); } catch { g.Clear(Color.FromArgb(18, 20, 26)); }
 51                using (var dim = new SolidBrush(Color.FromArgb(150, 8, 10, 14)))
 52                    g.FillRectangle(dim, 0, 0, vs.Width, vs.Height);
 53            }
 54        }
 55
 56        protected override void OnShown(EventArgs e)
 57        {
 58            base.OnShown(e);
 59            var c = PointToClient(Cursor.Position);
 60            int m = ROut + 44;
 61            c.X = Math.Max(m, Math.Min(Width - m, c.X));
 62            c.Y = Math.Max(m, Math.Min(Height - m, c.Y));
 63            center = c;
 64            try { SetForegroundWindow(Handle); } catch { }
 65            Activate();
 66            Focus();
 67            Invalidate();
 68        }
 69
 70        private float Sweep { get { return 360f / Items.Length; } }
 71
 72        private int HitTest(Point pt)
 73        {
 74            double dx = pt.X - center.X, dy = pt.Y - center.Y;
 75            double dist = Math.Sqrt(dx * dx + dy * dy);
 76            if (dist < RIn || dist > ROut + 70) return -1;
 77            double ang = Math.Atan2(dy, dx) * 180.0 / Math.PI; // 0 = right, clockwise (screen y down)
 78            double rel = ang - (-90 - Sweep / 2.0);
 79            rel = ((rel % 360) + 360) % 360;
 80            int i = (int)(rel / Sweep);
 81            if (i < 0) i = 0; if (i >= Items.Length) i = Items.Length - 1;
 82            return i;
 83        }
 84
 85        protected override void OnMouseMove(MouseEventArgs e)
 86        {
 87            base.OnMouseMove(e);
 88            int h = HitTest(e.Location);
 89            if (h != hovered) { hovered = h; Invalidate(); }
 90        }
 91
 92        protected override void OnMouseDown(MouseEventArgs e)
 93        {
 94            base.OnMouseDown(e);
 95            if (e.Button == MouseButtons.Left)
 96            {
 97                int h = HitTest(e.Location);
 98                if (h >= 0) { Choose(Items[h]); return; }
 99            }
100            Cancel(); // right-click, or click in the center hub / outside
101        }
102
103        protected override void OnKeyDown(KeyEventArgs e)
104        {
105            base.OnKeyDown(e);
106            if (e.KeyCode == Keys.Escape) { Cancel(); return; }
107            if (e.KeyCode == Keys.Enter && hovered >= 0) { Choose(Items[hovered]); return; }
108            int n = -1;
109            if (e.KeyCode >= Keys.D1 && e.KeyCode <= Keys.D9) n = e.KeyCode - Keys.D1;
110            else if (e.KeyCode >= Keys.NumPad1 && e.KeyCode <= Keys.NumPad9) n = e.KeyCode - Keys.NumPad1;
111            if (n >= 0 && n < Items.Length) Choose(Items[n]);
112        }
113
114        protected override void OnDeactivate(EventArgs e) { base.OnDeactivate(e); Cancel(); }
115
116        private void Choose(AppMode m)
117        {
118            var h = ItemChosen;
119            Close();
120            if (h != null) h(m);
121        }
122
123        private void Cancel() { Close(); }
124
125        protected override void OnPaint(PaintEventArgs e)
126        {
127            var g = e.Graphics;
128            if (backdrop != null) g.DrawImageUnscaled(backdrop, 0, 0);
129            g.SmoothingMode = SmoothingMode.AntiAlias;
130            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
131
132            var outer = new Rectangle(center.X - ROut, center.Y - ROut, ROut * 2, ROut * 2);
133            float sweep = Sweep;
134
135            for (int i = 0; i < Items.Length; i++)
136            {
137                var mode = Items[i];
138                float centerAng = -90 + i * sweep;
139                float start = centerAng - sweep / 2f + 1.2f; // small gap between slices
140                float sw = sweep - 2.4f;
141                bool hot = (i == hovered);
142
143                var acc = AppModes.Accent(mode);
144                using (var path = new GraphicsPath())
145                {
146                    path.AddArc(outer, start, sw);
147                    var inner = new Rectangle(center.X - RIn, center.Y - RIn, RIn * 2, RIn * 2);
148                    path.AddArc(inner, start + sw, -sw);
149                    path.CloseFigure();
150
151                    int fillA = hot ? 210 : 120;
152                    using (var b = new SolidBrush(Color.FromArgb(fillA, acc.R, acc.G, acc.B)))
153                        g.FillPath(b, path);
154                    using (var pen = new Pen(Color.FromArgb(hot ? 255 : 90, 255, 255, 255), hot ? 2.2f : 1f))
155                        g.DrawPath(pen, path);
156                }
157
158                // Label + glyph + hotkey number, placed along the slice's center angle.
159                double rad = centerAng * Math.PI / 180.0;
160                float rMid = (RIn + ROut) / 2f;
161                float lx = center.X + (float)Math.Cos(rad) * rMid;
162                float ly = center.Y + (float)Math.Sin(rad) * rMid;
163                using (var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
164                using (var txt = new SolidBrush(Color.White))
165                using (var sub = new SolidBrush(Color.FromArgb(220, 235, 240, 250)))
166                {
167                    g.DrawString(AppModes.Glyph(mode), glyphFont, txt, lx, ly - 16, sf);
168                    g.DrawString(AppModes.Title(mode), labelFont, txt, lx, ly + 14, sf);
169                    g.DrawString((i + 1).ToString(), hubFont, sub, lx, ly + 34, sf);
170                }
171            }
172
173            // Center hub
174            var hub = new Rectangle(center.X - RIn + 6, center.Y - RIn + 6, (RIn - 6) * 2, (RIn - 6) * 2);
175            using (var b = new SolidBrush(Color.FromArgb(200, 16, 18, 24))) g.FillEllipse(b, hub);
176            using (var p = new Pen(Color.FromArgb(120, 255, 255, 255), 1f)) g.DrawEllipse(p, hub);
177            using (var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
178            using (var t = new SolidBrush(Color.FromArgb(200, 220, 225, 235)))
179                g.DrawString("Esc", hubFont, t, center.X, center.Y, sf);
180        }
181
182        protected override void Dispose(bool disposing)
183        {
184            if (disposing)
185            {
186                if (backdrop != null) { backdrop.Dispose(); backdrop = null; }
187                labelFont.Dispose(); glyphFont.Dispose(); hubFont.Dispose();
188            }
189            base.Dispose(disposing);
190        }
191    }
192}