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

DarkComboBox.cs

339 строк · 13,256 байт · модуль UI
  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}