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}