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

TextBlockPopup.cs

317 строк · 10,773 байт · модуль UI
  1using System;
  2using System.Drawing;
  3using System.Collections.Generic;
  4using System.Windows.Forms;
  5using WindowCapture.Models;
  6
  7namespace WindowCapture.UI
  8{
  9    // Popup for editing TextBlock properties with fade-in animation (real-time changes)
 10    public class TextBlockPopup : Form
 11    {
 12        private static readonly Color DarkBg = Color.FromArgb(30, 30, 32);
 13        private static readonly Color DarkPanel = Color.FromArgb(40, 40, 42);
 14        private static readonly Color TextColor = Color.FromArgb(200, 200, 200);
 15
 16        // Get all installed system fonts
 17        private static List<string> GetSystemFonts()
 18        {
 19            var fonts = new List<string>();
 20            foreach (var family in System.Drawing.FontFamily.Families)
 21            {
 22                fonts.Add(family.Name);
 23            }
 24            return fonts;
 25        }
 26
 27        private TextBlock textBlock;
 28        private Action onChanged;
 29        private Timer fadeTimer;
 30        private double currentOpacity = 0;
 31        private bool fadingIn = true;
 32        private bool isClosing = false;
 33
 34        private DarkComboBox cmbFont;
 35        private DarkFontSizeNumeric nudFontSize;
 36        private Button btnTextColor;
 37        private Button btnShadowColor;
 38        private DarkNumeric nudShadowOffset;
 39        private DarkNumeric nudDepth3D;
 40        private Button btnSideColor;
 41
 42        public TextBlockPopup(TextBlock tb, Point screenLocation, Action onChange)
 43        {
 44            textBlock = tb;
 45            onChanged = onChange;
 46
 47            FormBorderStyle = FormBorderStyle.None;
 48            StartPosition = FormStartPosition.Manual;
 49            ShowInTaskbar = false;
 50            BackColor = DarkBg;
 51            Size = new Size(280, 210);
 52            Location = screenLocation;
 53            Opacity = 0;
 54
 55            InitializeControls();
 56            StartFadeIn();
 57        }
 58
 59        private void InitializeControls()
 60        {
 61            int y = 12;
 62            int labelX = 12;
 63            int controlX = 100;
 64
 65            // Font - all system fonts
 66            AddLabel("Font:", labelX, y);
 67            cmbFont = new DarkComboBox();
 68            cmbFont.Location = new Point(controlX, y);
 69            cmbFont.Size = new Size(160, 26);
 70            var systemFonts = GetSystemFonts();
 71            foreach (string font in systemFonts)
 72                cmbFont.Items.Add(font);
 73            string currentFont = textBlock.TextFont.FontFamily.Name;
 74            int fontIdx = cmbFont.Items.IndexOf(currentFont);
 75            if (fontIdx >= 0)
 76                cmbFont.SelectedIndex = fontIdx;
 77            else if (cmbFont.Items.Count > 0)
 78                cmbFont.SelectedIndex = 0;
 79            cmbFont.SelectedIndexChanged += (s, e) => ApplyChanges();
 80            Controls.Add(cmbFont);
 81            y += 34;
 82
 83            // Font size (geometric progression after 16)
 84            AddLabel("Size:", labelX, y);
 85            nudFontSize = new DarkFontSizeNumeric();
 86            nudFontSize.Location = new Point(controlX, y);
 87            nudFontSize.Value = (int)textBlock.TextFont.Size;
 88            nudFontSize.ValueChanged += (s, e) => ApplyChanges();
 89            Controls.Add(nudFontSize);
 90            y += 34;
 91
 92            // Text color
 93            AddLabel("Color:", labelX, y);
 94            btnTextColor = CreateColorButton(controlX, y, textBlock.TextColor);
 95            Controls.Add(btnTextColor);
 96            y += 34;
 97
 98            // Shadow color
 99            AddLabel("Shadow:", labelX, y);
100            btnShadowColor = CreateColorButton(controlX, y, textBlock.ShadowColor);
101            Controls.Add(btnShadowColor);
102
103            // Shadow offset (0-50)
104            nudShadowOffset = new DarkNumeric();
105            nudShadowOffset.Location = new Point(controlX + 90, y);
106            nudShadowOffset.Size = new Size(60, 26);
107            nudShadowOffset.Minimum = 0;
108            nudShadowOffset.Maximum = 50;
109            nudShadowOffset.Value = textBlock.ShadowOffset;
110            nudShadowOffset.ValueChanged += (s, e) => ApplyChanges();
111            Controls.Add(nudShadowOffset);
112            y += 34;
113
114            // 3D Depth
115            AddLabel("3D Depth:", labelX, y);
116            nudDepth3D = new DarkNumeric();
117            nudDepth3D.Location = new Point(controlX, y);
118            nudDepth3D.Size = new Size(60, 26);
119            nudDepth3D.Minimum = 0;
120            nudDepth3D.Maximum = 100;
121            nudDepth3D.Value = textBlock.Depth3D;
122            nudDepth3D.ValueChanged += (s, e) => ApplyChanges();
123            Controls.Add(nudDepth3D);
124
125            // Side color
126            btnSideColor = CreateColorButton(controlX + 90, y, textBlock.SideColor);
127            Controls.Add(btnSideColor);
128
129            // Close on click outside
130            Deactivate += (s, e) => { if (!isClosing) StartFadeOut(); };
131
132            // Paint border
133            Paint += (s, e) =>
134            {
135                using (var pen = new Pen(DarkPanel, 2))
136                    e.Graphics.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
137            };
138        }
139
140        private Label AddLabel(string text, int x, int y)
141        {
142            Label lbl = new Label();
143            lbl.Text = text;
144            lbl.Location = new Point(x, y + 4);
145            lbl.AutoSize = true;
146            lbl.ForeColor = TextColor;
147            lbl.Font = new Font("Segoe UI", 9f);
148            Controls.Add(lbl);
149            return lbl;
150        }
151
152        private Button CreateColorButton(int x, int y, Color color)
153        {
154            Button btn = new Button();
155            btn.Location = new Point(x, y);
156            btn.Size = new Size(70, 26);
157            btn.BackColor = color;
158            btn.FlatStyle = FlatStyle.Flat;
159            btn.FlatAppearance.BorderSize = 0;
160            btn.Cursor = Cursors.Hand;
161            btn.Click += (s, e) =>
162            {
163                isClosing = true; // Prevent popup from closing when color picker opens
164                using (var dlg = new DarkColorPicker(btn.BackColor))
165                {
166                    if (dlg.ShowDialog() == DialogResult.OK)
167                    {
168                        btn.BackColor = dlg.SelectedColor;
169                        ApplyChanges();
170                    }
171                }
172                isClosing = false;
173            };
174            return btn;
175        }
176
177        private void ApplyChanges()
178        {
179            string fontName = cmbFont.SelectedItem;
180            float fontSize = nudFontSize.Value;
181
182            // Calculate center of text BEFORE changing font
183            SizeF oldSize;
184            using (var bmp = new Bitmap(1, 1))
185            using (var g = Graphics.FromImage(bmp))
186            {
187                oldSize = g.MeasureString(textBlock.Text, textBlock.TextFont);
188            }
189            float centerX = textBlock.Location.X + oldSize.Width / 2;
190            float centerY = textBlock.Location.Y + oldSize.Height / 2;
191
192            // Apply new font
193            Font newFont;
194            try
195            {
196                newFont = new Font(fontName, fontSize);
197            }
198            catch
199            {
200                newFont = new Font("Segoe UI", fontSize);
201            }
202            textBlock.TextFont = newFont;
203
204            // Measure new size and adjust location to keep center
205            SizeF newSize;
206            using (var bmp = new Bitmap(1, 1))
207            using (var g = Graphics.FromImage(bmp))
208            {
209                newSize = g.MeasureString(textBlock.Text, newFont);
210            }
211            textBlock.Location = new Point(
212                (int)(centerX - newSize.Width / 2),
213                (int)(centerY - newSize.Height / 2));
214
215            textBlock.TextColor = btnTextColor.BackColor;
216            textBlock.ShadowColor = btnShadowColor.BackColor;
217            textBlock.ShadowOffset = nudShadowOffset.Value;
218            textBlock.Depth3D = nudDepth3D.Value;
219            textBlock.SideColor = btnSideColor.BackColor;
220
221            if (onChanged != null)
222                onChanged();
223        }
224
225        private void StartFadeIn()
226        {
227            fadingIn = true;
228            currentOpacity = 0;
229            fadeTimer = new Timer();
230            fadeTimer.Interval = 16; // ~60fps
231            fadeTimer.Tick += FadeTimer_Tick;
232            fadeTimer.Start();
233        }
234
235        private void StartFadeOut()
236        {
237            if (isClosing) return;
238            isClosing = true;
239            fadingIn = false;
240            fadeTimer = new Timer();
241            fadeTimer.Interval = 16;
242            fadeTimer.Tick += FadeTimer_Tick;
243            fadeTimer.Start();
244        }
245
246        private void FadeTimer_Tick(object sender, EventArgs e)
247        {
248            // Guard against null timer or disposed form
249            if (fadeTimer == null || IsDisposed || !IsHandleCreated)
250            {
251                var timer = sender as Timer;
252                if (timer != null)
253                {
254                    timer.Stop();
255                    timer.Dispose();
256                }
257                return;
258            }
259
260            try
261            {
262                if (fadingIn)
263                {
264                    // Bezier ease-out curve
265                    currentOpacity += 0.08;
266                    double t = Math.Min(1, currentOpacity);
267                    double bezier = t * (2 - t); // ease-out quad
268                    Opacity = bezier;
269
270                    if (currentOpacity >= 1)
271                    {
272                        fadeTimer.Stop();
273                        fadeTimer.Dispose();
274                        fadeTimer = null;
275                        Opacity = 1;
276                    }
277                }
278                else
279                {
280                    currentOpacity -= 0.12;
281                    double t = Math.Max(0, currentOpacity);
282                    double bezier = t * t; // ease-in quad
283                    Opacity = bezier;
284
285                    if (currentOpacity <= 0)
286                    {
287                        fadeTimer.Stop();
288                        fadeTimer.Dispose();
289                        fadeTimer = null;
290                        Close();
291                    }
292                }
293            }
294            catch (ObjectDisposedException)
295            {
296                // Form was disposed during animation
297                if (fadeTimer != null)
298                {
299                    fadeTimer.Stop();
300                    fadeTimer.Dispose();
301                    fadeTimer = null;
302                }
303            }
304        }
305
306        protected override void Dispose(bool disposing)
307        {
308            if (disposing && fadeTimer != null)
309            {
310                fadeTimer.Stop();
311                fadeTimer.Dispose();
312                fadeTimer = null;
313            }
314            base.Dispose(disposing);
315        }
316    }
317}