1using System; 2using System.Drawing; 3using System.Drawing.Drawing2D; 4using System.Windows.Forms; 5using System.Collections.Generic; 6using WindowCapture.Helpers; 7 8namespace WindowCapture.UI 9{ 10 // Custom dark combo box - no light elements 11 public class DarkComboBox : Control 12 { 13 private static readonly Color GE = Color.FromArgb(225, 232, 245); 14 private static readonly Color DarkBg = Color.FromArgb(12, GE); 15 private static readonly Color DarkHover = Color.FromArgb(25, GE); 16 private static readonly Color DarkDropdown = Color.FromArgb(20, 22, 28); 17 private static readonly Color TextColor = Color.FromArgb(200, GE); 18 private static readonly Color DimColor = Color.FromArgb(80, GE); 19 private static readonly Color SelectColor = Color.FromArgb(40, GE); 20 21 private List<string> _items = new List<string>(); 22 private int _selectedIndex = -1; 23 private bool isOpen = false; 24 private bool hoverArrow = false; 25 private Form dropdownForm; 26 private int lastCloseTick; // suppresses click-to-reopen right after a deactivate-close 27 28 public event EventHandler SelectedIndexChanged; 29 30 public List<string> Items { get { return _items; } } 31 public int SelectedIndex 32 { 33 get { return _selectedIndex; } 34 set 35 { 36 if (_selectedIndex != value) 37 { 38 _selectedIndex = value; 39 Invalidate(); 40 if (SelectedIndexChanged != null) SelectedIndexChanged(this, EventArgs.Empty); 41 } 42 } 43 } 44 public string SelectedItem 45 { 46 get { return _selectedIndex >= 0 && _selectedIndex < _items.Count ? _items[_selectedIndex] : ""; } 47 set 48 { 49 int idx = _items.IndexOf(value); 50 if (idx >= 0 && idx != _selectedIndex) 51 { 52 _selectedIndex = idx; 53 Invalidate(); 54 if (SelectedIndexChanged != null) SelectedIndexChanged(this, EventArgs.Empty); 55 } 56 } 57 } 58 59 public DarkComboBox() 60 { 61 SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.SupportsTransparentBackColor, true); 62 BackColor = Color.Transparent; 63 Size = new Size(140, 26); 64 Font = new Font("Segoe UI", 9f); 65 Cursor = Cursors.Hand; 66 } 67 68 protected override void OnPaint(PaintEventArgs e) 69 { 70 var g = e.Graphics; 71 float glow = GlowHelper.ComputeGlow(this); 72 // Glass fill + border with glow 73 int fillA = 12 + (int)(18 * glow); 74 using (var brush = new SolidBrush(Color.FromArgb(fillA, GE))) 75 g.FillRectangle(brush, 0, 0, Width, Height); 76 int borderA = 40 + (int)(60 * glow); 77 using (var pen = new Pen(Color.FromArgb(borderA, GE), 1f)) 78 g.DrawRectangle(pen, 0, 0, Width - 1, Height - 1); 79 80 g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; 81 Color tc = SettingsDialog.AdaptiveTextColor; 82 string text = SelectedItem; 83 int textA = 160 + (int)(65 * glow); 84 using (var brush = new SolidBrush(Color.FromArgb(textA, tc))) 85 { 86 var sf = new StringFormat { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Center }; 87 g.DrawString(text, Font, brush, new Rectangle(8, 0, Width - 28, Height), sf); 88 } 89 90 // Arrow button 91 int btnW = 24; 92 if (hoverArrow) 93 using (var brush = new SolidBrush(DarkHover)) g.FillRectangle(brush, Width - btnW, 1, btnW - 1, Height - 2); 94 int sepA = 30 + (int)(30 * glow); 95 using (var pen = new Pen(Color.FromArgb(sepA, tc), 1f)) g.DrawLine(pen, Width - btnW, 0, Width - btnW, Height); 96 97 int cx = Width - btnW / 2, cy = Height / 2; 98 Point[] pts = new Point[] { new Point(cx - 4, cy - 2), new Point(cx + 4, cy - 2), new Point(cx, cy + 2) }; 99 int arrowA = (int)((hoverArrow ? 180 : 80) + (40 * glow)); 100 if (arrowA > 255) arrowA = 255; 101 using (var brush = new SolidBrush(Color.FromArgb(arrowA, tc))) 102 g.FillPolygon(brush, pts); 103 } 104 105 protected override void OnMouseMove(MouseEventArgs e) 106 { 107 bool newHover = e.X >= Width - 24; 108 if (newHover != hoverArrow) 109 { 110 hoverArrow = newHover; 111 Invalidate(); 112 } 113 } 114 115 protected override void OnMouseLeave(EventArgs e) 116 { 117 hoverArrow = false; 118 Invalidate(); 119 } 120 121 protected override void OnMouseClick(MouseEventArgs e) 122 { 123 if (e.Button == MouseButtons.Left) 124 { 125 // Clicking an open combo deactivates the dropdown form first (which closes it), 126 // so without this guard the click would immediately reopen it. Swallow the reopen. 127 if (!isOpen && unchecked(Environment.TickCount - lastCloseTick) < 250) return; 128 ToggleDropdown(); 129 } 130 } 131 132 private void ToggleDropdown() 133 { 134 if (isOpen) 135 { 136 CloseDropdown(); 137 } 138 else 139 { 140 OpenDropdown(); 141 } 142 } 143 144 private void OpenDropdown() 145 { 146 if (_items.Count == 0) return; 147 148 isOpen = true; 149 int itemH = 28; 150 int maxVisible = Math.Min(_items.Count, 8); 151 int dropH = itemH * maxVisible + 4; 152 bool needScroll = _items.Count > maxVisible; 153 int scrollW = needScroll ? 6 : 0; 154 155 dropdownForm = new Form(); 156 dropdownForm.FormBorderStyle = FormBorderStyle.None; 157 dropdownForm.StartPosition = FormStartPosition.Manual; 158 dropdownForm.ShowInTaskbar = false; 159 dropdownForm.BackColor = DarkDropdown; 160 dropdownForm.Size = new Size(Width, dropH); 161 162 Point screenPos = PointToScreen(new Point(0, Height)); 163 dropdownForm.Location = screenPos; 164 165 // Content panel (no system scrollbars) 166 Panel contentPanel = new Panel(); 167 contentPanel.Location = new Point(0, 0); 168 contentPanel.Size = new Size(Width - scrollW, dropH); 169 contentPanel.BackColor = DarkDropdown; 170 171 // Inner panel for items 172 Panel itemsPanel = new Panel(); 173 itemsPanel.Location = new Point(0, 0); 174 itemsPanel.Size = new Size(Width - scrollW, _items.Count * itemH); 175 itemsPanel.BackColor = DarkDropdown; 176 177 for (int i = 0; i < _items.Count; i++) 178 { 179 Panel itemPanel = new Panel(); 180 itemPanel.Location = new Point(0, i * itemH); 181 itemPanel.Size = new Size(Width - scrollW, itemH); 182 itemPanel.BackColor = DarkDropdown; 183 itemPanel.Tag = i; 184 itemPanel.Cursor = Cursors.Hand; 185 186 Label lbl = new Label(); 187 lbl.Text = _items[i]; 188 try { lbl.Font = new Font(_items[i], 9f); } 189 catch { lbl.Font = new Font("Segoe UI", 9f); } 190 lbl.ForeColor = TextColor; 191 lbl.AutoSize = false; 192 lbl.Dock = DockStyle.Fill; 193 lbl.TextAlign = ContentAlignment.MiddleLeft; 194 lbl.Padding = new Padding(8, 0, 0, 0); 195 lbl.Tag = i; 196 lbl.Cursor = Cursors.Hand; 197 198 int idx = i; 199 EventHandler mouseEnter = (s, ev) => { itemPanel.BackColor = SelectColor; }; 200 EventHandler mouseLeave = (s, ev) => { itemPanel.BackColor = DarkDropdown; }; 201 MouseEventHandler mouseClick = (s, ev) => { SelectItem(idx); }; 202 203 itemPanel.MouseEnter += mouseEnter; 204 itemPanel.MouseLeave += mouseLeave; 205 itemPanel.MouseClick += mouseClick; 206 lbl.MouseEnter += mouseEnter; 207 lbl.MouseLeave += mouseLeave; 208 lbl.MouseClick += mouseClick; 209 210 itemPanel.Controls.Add(lbl); 211 itemsPanel.Controls.Add(itemPanel); 212 } 213 214 contentPanel.Controls.Add(itemsPanel); 215 216 // Custom dark scrollbar 217 Panel scrollTrack = null; 218 Panel scrollThumb = null; 219 int scrollOffset = 0; 220 int maxScroll = Math.Max(0, _items.Count * itemH - dropH + 4); 221 222 if (needScroll) 223 { 224 scrollTrack = new Panel(); 225 scrollTrack.Location = new Point(Width - scrollW, 0); 226 scrollTrack.Size = new Size(scrollW, dropH); 227 scrollTrack.BackColor = Color.FromArgb(35, 35, 38); 228 229 int thumbH = Math.Max(20, (dropH * dropH) / (_items.Count * itemH)); 230 scrollThumb = new Panel(); 231 scrollThumb.Location = new Point(1, 0); 232 scrollThumb.Size = new Size(scrollW - 2, thumbH); 233 scrollThumb.BackColor = Color.FromArgb(70, 70, 75); 234 235 bool thumbDragging = false; 236 int thumbDragStart = 0; 237 int scrollStart = 0; 238 239 scrollThumb.MouseDown += (s, ev) => { 240 thumbDragging = true; 241 thumbDragStart = ev.Y; 242 scrollStart = scrollOffset; 243 }; 244 scrollThumb.MouseMove += (s, ev) => { 245 if (thumbDragging) 246 { 247 int trackH = dropH - thumbH; 248 int delta = ev.Y - thumbDragStart; 249 int newThumbY = Math.Max(0, Math.Min(trackH, scrollThumb.Top + delta)); 250 scrollOffset = (trackH > 0) ? (newThumbY * maxScroll / trackH) : 0; 251 scrollThumb.Top = newThumbY; 252 itemsPanel.Top = -scrollOffset; 253 thumbDragStart = ev.Y; 254 } 255 }; 256 scrollThumb.MouseUp += (s, ev) => { thumbDragging = false; }; 257 258 scrollTrack.Controls.Add(scrollThumb); 259 dropdownForm.Controls.Add(scrollTrack); 260 261 // Mouse wheel on dropdown 262 MouseEventHandler wheelHandler = null; 263 wheelHandler = (s, ev) => { 264 int step = itemH; 265 scrollOffset = Math.Max(0, Math.Min(maxScroll, scrollOffset - (ev.Delta > 0 ? step : -step))); 266 itemsPanel.Top = -scrollOffset; 267 int trackH = dropH - thumbH; 268 scrollThumb.Top = (maxScroll > 0) ? (scrollOffset * trackH / maxScroll) : 0; 269 }; 270 271 contentPanel.MouseWheel += wheelHandler; 272 itemsPanel.MouseWheel += wheelHandler; 273 foreach (Control c in itemsPanel.Controls) 274 { 275 c.MouseWheel += wheelHandler; 276 foreach (Control cc in c.Controls) 277 cc.MouseWheel += wheelHandler; 278 } 279 } 280 281 dropdownForm.Controls.Add(contentPanel); 282 dropdownForm.Deactivate += (s, ev) => { CloseDropdown(); }; 283 dropdownForm.Show(); 284 dropdownForm.Focus(); 285 } 286 287 private void SelectItem(int index) 288 { 289 bool changed = _selectedIndex != index; 290 _selectedIndex = index; 291 Invalidate(); 292 // Fire event BEFORE closing dropdown to ensure real-time update 293 if (changed && SelectedIndexChanged != null) 294 SelectedIndexChanged(this, EventArgs.Empty); 295 CloseDropdown(); 296 } 297 298 private void CloseDropdown() 299 { 300 if (!isOpen) return; 301 isOpen = false; 302 lastCloseTick = Environment.TickCount; 303 if (dropdownForm != null && !dropdownForm.IsDisposed) 304 { 305 var form = dropdownForm; 306 dropdownForm = null; 307 form.Close(); 308 form.Dispose(); 309 } 310 } 311 312 protected override void OnMouseWheel(MouseEventArgs e) 313 { 314 if (!isOpen && _items.Count > 0) 315 { 316 int newIdx = _selectedIndex + (e.Delta > 0 ? -1 : 1); 317 if (newIdx >= 0 && newIdx < _items.Count) 318 { 319 bool changed = _selectedIndex != newIdx; 320 _selectedIndex = newIdx; 321 Invalidate(); 322 // Fire event for real-time update 323 if (changed && SelectedIndexChanged != null) 324 SelectedIndexChanged(this, EventArgs.Empty); 325 } 326 } 327 } 328 329 public void AddItem(string item) 330 { 331 _items.Add(item); 332 } 333 334 public int FindItem(string item) 335 { 336 return _items.IndexOf(item); 337 } 338 } 339}