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}